-
Notifications
You must be signed in to change notification settings - Fork 571
[SDK] Support TracerConfigurator updates #4065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
75137db
26c9092
01d6d07
6ee3f69
e7398ea
5e79961
7bf4ca3
1bafea8
ca61658
219efc6
b22d0b2
9b3d78b
d8d0922
9646ce7
a9701ea
57c8c3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Copyright The OpenTelemetry Authors | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| add_executable(example_tracer_configurator main.cc) | ||
| target_link_libraries( | ||
| example_tracer_configurator PRIVATE opentelemetry-cpp::trace | ||
| opentelemetry-cpp::ostream_span_exporter) | ||
|
|
||
| if(BUILD_TESTING) | ||
| add_test(NAME examples.tracer_configurator | ||
| COMMAND "$<TARGET_FILE:example_tracer_configurator>") | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| // This example shows how to use TracerProvider::UpdateTracerConfigurator to control which | ||
| // tracers are enabled at runtime. Updating the TracerConfigurator affects all existing and future | ||
| // tracers provided by the TracerProvider. It is thread safe and can be called concurrently with | ||
| // tracer and span creation. | ||
| // | ||
| // Two instrumentation scopes are shown: | ||
| // 1. "my_library" (example instrumented user code), | ||
| // 2. "external_library_foo" (example instrumented third-party dependency). | ||
| // | ||
| // The example simulates a debugging workflow where only the tracer configuration is changed at | ||
| // runtime through the provider: | ||
| // | ||
| // Stage 1 – Start with all tracing disabled (low-overhead production default). | ||
| // Stage 2 – An issue is reported. Enable only the user library traces to get an initial signal. | ||
| // Stage 3 – The issue involves external libraries too. Enable all traces for full visibility. | ||
| // Stage 4 – Investigation complete. Disable all tracing again. | ||
|
|
||
| #include <initializer_list> | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <utility> | ||
| #include <vector> | ||
|
|
||
| #include "opentelemetry/exporters/ostream/span_exporter_factory.h" | ||
| #include "opentelemetry/nostd/shared_ptr.h" | ||
| #include "opentelemetry/nostd/string_view.h" | ||
| #include "opentelemetry/sdk/instrumentationscope/scope_configurator.h" | ||
| #include "opentelemetry/sdk/resource/resource.h" | ||
| #include "opentelemetry/sdk/trace/exporter.h" | ||
| #include "opentelemetry/sdk/trace/processor.h" | ||
| #include "opentelemetry/sdk/trace/random_id_generator.h" | ||
| #include "opentelemetry/sdk/trace/samplers/always_on.h" | ||
| #include "opentelemetry/sdk/trace/simple_processor_factory.h" | ||
| #include "opentelemetry/sdk/trace/tracer_config.h" | ||
| #include "opentelemetry/sdk/trace/tracer_provider.h" | ||
| #include "opentelemetry/trace/span.h" | ||
| #include "opentelemetry/trace/tracer.h" | ||
|
|
||
| namespace trace_sdk = opentelemetry::sdk::trace; | ||
| namespace trace_exporter = opentelemetry::exporter::trace; | ||
| namespace scope_cfg = opentelemetry::sdk::instrumentationscope; | ||
| namespace nostd = opentelemetry::nostd; | ||
|
|
||
| namespace | ||
| { | ||
| void DoWork(opentelemetry::nostd::shared_ptr<opentelemetry::trace::Tracer> &tracer, | ||
| nostd::string_view tracer_name, | ||
| nostd::string_view operation) | ||
| { | ||
| auto span = tracer->StartSpan(operation); | ||
| std::cout << (span->IsRecording() ? "[active] " : "[off] ") << tracer_name << " / " | ||
| << operation << "\n"; | ||
| span->End(); | ||
| } | ||
|
|
||
| // Builds a ScopeConfigurator that enables all tracers (the default). | ||
| std::unique_ptr<scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>> EnableAll() | ||
| { | ||
| return std::make_unique<scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>>( | ||
| scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>::Builder( | ||
| trace_sdk::TracerConfig::Default()) | ||
| .Build()); | ||
| } | ||
|
|
||
| // Builds a ScopeConfigurator that enables only the named tracers; all others are disabled. | ||
| std::unique_ptr<scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>> EnableOnly( | ||
| std::initializer_list<nostd::string_view> names) | ||
| { | ||
| scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>::Builder builder( | ||
| trace_sdk::TracerConfig::Disabled()); | ||
| for (nostd::string_view name : names) | ||
| { | ||
| builder.AddConditionNameEquals(name, trace_sdk::TracerConfig::Default()); | ||
| } | ||
| return std::make_unique<scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>>(builder.Build()); | ||
| } | ||
|
|
||
| // Builds a ScopeConfigurator that disables all tracers. | ||
| std::unique_ptr<scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>> DisableAll() | ||
| { | ||
| return std::make_unique<scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>>( | ||
| scope_cfg::ScopeConfigurator<trace_sdk::TracerConfig>::Builder( | ||
| trace_sdk::TracerConfig::Disabled()) | ||
| .Build()); | ||
| } | ||
| } // namespace | ||
|
|
||
| int main() | ||
| { | ||
| auto exporter = trace_exporter::OStreamSpanExporterFactory::Create(); | ||
| auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter)); | ||
|
|
||
| // Start with all tracing disabled | ||
| auto provider = std::make_shared<trace_sdk::TracerProvider>( | ||
| std::move(processor), opentelemetry::sdk::resource::Resource::Create({}), | ||
| std::make_unique<trace_sdk::AlwaysOnSampler>(), | ||
| std::make_unique<trace_sdk::RandomIdGenerator>(), DisableAll()); | ||
|
|
||
| auto my_library_tracer = provider->GetTracer("my_library"); | ||
| auto external_library_foo_tracer = provider->GetTracer("external_library_foo"); | ||
|
|
||
| // ------------------------------------------------------------------------- | ||
| // Stage 1: all tracing disabled | ||
| // ------------------------------------------------------------------------- | ||
| std::cout << "=== Stage 1: disable all (production default) ===\n"; | ||
|
|
||
| DoWork(my_library_tracer, "my_library", "DoWork"); // disabled | ||
| DoWork(external_library_foo_tracer, "external_library_foo", "FooOperation"); // disabled | ||
|
|
||
| // ------------------------------------------------------------------------- | ||
| // Stage 2: enable only user library tracing | ||
| // | ||
| // EnableOnly() sets Disabled() as the default and adds explicit per-name | ||
| // overrides. Existing tracer handles reflect the change immediately | ||
| // ------------------------------------------------------------------------- | ||
| std::cout << "\n=== Stage 2: enable only 'my_library' ===\n"; | ||
|
|
||
| provider->UpdateTracerConfigurator(EnableOnly({"my_library"})); | ||
|
|
||
| DoWork(my_library_tracer, "my_library", "DoWork"); // enabled | ||
| DoWork(external_library_foo_tracer, "external_library_foo", "FooOperation"); // disabled | ||
|
|
||
| // ------------------------------------------------------------------------- | ||
| // Stage 3: enable tracing in all libraries | ||
| // ------------------------------------------------------------------------- | ||
| std::cout << "\n=== Stage 3: enable all ===\n"; | ||
|
|
||
| provider->UpdateTracerConfigurator(EnableAll()); | ||
|
|
||
| DoWork(my_library_tracer, "my_library", "DoWork"); // enabled | ||
| DoWork(external_library_foo_tracer, "external_library_foo", "FooOperation"); // enabled | ||
|
|
||
| // ------------------------------------------------------------------------- | ||
| // Stage 4: disable all tracing again | ||
| // ------------------------------------------------------------------------- | ||
| std::cout << "\n=== Stage 4: disable all ===\n"; | ||
|
|
||
| provider->UpdateTracerConfigurator(DisableAll()); | ||
|
|
||
| DoWork(my_library_tracer, "my_library", "DoWork"); // disabled | ||
| DoWork(external_library_foo_tracer, "external_library_foo", "FooOperation"); // disabled | ||
|
|
||
| provider->ForceFlush(); | ||
| provider->Shutdown(); | ||
| return 0; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
|
|
||
| #include <stdint.h> | ||
|
|
||
| #include <atomic> | ||
| #include <mutex> | ||
| #include "opentelemetry/common/key_value_iterable.h" | ||
| #include "opentelemetry/nostd/shared_ptr.h" | ||
| #include "opentelemetry/nostd/string_view.h" | ||
|
|
@@ -103,11 +105,26 @@ class Tracer final : public opentelemetry::trace::Tracer, | |
| Sampler &GetSampler() { return context_->GetSampler(); } | ||
|
|
||
| private: | ||
| // TracerProvider needs access to UpdateTracerConfig to propagate configuration updates to | ||
| // existing tracers. | ||
| friend class TracerProvider; | ||
|
|
||
| /** | ||
| * Update this tracer's TracerConfig. Called only by | ||
| * TracerProvider::UpdateTracerConfigurator when the provider-level | ||
| * TracerConfigurator is replaced at runtime. | ||
| */ | ||
| void UpdateTracerConfig(TracerConfig config) noexcept; | ||
|
|
||
| // order of declaration is important here - instrumentation scope should destroy after | ||
| // tracer-context. | ||
| std::shared_ptr<InstrumentationScope> instrumentation_scope_; | ||
| std::shared_ptr<TracerContext> context_; | ||
| mutable std::mutex tracer_config_mutex_; | ||
| TracerConfig tracer_config_; | ||
| #if OPENTELEMETRY_ABI_VERSION_NO < 2 | ||
| std::atomic<bool> is_enabled_{false}; | ||
| #endif | ||
|
Comment on lines
+125
to
+127
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First, the check on ABI_VERSION can be removed, this is in the SDK. Second, the initialization MUST use {value}, see PR #2244:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks. This
Good catch. I'll default initialize it in the header. CI is may not be warning on this since it is set in the constructor member initializer list. |
||
| static const std::shared_ptr<opentelemetry::trace::NoopTracer> kNoopTracer; | ||
| }; | ||
| } // namespace trace | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,12 @@ | |
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| #include <stdint.h> | ||
| #if OPENTELEMETRY_ABI_VERSION_NO < 2 | ||
| # include <atomic> | ||
| #endif | ||
| #include <chrono> | ||
| #include <map> | ||
| #include <mutex> | ||
| #include <new> | ||
| #include <utility> | ||
|
|
||
|
|
@@ -44,6 +48,10 @@ Tracer::Tracer(std::shared_ptr<TracerContext> context, | |
| : instrumentation_scope_{std::move(instrumentation_scope)}, | ||
| context_{std::move(context)}, | ||
| tracer_config_(context_->GetTracerConfigurator().ComputeConfig(*instrumentation_scope_)) | ||
| #if OPENTELEMETRY_ABI_VERSION_NO < 2 | ||
| , | ||
| is_enabled_(tracer_config_.IsEnabled()) | ||
| #endif | ||
| { | ||
| #if OPENTELEMETRY_ABI_VERSION_NO >= 2 | ||
| UpdateEnabled(tracer_config_.IsEnabled()); | ||
|
|
@@ -56,7 +64,12 @@ nostd::shared_ptr<opentelemetry::trace::Span> Tracer::StartSpan( | |
| const opentelemetry::trace::SpanContextKeyValueIterable &links, | ||
| const opentelemetry::trace::StartSpanOptions &options) noexcept | ||
| { | ||
| if (!tracer_config_.IsEnabled()) | ||
| // Check if the tracer is enabled using the API Tracer::Enabled() accessor if available. | ||
| #if OPENTELEMETRY_ABI_VERSION_NO >= 2 | ||
| if (!Enabled()) | ||
| #else | ||
| if (!is_enabled_.load(std::memory_order_relaxed)) | ||
| #endif | ||
| { | ||
| return kNoopTracer->StartSpan(name, attributes, links, options); | ||
| } | ||
|
|
@@ -197,6 +210,22 @@ void Tracer::CloseWithMicroseconds(uint64_t timeout) noexcept | |
| std::chrono::microseconds{static_cast<std::chrono::microseconds::rep>(timeout)}); | ||
| } | ||
| } | ||
|
|
||
| void Tracer::UpdateTracerConfig(TracerConfig config) noexcept | ||
| { | ||
| const bool enabled = config.IsEnabled(); | ||
| { | ||
| std::lock_guard<std::mutex> lock(tracer_config_mutex_); | ||
| tracer_config_ = config; | ||
| } | ||
|
|
||
| #if OPENTELEMETRY_ABI_VERSION_NO >= 2 | ||
| UpdateEnabled(enabled); | ||
| #else | ||
| is_enabled_.store(enabled, std::memory_order_relaxed); | ||
| #endif | ||
|
Comment on lines
+222
to
+226
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, so UpdateEnabled() is a method that exists only in the API, starting with ABI version 2. How about, in ABI version 1, defining the very same method with the same implementation, only in the SDK, instead of implementing a different attribute name (is_enabled) and type (std::atomic) ? This code then becomes: in all cases. Likewise, define method Basically, the pattern is the opposite of Here Enabled() can be in the SDK in ABI v1, promoted to the API in ABI v2.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. I'll try this way. |
||
| } | ||
|
|
||
| } // namespace trace | ||
| } // namespace sdk | ||
| OPENTELEMETRY_END_NAMESPACE | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very nice.
Could you paste the test output in a comment, for reviewers ?