Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Increment the:

## [Unreleased]

* [SDK] Add `TracerProvider::UpdateTracerConfigurator()` and example
[#4065](https://github.com/open-telemetry/opentelemetry-cpp/issues/4065)

## [1.27.0] 2026-05-13

* [RELEASE] Bump main branch to 1.27.0-dev
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_subdirectory(metrics_simple)
add_subdirectory(multithreaded)
add_subdirectory(multi_processor)
add_subdirectory(environment_carrier)
add_subdirectory(tracer_configurator)
add_subdirectory(explicit_parent)

if(WITH_EXAMPLES_HTTP)
Expand Down
12 changes: 12 additions & 0 deletions examples/tracer_configurator/CMakeLists.txt
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()
146 changes: 146 additions & 0 deletions examples/tracer_configurator/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// 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 <memory>
#include <string>
#include <utility>
#include <vector>

#include "opentelemetry/exporters/ostream/span_exporter_factory.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h"
#include "opentelemetry/sdk/resource/resource.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/version.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;
}
18 changes: 17 additions & 1 deletion sdk/include/opentelemetry/sdk/trace/tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

#include <stdint.h>

#include <atomic>
#include "opentelemetry/common/key_value_iterable.h"
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/sdk/common/atomic_shared_ptr.h"
#include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/trace/id_generator.h"
Expand Down Expand Up @@ -103,11 +105,25 @@ 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(const 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_;
TracerConfig tracer_config_;
opentelemetry::sdk::common::AtomicSharedPtr<const TracerConfig> tracer_config_;
#if OPENTELEMETRY_ABI_VERSION_NO < 2
std::atomic<bool> is_enabled_;
#endif
static const std::shared_ptr<opentelemetry::trace::NoopTracer> kNoopTracer;
};
} // namespace trace
Expand Down
9 changes: 9 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/tracer_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ class TracerContext
*/
opentelemetry::sdk::trace::IdGenerator &GetIdGenerator() const noexcept;

/**
* Replace the TracerConfigurator for this context.
*
* Note: This method is not thread safe.
* @param tracer_configurator The new configurator.
*/
void SetTracerConfigurator(std::unique_ptr<instrumentationscope::ScopeConfigurator<TracerConfig>>
tracer_configurator) noexcept;

/**
* Force all active SpanProcessors to flush any buffered spans
* within the given timeout.
Expand Down
10 changes: 10 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/tracer_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ class OPENTELEMETRY_EXPORT TracerProvider final : public opentelemetry::trace::T
*/
void AddProcessor(std::unique_ptr<SpanProcessor> processor) noexcept;

/**
* Update the TracerConfigurator for this provider, recreate and propagate the resulting
* TracerConfig to all existing Tracers while new Tracers will use the updated configuration.
*
* @param tracer_configurator The new configurator.
*/
void UpdateTracerConfigurator(
std::unique_ptr<instrumentationscope::ScopeConfigurator<TracerConfig>>
tracer_configurator) noexcept;

/**
* Obtain the resource associated with this tracer provider.
* @return The resource for this tracer provider.
Expand Down
28 changes: 25 additions & 3 deletions sdk/src/trace/tracer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@ Tracer::Tracer(std::shared_ptr<TracerContext> context,
std::unique_ptr<InstrumentationScope> instrumentation_scope) noexcept
: instrumentation_scope_{std::move(instrumentation_scope)},
context_{std::move(context)},
tracer_config_(context_->GetTracerConfigurator().ComputeConfig(*instrumentation_scope_))
tracer_config_(std::make_shared<const TracerConfig>(
context_->GetTracerConfigurator().ComputeConfig(*instrumentation_scope_)))
#if OPENTELEMETRY_ABI_VERSION_NO < 2
,
is_enabled_(tracer_config_.load()->IsEnabled())
#endif
{
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
UpdateEnabled(tracer_config_.IsEnabled());
UpdateEnabled(tracer_config_.load()->IsEnabled());
#endif
}

Expand All @@ -58,7 +63,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);
}
Expand Down Expand Up @@ -199,6 +209,18 @@ void Tracer::CloseWithMicroseconds(uint64_t timeout) noexcept
std::chrono::microseconds{static_cast<std::chrono::microseconds::rep>(timeout)});
}
}

void Tracer::UpdateTracerConfig(const TracerConfig &config) noexcept
{
tracer_config_.store(std::make_shared<const TracerConfig>(config));

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
UpdateEnabled(config.IsEnabled());
#else
is_enabled_.store(config.IsEnabled(), std::memory_order_relaxed);
#endif
}

} // namespace trace
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
14 changes: 14 additions & 0 deletions sdk/src/trace/tracer_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <utility>
#include <vector>

#include "opentelemetry/sdk/common/global_log_handler.h"
#include "opentelemetry/sdk/instrumentationscope/scope_configurator.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/trace/id_generator.h"
Expand Down Expand Up @@ -64,6 +65,19 @@ void TracerContext::AddProcessor(std::unique_ptr<SpanProcessor> processor) noexc
multi_processor->AddProcessor(std::move(processor));
}

void TracerContext::SetTracerConfigurator(
std::unique_ptr<instrumentationscope::ScopeConfigurator<TracerConfig>>
tracer_configurator) noexcept
{
if (!tracer_configurator)
{
OTEL_INTERNAL_LOG_ERROR(
"[TracerContext::SetTracerConfigurator] tracer_configurator must not be null, ignoring.");
return;
}
tracer_configurator_ = std::move(tracer_configurator);
}

SpanProcessor &TracerContext::GetProcessor() const noexcept
{
return *processor_;
Expand Down
20 changes: 20 additions & 0 deletions sdk/src/trace/tracer_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,26 @@ void TracerProvider::AddProcessor(std::unique_ptr<SpanProcessor> processor) noex
context_->AddProcessor(std::move(processor));
}

void TracerProvider::UpdateTracerConfigurator(
std::unique_ptr<instrumentationscope::ScopeConfigurator<TracerConfig>>
tracer_configurator) noexcept
{
// The only way to set the TracerConfig of a tracer is on Tracer construction in
// TracerProvider::GetTracer or through Tracer::UpdateTracerConfig (which is private and only
// accessed by TracerProvider).
//
// Lock the provider mutex while updating the TracerConfiguartor in the context and setting the
// new TracerConfig of all existing tracers.
const std::lock_guard<std::mutex> guard(lock_);
context_->SetTracerConfigurator(std::move(tracer_configurator));
Comment thread
dbarker marked this conversation as resolved.
for (auto &tracer : tracers_)
{
auto new_config =
context_->GetTracerConfigurator().ComputeConfig(tracer->GetInstrumentationScope());
tracer->UpdateTracerConfig(new_config);
}
}

const resource::Resource &TracerProvider::GetResource() const noexcept
{
return context_->GetResource();
Expand Down
1 change: 1 addition & 0 deletions sdk/test/trace/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ cc_test(
"trace",
],
deps = [
"//exporters/memory:in_memory_span_exporter",
"//sdk/src/resource",
"//sdk/src/trace",
"@com_google_googletest//:gtest_main",
Expand Down
Loading
Loading