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)

* [RELEASE] Bump main branch to 1.28.0-dev
[#4081](https://github.com/open-telemetry/opentelemetry-cpp/pull/4081)

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()
149 changes: 149 additions & 0 deletions examples/tracer_configurator/main.cc
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.
Comment on lines +13 to +19
Copy link
Copy Markdown
Member

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 ?


#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
Expand Up @@ -45,6 +45,9 @@ class ScopeConfigurator
* criteria defined by the function.
* @param scope_config the scope configuration to return for the matched scope.
* @return this
*
* @note The scope_matcher function MUST return quickly and ideally be a pure (side-effect-free)
* function.
*/
Builder &AddCondition(std::function<bool(const InstrumentationScope &)> scope_matcher,
T scope_config)
Expand Down
17 changes: 17 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/tracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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:

[SDK] Valgrind errors on std::atomic variables (#2244)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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.

Thanks. This sdk::trace::Tracer::is_enabled_ atomic was added for only ABIv1 since the API Tracer::enabled_ exists only for ABIv2. I think we should keep it guarded here in the SDK Tracer since it is redundant with the API Tracer boolean in ABIv2. When the project moves to ABIv2 as the default and ABIv1 is deprecated this SDK Tracer::is_enabled_ atomic should be removed. Does that sound reasonable?

Second, the initialization MUST use {value}

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
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
15 changes: 15 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,21 @@ 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.
*
* @note Calling the TracerProvider::GetTracer from within
* the ScopeConfigurator<TracerConfig>::ComputeConfig
* function (as a scope_matcher callback set with ScopeConfigurator<TracerConfig>::AddCondition)
* is not supported and will result in a deadlock.
*/
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
31 changes: 30 additions & 1 deletion sdk/src/trace/tracer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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());
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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:

UpdateEnabled(enabled);

in all cases.

Likewise, define method Enabled() and member enabled_ in the SDK in ABI v1.

Basically, the pattern is the opposite of ForceFlushWithMicroseconds() which was demoted from the API to the SDK.

Here Enabled() can be in the SDK in ABI v1, promoted to the API in ABI v2.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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
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
30 changes: 30 additions & 0 deletions sdk/src/trace/tracer_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,36 @@ 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
{
if (!tracer_configurator)
{
OTEL_INTERNAL_LOG_ERROR(
"[TracerProvider::UpdateTracerConfigurator] tracer_configurator must not be null, "
"ignoring.");
return;
}

// Lock the provider mutex to ensure that calls to GetTracer are exclusive with respect to the
// TracerConfigurator update and corresponding TracerConfig updates. This ensures that a Tracer
// will never be returned from GetTracer with a TracerConfig that is out of date with respect to
// the provider-level TracerConfigurator.
const std::lock_guard<std::mutex> guard(lock_);
context_->SetTracerConfigurator(std::move(tracer_configurator));

// 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).
for (auto &tracer : tracers_)
{
TracerConfig 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