// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include // IWYU pragma: no_include #include "opentelemetry/common/timestamp.h" #include "opentelemetry/exporters/otlp/otlp_file_client.h" #include "opentelemetry/exporters/otlp/otlp_file_client_options.h" #include "opentelemetry/exporters/otlp/otlp_file_client_runtime_options.h" #include "opentelemetry/exporters/otlp/otlp_recordable.h" #include "opentelemetry/exporters/otlp/otlp_recordable_utils.h" #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/exporter_utils.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/resource/resource.h" #include "opentelemetry/sdk/trace/recordable.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/span_metadata.h" #include "opentelemetry/trace/trace_flags.h" #include "opentelemetry/trace/trace_id.h" #include "opentelemetry/trace/trace_state.h" #include "opentelemetry/version.h" // clang-format off #include "opentelemetry/exporters/otlp/protobuf_include_prefix.h" // IWYU pragma: keep #include #include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" #include "opentelemetry/proto/trace/v1/trace.pb.h" #include "opentelemetry/exporters/otlp/protobuf_include_suffix.h" // IWYU pragma: keep // clang-format on using namespace testing; OPENTELEMETRY_BEGIN_NAMESPACE namespace exporter { namespace otlp { namespace resource = opentelemetry::sdk::resource; class ProtobufGlobalSymbolGuard { public: ProtobufGlobalSymbolGuard() {} ~ProtobufGlobalSymbolGuard() { google::protobuf::ShutdownProtobufLibrary(); } }; static std::tm GetLocalTime(std::chrono::system_clock::time_point tp) { std::time_t now = std::chrono::system_clock::to_time_t(tp); #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || defined(__STDC_LIB_EXT1__) std::tm ret; localtime_s(&now, &ret); #elif defined(_MSC_VER) && _MSC_VER >= 1300 std::tm ret; localtime_s(&ret, &now); #elif defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || \ defined(_POSIX_SOURCE) std::tm ret; localtime_r(&now, &ret); #else std::tm ret = *localtime(&now); #endif return ret; } template static nostd::span MakeSpan(T (&array)[N]) { return nostd::span(array); } static resource::Resource MakeResource() { static ProtobufGlobalSymbolGuard global_symbol_guard; resource::ResourceAttributes resource_attributes = {{"service.name", "unit_test_service"}, {"tenant.id", "test_user"}}; resource_attributes["bool_value"] = true; resource_attributes["int32_value"] = static_cast(1); resource_attributes["uint32_value"] = static_cast(2); resource_attributes["int64_value"] = static_cast(0x1100000000LL); resource_attributes["uint64_value"] = static_cast(0x1200000000ULL); resource_attributes["double_value"] = static_cast(3.1); resource_attributes["vec_bool_value"] = std::vector{true, false, true}; resource_attributes["vec_int32_value"] = std::vector{1, 2}; resource_attributes["vec_uint32_value"] = std::vector{3, 4}; resource_attributes["vec_int64_value"] = std::vector{5, 6}; resource_attributes["vec_uint64_value"] = std::vector{7, 8}; resource_attributes["vec_double_value"] = std::vector{3.2, 3.3}; resource_attributes["vec_string_value"] = std::vector{"vector", "string"}; return resource::Resource::Create(resource_attributes); } static opentelemetry::nostd::unique_ptr< opentelemetry::sdk::instrumentationscope::InstrumentationScope> MakeInstrumentationScope() { return opentelemetry::sdk::instrumentationscope::InstrumentationScope::Create( "otlp_file_client_test", "1.11.0", "https://opentelemetry.io/schemas/1.11.0"); } static std::unique_ptr MakeRecordable( const resource::Resource &resource, const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope) { OtlpRecordable *recordable = new OtlpRecordable(); recordable->SetResource(resource); recordable->SetInstrumentationScope(instrumentation_scope); recordable->SetName("otlp_file_client_test_span"); recordable->SetSpanKind(opentelemetry::trace::SpanKind::kInternal); recordable->SetAttribute("test-attribute-key", "test-attribute-value"); recordable->SetDuration(std::chrono::nanoseconds(1234567890)); recordable->SetStartTime( opentelemetry::common::SystemTimestamp(std::chrono::system_clock::now())); recordable->SetStatus(opentelemetry::trace::StatusCode::kOk, "success"); { constexpr uint8_t trace_id_buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}; constexpr uint8_t span_id_buf[] = {1, 2, 3, 4, 5, 6, 7, 8}; constexpr uint8_t parent_span_id_buf[] = {8, 7, 6, 5, 4, 3, 2, 1}; opentelemetry::trace::TraceId trace_id{trace_id_buf}; opentelemetry::trace::SpanId span_id{span_id_buf}; opentelemetry::trace::SpanId parent_span_id{parent_span_id_buf}; const auto trace_state = opentelemetry::trace::TraceState::GetDefault()->Set("key1", "value"); const opentelemetry::trace::SpanContext span_context{ trace_id, span_id, opentelemetry::trace::TraceFlags{opentelemetry::trace::TraceFlags::kIsSampled}, true, trace_state}; recordable->SetIdentity(span_context, parent_span_id); } return std::unique_ptr(recordable); } TEST(OtlpFileClientTest, Shutdown) { opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request; auto client = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpFileClient( opentelemetry::exporter::otlp::OtlpFileClientOptions(), opentelemetry::exporter::otlp::OtlpFileClientRuntimeOptions())); ASSERT_FALSE(client->IsShutdown()); ASSERT_TRUE(client->Shutdown()); ASSERT_TRUE(client->IsShutdown()); auto result = client->Export(request, 1); EXPECT_EQ(result, opentelemetry::sdk::common::ExportResult::kFailure); } TEST(OtlpFileClientTest, ExportToOstreamTest) { auto resource = MakeResource(); auto instrumentation_scope = MakeInstrumentationScope(); std::unique_ptr recordable[] = { MakeRecordable(resource, *instrumentation_scope)}; opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request; OtlpRecordableUtils::PopulateRequest(MakeSpan(recordable), &request); std::stringstream output_stream; opentelemetry::exporter::otlp::OtlpFileClientOptions opts; opentelemetry::exporter::otlp::OtlpFileClientRuntimeOptions rt_opts; opts.backend_options = std::ref(output_stream); auto client = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpFileClient(std::move(opts), std::move(rt_opts))); client->Export(request, 1); { auto check_json = nlohmann::json::parse(output_stream.str(), nullptr, false); auto resource_span = *check_json["resourceSpans"].begin(); auto scope_span = *resource_span["scopeSpans"].begin(); auto span = *scope_span["spans"].begin(); auto received_trace_id = span["traceId"].get(); EXPECT_EQ(received_trace_id, "01020304050607080102030405060708"); auto received_name = span["name"].get(); EXPECT_EQ(received_name, request.resource_spans(0).scope_spans(0).spans(0).name()); auto receivec_attributes = span["attributes"]; int attribute_found = 0; for (auto iter = receivec_attributes.begin(); iter != receivec_attributes.end(); ++iter) { auto key = (*iter)["key"].get(); if (key == "test-attribute-key") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "test-attribute-value"); } } EXPECT_EQ(attribute_found, 1); auto receivec_resource_attributes = resource_span["resource"]["attributes"]; for (auto iter = receivec_resource_attributes.begin(); iter != receivec_resource_attributes.end(); ++iter) { auto key = (*iter)["key"].get(); if (key == "service.name") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "unit_test_service"); } else if (key == "tenant.id") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "test_user"); } else if (key == "int32_value") { auto value = (*iter)["value"]["intValue"]; ++attribute_found; if (value.is_number()) { EXPECT_EQ(value.get(), 1); } else { EXPECT_EQ(value.get(), "1"); } } } EXPECT_EQ(attribute_found, 4); } } TEST(OtlpFileClientTest, ExportToFileSystemRotateIndexTest) { auto resource = MakeResource(); auto instrumentation_scope = MakeInstrumentationScope(); std::unique_ptr recordable[] = { MakeRecordable(resource, *instrumentation_scope)}; opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request; OtlpRecordableUtils::PopulateRequest(MakeSpan(recordable), &request); std::stringstream output_stream; // Clear old files { std::fstream clear_file1("otlp_file_client_test_dir/trace-1.jsonl", std::ios::out | std::ios::trunc); std::fstream clear_file2("otlp_file_client_test_dir/trace-2.jsonl", std::ios::out | std::ios::trunc); std::fstream clear_file3("otlp_file_client_test_dir/trace-3.jsonl", std::ios::out | std::ios::trunc); std::fstream clear_file4("otlp_file_client_test_dir/trace-latest.jsonl", std::ios::out | std::ios::trunc); } opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions backend_opts; backend_opts.file_pattern = "otlp_file_client_test_dir/trace-%n.jsonl"; backend_opts.alias_pattern = "otlp_file_client_test_dir/trace-latest.jsonl"; // Smaller than the size of one record, so it will rotate after each record. backend_opts.file_size = 1500; backend_opts.rotate_size = 3; opentelemetry::exporter::otlp::OtlpFileClientOptions opts; opentelemetry::exporter::otlp::OtlpFileClientRuntimeOptions rt_opts; opts.backend_options = backend_opts; auto client = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpFileClient(std::move(opts), std::move(rt_opts))); // Write 5 records with rotatation index 1,2,3,1,2 for (int i = 0; i < 4; ++i) { client->Export(request, 1); } request.mutable_resource_spans(0)->set_schema_url("https://opentelemetry.io/schemas/1.12.0"); client->Export(request, 1); client->ForceFlush(); std::unique_ptr input_file[5] = { std::unique_ptr( new std::ifstream("otlp_file_client_test_dir/trace-1.jsonl", std::ios::in)), std::unique_ptr( new std::ifstream("otlp_file_client_test_dir/trace-2.jsonl", std::ios::in)), std::unique_ptr( new std::ifstream("otlp_file_client_test_dir/trace-3.jsonl", std::ios::in)), std::unique_ptr( new std::ifstream("otlp_file_client_test_dir/trace-4.jsonl", std::ios::in)), std::unique_ptr( new std::ifstream("otlp_file_client_test_dir/trace-latest.jsonl", std::ios::in))}; EXPECT_TRUE(input_file[0]->is_open()); EXPECT_TRUE(input_file[1]->is_open()); EXPECT_TRUE(input_file[2]->is_open()); EXPECT_FALSE(input_file[3]->is_open()); EXPECT_TRUE(input_file[4]->is_open()); std::string jsonl[4]; std::getline(*input_file[0], jsonl[0]); std::getline(*input_file[1], jsonl[1]); std::getline(*input_file[2], jsonl[2]); std::getline(*input_file[4], jsonl[3]); EXPECT_EQ(jsonl[0], jsonl[2]); EXPECT_EQ(jsonl[1], jsonl[3]); { auto check_json = nlohmann::json::parse(jsonl[0], nullptr, false); auto resource_span = *check_json["resourceSpans"].begin(); auto scope_span = *resource_span["scopeSpans"].begin(); auto span = *scope_span["spans"].begin(); auto received_trace_id = span["traceId"].get(); EXPECT_EQ(received_trace_id, "01020304050607080102030405060708"); auto received_name = span["name"].get(); EXPECT_EQ(received_name, request.resource_spans(0).scope_spans(0).spans(0).name()); auto receivec_attributes = span["attributes"]; int attribute_found = 0; for (auto iter = receivec_attributes.begin(); iter != receivec_attributes.end(); ++iter) { auto key = (*iter)["key"].get(); if (key == "test-attribute-key") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "test-attribute-value"); } } EXPECT_EQ(attribute_found, 1); auto receivec_resource_attributes = resource_span["resource"]["attributes"]; for (auto iter = receivec_resource_attributes.begin(); iter != receivec_resource_attributes.end(); ++iter) { auto key = (*iter)["key"].get(); if (key == "service.name") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "unit_test_service"); } else if (key == "tenant.id") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "test_user"); } else if (key == "int32_value") { auto value = (*iter)["value"]["intValue"]; ++attribute_found; if (value.is_number()) { EXPECT_EQ(value.get(), 1); } else { EXPECT_EQ(value.get(), "1"); } } } EXPECT_EQ(attribute_found, 4); } } TEST(OtlpFileClientTest, ExportToFileSystemRotateByTimeTest) { auto resource = MakeResource(); auto instrumentation_scope = MakeInstrumentationScope(); std::unique_ptr recordable[] = { MakeRecordable(resource, *instrumentation_scope)}; opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest request; OtlpRecordableUtils::PopulateRequest(MakeSpan(recordable), &request); std::stringstream output_stream; opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions backend_opts; backend_opts.file_pattern = "otlp_file_client_test_dir/trace-%Y-%m-%d-%H-%M-%S.jsonl"; backend_opts.alias_pattern = ""; // Smaller than the size of one record, so it will rotate after each record. backend_opts.file_size = 1500; opentelemetry::exporter::otlp::OtlpFileClientOptions opts; opentelemetry::exporter::otlp::OtlpFileClientRuntimeOptions rt_opts; opts.backend_options = backend_opts; auto client = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpFileClient(std::move(opts), std::move(rt_opts))); auto start_time = std::chrono::system_clock::now(); client->Export(request, 1); std::this_thread::sleep_for(std::chrono::seconds{1}); client->Export(request, 1); client->ForceFlush(); std::unique_ptr input_file[2]; std::size_t found_file_index = 0; // Try to load the file in 5s, it should finished. for (int i = 0; i < 5; ++i) { char file_path_buf[256] = {0}; std::tm local_tm = GetLocalTime(start_time); std::strftime(file_path_buf, sizeof(file_path_buf) - 1, "otlp_file_client_test_dir/trace-%Y-%m-%d-%H-%M-%S.jsonl", &local_tm); start_time += std::chrono::seconds{1}; input_file[found_file_index] = std::unique_ptr(new std::ifstream(file_path_buf, std::ios::in)); if (input_file[found_file_index]->is_open()) { ++found_file_index; } if (found_file_index >= 2) { break; } } ASSERT_EQ(found_file_index, 2); std::string jsonl[2]; std::getline(*input_file[0], jsonl[0]); std::getline(*input_file[1], jsonl[1]); EXPECT_EQ(jsonl[0], jsonl[1]); { auto check_json = nlohmann::json::parse(jsonl[0], nullptr, false); auto resource_span = *check_json["resourceSpans"].begin(); auto scope_span = *resource_span["scopeSpans"].begin(); auto span = *scope_span["spans"].begin(); auto received_trace_id = span["traceId"].get(); EXPECT_EQ(received_trace_id, "01020304050607080102030405060708"); auto received_name = span["name"].get(); EXPECT_EQ(received_name, request.resource_spans(0).scope_spans(0).spans(0).name()); auto receivec_attributes = span["attributes"]; int attribute_found = 0; for (auto iter = receivec_attributes.begin(); iter != receivec_attributes.end(); ++iter) { auto key = (*iter)["key"].get(); if (key == "test-attribute-key") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "test-attribute-value"); } } EXPECT_EQ(attribute_found, 1); auto receivec_resource_attributes = resource_span["resource"]["attributes"]; for (auto iter = receivec_resource_attributes.begin(); iter != receivec_resource_attributes.end(); ++iter) { auto key = (*iter)["key"].get(); if (key == "service.name") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "unit_test_service"); } else if (key == "tenant.id") { auto value = (*iter)["value"]["stringValue"]; ++attribute_found; EXPECT_EQ(value.get(), "test_user"); } else if (key == "int32_value") { auto value = (*iter)["value"]["intValue"]; ++attribute_found; if (value.is_number()) { EXPECT_EQ(value.get(), 1); } else { EXPECT_EQ(value.get(), "1"); } } } EXPECT_EQ(attribute_found, 4); } } // Test client configuration options TEST(OtlpFileClientTest, ConfigTest) { { opentelemetry::exporter::otlp::OtlpFileClientOptions opts; opentelemetry::exporter::otlp::OtlpFileClientRuntimeOptions rt_opts; opts.console_debug = true; opts.backend_options = std::ref(std::cout); auto client = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpFileClient(std::move(opts), std::move(rt_opts))); ASSERT_TRUE(client->GetOptions().console_debug); ASSERT_TRUE(opentelemetry::nostd::holds_alternative>( client->GetOptions().backend_options)); } { opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions backend_opts; backend_opts.file_pattern = "test_file_pattern.jsonl"; opentelemetry::exporter::otlp::OtlpFileClientOptions opts; opentelemetry::exporter::otlp::OtlpFileClientRuntimeOptions rt_opts; opts.console_debug = false; opts.backend_options = backend_opts; auto client = std::unique_ptr( new opentelemetry::exporter::otlp::OtlpFileClient(std::move(opts), std::move(rt_opts))); ASSERT_FALSE(client->GetOptions().console_debug); ASSERT_TRUE(opentelemetry::nostd::holds_alternative< opentelemetry::exporter::otlp::OtlpFileClientFileSystemOptions>( client->GetOptions().backend_options)); EXPECT_EQ( opentelemetry::nostd::get( client->GetOptions().backend_options) .file_pattern, "test_file_pattern.jsonl"); } } } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE