From a0f9b1a5d0abbda2d42d20e3a210c68a84372712 Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Mon, 13 Jul 2026 12:48:43 -0400 Subject: [PATCH 1/4] Test the SdkBuilder and ConfiguredSdk classes in unit and integration tests. Fix issues uncovered. --- .../sdk/configuration/configuration.h | 3 +- .../sdk/configuration/configured_sdk.h | 3 +- sdk/src/configuration/sdk_builder.cc | 35 +- sdk/test/configuration/BUILD | 42 +- sdk/test/configuration/CMakeLists.txt | 20 +- sdk/test/configuration/configured_sdk_test.cc | 313 ++++++ .../programmatic_configuration_test.cc | 949 ++++++++++++++++++ sdk/test/configuration/sdk_builder_test.cc | 106 +- 8 files changed, 1448 insertions(+), 23 deletions(-) create mode 100644 sdk/test/configuration/configured_sdk_test.cc create mode 100644 sdk/test/configuration/programmatic_configuration_test.cc diff --git a/sdk/include/opentelemetry/sdk/configuration/configuration.h b/sdk/include/opentelemetry/sdk/configuration/configuration.h index 05dfffcb8..a7fbac484 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configuration.h +++ b/sdk/include/opentelemetry/sdk/configuration/configuration.h @@ -58,7 +58,8 @@ namespace configuration class Configuration { public: - Configuration(std::unique_ptr doc) : doc_(std::move(doc)) {} + Configuration() = default; + explicit Configuration(std::unique_ptr doc) : doc_(std::move(doc)) {} Configuration(Configuration &&) = delete; Configuration(const Configuration &) = delete; Configuration &operator=(Configuration &&) = delete; diff --git a/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h b/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h index 64d2be0b0..6362324d4 100644 --- a/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h +++ b/sdk/include/opentelemetry/sdk/configuration/configured_sdk.h @@ -48,7 +48,8 @@ class ConfiguredSdk */ void UnInstall(); - opentelemetry::sdk::common::internal_log::LogLevel log_level; + opentelemetry::sdk::common::internal_log::LogLevel log_level{ + opentelemetry::sdk::common::internal_log::LogLevel::Info}; opentelemetry::sdk::resource::Resource resource; std::shared_ptr tracer_provider; std::shared_ptr propagator; diff --git a/sdk/src/configuration/sdk_builder.cc b/sdk/src/configuration/sdk_builder.cc index c07776185..4f2dad094 100644 --- a/sdk/src/configuration/sdk_builder.cc +++ b/sdk/src/configuration/sdk_builder.cc @@ -43,6 +43,7 @@ #include "opentelemetry/sdk/configuration/console_span_exporter_configuration.h" #include "opentelemetry/sdk/configuration/double_array_attribute_value_configuration.h" #include "opentelemetry/sdk/configuration/double_attribute_value_configuration.h" +#include "opentelemetry/sdk/configuration/exemplar_filter.h" #include "opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h" #include "opentelemetry/sdk/configuration/extension_log_record_exporter_builder.h" #include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" @@ -142,7 +143,6 @@ #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/simple_log_record_processor_factory.h" #include "opentelemetry/sdk/metrics/aggregation/aggregation_config.h" -#include "opentelemetry/sdk/metrics/exemplar/filter_type.h" #include "opentelemetry/sdk/metrics/export/metric_producer.h" #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h" #include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_options.h" @@ -180,7 +180,7 @@ #include "src/common/wildcard_match.h" #ifdef ENABLE_METRICS_EXEMPLAR_PREVIEW -# include "opentelemetry/sdk/configuration/exemplar_filter.h" +# include "opentelemetry/sdk/metrics/exemplar/filter_type.h" #endif OPENTELEMETRY_BEGIN_NAMESPACE @@ -724,7 +724,8 @@ class AggregationConfigBuilder aggregation_type = opentelemetry::sdk::metrics::AggregationType::kSum; } - opentelemetry::sdk::metrics::AggregationType aggregation_type; + opentelemetry::sdk::metrics::AggregationType aggregation_type{ + opentelemetry::sdk::metrics::AggregationType::kDefault}; std::unique_ptr aggregation_config; private: @@ -853,7 +854,15 @@ std::unique_ptr SdkBuilder::CreateParentBase std::unique_ptr local_parent_sampled_sdk; std::unique_ptr local_parent_not_sampled_sdk; - auto root_sdk = SdkBuilder::CreateSampler(model->root); + std::unique_ptr root_sdk; + if (model->root) + { + root_sdk = SdkBuilder::CreateSampler(model->root); + } + else + { + root_sdk = opentelemetry::sdk::trace::AlwaysOnSamplerFactory::Create(); + } if (model->remote_parent_sampled != nullptr) { @@ -1055,11 +1064,7 @@ std::unique_ptr SdkBuilder::CreateBatc opentelemetry::sdk::trace::BatchSpanProcessorOptions options; options.schedule_delay_millis = std::chrono::milliseconds(model->schedule_delay); - -#ifdef LATER - options.xxx = model->export_timeout; -#endif - + options.export_timeout = std::chrono::milliseconds(model->export_timeout); options.max_queue_size = model->max_queue_size; options.max_export_batch_size = model->max_export_batch_size; @@ -1159,6 +1164,12 @@ std::unique_ptr SdkBuilder::CreateTra { sampler = CreateSampler(model->sampler); } + else + { + // Spec default: parentbased_always_on + sampler = opentelemetry::sdk::trace::ParentBasedSamplerFactory::Create( + opentelemetry::sdk::trace::AlwaysOnSamplerFactory::Create()); + } std::vector> sdk_processors; @@ -1663,7 +1674,10 @@ void SdkBuilder::AddView( std::shared_ptr sdk_aggregation_config; - sdk_aggregation_config = CreateAggregationConfig(stream->aggregation, sdk_aggregation_type); + if (stream->aggregation) + { + sdk_aggregation_config = CreateAggregationConfig(stream->aggregation, sdk_aggregation_type); + } std::unique_ptr sdk_attribute_processor; @@ -1870,6 +1884,7 @@ SdkBuilder::CreateBatchLogRecordProcessor( opentelemetry::sdk::logs::BatchLogRecordProcessorOptions options; options.schedule_delay_millis = std::chrono::milliseconds(model->schedule_delay); + options.export_timeout_millis = std::chrono::milliseconds(model->export_timeout); options.max_queue_size = model->max_queue_size; options.max_export_batch_size = model->max_export_batch_size; diff --git a/sdk/test/configuration/BUILD b/sdk/test/configuration/BUILD index f16cc267c..7b3f6ddd6 100644 --- a/sdk/test/configuration/BUILD +++ b/sdk/test/configuration/BUILD @@ -9,8 +9,48 @@ cc_test( "sdk_builder_test.cc", ], tags = [ + "config", + "test", + ], + deps = [ + "//sdk:headers", + "//sdk/src/configuration", + "//sdk/src/logs", + "//sdk/src/metrics", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "configured_sdk_test", + srcs = [ + "configured_sdk_test.cc", + ], + tags = [ + "config", + "test", + ], + deps = [ + "//sdk:headers", + "//sdk/src/configuration", + "//sdk/src/logs", + "//sdk/src/metrics", + "//sdk/src/resource", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "programmatic_configuration_test", + srcs = [ + "programmatic_configuration_test.cc", + ], + tags = [ + "config", "test", - "yaml", ], deps = [ "//sdk:headers", diff --git a/sdk/test/configuration/CMakeLists.txt b/sdk/test/configuration/CMakeLists.txt index 5385ddf0d..724fef6ae 100644 --- a/sdk/test/configuration/CMakeLists.txt +++ b/sdk/test/configuration/CMakeLists.txt @@ -1,9 +1,20 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 +foreach(testname sdk_builder_test configured_sdk_test + programmatic_configuration_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} PRIVATE ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_configuration) + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX config. + TEST_LIST ${testname}) +endforeach() + foreach( testname - sdk_builder_test yaml_logs_test yaml_metrics_test yaml_propagator_test @@ -12,10 +23,11 @@ foreach( yaml_trace_test yaml_distribution_test) add_executable(${testname} "${testname}.cc") - target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} opentelemetry_configuration) + target_link_libraries( + ${testname} PRIVATE ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_configuration) gtest_add_tests( TARGET ${testname} - TEST_PREFIX trace. + TEST_PREFIX yaml. TEST_LIST ${testname}) endforeach() diff --git a/sdk/test/configuration/configured_sdk_test.cc b/sdk/test/configuration/configured_sdk_test.cc new file mode 100644 index 000000000..449f4e5e6 --- /dev/null +++ b/sdk/test/configuration/configured_sdk_test.cc @@ -0,0 +1,313 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include + +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/context/propagation/noop_propagator.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/metrics/noop.h" +#include "opentelemetry/metrics/provider.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/trace/noop.h" +#include "opentelemetry/trace/provider.h" + +#include "opentelemetry/sdk/common/exporter_utils.h" +#include "opentelemetry/sdk/common/global_log_handler.h" +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/configured_sdk.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/logger_provider_configuration.h" +#include "opentelemetry/sdk/configuration/meter_provider_configuration.h" +#include "opentelemetry/sdk/configuration/metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/propagator_configuration.h" +#include "opentelemetry/sdk/configuration/push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/resource_configuration.h" +#include "opentelemetry/sdk/configuration/severity_number.h" +#include "opentelemetry/sdk/configuration/simple_log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/simple_span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" + +namespace nostd = opentelemetry::nostd; +namespace trace = opentelemetry::trace; +namespace logs = opentelemetry::logs; +namespace metrics = opentelemetry::metrics; +namespace propagation = opentelemetry::context::propagation; + +namespace common_sdk = opentelemetry::sdk::common; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace config_sdk = opentelemetry::sdk::configuration; +namespace internal_log = opentelemetry::sdk::common::internal_log; + +namespace +{ +//------------------------------------------------------------------------------ +// Noop Exporters + +class NoopSpanExporter : public trace_sdk::SpanExporter +{ +public: + NoopSpanExporter() = default; + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + common_sdk::ExportResult Export( + const nostd::span> &) noexcept override + { + return common_sdk::ExportResult::kSuccess; + } + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } +}; + +class NoopLogRecordExporter : public logs_sdk::LogRecordExporter +{ +public: + NoopLogRecordExporter() = default; + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + common_sdk::ExportResult Export( + const nostd::span> &) noexcept override + { + return common_sdk::ExportResult::kSuccess; + } + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } +}; + +class NoopPushMetricExporter : public metrics_sdk::PushMetricExporter +{ +public: + NoopPushMetricExporter() = default; + common_sdk::ExportResult Export(const metrics_sdk::ResourceMetrics &) noexcept override + { + return common_sdk::ExportResult::kSuccess; + } + metrics_sdk::AggregationTemporality GetAggregationTemporality( + metrics_sdk::InstrumentType) const noexcept override + { + return metrics_sdk::AggregationTemporality::kCumulative; + } + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } +}; + +//------------------------------------------------------------------------------ +// Configuration Builders + +class NoopSpanExporterBuilder : public config_sdk::ExtensionSpanExporterBuilder +{ +public: + std::unique_ptr Build( + const config_sdk::ExtensionSpanExporterConfiguration *) const override + { + return std::make_unique(); + } +}; + +class NoopLogRecordExporterBuilder : public config_sdk::ExtensionLogRecordExporterBuilder +{ +public: + std::unique_ptr Build( + const config_sdk::ExtensionLogRecordExporterConfiguration *) const override + { + return std::make_unique(); + } +}; + +class NoopPushMetricExporterBuilder : public config_sdk::ExtensionPushMetricExporterBuilder +{ +public: + std::unique_ptr Build( + const config_sdk::ExtensionPushMetricExporterConfiguration *) const override + { + return std::make_unique(); + } +}; + +//------------------------------------------------------------------------------ +// ConfiguredSdkTest fixture + +class ConfiguredSdkTest : public ::testing::Test +{ +protected: + void SetUp() override + { + MakeRegistry(); + SetNullProviders(); + } + + void TearDown() override + { + if (sdk_) + { + sdk_->UnInstall(); + } + SetNoopProviders(); + } + + void SetNullProviders() + { + propagation::GlobalTextMapPropagator::SetGlobalPropagator({}); + trace::Provider::SetTracerProvider({}); + logs::Provider::SetLoggerProvider({}); + metrics::Provider::SetMeterProvider({}); + + ASSERT_EQ(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); + ASSERT_EQ(trace::Provider::GetTracerProvider(), nullptr); + ASSERT_EQ(logs::Provider::GetLoggerProvider(), nullptr); + ASSERT_EQ(metrics::Provider::GetMeterProvider(), nullptr); + } + + void SetNoopProviders() + { + propagation::GlobalTextMapPropagator::SetGlobalPropagator( + {std::make_shared()}); + trace::Provider::SetTracerProvider({std::make_shared()}); + logs::Provider::SetLoggerProvider({std::make_shared()}); + metrics::Provider::SetMeterProvider({std::make_shared()}); + + ASSERT_NE(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); + ASSERT_NE(trace::Provider::GetTracerProvider(), nullptr); + ASSERT_NE(logs::Provider::GetLoggerProvider(), nullptr); + ASSERT_NE(metrics::Provider::GetMeterProvider(), nullptr); + } + + void CreateSdk(const std::unique_ptr &model) + { + ASSERT_TRUE(sdk_ == nullptr); + ASSERT_NO_THROW(sdk_ = config_sdk::ConfiguredSdk::Create(registry_, model)); + ASSERT_FALSE(sdk_ == nullptr); + } + + void MakeRegistry() + { + registry_ = std::make_shared(); + registry_->SetExtensionSpanExporterBuilder("noop", std::make_unique()); + registry_->SetExtensionLogRecordExporterBuilder( + "noop", std::make_unique()); + registry_->SetExtensionPushMetricExporterBuilder( + "noop", std::make_unique()); + } + + static std::unique_ptr MakeTracerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "noop"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeLoggerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "noop"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeMeterProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "noop"; + auto reader = std::make_unique(); + reader->exporter = std::move(exporter); + reader->interval = 3'600'000; // milliseconds. Set to a large value and rely on ForceFlush to + // trigger collection. + reader->timeout = 60'000; // milliseconds + auto config = std::make_unique(); + config->readers.emplace_back(std::move(reader)); + return config; + } + + std::shared_ptr registry_; + std::unique_ptr sdk_; +}; + +} // namespace + +//------------------------------------------------------------------------------ +// ConfiguredSdk Tests. These are intended to cover just the API and implementation of the +// ConfiguredSdk class. +// For integration testing to determine if the SDK is configured correctly, see +// the programmatic_configuration_test.cc file + +TEST_F(ConfiguredSdkTest, ConfiguredSdkDefaultLogLevel) +{ + // default log level is info + auto model = std::make_unique(); + model->resource = std::make_unique(); + CreateSdk(model); + sdk_->Install(); + EXPECT_EQ(sdk_->log_level, internal_log::LogLevel::Info); + EXPECT_EQ(internal_log::GlobalLogHandler::GetLogLevel(), internal_log::LogLevel::Info); +} + +TEST_F(ConfiguredSdkTest, ConfiguredSdkSetLogLevel) +{ + auto model = std::make_unique(); + model->resource = std::make_unique(); + model->log_level = config_sdk::SeverityNumber::debug; + CreateSdk(model); + sdk_->Install(); + EXPECT_EQ(sdk_->log_level, internal_log::LogLevel::Debug); + EXPECT_EQ(internal_log::GlobalLogHandler::GetLogLevel(), internal_log::LogLevel::Debug); +} + +TEST_F(ConfiguredSdkTest, ConfiguredSdkInstallUninstall) +{ + auto model = std::make_unique(); + model->resource = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->logger_provider = MakeLoggerProviderConfig(); + model->meter_provider = MakeMeterProviderConfig(); + model->propagator = std::make_unique(); + model->propagator->composite_list = "tracecontext,baggage,b3,b3multi,jaeger"; + + CreateSdk(model); + sdk_->Install(); + EXPECT_NE(trace::Provider::GetTracerProvider(), nullptr); + EXPECT_NE(logs::Provider::GetLoggerProvider(), nullptr); + EXPECT_NE(metrics::Provider::GetMeterProvider(), nullptr); + EXPECT_NE(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); + + sdk_->UnInstall(); + EXPECT_EQ(trace::Provider::GetTracerProvider(), nullptr); + EXPECT_EQ(logs::Provider::GetLoggerProvider(), nullptr); + EXPECT_EQ(metrics::Provider::GetMeterProvider(), nullptr); + EXPECT_EQ(propagation::GlobalTextMapPropagator::GetGlobalPropagator(), nullptr); +} diff --git a/sdk/test/configuration/programmatic_configuration_test.cc b/sdk/test/configuration/programmatic_configuration_test.cc new file mode 100644 index 000000000..492d9a627 --- /dev/null +++ b/sdk/test/configuration/programmatic_configuration_test.cc @@ -0,0 +1,949 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/baggage/baggage.h" +#include "opentelemetry/baggage/baggage_context.h" +#include "opentelemetry/common/key_value_iterable_view.h" +#include "opentelemetry/context/context.h" +#include "opentelemetry/context/propagation/global_propagator.h" +#include "opentelemetry/context/propagation/noop_propagator.h" +#include "opentelemetry/context/propagation/text_map_propagator.h" +#include "opentelemetry/context/runtime_context.h" +#include "opentelemetry/logs/logger.h" +#include "opentelemetry/logs/logger_provider.h" +#include "opentelemetry/logs/noop.h" +#include "opentelemetry/logs/provider.h" +#include "opentelemetry/logs/severity.h" +#include "opentelemetry/metrics/meter.h" +#include "opentelemetry/metrics/meter_provider.h" +#include "opentelemetry/metrics/noop.h" +#include "opentelemetry/metrics/provider.h" +#include "opentelemetry/metrics/sync_instruments.h" +#include "opentelemetry/nostd/shared_ptr.h" +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/context.h" +#include "opentelemetry/trace/noop.h" +#include "opentelemetry/trace/provider.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/tracer.h" +#include "opentelemetry/trace/tracer_provider.h" + +#include "opentelemetry/sdk/configuration/aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/always_off_sampler_configuration.h" +#include "opentelemetry/sdk/configuration/base2_exponential_bucket_histogram_aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/batch_log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/batch_span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/configuration.h" +#include "opentelemetry/sdk/configuration/configured_sdk.h" +#include "opentelemetry/sdk/configuration/explicit_bucket_histogram_aggregation_configuration.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_builder.h" +#include "opentelemetry/sdk/configuration/extension_span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/instrument_type.h" +#include "opentelemetry/sdk/configuration/log_record_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_limits_configuration.h" +#include "opentelemetry/sdk/configuration/log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/logger_config_configuration.h" +#include "opentelemetry/sdk/configuration/logger_configurator_configuration.h" +#include "opentelemetry/sdk/configuration/logger_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/logger_provider_configuration.h" +#include "opentelemetry/sdk/configuration/meter_config_configuration.h" +#include "opentelemetry/sdk/configuration/meter_configurator_configuration.h" +#include "opentelemetry/sdk/configuration/meter_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/meter_provider_configuration.h" +#include "opentelemetry/sdk/configuration/metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/parent_based_sampler_configuration.h" +#include "opentelemetry/sdk/configuration/periodic_metric_reader_configuration.h" +#include "opentelemetry/sdk/configuration/propagator_configuration.h" +#include "opentelemetry/sdk/configuration/push_metric_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/resource_configuration.h" +#include "opentelemetry/sdk/configuration/sampler_configuration.h" +#include "opentelemetry/sdk/configuration/severity_number.h" +#include "opentelemetry/sdk/configuration/simple_log_record_processor_configuration.h" +#include "opentelemetry/sdk/configuration/simple_span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/span_exporter_configuration.h" +#include "opentelemetry/sdk/configuration/span_processor_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_config_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_configurator_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/tracer_provider_configuration.h" +#include "opentelemetry/sdk/configuration/view_configuration.h" +#include "opentelemetry/sdk/configuration/view_selector_configuration.h" +#include "opentelemetry/sdk/configuration/view_stream_configuration.h" + +#include "opentelemetry/sdk/common/exporter_utils.h" +#include "opentelemetry/sdk/logs/exporter.h" +#include "opentelemetry/sdk/logs/logger_provider.h" +#include "opentelemetry/sdk/logs/read_write_log_record.h" +#include "opentelemetry/sdk/metrics/data/metric_data.h" +#include "opentelemetry/sdk/metrics/data/point_data.h" +#include "opentelemetry/sdk/metrics/export/metric_producer.h" +#include "opentelemetry/sdk/metrics/instruments.h" +#include "opentelemetry/sdk/metrics/meter_provider.h" +#include "opentelemetry/sdk/metrics/push_metric_exporter.h" +#include "opentelemetry/sdk/resource/resource.h" +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" + +namespace nostd = opentelemetry::nostd; +namespace trace = opentelemetry::trace; +namespace logs = opentelemetry::logs; +namespace metrics = opentelemetry::metrics; +namespace common = opentelemetry::common; +namespace baggage = opentelemetry::baggage; +namespace propagation = opentelemetry::context::propagation; +namespace context = opentelemetry::context; +namespace common_sdk = opentelemetry::sdk::common; +namespace logs_sdk = opentelemetry::sdk::logs; +namespace metrics_sdk = opentelemetry::sdk::metrics; +namespace trace_sdk = opentelemetry::sdk::trace; +namespace config_sdk = opentelemetry::sdk::configuration; + +namespace +{ +// --------------------------------------------------------------------------- +// Shared export buffers + +using LogRecordBuffer = std::vector>; +using SpanBuffer = std::vector>; +using MetricBuffer = std::vector; + +// --------------------------------------------------------------------------- +// Recording exporters to support integration testing of the configured SDK. +// These exporters record the data they receive into a buffer for later inspection. + +class RecordingSpanExporter : public trace_sdk::SpanExporter +{ +public: + explicit RecordingSpanExporter(std::shared_ptr buffer) : buffer_(std::move(buffer)) {} + + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + + common_sdk::ExportResult Export( + const nostd::span> &spans) noexcept override + { + for (auto &span : spans) + { + buffer_->emplace_back(static_cast(span.release())); + } + return common_sdk::ExportResult::kSuccess; + } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr buffer_; +}; + +class RecordingLogRecordExporter : public logs_sdk::LogRecordExporter +{ +public: + explicit RecordingLogRecordExporter(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + + std::unique_ptr MakeRecordable() noexcept override + { + return std::make_unique(); + } + + common_sdk::ExportResult Export( + const nostd::span> &records) noexcept override + { + for (auto &rec : records) + { + buffer_->emplace_back(static_cast(rec.release())); + } + return common_sdk::ExportResult::kSuccess; + } + + bool RecordableEnforcesLogRecordLimits() const noexcept override { return true; } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr buffer_; +}; + +class RecordingPushMetricExporter : public metrics_sdk::PushMetricExporter +{ +public: + explicit RecordingPushMetricExporter(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + + common_sdk::ExportResult Export( + const metrics_sdk::ResourceMetrics &resource_metrics) noexcept override + { + for (const auto &scope : resource_metrics.scope_metric_data_) + { + for (const auto &metric : scope.metric_data_) + { + buffer_->emplace_back(metric); + } + } + return common_sdk::ExportResult::kSuccess; + } + + metrics_sdk::AggregationTemporality GetAggregationTemporality( + metrics_sdk::InstrumentType) const noexcept override + { + return metrics_sdk::AggregationTemporality::kCumulative; + } + + bool ForceFlush(std::chrono::microseconds) noexcept override { return true; } + bool Shutdown(std::chrono::microseconds) noexcept override { return true; } + +private: + std::shared_ptr buffer_; +}; + +// --------------------------------------------------------------------------- +// Configuration Builders for recording exporters + +class RecordingSpanExporterBuilder : public config_sdk::ExtensionSpanExporterBuilder +{ +public: + explicit RecordingSpanExporterBuilder(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + std::unique_ptr Build( + const config_sdk::ExtensionSpanExporterConfiguration *) const override + { + return std::make_unique(buffer_); + } + +private: + std::shared_ptr buffer_; +}; + +class RecordingLogRecordExporterBuilder : public config_sdk::ExtensionLogRecordExporterBuilder +{ +public: + explicit RecordingLogRecordExporterBuilder(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + std::unique_ptr Build( + const config_sdk::ExtensionLogRecordExporterConfiguration *) const override + { + return std::make_unique(buffer_); + } + +private: + std::shared_ptr buffer_; +}; + +class RecordingPushMetricExporterBuilder : public config_sdk::ExtensionPushMetricExporterBuilder +{ +public: + explicit RecordingPushMetricExporterBuilder(std::shared_ptr buffer) + : buffer_(std::move(buffer)) + {} + std::unique_ptr Build( + const config_sdk::ExtensionPushMetricExporterConfiguration *) const override + { + auto exporter = std::make_unique(buffer_); + return exporter; + } + +private: + std::shared_ptr buffer_; +}; + +// --------------------------------------------------------------------------- +// TextMapCarrier for propagator tests. + +class MapCarrier : public propagation::TextMapCarrier +{ +public: + nostd::string_view Get(nostd::string_view key) const noexcept override + { + auto it = map_.find(std::string(key)); + return it != map_.end() ? nostd::string_view(it->second) : ""; + } + void Set(nostd::string_view key, nostd::string_view value) noexcept override + { + map_[std::string(key)] = std::string(value); + } + + const std::map &map() const { return map_; } + +private: + std::map map_; +}; + +//--------------------------------------------------------------------------- +// ProgrammaticConfigTest fixture: This supports integration testing of the configured SDK. +// It registers the recording exporters and maintains buffers for inspection of exported signal +// data. + +class ProgrammaticConfigTest : public ::testing::Test +{ +protected: + void SetUp() override + { + MakeRegistry(); + + propagation::GlobalTextMapPropagator::SetGlobalPropagator( + {std::make_shared()}); + trace::Provider::SetTracerProvider({std::make_shared()}); + logs::Provider::SetLoggerProvider({std::make_shared()}); + metrics::Provider::SetMeterProvider({std::make_shared()}); + } + + void TearDown() override + { + if (sdk_) + { + sdk_->UnInstall(); + } + } + + void CreateAndInstallSdk(const std::unique_ptr &model) + { + ASSERT_TRUE(sdk_ == nullptr); + ASSERT_NO_THROW(sdk_ = config_sdk::ConfiguredSdk::Create(registry_, model)); + ASSERT_FALSE(sdk_ == nullptr); + sdk_->Install(); + } + + void MakeRegistry() + { + registry_ = std::make_shared(); + registry_->SetExtensionSpanExporterBuilder( + "recording", std::make_unique(span_buffer_)); + registry_->SetExtensionLogRecordExporterBuilder( + "recording", std::make_unique(log_buffer_)); + registry_->SetExtensionPushMetricExporterBuilder( + "recording", std::make_unique(metric_buffer_)); + } + + static std::unique_ptr MakeTracerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeLoggerProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto config = std::make_unique(); + config->processors.emplace_back(std::move(processor)); + return config; + } + + static std::unique_ptr MakeMeterProviderConfig() + { + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto reader = std::make_unique(); + reader->exporter = std::move(exporter); + reader->interval = 3'600'000; // milliseconds. Set to a large value and rely on ForceFlush to + // trigger collection. + reader->timeout = 60'000; // milliseconds + auto config = std::make_unique(); + config->readers.emplace_back(std::move(reader)); + return config; + } + + std::shared_ptr span_buffer_{std::make_shared()}; + std::shared_ptr log_buffer_{std::make_shared()}; + std::shared_ptr metric_buffer_{std::make_shared()}; + + std::shared_ptr registry_; + std::unique_ptr sdk_; +}; + +} // namespace + +//--------------------------------------------------------------------------- +// Resource configuration tests + +TEST_F(ProgrammaticConfigTest, ResourceAttributesFromList) +{ + auto resource_config = std::make_unique(); + resource_config->attributes_list = "service.name=test-service,service.version=1.0"; + auto model = std::make_unique(); + model->resource = std::move(resource_config); + + CreateAndInstallSdk(model); + + const auto &attributes = sdk_->resource.GetAttributes(); + ASSERT_NE(attributes.find("service.name"), attributes.end()); + EXPECT_EQ(nostd::get(attributes.at("service.name")), "test-service"); + ASSERT_NE(attributes.find("service.version"), attributes.end()); + EXPECT_EQ(nostd::get(attributes.at("service.version")), "1.0"); +} + +//-------------------------------------------------------------------------- +// Disabled SDK configuration tests + +TEST_F(ProgrammaticConfigTest, DisabledConfigProducesNullProviders) +{ + auto model = std::make_unique(); + model->disabled = true; + model->tracer_provider = MakeTracerProviderConfig(); + model->logger_provider = MakeLoggerProviderConfig(); + model->meter_provider = MakeMeterProviderConfig(); + + CreateAndInstallSdk(model); + + EXPECT_EQ(sdk_->tracer_provider, nullptr); + EXPECT_EQ(sdk_->logger_provider, nullptr); + EXPECT_EQ(sdk_->meter_provider, nullptr); + EXPECT_EQ(sdk_->propagator, nullptr); +} + +TEST_F(ProgrammaticConfigTest, EnabledConfigProducesProviders) +{ + auto model = std::make_unique(); + model->disabled = false; + model->tracer_provider = MakeTracerProviderConfig(); + model->logger_provider = MakeLoggerProviderConfig(); + model->meter_provider = MakeMeterProviderConfig(); + model->propagator = std::make_unique(); + model->propagator->composite_list = "tracecontext"; + + CreateAndInstallSdk(model); + + EXPECT_NE(sdk_->tracer_provider, nullptr); + EXPECT_NE(sdk_->logger_provider, nullptr); + EXPECT_NE(sdk_->meter_provider, nullptr); + EXPECT_NE(sdk_->propagator, nullptr); +} + +//--------------------------------------------------------------------------- +// LoggerProvider tests + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithDefaults) +{ + auto logger_provider_config = MakeLoggerProviderConfig(); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + + ASSERT_NE(sdk_->logger_provider, nullptr); + + auto logger = logs::Provider::GetLoggerProvider()->GetLogger("test"); + logger->EmitLogRecord( + logs::Severity::kInfo, nostd::string_view("test-message"), + common::MakeAttributes({{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}})); + + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithLogRecordLimits) +{ + config_sdk::LogRecordLimitsConfiguration limits{ + 0, 0}; // TODO: Remove the default initialization once the limit members are initalized. + limits.attribute_count_limit = 2; + limits.attribute_value_length_limit = 5; + + auto logger_provider_config = MakeLoggerProviderConfig(); + logger_provider_config->limits = + std::make_unique(limits); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + auto logger = logs::Provider::GetLoggerProvider()->GetLogger("test"); + logger->EmitLogRecord( + logs::Severity::kInfo, nostd::string_view("test-message"), + common::MakeAttributes({{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}})); + + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); + auto *record = log_buffer_->front().get(); + EXPECT_EQ(nostd::get(record->GetBody()), "test-message"); + const auto &attributes = record->GetAttributes(); + EXPECT_EQ(attributes.size(), limits.attribute_count_limit); + for (const auto &attr : attributes) + { + EXPECT_EQ(nostd::get(attr.second).size(), limits.attribute_value_length_limit); + } +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithLoggerConfigurator) +{ + auto error_logger_matcher = config_sdk::LoggerMatcherAndConfigConfiguration(); + error_logger_matcher.name = "error_logger"; + error_logger_matcher.config.enabled = true; + error_logger_matcher.config.minimum_severity = config_sdk::SeverityNumber::error; + + auto logger_configurator = std::make_unique(); + logger_configurator->default_config.enabled = true; + logger_configurator->default_config.minimum_severity = config_sdk::SeverityNumber::info; + logger_configurator->loggers.emplace_back(std::move(error_logger_matcher)); + + auto logger_provider_config = MakeLoggerProviderConfig(); + logger_provider_config->logger_configurator = std::move(logger_configurator); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + // The default logger should be enabled and have a minimum severity of info + auto default_logger = logs::Provider::GetLoggerProvider()->GetLogger("default_logger"); + default_logger->EmitLogRecord( + logs::Severity::kInfo, nostd::string_view("test-message"), + common::MakeAttributes({{"key1", "value1"}, {"key2", "value2"}, {"key3", "value3"}})); + EXPECT_TRUE(default_logger->Enabled(logs::Severity::kInfo)); + + // The error_logger should be enabled and have a minimum severity of error + auto error_logger = logs::Provider::GetLoggerProvider()->GetLogger("error_logger"); + error_logger->EmitLogRecord(logs::Severity::kError, nostd::string_view("test-message")); + + EXPECT_FALSE(error_logger->Enabled(logs::Severity::kInfo)); + EXPECT_TRUE(error_logger->Enabled(logs::Severity::kError)); + + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 2); +} + +// TODO: Re-enable this test once the BatchLogRecordProcessorConfiguration is initialized with spec +// defaults. +TEST_F(ProgrammaticConfigTest, DISABLED_LoggerProviderWithBatchProcessorDefaults) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto logger_provider_config = std::make_unique(); + logger_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + logs::Provider::GetLoggerProvider()->GetLogger("test")->Info("test-message"); + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, LoggerProviderWithBatchProcessorConfigured) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + processor->schedule_delay = 60000; + processor->max_queue_size = 100; + processor->max_export_batch_size = 50; + processor->export_timeout = 5000; + auto logger_provider_config = std::make_unique(); + logger_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->logger_provider = std::move(logger_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->logger_provider, nullptr); + + logs::Provider::GetLoggerProvider()->GetLogger("test")->Info("test-message"); + ASSERT_TRUE(sdk_->logger_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->logger_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(log_buffer_->size(), 1); +} + +//-------------------------------------------------------------------------- +// MeterProvider tests + +TEST_F(ProgrammaticConfigTest, MeterProviderWithDefaults) +{ + auto model = std::make_unique(); + model->meter_provider = MakeMeterProviderConfig(); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->meter_provider, nullptr); + + metrics::Provider::GetMeterProvider() + ->GetMeter("test") + ->CreateUInt64Counter("test-counter") + ->Add(1); + + ASSERT_TRUE(sdk_->meter_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->meter_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(metric_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, MeterProviderWithMeterConfigurator) +{ + auto disabled_meter_config = config_sdk::MeterMatcherAndConfigConfiguration(); + disabled_meter_config.name = "disabled-meter"; + disabled_meter_config.config.enabled = false; + + auto meter_configurator = std::make_unique(); + meter_configurator->default_config.enabled = true; + meter_configurator->meters.push_back(disabled_meter_config); + + auto model = std::make_unique(); + model->meter_provider = MakeMeterProviderConfig(); + model->meter_provider->meter_configurator = std::move(meter_configurator); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->meter_provider, nullptr); + + auto default_meter = metrics::Provider::GetMeterProvider()->GetMeter("default-meter"); + default_meter->CreateUInt64Counter("test-counter")->Add(1); + + auto disabled_meter = metrics::Provider::GetMeterProvider()->GetMeter(disabled_meter_config.name); + disabled_meter->CreateUInt64Counter("disabled-test-counter")->Add(1); + + ASSERT_TRUE(sdk_->meter_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->meter_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(metric_buffer_->size(), 1); + for (const auto &metric : *metric_buffer_) + { + EXPECT_NE(metric.instrument_descriptor.name_, "disabled-test-counter"); + } +} + +TEST_F(ProgrammaticConfigTest, MeterProviderWithViews) +{ + // View 1: Base2 exponential aggregation + const std::size_t max_scale = 10; + const std::size_t max_buckets = 135; + auto base2_histogram_aggregation = + std::make_unique(); + base2_histogram_aggregation->max_scale = max_scale; + base2_histogram_aggregation->max_size = max_buckets; + + auto base2_histogram_stream = std::make_unique(); + base2_histogram_stream->aggregation = std::move(base2_histogram_aggregation); + + auto base2_histogram_selector = std::make_unique(); + base2_histogram_selector->instrument_type = config_sdk::InstrumentType::histogram; + base2_histogram_selector->instrument_name = "exponential-histogram"; + + auto base2_histogram_view = std::make_unique(); + base2_histogram_view->selector = std::move(base2_histogram_selector); + base2_histogram_view->stream = std::move(base2_histogram_stream); + + // View 2: Explicit bucket histogram aggregation. + auto explicit_histogram_aggregation = + std::make_unique(); + explicit_histogram_aggregation->boundaries = {0.0, 10.0, 50.0, 100.0}; + explicit_histogram_aggregation->record_min_max = true; + + auto explicit_histogram_stream = std::make_unique(); + explicit_histogram_stream->aggregation = std::move(explicit_histogram_aggregation); + + auto explicit_selector = std::make_unique(); + explicit_selector->instrument_type = config_sdk::InstrumentType::histogram; + explicit_selector->instrument_name = "explicit-histogram"; + + auto explicit_histogram_view = std::make_unique(); + explicit_histogram_view->selector = std::move(explicit_selector); + explicit_histogram_view->stream = std::move(explicit_histogram_stream); + + // View 3: no aggregation set + auto default_stream = std::make_unique(); + default_stream->aggregation = nullptr; // intentionally null + + auto default_selector = std::make_unique(); + default_selector->instrument_type = config_sdk::InstrumentType::counter; + default_selector->instrument_name = "default-counter"; + + auto default_view = std::make_unique(); + default_view->selector = std::move(default_selector); + default_view->stream = std::move(default_stream); + + auto meter_provider_config = MakeMeterProviderConfig(); + meter_provider_config->views.emplace_back(std::move(base2_histogram_view)); + meter_provider_config->views.emplace_back(std::move(explicit_histogram_view)); + meter_provider_config->views.emplace_back(std::move(default_view)); + + auto model = std::make_unique(); + model->meter_provider = std::move(meter_provider_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->meter_provider, nullptr); + + auto context = context::Context{}; + auto meter = metrics::Provider::GetMeterProvider()->GetMeter("test"); + meter->CreateDoubleHistogram("exponential-histogram")->Record(42.0, context); + meter->CreateDoubleHistogram("explicit-histogram")->Record(42.0, context); + meter->CreateUInt64Counter("default-counter")->Add(1, context); + ASSERT_TRUE(sdk_->meter_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->meter_provider->Shutdown(std::chrono::milliseconds(5000))); + + // check that instances of the three data points were collected and are of the right type. + EXPECT_GE(metric_buffer_->size(), 3); + bool found_base2_histogram = false; + bool found_explicit_histogram = false; + bool found_default_counter = false; + + for (const auto &metric : *metric_buffer_) + { + auto &point_data = metric.point_data_attr_.front().point_data; + if (nostd::holds_alternative(point_data)) + { + found_base2_histogram = true; + auto &base2_point_data = + nostd::get(point_data); + EXPECT_EQ(base2_point_data.max_buckets_, max_buckets); + EXPECT_LE(base2_point_data.scale_, max_scale); + } + else if (nostd::holds_alternative(point_data)) + { + found_explicit_histogram = true; + } + else if (nostd::holds_alternative(point_data)) + { + found_default_counter = true; + } + } + + EXPECT_TRUE(found_base2_histogram); + EXPECT_TRUE(found_explicit_histogram); + EXPECT_TRUE(found_default_counter); +} + +//--------------------------------------------------------------------------- +// TracerProvider tests + +TEST_F(ProgrammaticConfigTest, TracerProviderWithDefaults) +{ + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + auto default_tracer = trace::Provider::GetTracerProvider()->GetTracer("default-tracer"); + default_tracer->StartSpan("test-span")->End(); + + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_EQ(span_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithTracerConfigurator) +{ + auto disabled_tracer_matcher = config_sdk::TracerMatcherAndConfigConfiguration(); + disabled_tracer_matcher.name = "disabled-tracer"; + disabled_tracer_matcher.config.enabled = false; + + auto tracer_configurator = std::make_unique(); + tracer_configurator->default_config.enabled = true; + tracer_configurator->tracers.push_back(disabled_tracer_matcher); + + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->tracer_provider->tracer_configurator = std::move(tracer_configurator); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + auto default_tracer = trace::Provider::GetTracerProvider()->GetTracer("default-tracer"); + default_tracer->StartSpan("test-span")->End(); + + auto disabled_tracer = trace::Provider::GetTracerProvider()->GetTracer("disabled-tracer"); + disabled_tracer->StartSpan("disabled-test-span")->End(); + + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + + ASSERT_EQ(span_buffer_->size(), 1); + EXPECT_NE(span_buffer_->at(0)->GetName(), "disabled-test-span"); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithSampler) +{ + auto sampler = std::make_unique(); + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->tracer_provider->sampler = std::move(sampler); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_EQ(span_buffer_->size(), 0); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithParentBasedSamplerNullRoot) +{ + auto sampler = std::make_unique(); + sampler->root = nullptr; // explicitly null + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->tracer_provider->sampler = std::move(sampler); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->tracer_provider, nullptr); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_EQ(span_buffer_->size(), 1); +} + +// TODO: Re-enable this test once the BatchSpanProcessorConfiguration is initialized with spec +// defaults. +TEST_F(ProgrammaticConfigTest, DISABLED_TracerProviderWithBatchProcessor) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->exporter = std::move(exporter); + auto tracer_provider_config = std::make_unique(); + tracer_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->tracer_provider = std::move(tracer_provider_config); + + CreateAndInstallSdk(model); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(span_buffer_->size(), 1); +} + +TEST_F(ProgrammaticConfigTest, TracerProviderWithBatchProcessorConfigured) +{ + auto exporter = std::make_unique(); + exporter->name = "recording"; + auto processor = std::make_unique(); + processor->schedule_delay = 60000; + processor->max_queue_size = 100; + processor->max_export_batch_size = 50; + processor->export_timeout = 5000; + processor->exporter = std::move(exporter); + auto tracer_provider_config = std::make_unique(); + tracer_provider_config->processors.emplace_back(std::move(processor)); + + auto model = std::make_unique(); + model->tracer_provider = std::move(tracer_provider_config); + + CreateAndInstallSdk(model); + + trace::Provider::GetTracerProvider()->GetTracer("test")->StartSpan("test-span")->End(); + ASSERT_TRUE(sdk_->tracer_provider->ForceFlush(std::chrono::milliseconds(5000))); + ASSERT_TRUE(sdk_->tracer_provider->Shutdown(std::chrono::milliseconds(5000))); + + EXPECT_GE(span_buffer_->size(), 1); +} + +//--------------------------------------------------------------------------- +// Propagator tests + +namespace +{ +void CheckPropagators() +{ + auto tracer = trace::Provider::GetTracerProvider()->GetTracer("test"); + + auto span = tracer->StartSpan("test-span"); + auto ctx0 = context::RuntimeContext::GetCurrent(); + auto ctx1 = trace::SetSpan(ctx0, span); + + // Add baggage so the baggage propagator has something to inject + auto baggage = baggage::Baggage::GetDefault()->Set("key", "value"); + auto ctx = baggage::SetBaggage(ctx1, baggage); + + MapCarrier carrier; + propagation::GlobalTextMapPropagator::GetGlobalPropagator()->Inject(carrier, ctx); + + ASSERT_NE(carrier.map().find("traceparent"), carrier.map().end()); // tracecontext + // traceparent format: "00-<32 hex trace_id>-<16 hex span_id>-<2 hex flags>" = 55 chars + const std::string &traceparent = carrier.map().at("traceparent"); + EXPECT_EQ(traceparent.size(), 55U); + EXPECT_EQ(traceparent.substr(0, 3), "00-"); + EXPECT_NE(carrier.map().find("baggage"), carrier.map().end()); // baggage + EXPECT_NE(carrier.map().find("b3"), carrier.map().end()); // b3 single + EXPECT_NE(carrier.map().find("X-B3-TraceId"), carrier.map().end()); // b3multi + EXPECT_NE(carrier.map().find("uber-trace-id"), carrier.map().end()); // jaeger +} +} // namespace + +TEST_F(ProgrammaticConfigTest, PropagatorsCompositeList) +{ + auto propagator_config = std::make_unique(); + propagator_config->composite_list = "tracecontext,baggage,b3,b3multi,jaeger"; + + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->propagator = std::move(propagator_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->propagator, nullptr); + + CheckPropagators(); +} + +TEST_F(ProgrammaticConfigTest, PropagatorsComposite) +{ + auto propagator_config = std::make_unique(); + propagator_config->composite.emplace_back("tracecontext"); + propagator_config->composite.emplace_back("baggage"); + propagator_config->composite.emplace_back("b3"); + propagator_config->composite.emplace_back("b3multi"); + propagator_config->composite.emplace_back("jaeger"); + + auto model = std::make_unique(); + model->tracer_provider = MakeTracerProviderConfig(); + model->propagator = std::move(propagator_config); + + CreateAndInstallSdk(model); + ASSERT_NE(sdk_->propagator, nullptr); + + CheckPropagators(); +} diff --git a/sdk/test/configuration/sdk_builder_test.cc b/sdk/test/configuration/sdk_builder_test.cc index cccb6d37e..736156150 100644 --- a/sdk/test/configuration/sdk_builder_test.cc +++ b/sdk/test/configuration/sdk_builder_test.cc @@ -2,24 +2,39 @@ // SPDX-License-Identifier: Apache-2.0 #include + #include #include +#include #include - #include "opentelemetry/logs/severity.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/sdk/configuration/always_off_sampler_configuration.h" +#include "opentelemetry/sdk/configuration/always_on_sampler_configuration.h" #include "opentelemetry/sdk/configuration/logger_config_configuration.h" #include "opentelemetry/sdk/configuration/logger_configurator_configuration.h" #include "opentelemetry/sdk/configuration/logger_matcher_and_config_configuration.h" +#include "opentelemetry/sdk/configuration/parent_based_sampler_configuration.h" #include "opentelemetry/sdk/configuration/registry.h" +#include "opentelemetry/sdk/configuration/sampler_configuration.h" #include "opentelemetry/sdk/configuration/sdk_builder.h" #include "opentelemetry/sdk/configuration/severity_number.h" +#include "opentelemetry/sdk/configuration/trace_id_ratio_based_sampler_configuration.h" #include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h" #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" #include "opentelemetry/sdk/logs/logger_config.h" +#include "opentelemetry/sdk/trace/sampler.h" -namespace config_sdk = opentelemetry::sdk::configuration; -namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace logs = opentelemetry::logs; namespace logs_sdk = opentelemetry::sdk::logs; +namespace scope_sdk = opentelemetry::sdk::instrumentationscope; +namespace config_sdk = opentelemetry::sdk::configuration; + +//------------------------------------------------------------------------------ +// Tests for the SdkBuilder class methods that create SDK components from configuration models +// These tests focus on the API of the SdkBuilder for creating SDK components that can be +// independently verified. For full integration tests of the SdkBuilder with configuration models, +// see the programmatic_configuration_test.cc file. TEST(SdkBuilder, CreateLoggerConfigurator) { @@ -61,14 +76,93 @@ TEST(SdkBuilder, CreateLoggerConfigurator) logs_sdk::LoggerConfig sdk_logger_config_2 = logger_configurator->ComputeConfig(*scope_2); EXPECT_TRUE(sdk_logger_config_default.IsEnabled()); - EXPECT_EQ(sdk_logger_config_default.GetMinimumSeverity(), opentelemetry::logs::Severity::kWarn); + EXPECT_EQ(sdk_logger_config_default.GetMinimumSeverity(), logs::Severity::kWarn); EXPECT_FALSE(sdk_logger_config_default.IsTraceBased()); EXPECT_TRUE(sdk_logger_config_1.IsEnabled()); - EXPECT_EQ(sdk_logger_config_1.GetMinimumSeverity(), opentelemetry::logs::Severity::kError3); + EXPECT_EQ(sdk_logger_config_1.GetMinimumSeverity(), logs::Severity::kError3); EXPECT_FALSE(sdk_logger_config_1.IsTraceBased()); EXPECT_FALSE(sdk_logger_config_2.IsEnabled()); - EXPECT_EQ(sdk_logger_config_2.GetMinimumSeverity(), opentelemetry::logs::Severity::kDebug); + EXPECT_EQ(sdk_logger_config_2.GetMinimumSeverity(), logs::Severity::kDebug); EXPECT_TRUE(sdk_logger_config_2.IsTraceBased()); } + +TEST(SdkBuilder, CreateParentBasedSampler) +{ + // parent based with no root configured should default to always on + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + parent_based_sampler_config.root = nullptr; + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, R"(ParentBased{AlwaysOnSampler})"); + } + + // parent based with root always on + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + parent_based_sampler_config.root = std::make_unique(); + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, R"(ParentBased{AlwaysOnSampler})"); + } + + // parent based with root always off + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + parent_based_sampler_config.root = + std::make_unique(); + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, R"(ParentBased{AlwaysOffSampler})"); + } + + // parent based with a custom root sampler + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + auto trace_id_ratio_based_sampler_config = + std::make_unique(); + trace_id_ratio_based_sampler_config->ratio = 0.5; + parent_based_sampler_config.root = std::move(trace_id_ratio_based_sampler_config); + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, + R"(ParentBased{TraceIdRatioBasedSampler{0.500000}})"); + } + + // parent based with all sub samplers set + { + config_sdk::ParentBasedSamplerConfiguration parent_based_sampler_config; + auto trace_id_ratio_based_sampler_config = + std::make_unique(); + trace_id_ratio_based_sampler_config->ratio = 0.25; + parent_based_sampler_config.root = std::move(trace_id_ratio_based_sampler_config); + + auto always_off_sampler_config = std::make_unique(); + parent_based_sampler_config.remote_parent_sampled = std::move(always_off_sampler_config); + + auto always_on_sampler_config = std::make_unique(); + parent_based_sampler_config.remote_parent_not_sampled = std::move(always_on_sampler_config); + + auto trace_id_ratio_based_sampler_config_2 = + std::make_unique(); + trace_id_ratio_based_sampler_config_2->ratio = 0.35; + parent_based_sampler_config.local_parent_sampled = + std::move(trace_id_ratio_based_sampler_config_2); + + auto always_off_sampler_config_2 = + std::make_unique(); + parent_based_sampler_config.local_parent_not_sampled = std::move(always_off_sampler_config_2); + + config_sdk::SdkBuilder builder(std::make_shared()); + auto sampler = builder.CreateParentBasedSampler(&parent_based_sampler_config); + ASSERT_NE(sampler, nullptr); + EXPECT_EQ(std::string{sampler->GetDescription()}, + R"(ParentBased{TraceIdRatioBasedSampler{0.250000}})"); + } +} From 59486187d291c09441a5fb27440903d820a6a7c3 Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Mon, 13 Jul 2026 15:49:41 -0400 Subject: [PATCH 2/4] add changelog entry. fix spelling in test --- CHANGELOG.md | 3 +++ sdk/test/configuration/programmatic_configuration_test.cc | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c57ff645..be28afb3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Increment the: ## [Unreleased] +* [CONFIGURATION] Programmatic configuration use case tests and fixes + [#4243](https://github.com/open-telemetry/opentelemetry-cpp/pull/4243) + * [SDK] Apply metric cardinality limits to non-overflow attribute sets and reserve the overflow point separately. [#4236](https://github.com/open-telemetry/opentelemetry-cpp/pull/4236) diff --git a/sdk/test/configuration/programmatic_configuration_test.cc b/sdk/test/configuration/programmatic_configuration_test.cc index 492d9a627..0fec89094 100644 --- a/sdk/test/configuration/programmatic_configuration_test.cc +++ b/sdk/test/configuration/programmatic_configuration_test.cc @@ -470,7 +470,7 @@ TEST_F(ProgrammaticConfigTest, LoggerProviderWithDefaults) TEST_F(ProgrammaticConfigTest, LoggerProviderWithLogRecordLimits) { config_sdk::LogRecordLimitsConfiguration limits{ - 0, 0}; // TODO: Remove the default initialization once the limit members are initalized. + 0, 0}; // TODO: Remove the default initialization once the limit members are initialized. limits.attribute_count_limit = 2; limits.attribute_value_length_limit = 5; From 1ba3a7b25f57080d6c1b0da6c77aecea8ea07c9b Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Thu, 16 Jul 2026 17:39:02 -0400 Subject: [PATCH 3/4] enable batch processor configuration tests --- sdk/test/configuration/programmatic_configuration_test.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/test/configuration/programmatic_configuration_test.cc b/sdk/test/configuration/programmatic_configuration_test.cc index 0fec89094..2c07365cd 100644 --- a/sdk/test/configuration/programmatic_configuration_test.cc +++ b/sdk/test/configuration/programmatic_configuration_test.cc @@ -544,9 +544,7 @@ TEST_F(ProgrammaticConfigTest, LoggerProviderWithLoggerConfigurator) EXPECT_GE(log_buffer_->size(), 2); } -// TODO: Re-enable this test once the BatchLogRecordProcessorConfiguration is initialized with spec -// defaults. -TEST_F(ProgrammaticConfigTest, DISABLED_LoggerProviderWithBatchProcessorDefaults) +TEST_F(ProgrammaticConfigTest, LoggerProviderWithBatchProcessorDefaults) { auto exporter = std::make_unique(); exporter->name = "recording"; @@ -836,7 +834,7 @@ TEST_F(ProgrammaticConfigTest, TracerProviderWithParentBasedSamplerNullRoot) // TODO: Re-enable this test once the BatchSpanProcessorConfiguration is initialized with spec // defaults. -TEST_F(ProgrammaticConfigTest, DISABLED_TracerProviderWithBatchProcessor) +TEST_F(ProgrammaticConfigTest, TracerProviderWithBatchProcessor) { auto exporter = std::make_unique(); exporter->name = "recording"; From b5c19ecb43623f3486bcc82767bb25de24a3fc11 Mon Sep 17 00:00:00 2001 From: Douglas Barker Date: Fri, 17 Jul 2026 10:47:53 -0400 Subject: [PATCH 4/4] disable the test cases that rely on the PeriodicExportingMetricReader due to threading issues --- .../configuration/programmatic_configuration_test.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sdk/test/configuration/programmatic_configuration_test.cc b/sdk/test/configuration/programmatic_configuration_test.cc index 2c07365cd..6e00c6c52 100644 --- a/sdk/test/configuration/programmatic_configuration_test.cc +++ b/sdk/test/configuration/programmatic_configuration_test.cc @@ -595,7 +595,10 @@ TEST_F(ProgrammaticConfigTest, LoggerProviderWithBatchProcessorConfigured) //-------------------------------------------------------------------------- // MeterProvider tests -TEST_F(ProgrammaticConfigTest, MeterProviderWithDefaults) +// TODO: These test cases may timeout due to threading in the PeriodicExportingMetricReader +// that cause ForceFlush or Shutdown to block indefinitely. Disabling for now until we can fix the +// underlying issue. +TEST_F(ProgrammaticConfigTest, DISABLED_MeterProviderWithDefaults) { auto model = std::make_unique(); model->meter_provider = MakeMeterProviderConfig(); @@ -614,7 +617,7 @@ TEST_F(ProgrammaticConfigTest, MeterProviderWithDefaults) EXPECT_GE(metric_buffer_->size(), 1); } -TEST_F(ProgrammaticConfigTest, MeterProviderWithMeterConfigurator) +TEST_F(ProgrammaticConfigTest, DISABLED_MeterProviderWithMeterConfigurator) { auto disabled_meter_config = config_sdk::MeterMatcherAndConfigConfiguration(); disabled_meter_config.name = "disabled-meter"; @@ -647,7 +650,7 @@ TEST_F(ProgrammaticConfigTest, MeterProviderWithMeterConfigurator) } } -TEST_F(ProgrammaticConfigTest, MeterProviderWithViews) +TEST_F(ProgrammaticConfigTest, DISABLED_MeterProviderWithViews) { // View 1: Base2 exponential aggregation const std::size_t max_scale = 10;