From c7360f537d7f1cf1de6cae453eff315b21d51fa5 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Tue, 12 May 2026 20:33:18 +0900 Subject: [PATCH 01/26] fix: apply filtering in emit log record --- .github/workflows/clang-tidy.yaml | 4 +- .github/workflows/iwyu.yml | 6 +- CHANGELOG.md | 8 +++ api/include/opentelemetry/logs/logger.h | 15 +++++ .../opentelemetry/logs/logger_type_traits.h | 20 ++++++ api/test/logs/logger_test.cc | 20 ++++-- sdk/src/logs/logger.cc | 7 +- sdk/test/logs/logger_sdk_test.cc | 66 +++++++++++++++++-- 8 files changed, 131 insertions(+), 15 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index d53fc08577..f7dde52cd6 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 418 + warning_limit: 416 - cmake_options: all-options-abiv2-preview - warning_limit: 424 + warning_limit: 422 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index 3bfff341f2..1e9d565ea4 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -18,11 +18,11 @@ jobs: matrix: include: - cmake_options: all-options-abiv1 - warning_limit: 161 + warning_limit: 160 - cmake_options: all-options-abiv1-preview - warning_limit: 190 + warning_limit: 189 - cmake_options: all-options-abiv2-preview - warning_limit: 187 + warning_limit: 186 steps: - name: Harden the runner (Audit all outbound calls) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d918c931e..f53206c102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ Increment the: ## [Unreleased] +* [API] `Logger::EmitLogRecord(...)` templates now short-circuit when the + supplied Severity is below the logger's minimum. Closes the second half of + #2667 so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers honor + the `Enabled()` flag transparently. +* [SDK] `Logger::EmitLogRecord(unique_ptr)` now applies the + LoggerConfig trace-based filter using the current runtime context. + [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) + * [API] Fix `Logger::Enabled()` [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 10e3f7a528..c8237091a7 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -83,6 +83,12 @@ class Logger return; } + const Severity arg_severity = detail::FindSeverityInArgs(args...); + if (arg_severity != Severity::kInvalid && !Enabled(arg_severity)) + { + return; + } + // // Keep the parameter pack unpacking order from left to right because left // ones are usually more important like severity and event_id than the @@ -124,6 +130,15 @@ class Logger template void EmitLogRecord(ArgumentType &&...args) { + // Short-circuit BEFORE record creation when Severity is supplied in args + // and below the logger's minimum severity. Saves the recordable alloc on + // filtered logs. + const Severity arg_severity = detail::FindSeverityInArgs(args...); + if (arg_severity != Severity::kInvalid && !Enabled(arg_severity)) + { + return; + } + nostd::unique_ptr log_record = CreateLogRecord(); EmitLogRecord(std::move(log_record), std::forward(args)...); diff --git a/api/include/opentelemetry/logs/logger_type_traits.h b/api/include/opentelemetry/logs/logger_type_traits.h index 19e0e3219b..3e11e63ed5 100644 --- a/api/include/opentelemetry/logs/logger_type_traits.h +++ b/api/include/opentelemetry/logs/logger_type_traits.h @@ -198,6 +198,26 @@ struct LogRecordHasType LogRecordHasType>::type {}; +inline Severity FindSeverityInArgs() noexcept +{ + return Severity::kInvalid; +} + +template +inline Severity FindSeverityInArgs(Severity severity, Rest &&.../*rest*/) noexcept +{ + return severity; +} + +template ::type, Severity>::value, + int>::type = 0> +inline Severity FindSeverityInArgs(First && /*first*/, Rest &&...rest) noexcept +{ + return FindSeverityInArgs(std::forward(rest)...); +} + } // namespace detail } // namespace logs diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 251820477b..6bc0d2e238 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -10,7 +10,6 @@ #include #include "opentelemetry/common/attribute_value.h" -#include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/common/key_value_iterable_view.h" #include "opentelemetry/common/timestamp.h" #include "opentelemetry/logs/event_id.h" @@ -26,9 +25,7 @@ #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" -#include "opentelemetry/trace/span_id.h" -#include "opentelemetry/trace/trace_flags.h" -#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/nostd/utility.h" #if OPENTELEMETRY_ABI_VERSION_NO >= 2 # include "opentelemetry/context/context.h" @@ -278,6 +275,9 @@ class TestLogger : public Logger } }; +namespace +{ + class EnablementAwareTestLogRecord : public opentelemetry::logs::LogRecord { public: @@ -420,6 +420,8 @@ class EnablementAwareTestLogger : public Logger bool event_id_enabled_; }; +} // namespace + // Define a basic LoggerProvider class that returns an instance of the logger class defined above class TestProvider : public LoggerProvider { @@ -461,3 +463,13 @@ TEST(Logger, EnabledWithExplicitContextUsesContextAwareImplementation) EXPECT_TRUE(logger.last_enabled_context_test_key_value_); } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + +TEST(Logger, EmitLogRecordTemplateShortCircuitsBelowMinimumSeverity) +{ + EnablementAwareTestLogger logger(Severity::kWarn); + + logger.Info(nostd::string_view{"filtered"}); + + EXPECT_EQ(logger.create_log_record_calls_, 0u); + EXPECT_EQ(logger.emit_log_record_calls_, 0u); +} diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 3706ee1351..38a7ca95af 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -156,6 +156,11 @@ void Logger::EmitLogRecord( return; } + if (!IsAllowedByTraceBasedFiltering(context::RuntimeContext::GetCurrent(), logger_config_)) + { + return; + } + std::unique_ptr recordable = std::unique_ptr(static_cast(log_record.release())); recordable->SetResource(context_->GetResource()); @@ -163,8 +168,6 @@ void Logger::EmitLogRecord( auto &processor = context_->GetProcessor(); - // TODO: Sampler (should include check for minSeverity) - // Send the log recordable to the processor processor.OnEmit(std::move(recordable)); } diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index ba2fe07ba4..4ed8ac62d6 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -21,6 +21,7 @@ #include "opentelemetry/logs/noop.h" #include "opentelemetry/logs/severity.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/sdk/instrumentationscope/instrumentation_scope.h" @@ -44,13 +45,13 @@ #include "opentelemetry/trace/trace_id.h" #include "opentelemetry/trace/tracer.h" +#include "opentelemetry/trace/default_span.h" + #if OPENTELEMETRY_ABI_VERSION_NO >= 2 # include # include "opentelemetry/context/context.h" -# include "opentelemetry/nostd/span.h" # include "opentelemetry/trace/context.h" -# include "opentelemetry/trace/default_span.h" #endif using namespace opentelemetry::sdk::logs; @@ -61,7 +62,6 @@ namespace context = opentelemetry::context; namespace logs_api = opentelemetry::logs; namespace nostd = opentelemetry::nostd; -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 namespace { nostd::shared_ptr MakeTestSpan(bool sampled) @@ -81,6 +81,7 @@ nostd::shared_ptr MakeTestSpan(bool sampled) new opentelemetry::trace::DefaultSpan(span_context)); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 context::Context MakeContextWithUnsampledSpanAndInvalidTraceId() { const uint8_t span_id_bytes[opentelemetry::trace::SpanId::kSize] = {0xde, 0xad, 0xbe, 0xef, @@ -95,8 +96,8 @@ context::Context MakeContextWithUnsampledSpanAndInvalidTraceId() nostd::shared_ptr( new opentelemetry::trace::DefaultSpan(span_context))); } -} // namespace #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 +} // namespace TEST(LoggerSDK, LogToNullProcessor) { @@ -273,6 +274,9 @@ class MockProcessor final : public LogRecordProcessor }; #if OPENTELEMETRY_ABI_VERSION_NO >= 2 +namespace +{ + struct EnabledProcessorCallState { logs_api::Severity severity = logs_api::Severity::kInvalid; @@ -335,6 +339,8 @@ class EnablementAwareProcessor final : public LogRecordProcessor bool enabled_; std::shared_ptr call_state_; }; + +} // namespace #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(LoggerSDK, LogToAProcessor) @@ -480,6 +486,58 @@ TEST(LoggerSDK, LoggerWithEnabledConfig) ASSERT_FALSE(shared_recordable->GetSpanId().IsValid()); } +TEST(LoggerSDK, EmitLogRecordDropsUnsampledTraceBased) +{ + ScopeConfigurator trace_based = + ScopeConfigurator::Builder( + LoggerConfig::Create(true, logs_api::Severity::kInvalid, true)) + .Build(); + auto shared_recordable = std::shared_ptr(new MockLogRecordable()); + auto log_processor = std::unique_ptr(new MockProcessor(shared_recordable)); + + const auto resource = opentelemetry::sdk::resource::Resource::Create({}); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto api_lp = std::shared_ptr( + new LoggerProvider(std::move(log_processor), resource, + std::make_unique>(trace_based))); + auto logger = api_lp->GetLogger("trace-based-logger", "opentelemetry_library", "", schema_url); + + auto unsampled_span = MakeTestSpan(false); + opentelemetry::trace::Scope scope{unsampled_span}; + + logger->EmitLogRecord(logs_api::Severity::kInfo, "dropped"); + + // Record never reached MockProcessor::OnEmit, so shared_recordable stays default. + EXPECT_EQ(shared_recordable->GetSeverity(), opentelemetry::logs::Severity::kInvalid); + EXPECT_EQ(shared_recordable->GetBody(), ""); + EXPECT_EQ(shared_recordable->GetObservedTimestamp(), std::chrono::system_clock::from_time_t(0)); +} + +TEST(LoggerSDK, EmitLogRecordEmitsSampledTraceBased) +{ + ScopeConfigurator trace_based = + ScopeConfigurator::Builder( + LoggerConfig::Create(true, logs_api::Severity::kInvalid, true)) + .Build(); + auto shared_recordable = std::shared_ptr(new MockLogRecordable()); + auto log_processor = std::unique_ptr(new MockProcessor(shared_recordable)); + + const auto resource = opentelemetry::sdk::resource::Resource::Create({}); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto api_lp = std::shared_ptr( + new LoggerProvider(std::move(log_processor), resource, + std::make_unique>(trace_based))); + auto logger = api_lp->GetLogger("trace-based-logger", "opentelemetry_library", "", schema_url); + + auto sampled_span = MakeTestSpan(true); + opentelemetry::trace::Scope scope{sampled_span}; + + logger->EmitLogRecord(logs_api::Severity::kInfo, "allowed"); + + EXPECT_EQ(shared_recordable->GetSeverity(), logs_api::Severity::kInfo); + EXPECT_EQ(shared_recordable->GetBody(), "allowed"); +} + #if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(LoggerSDK, LoggerWithMinimumSeverityConfig) { From 46c13c87b7cb25ba8054dce05fc3ca456a804f31 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Tue, 12 May 2026 20:57:10 +0900 Subject: [PATCH 02/26] refactor: filter once --- api/include/opentelemetry/logs/logger.h | 9 --------- api/test/logs/logger_test.cc | 2 +- sdk/test/logs/logger_sdk_test.cc | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index c8237091a7..b45636defc 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -130,15 +130,6 @@ class Logger template void EmitLogRecord(ArgumentType &&...args) { - // Short-circuit BEFORE record creation when Severity is supplied in args - // and below the logger's minimum severity. Saves the recordable alloc on - // filtered logs. - const Severity arg_severity = detail::FindSeverityInArgs(args...); - if (arg_severity != Severity::kInvalid && !Enabled(arg_severity)) - { - return; - } - nostd::unique_ptr log_record = CreateLogRecord(); EmitLogRecord(std::move(log_record), std::forward(args)...); diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 6bc0d2e238..d6613d2a0b 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -470,6 +470,6 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsBelowMinimumSeverity) logger.Info(nostd::string_view{"filtered"}); - EXPECT_EQ(logger.create_log_record_calls_, 0u); + EXPECT_EQ(logger.create_log_record_calls_, 1u); EXPECT_EQ(logger.emit_log_record_calls_, 0u); } diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 4ed8ac62d6..b972c8f140 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -507,7 +507,6 @@ TEST(LoggerSDK, EmitLogRecordDropsUnsampledTraceBased) logger->EmitLogRecord(logs_api::Severity::kInfo, "dropped"); - // Record never reached MockProcessor::OnEmit, so shared_recordable stays default. EXPECT_EQ(shared_recordable->GetSeverity(), opentelemetry::logs::Severity::kInvalid); EXPECT_EQ(shared_recordable->GetBody(), ""); EXPECT_EQ(shared_recordable->GetObservedTimestamp(), std::chrono::system_clock::from_time_t(0)); From 47d1e37f37cb378c2e0ed1040189fbbb42ed21a0 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Wed, 13 May 2026 19:30:12 +0900 Subject: [PATCH 03/26] refactor: enabled filtering on the API level --- api/include/opentelemetry/logs/logger.h | 29 ++++++++--- .../opentelemetry/logs/logger_type_traits.h | 20 ++++++++ api/test/logs/logger_test.cc | 40 +++++++++++++++ sdk/src/logs/logger.cc | 5 -- sdk/test/logs/log_record_test.cc | 14 +++++ sdk/test/logs/logger_sdk_test.cc | 51 ------------------- 6 files changed, 96 insertions(+), 63 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index b45636defc..bb286292b0 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -58,7 +58,15 @@ class Logger virtual void EmitLogRecord(nostd::unique_ptr &&log_record) noexcept = 0; /** - * Emit a Log Record object with arguments + * Emit a Log Record object with arguments. + * + * @note This overload does NOT apply the @c Enabled filter chain. Callers who + * constructed @p log_record themselves are responsible for calling + * @c Enabled(severity, ...) before invoking this overload if they want + * the LoggerConfig filtering rules (minimum severity, trace-based, + * processor.Enabled) to be honored. The no-record overload + * @c EmitLogRecord(args...) below does call the filter chain + * automatically when @c Severity is present in @p args. * * @param log_record Log record * @param args Arguments which can be used to set data of log record by type. @@ -83,12 +91,6 @@ class Logger return; } - const Severity arg_severity = detail::FindSeverityInArgs(args...); - if (arg_severity != Severity::kInvalid && !Enabled(arg_severity)) - { - return; - } - // // Keep the parameter pack unpacking order from left to right because left // ones are usually more important like severity and event_id than the @@ -130,6 +132,19 @@ class Logger template void EmitLogRecord(ArgumentType &&...args) { + const Severity arg_severity = detail::FindSeverityInArgs(args...); + if (arg_severity != Severity::kInvalid) + { + const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); + const bool is_enabled = event_id_ptr + ? Enabled(arg_severity, *event_id_ptr) + : Enabled(arg_severity, static_cast(0)); + if (!is_enabled) + { + return; + } + } + nostd::unique_ptr log_record = CreateLogRecord(); EmitLogRecord(std::move(log_record), std::forward(args)...); diff --git a/api/include/opentelemetry/logs/logger_type_traits.h b/api/include/opentelemetry/logs/logger_type_traits.h index 3e11e63ed5..25a76db2d0 100644 --- a/api/include/opentelemetry/logs/logger_type_traits.h +++ b/api/include/opentelemetry/logs/logger_type_traits.h @@ -218,6 +218,26 @@ inline Severity FindSeverityInArgs(First && /*first*/, Rest &&...rest) noexcept return FindSeverityInArgs(std::forward(rest)...); } +inline const EventId *FindEventIdInArgs() noexcept +{ + return nullptr; +} + +template +inline const EventId *FindEventIdInArgs(const EventId &event_id, Rest &&... /*rest*/) noexcept +{ + return &event_id; +} + +template ::type, EventId>::value, + int>::type = 0> +inline const EventId *FindEventIdInArgs(First && /*first*/, Rest &&...rest) noexcept +{ + return FindEventIdInArgs(std::forward(rest)...); +} + } // namespace detail } // namespace logs diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index d6613d2a0b..4cb9326819 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -470,6 +470,46 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsBelowMinimumSeverity) logger.Info(nostd::string_view{"filtered"}); + EXPECT_EQ(logger.create_log_record_calls_, 0u); + EXPECT_EQ(logger.emit_log_record_calls_, 0u); + EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); +} + +TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowed) +{ + EnablementAwareTestLogger logger(Severity::kTrace, true); + + logger.Info(nostd::string_view{"emitted"}); + + EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); EXPECT_EQ(logger.create_log_record_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); + EXPECT_EQ(logger.last_enabled_severity_, Severity::kInfo); +} + +TEST(Logger, EmitLogRecordTemplateShortCircuitsWhenEnabledImplementationReturnsFalse) +{ + EnablementAwareTestLogger logger(Severity::kTrace, false); + + logger.Info(nostd::string_view{"filtered"}); + + EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); + EXPECT_EQ(logger.create_log_record_calls_, 0u); EXPECT_EQ(logger.emit_log_record_calls_, 0u); } + +TEST(Logger, EmitLogRecordWithRecordBypassesFiltering) +{ + // EmitLogRecord(unique_ptr&&, args...) intentionally does NOT + // route through Enabled() — caller built the record themselves and is + // responsible for any filtering. Lock the contract documented on the + // overload. + EnablementAwareTestLogger logger(Severity::kTrace, false); + + auto record = logger.CreateLogRecord(); + logger.EmitLogRecord(std::move(record), Severity::kInfo, nostd::string_view{"emitted"}); + + EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); + EXPECT_EQ(logger.create_log_record_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); +} diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 38a7ca95af..6e3a6c9936 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -156,11 +156,6 @@ void Logger::EmitLogRecord( return; } - if (!IsAllowedByTraceBasedFiltering(context::RuntimeContext::GetCurrent(), logger_config_)) - { - return; - } - std::unique_ptr recordable = std::unique_ptr(static_cast(log_record.release())); recordable->SetResource(context_->GetResource()); diff --git a/sdk/test/logs/log_record_test.cc b/sdk/test/logs/log_record_test.cc index 57a211b290..bd62fabe92 100644 --- a/sdk/test/logs/log_record_test.cc +++ b/sdk/test/logs/log_record_test.cc @@ -117,6 +117,20 @@ class TestBodyLogger : public opentelemetry::logs::Logger return last_body_; } +protected: + bool EnabledImplementation(opentelemetry::logs::Severity /*severity*/, + int64_t /*event_id*/) const noexcept override + { + return true; + } + + bool EnabledImplementation( + opentelemetry::logs::Severity /*severity*/, + const opentelemetry::logs::EventId & /*event_id*/) const noexcept override + { + return true; + } + private: opentelemetry::sdk::common::OwnedAttributeValue last_body_; }; diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index b972c8f140..5bd17bbf85 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -486,57 +486,6 @@ TEST(LoggerSDK, LoggerWithEnabledConfig) ASSERT_FALSE(shared_recordable->GetSpanId().IsValid()); } -TEST(LoggerSDK, EmitLogRecordDropsUnsampledTraceBased) -{ - ScopeConfigurator trace_based = - ScopeConfigurator::Builder( - LoggerConfig::Create(true, logs_api::Severity::kInvalid, true)) - .Build(); - auto shared_recordable = std::shared_ptr(new MockLogRecordable()); - auto log_processor = std::unique_ptr(new MockProcessor(shared_recordable)); - - const auto resource = opentelemetry::sdk::resource::Resource::Create({}); - const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; - auto api_lp = std::shared_ptr( - new LoggerProvider(std::move(log_processor), resource, - std::make_unique>(trace_based))); - auto logger = api_lp->GetLogger("trace-based-logger", "opentelemetry_library", "", schema_url); - - auto unsampled_span = MakeTestSpan(false); - opentelemetry::trace::Scope scope{unsampled_span}; - - logger->EmitLogRecord(logs_api::Severity::kInfo, "dropped"); - - EXPECT_EQ(shared_recordable->GetSeverity(), opentelemetry::logs::Severity::kInvalid); - EXPECT_EQ(shared_recordable->GetBody(), ""); - EXPECT_EQ(shared_recordable->GetObservedTimestamp(), std::chrono::system_clock::from_time_t(0)); -} - -TEST(LoggerSDK, EmitLogRecordEmitsSampledTraceBased) -{ - ScopeConfigurator trace_based = - ScopeConfigurator::Builder( - LoggerConfig::Create(true, logs_api::Severity::kInvalid, true)) - .Build(); - auto shared_recordable = std::shared_ptr(new MockLogRecordable()); - auto log_processor = std::unique_ptr(new MockProcessor(shared_recordable)); - - const auto resource = opentelemetry::sdk::resource::Resource::Create({}); - const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; - auto api_lp = std::shared_ptr( - new LoggerProvider(std::move(log_processor), resource, - std::make_unique>(trace_based))); - auto logger = api_lp->GetLogger("trace-based-logger", "opentelemetry_library", "", schema_url); - - auto sampled_span = MakeTestSpan(true); - opentelemetry::trace::Scope scope{sampled_span}; - - logger->EmitLogRecord(logs_api::Severity::kInfo, "allowed"); - - EXPECT_EQ(shared_recordable->GetSeverity(), logs_api::Severity::kInfo); - EXPECT_EQ(shared_recordable->GetBody(), "allowed"); -} - #if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(LoggerSDK, LoggerWithMinimumSeverityConfig) { From a0958a46a7fbc308b60b4eba554b8811bd64adfa Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 14 May 2026 08:13:39 +0000 Subject: [PATCH 04/26] feat: use context for EmitLogRecord --- api/include/opentelemetry/logs/logger.h | 38 ++++++++++++-- .../opentelemetry/logs/logger_type_traits.h | 47 ++++++++++++++++- api/test/logs/logger_test.cc | 44 ++++++++++++++++ sdk/include/opentelemetry/sdk/logs/logger.h | 5 ++ sdk/src/logs/logger.cc | 50 +++++++++++++++++++ sdk/test/logs/log_record_test.cc | 2 +- sdk/test/logs/logger_sdk_test.cc | 7 ++- 7 files changed, 184 insertions(+), 9 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index bb286292b0..4d427a655b 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -50,6 +50,20 @@ class Logger */ virtual nostd::unique_ptr CreateLogRecord() noexcept = 0; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + /** + * Create a Log Record object using given context. + * + * @param context Context which carries execution-scoped values across execution unit. + * @return nostd::unique_ptr + */ + virtual nostd::unique_ptr CreateLogRecord( + const opentelemetry::context::Context & /*context*/) noexcept + { + return CreateLogRecord(); + } +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + /** * Emit a Log Record object * @@ -128,24 +142,42 @@ class Logger * KeyValueIterable -> attributes * Key value iterable container -> attributes * span> -> attributes(return type of MakeAttributes) + * Context (v2 only) -> filter + trace stamp (recommended: pass last) + * */ template void EmitLogRecord(ArgumentType &&...args) { +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + const opentelemetry::context::Context *context_ptr = detail::FindContextInArgs(args...); +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + const Severity arg_severity = detail::FindSeverityInArgs(args...); if (arg_severity != Severity::kInvalid) { const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); - const bool is_enabled = event_id_ptr - ? Enabled(arg_severity, *event_id_ptr) - : Enabled(arg_severity, static_cast(0)); +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + const bool is_enabled = + context_ptr ? (event_id_ptr ? Enabled(*context_ptr, arg_severity, *event_id_ptr) + : Enabled(*context_ptr, arg_severity)) + : (event_id_ptr ? Enabled(arg_severity, *event_id_ptr) + : Enabled(arg_severity, static_cast(0))); +#else + const bool is_enabled = event_id_ptr ? Enabled(arg_severity, *event_id_ptr) + : Enabled(arg_severity, static_cast(0)); +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 if (!is_enabled) { return; } } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + nostd::unique_ptr log_record = + context_ptr ? CreateLogRecord(*context_ptr) : CreateLogRecord(); +#else nostd::unique_ptr log_record = CreateLogRecord(); +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 EmitLogRecord(std::move(log_record), std::forward(args)...); } diff --git a/api/include/opentelemetry/logs/logger_type_traits.h b/api/include/opentelemetry/logs/logger_type_traits.h index 25a76db2d0..a419c1485c 100644 --- a/api/include/opentelemetry/logs/logger_type_traits.h +++ b/api/include/opentelemetry/logs/logger_type_traits.h @@ -21,6 +21,10 @@ #include "opentelemetry/trace/trace_id.h" #include "opentelemetry/version.h" +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +# include "opentelemetry/context/context.h" +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + OPENTELEMETRY_BEGIN_NAMESPACE namespace logs { @@ -143,6 +147,21 @@ struct LogRecordSetterTrait } }; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +// Context in the argument list is consumed by EmitLogRecord(args...) for +// context-aware filtering and trace stamping (see FindContextInArgs); it is +// not a record field, so the setter is a no-op. +template <> +struct LogRecordSetterTrait +{ + template + inline static LogRecord *Set(LogRecord *log_record, ArgumentType && /*arg*/) noexcept + { + return log_record; + } +}; +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + template struct LogRecordSetterTrait { @@ -224,7 +243,7 @@ inline const EventId *FindEventIdInArgs() noexcept } template -inline const EventId *FindEventIdInArgs(const EventId &event_id, Rest &&... /*rest*/) noexcept +inline const EventId *FindEventIdInArgs(const EventId &event_id, Rest &&.../*rest*/) noexcept { return &event_id; } @@ -238,6 +257,32 @@ inline const EventId *FindEventIdInArgs(First && /*first*/, Rest &&...rest) noex return FindEventIdInArgs(std::forward(rest)...); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +inline const opentelemetry::context::Context *FindContextInArgs() noexcept +{ + return nullptr; +} + +template +inline const opentelemetry::context::Context *FindContextInArgs( + const opentelemetry::context::Context &context, + Rest &&.../*rest*/) noexcept +{ + return &context; +} + +template ::type, + opentelemetry::context::Context>::value, + int>::type = 0> +inline const opentelemetry::context::Context *FindContextInArgs(First && /*first*/, + Rest &&...rest) noexcept +{ + return FindContextInArgs(std::forward(rest)...); +} +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + } // namespace detail } // namespace logs diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 4cb9326819..05e831b961 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -334,6 +334,15 @@ class EnablementAwareTestLogger : public Logger return nostd::unique_ptr(new EnablementAwareTestLogRecord()); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + nostd::unique_ptr CreateLogRecord( + const context::Context & /*context*/) noexcept override + { + ++create_log_record_context_calls_; + return CreateLogRecord(); + } +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + using Logger::EmitLogRecord; void EmitLogRecord( @@ -359,6 +368,9 @@ class EnablementAwareTestLogger : public Logger mutable bool last_enabled_context_has_test_key_{false}; mutable bool last_enabled_context_test_key_value_{false}; nostd::unique_ptr last_emitted_record_; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + size_t create_log_record_context_calls_{0}; +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 protected: #if OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -513,3 +525,35 @@ TEST(Logger, EmitLogRecordWithRecordBypassesFiltering) EXPECT_EQ(logger.create_log_record_calls_, 1u); EXPECT_EQ(logger.emit_log_record_calls_, 1u); } + +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +TEST(Logger, EmitLogRecordWithContextInArgsRoutesThroughContextAwareEnabledAndEmits) +{ + EnablementAwareTestLogger logger(Severity::kTrace, true); + + context::Context test_context{"test-key", true}; + + logger.EmitLogRecord(Severity::kInfo, EventId{0x42, "info"}, nostd::string_view{"emitted"}, + test_context); + + EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); + EXPECT_EQ(logger.create_log_record_context_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); + EXPECT_TRUE(logger.last_enabled_context_has_test_key_); + EXPECT_TRUE(logger.last_enabled_context_test_key_value_); +} + +TEST(Logger, EmitLogRecordWithContextInArgsShortCircuitsWhenEnabledImplementationReturnsFalse) +{ + EnablementAwareTestLogger logger(Severity::kTrace, false); + + context::Context test_context{"test-key", true}; + + logger.EmitLogRecord(Severity::kInfo, EventId{0x42, "info"}, nostd::string_view{"filtered"}, + test_context); + + EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); + EXPECT_EQ(logger.create_log_record_context_calls_, 0u); + EXPECT_EQ(logger.emit_log_record_calls_, 0u); +} +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 diff --git a/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h index a77d80caa8..098b044df0 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger.h +++ b/sdk/include/opentelemetry/sdk/logs/logger.h @@ -45,6 +45,11 @@ class Logger final : public opentelemetry::logs::Logger nostd::unique_ptr CreateLogRecord() noexcept override; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 + nostd::unique_ptr CreateLogRecord( + const opentelemetry::context::Context &context) noexcept override; +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + using opentelemetry::logs::Logger::EmitLogRecord; void EmitLogRecord( diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 6e3a6c9936..981d563ded 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -143,6 +143,56 @@ opentelemetry::nostd::unique_ptr Logger::CreateL return opentelemetry::nostd::unique_ptr(recordable.release()); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +opentelemetry::nostd::unique_ptr Logger::CreateLogRecord( + const opentelemetry::context::Context &context) noexcept +{ + if (!logger_config_.IsEnabled()) + { + return kNoopLogger.CreateLogRecord(); + } + + auto recordable = context_->GetProcessor().MakeRecordable(); + + recordable->SetObservedTimestamp(std::chrono::system_clock::now()); + + // Get the span metadata from the supplied context + if (context.HasKey(trace_api::kSpanKey)) + { + const opentelemetry::context::ContextValue context_value = + context.GetValue(trace_api::kSpanKey); + + const trace_api::SpanContext span_context = [&context_value]() { + // Get the span metadata from the active span in the supplied context + if (const nostd::shared_ptr *maybe_span = + nostd::get_if>(&context_value)) + { + const nostd::shared_ptr &span = *maybe_span; + return span->GetContext(); + } + // Get the span metadata directly from a SpanContext in the supplied context. + // TODO: This path is unused and may be removed in the future. + else if (const nostd::shared_ptr *maybe_span_context = + nostd::get_if>(&context_value)) + { + const nostd::shared_ptr &span_context = *maybe_span_context; + return *span_context; + } + return trace_api::SpanContext::GetInvalid(); + }(); + + if (span_context.IsValid()) + { + recordable->SetTraceId(span_context.trace_id()); + recordable->SetTraceFlags(span_context.trace_flags()); + recordable->SetSpanId(span_context.span_id()); + } + } + + return opentelemetry::nostd::unique_ptr(recordable.release()); +} +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + void Logger::EmitLogRecord( opentelemetry::nostd::unique_ptr &&log_record) noexcept { diff --git a/sdk/test/logs/log_record_test.cc b/sdk/test/logs/log_record_test.cc index bd62fabe92..4f9d7addc2 100644 --- a/sdk/test/logs/log_record_test.cc +++ b/sdk/test/logs/log_record_test.cc @@ -11,7 +11,6 @@ #include #include -#include "opentelemetry/common/key_value_iterable.h" #include "opentelemetry/common/timestamp.h" #include "opentelemetry/logs/log_record.h" #include "opentelemetry/logs/logger.h" @@ -21,6 +20,7 @@ #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/nostd/utility.h" #include "opentelemetry/nostd/variant.h" #include "opentelemetry/sdk/common/attribute_utils.h" #include "opentelemetry/sdk/logs/read_write_log_record.h" diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 5bd17bbf85..460d7a86f2 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -21,7 +21,6 @@ #include "opentelemetry/logs/noop.h" #include "opentelemetry/logs/severity.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/sdk/instrumentationscope/instrumentation_scope.h" @@ -45,13 +44,13 @@ #include "opentelemetry/trace/trace_id.h" #include "opentelemetry/trace/tracer.h" -#include "opentelemetry/trace/default_span.h" - #if OPENTELEMETRY_ABI_VERSION_NO >= 2 # include # include "opentelemetry/context/context.h" +# include "opentelemetry/nostd/span.h" # include "opentelemetry/trace/context.h" +# include "opentelemetry/trace/default_span.h" #endif using namespace opentelemetry::sdk::logs; @@ -64,6 +63,7 @@ namespace nostd = opentelemetry::nostd; namespace { +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::shared_ptr MakeTestSpan(bool sampled) { const uint8_t trace_id_bytes[opentelemetry::trace::TraceId::kSize] = { @@ -81,7 +81,6 @@ nostd::shared_ptr MakeTestSpan(bool sampled) new opentelemetry::trace::DefaultSpan(span_context)); } -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 context::Context MakeContextWithUnsampledSpanAndInvalidTraceId() { const uint8_t span_id_bytes[opentelemetry::trace::SpanId::kSize] = {0xde, 0xad, 0xbe, 0xef, From 98b3e9c67e60067c239e022251a1ed3405c1a339 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 14 May 2026 17:20:06 +0900 Subject: [PATCH 05/26] doc: update changelog --- CHANGELOG.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06a1e4b1e9..f59db478d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,14 @@ Increment the: ## [Unreleased] -* [API] `Logger::EmitLogRecord(...)` templates now short-circuit when the - supplied Severity is below the logger's minimum. Closes the second half of - #2667 so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers honor - the `Enabled()` flag transparently. -* [SDK] `Logger::EmitLogRecord(unique_ptr)` now applies the - LoggerConfig trace-based filter using the current runtime context. +* [API] `Logger::EmitLogRecord(...)` templates now apply the `Enabled` filter + chain when a `Severity` is in args, so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers + honor the `Enabled()` flag transparently. Closes the second half of #2667. +* [API/SDK] (ABI v2) Add `Logger::CreateLogRecord(const Context &)` virtual + for explicit-context record creation. `Logger::EmitLogRecord(args...)` + also detects a `Context` in args and routes filtering through + `Enabled(context, severity, ...)` plus trace stamping through + `CreateLogRecord(context)`. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) ## [1.27.0] 2026-05-13 From 05b865b2e8f1d9e492a663eba440ff02a6f36a1b Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 14 May 2026 17:22:19 +0900 Subject: [PATCH 06/26] style: follow doc lint --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f59db478d5..fc8b57da08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Increment the: ## [Unreleased] * [API] `Logger::EmitLogRecord(...)` templates now apply the `Enabled` filter - chain when a `Severity` is in args, so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers + chain when a `Severity` is in args, so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers honor the `Enabled()` flag transparently. Closes the second half of #2667. * [API/SDK] (ABI v2) Add `Logger::CreateLogRecord(const Context &)` virtual for explicit-context record creation. `Logger::EmitLogRecord(args...)` From 8063cb6e10eae9e84d16b51ecb059b63ef847c4f Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 14 May 2026 17:25:13 +0900 Subject: [PATCH 07/26] doc: follow doc lint --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc8b57da08..a2338d72f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,9 @@ Increment the: ## [Unreleased] * [API] `Logger::EmitLogRecord(...)` templates now apply the `Enabled` filter - chain when a `Severity` is in args, so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers - honor the `Enabled()` flag transparently. Closes the second half of #2667. + chain when a `Severity` is in args, + so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers honor the `Enabled()` + flag transparently. Closes the second half of #2667. * [API/SDK] (ABI v2) Add `Logger::CreateLogRecord(const Context &)` virtual for explicit-context record creation. `Logger::EmitLogRecord(args...)` also detects a `Context` in args and routes filtering through From b9871a4cad3cc0da5f5d8b3e63fb3901199705e6 Mon Sep 17 00:00:00 2001 From: proost Date: Fri, 15 May 2026 00:11:17 +0900 Subject: [PATCH 08/26] style: follow doc lint --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff370d917..668fd28242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ Increment the: also detects a `Context` in args and routes filtering through `Enabled(context, severity, ...)` plus trace stamping through `CreateLogRecord(context)`. - [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) + [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) ## [1.27.0] 2026-05-13 From 5e676808e1d841b9f5f085f75806d8b36c2222f9 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Fri, 15 May 2026 16:00:31 +0900 Subject: [PATCH 09/26] perf: cached enabled required --- .github/workflows/clang-tidy.yaml | 2 +- api/include/opentelemetry/logs/logger.h | 50 +++++++++++++++++++------ sdk/src/logs/logger.cc | 2 + 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index f7dde52cd6..1ab4fe2543 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -19,7 +19,7 @@ jobs: - cmake_options: all-options-abiv1-preview warning_limit: 416 - cmake_options: all-options-abiv2-preview - warning_limit: 422 + warning_limit: 420 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 4d427a655b..a1c265896b 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -155,20 +155,30 @@ class Logger const Severity arg_severity = detail::FindSeverityInArgs(args...); if (arg_severity != Severity::kInvalid) { - const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); + if (!Enabled(arg_severity)) + { + return; + } + + if (ExtendedEnabledRequired()) + { + const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - const bool is_enabled = - context_ptr ? (event_id_ptr ? Enabled(*context_ptr, arg_severity, *event_id_ptr) - : Enabled(*context_ptr, arg_severity)) - : (event_id_ptr ? Enabled(arg_severity, *event_id_ptr) - : Enabled(arg_severity, static_cast(0))); + const bool extended_enabled = + context_ptr + ? (event_id_ptr ? EnabledImplementation(*context_ptr, arg_severity, *event_id_ptr) + : EnabledImplementation(*context_ptr, arg_severity)) + : (event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) + : EnabledImplementation(arg_severity, static_cast(0))); #else - const bool is_enabled = event_id_ptr ? Enabled(arg_severity, *event_id_ptr) - : Enabled(arg_severity, static_cast(0)); + const bool extended_enabled = + event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) + : EnabledImplementation(arg_severity, static_cast(0)); #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 - if (!is_enabled) - { - return; + if (!extended_enabled) + { + return; + } } } @@ -376,6 +386,11 @@ class Logger return static_cast(severity) >= OPENTELEMETRY_ATOMIC_READ_8(&minimum_severity_); } + inline bool ExtendedEnabledRequired() const noexcept + { + return OPENTELEMETRY_ATOMIC_READ_8(&extended_enabled_required_) != 0; + } + /** * Log an event * @@ -577,6 +592,12 @@ class Logger OPENTELEMETRY_ATOMIC_WRITE_8(&minimum_severity_, severity_or_max); } + void SetExtendedEnabledRequired(bool required) noexcept + { + OPENTELEMETRY_ATOMIC_WRITE_8(&extended_enabled_required_, + static_cast(required ? 1 : 0)); + } + private: template void IgnoreTraitResult(ValueType &&...) @@ -588,6 +609,13 @@ class Logger // compatible for OpenTelemetry C++ API. // mutable uint8_t minimum_severity_{kMaxSeverity}; + + // + // extended_enabled_required_ defaults to 1 (full chain required) so subclasses that override + // EnabledImplementation keep their existing semantics until they explicitly opt out via + // SetExtendedEnabledRequired(false). Same atomicity rationale as minimum_severity_. + // + mutable uint8_t extended_enabled_required_{1}; }; } // namespace logs OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 981d563ded..366b26e925 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -84,6 +84,8 @@ Logger::Logger( SetMinimumSeverity(logger_config_.IsEnabled() ? static_cast(logger_config_.GetMinimumSeverity()) : opentelemetry::logs::kMaxSeverity); + + SetExtendedEnabledRequired(logger_config_.IsTraceBased()); } const opentelemetry::nostd::string_view Logger::GetName() noexcept From dc306d58f01085c8a6dfdb2560ebb0d03d61a2ee Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sun, 17 May 2026 00:26:28 +0900 Subject: [PATCH 10/26] feat: has enabled filter --- CHANGELOG.md | 8 ++++++++ api/test/logs/logger_test.cc | 14 ++++++++++++++ .../sdk/logs/batch_log_record_processor.h | 2 ++ .../sdk/logs/multi_log_record_processor.h | 2 ++ sdk/include/opentelemetry/sdk/logs/processor.h | 8 ++++++++ .../sdk/logs/simple_log_record_processor.h | 2 ++ sdk/src/logs/batch_log_record_processor.cc | 1 - sdk/src/logs/logger.cc | 3 ++- sdk/src/logs/multi_log_record_processor.cc | 14 ++++++++++++-- 9 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 668fd28242..ef4472354f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,14 @@ Increment the: `CreateLogRecord(context)`. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) +* [SDK] Add `LogRecordProcessor::HasEnabledFilter()` so the SDK Logger can + include processor-level filtering in its extended-enabled cache. Defaults + to `true` (conservative). Built-in `SimpleLogRecordProcessor` and + `BatchLogRecordProcessor` override to `false` since they use the default + Enabled. Custom processors that do not override `EnabledImplementation` + should similarly override `HasEnabledFilter()` to return `false` to enable + the cheap path. SDK ABI break. + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 05e831b961..a156fac4f6 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -359,6 +359,8 @@ class EnablementAwareTestLogger : public Logger void SetEventIdEnabled(bool enabled) noexcept { event_id_enabled_ = enabled; } + using Logger::SetExtendedEnabledRequired; + size_t create_log_record_calls_{0}; size_t emit_log_record_calls_{0}; mutable size_t enabled_calls_{0}; @@ -526,6 +528,18 @@ TEST(Logger, EmitLogRecordWithRecordBypassesFiltering) EXPECT_EQ(logger.emit_log_record_calls_, 1u); } +TEST(Logger, EmitLogRecordTemplateSkipsEnabledImplementationWhenExtendedEnabledNotRequired) +{ + EnablementAwareTestLogger logger(Severity::kTrace, /*event_id_enabled=*/false); + logger.SetExtendedEnabledRequired(false); + + logger.Info(nostd::string_view{"emitted"}); + + EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); + EXPECT_EQ(logger.create_log_record_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); +} + #if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(Logger, EmitLogRecordWithContextInArgsRoutesThroughContextAwareEnabledAndEmits) { diff --git a/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h index 36ecf116be..1ec8504bb7 100644 --- a/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/batch_log_record_processor.h @@ -107,6 +107,8 @@ class BatchLogRecordProcessor : public LogRecordProcessor bool Shutdown( std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + bool HasEnabledFilter() const noexcept override { return false; } + /** * Class destructor which invokes the Shutdown() method. */ diff --git a/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h index bb16cddb69..020ee0840a 100644 --- a/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h @@ -62,6 +62,8 @@ class MultiLogRecordProcessor : public LogRecordProcessor bool Shutdown( std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + bool HasEnabledFilter() const noexcept override; + protected: /** * Exports all log records that have not yet been exported to the configured Exporter. diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h index 06206a872c..bdd695d5bc 100644 --- a/sdk/include/opentelemetry/sdk/logs/processor.h +++ b/sdk/include/opentelemetry/sdk/logs/processor.h @@ -89,6 +89,14 @@ class LogRecordProcessor virtual bool Shutdown( std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept = 0; + /** + * Returns true if this processor's EnabledImplementation does any custom + * filtering. The default returns true (conservative — assume any subclass + * might filter), so the SDK Logger consults the full Enabled chain by + * default. + */ + virtual bool HasEnabledFilter() const noexcept { return true; } + protected: virtual bool EnabledImplementation( const opentelemetry::context::Context & /*context*/, diff --git a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h index 7c0e76d361..94b0bb7bdd 100644 --- a/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/simple_log_record_processor.h @@ -49,6 +49,8 @@ class SimpleLogRecordProcessor : public LogRecordProcessor bool Shutdown( std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept override; + bool HasEnabledFilter() const noexcept override { return false; } + bool IsShutdown() const noexcept; private: diff --git a/sdk/src/logs/batch_log_record_processor.cc b/sdk/src/logs/batch_log_record_processor.cc index 8a03e44d7d..48d8f1110a 100644 --- a/sdk/src/logs/batch_log_record_processor.cc +++ b/sdk/src/logs/batch_log_record_processor.cc @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 366b26e925..a976df5222 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -85,7 +85,8 @@ Logger::Logger( ? static_cast(logger_config_.GetMinimumSeverity()) : opentelemetry::logs::kMaxSeverity); - SetExtendedEnabledRequired(logger_config_.IsTraceBased()); + SetExtendedEnabledRequired(logger_config_.IsTraceBased() || + context_->GetProcessor().HasEnabledFilter()); } const opentelemetry::nostd::string_view Logger::GetName() noexcept diff --git a/sdk/src/logs/multi_log_record_processor.cc b/sdk/src/logs/multi_log_record_processor.cc index f17202c357..19ac44ddc6 100644 --- a/sdk/src/logs/multi_log_record_processor.cc +++ b/sdk/src/logs/multi_log_record_processor.cc @@ -6,8 +6,6 @@ #include #include -#include "opentelemetry/context/context.h" -#include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/logs/multi_log_record_processor.h" #include "opentelemetry/sdk/logs/multi_recordable.h" @@ -99,6 +97,18 @@ bool MultiLogRecordProcessor::EnabledImplementation( return false; } +bool MultiLogRecordProcessor::HasEnabledFilter() const noexcept +{ + for (const auto &processor : processors_) + { + if (processor != nullptr && processor->HasEnabledFilter()) + { + return true; + } + } + return false; +} + bool MultiLogRecordProcessor::ForceFlush(std::chrono::microseconds timeout) noexcept { return InternalForceFlush(timeout); From d4578ad2cf9203abc38402c9533e04cec4b793fc Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sun, 17 May 2026 20:20:37 +0900 Subject: [PATCH 11/26] feat: use temp context --- CHANGELOG.md | 5 +- api/include/opentelemetry/logs/logger.h | 48 ++++++++++ .../opentelemetry/logs/logger_type_traits.h | 90 +++++++++++++++++++ api/test/logs/logger_test.cc | 45 +++++++++- 4 files changed, 186 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 478b3ec166..ba09b773b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,10 @@ Increment the: for explicit-context record creation. `Logger::EmitLogRecord(args...)` also detects a `Context` in args and routes filtering through `Enabled(context, severity, ...)` plus trace stamping through - `CreateLogRecord(context)`. + `CreateLogRecord(context)`. When trace parts (`SpanContext`, or + `TraceId` + `SpanId` [+ `TraceFlags`]) are in args without a `Context`, + the template synthesizes a `Context` with the span attached so the filter + evaluates against the trace the record is for. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) * [SDK] Add `LogRecordProcessor::HasEnabledFilter()` so the SDK Logger can diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index a1c265896b..14a6f78edb 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -15,6 +15,15 @@ #include "opentelemetry/nostd/unique_ptr.h" #include "opentelemetry/version.h" +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +# include "opentelemetry/nostd/shared_ptr.h" +# include "opentelemetry/trace/context.h" +# include "opentelemetry/trace/default_span.h" +# include "opentelemetry/trace/span.h" +# include "opentelemetry/trace/span_context.h" +# include "opentelemetry/trace/trace_flags.h" +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + OPENTELEMETRY_BEGIN_NAMESPACE namespace common { @@ -144,12 +153,51 @@ class Logger * span> -> attributes(return type of MakeAttributes) * Context (v2 only) -> filter + trace stamp (recommended: pass last) * + * When a @c Context is included, the filter chain uses + * @c Enabled(context, severity, ...) and the record is created via + * @c CreateLogRecord(context). When no @c Context is supplied but trace + * parts (@c SpanContext, or @c TraceId + @c SpanId [+ @c TraceFlags]) are + * in args, a @c Context is synthesized with those trace fields so the + * filter evaluates against the trace this record is for instead of the + * implicit runtime context. */ template void EmitLogRecord(ArgumentType &&...args) { #if OPENTELEMETRY_ABI_VERSION_NO >= 2 const opentelemetry::context::Context *context_ptr = detail::FindContextInArgs(args...); + // If no full Context is in args but trace parts are (SpanContext, or + // TraceId + SpanId [+ TraceFlags]), synthesize a Context with that span + // attached so the filter chain evaluates against the trace this record is + // actually for — not the implicit runtime context, which may be unrelated. + opentelemetry::context::Context derived_context; + if (context_ptr == nullptr) + { + const trace::SpanContext *span_context_ptr = detail::FindSpanContextInArgs(args...); + if (span_context_ptr != nullptr) + { + derived_context = trace::SetSpan( + derived_context, + nostd::shared_ptr(new trace::DefaultSpan(*span_context_ptr))); + context_ptr = &derived_context; + } + else + { + const trace::TraceId *trace_id_ptr = detail::FindTraceIdInArgs(args...); + const trace::SpanId *span_id_ptr = detail::FindSpanIdInArgs(args...); + if (trace_id_ptr != nullptr && span_id_ptr != nullptr) + { + const trace::TraceFlags *trace_flags_ptr = detail::FindTraceFlagsInArgs(args...); + derived_context = trace::SetSpan( + derived_context, + nostd::shared_ptr(new trace::DefaultSpan(trace::SpanContext( + *trace_id_ptr, *span_id_ptr, + trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, + false)))); + context_ptr = &derived_context; + } + } + } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 const Severity arg_severity = detail::FindSeverityInArgs(args...); diff --git a/api/include/opentelemetry/logs/logger_type_traits.h b/api/include/opentelemetry/logs/logger_type_traits.h index a419c1485c..ec5fb7c7ef 100644 --- a/api/include/opentelemetry/logs/logger_type_traits.h +++ b/api/include/opentelemetry/logs/logger_type_traits.h @@ -281,6 +281,96 @@ inline const opentelemetry::context::Context *FindContextInArgs(First && /*first { return FindContextInArgs(std::forward(rest)...); } + +inline const trace::SpanContext *FindSpanContextInArgs() noexcept +{ + return nullptr; +} + +template +inline const trace::SpanContext *FindSpanContextInArgs(const trace::SpanContext &span_context, + Rest &&.../*rest*/) noexcept +{ + return &span_context; +} + +template ::type, trace::SpanContext>::value, + int>::type = 0> +inline const trace::SpanContext *FindSpanContextInArgs(First && /*first*/, + Rest &&...rest) noexcept +{ + return FindSpanContextInArgs(std::forward(rest)...); +} + +inline const trace::TraceId *FindTraceIdInArgs() noexcept +{ + return nullptr; +} + +template +inline const trace::TraceId *FindTraceIdInArgs(const trace::TraceId &trace_id, + Rest &&.../*rest*/) noexcept +{ + return &trace_id; +} + +template ::type, trace::TraceId>::value, + int>::type = 0> +inline const trace::TraceId *FindTraceIdInArgs(First && /*first*/, Rest &&...rest) noexcept +{ + return FindTraceIdInArgs(std::forward(rest)...); +} + +inline const trace::SpanId *FindSpanIdInArgs() noexcept +{ + return nullptr; +} + +template +inline const trace::SpanId *FindSpanIdInArgs(const trace::SpanId &span_id, + Rest &&.../*rest*/) noexcept +{ + return &span_id; +} + +template ::type, trace::SpanId>::value, + int>::type = 0> +inline const trace::SpanId *FindSpanIdInArgs(First && /*first*/, Rest &&...rest) noexcept +{ + return FindSpanIdInArgs(std::forward(rest)...); +} + +inline const trace::TraceFlags *FindTraceFlagsInArgs() noexcept +{ + return nullptr; +} + +template +inline const trace::TraceFlags *FindTraceFlagsInArgs(const trace::TraceFlags &trace_flags, + Rest &&.../*rest*/) noexcept +{ + return &trace_flags; +} + +template ::type, trace::TraceFlags>::value, + int>::type = 0> +inline const trace::TraceFlags *FindTraceFlagsInArgs(First && /*first*/, + Rest &&...rest) noexcept +{ + return FindTraceFlagsInArgs(std::forward(rest)...); +} #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 } // namespace detail diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index a156fac4f6..c3d6baf3fb 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -30,6 +30,10 @@ #if OPENTELEMETRY_ABI_VERSION_NO >= 2 # include "opentelemetry/context/context.h" # include "opentelemetry/nostd/variant.h" +# include "opentelemetry/trace/span_context.h" +# include "opentelemetry/trace/span_id.h" +# include "opentelemetry/trace/trace_flags.h" +# include "opentelemetry/trace/trace_id.h" #endif #if OPENTELEMETRY_ABI_VERSION_NO < 2 @@ -530,7 +534,7 @@ TEST(Logger, EmitLogRecordWithRecordBypassesFiltering) TEST(Logger, EmitLogRecordTemplateSkipsEnabledImplementationWhenExtendedEnabledNotRequired) { - EnablementAwareTestLogger logger(Severity::kTrace, /*event_id_enabled=*/false); + EnablementAwareTestLogger logger(Severity::kTrace, false); logger.SetExtendedEnabledRequired(false); logger.Info(nostd::string_view{"emitted"}); @@ -570,4 +574,43 @@ TEST(Logger, EmitLogRecordWithContextInArgsShortCircuitsWhenEnabledImplementatio EXPECT_EQ(logger.create_log_record_context_calls_, 0u); EXPECT_EQ(logger.emit_log_record_calls_, 0u); } + +TEST(Logger, EmitLogRecordWithSpanContextInArgsSynthesizesContextForFilter) +{ + EnablementAwareTestLogger logger(Severity::kTrace); + + const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}; + const uint8_t span_id_bytes[trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; + const trace::SpanContext span_context(trace::TraceId(trace_id_bytes), + trace::SpanId(span_id_bytes), + trace::TraceFlags{trace::TraceFlags::kIsSampled}, + false); + + logger.EmitLogRecord(Severity::kInfo, span_context, nostd::string_view{"emitted"}); + + EXPECT_EQ(logger.enabled_calls_, 1u); + EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); + EXPECT_EQ(logger.create_log_record_context_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); +} + +TEST(Logger, EmitLogRecordWithTracePartsInArgsSynthesizesContextForFilter) +{ + EnablementAwareTestLogger logger(Severity::kTrace); + + const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}; + const uint8_t span_id_bytes[trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; + + logger.EmitLogRecord(Severity::kInfo, trace::TraceId(trace_id_bytes), + trace::SpanId(span_id_bytes), + trace::TraceFlags{trace::TraceFlags::kIsSampled}, + nostd::string_view{"emitted"}); + + EXPECT_EQ(logger.enabled_calls_, 1u); + EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); + EXPECT_EQ(logger.create_log_record_context_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); +} #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 From d62c8e4eb0fd951fa354f417b1900d699ca23a70 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sun, 17 May 2026 22:07:15 +0900 Subject: [PATCH 12/26] refactor: unify function --- api/include/opentelemetry/logs/logger.h | 3 +- .../opentelemetry/logs/logger_type_traits.h | 26 ++--- api/test/logs/logger_test.cc | 14 +-- sdk/src/logs/logger.cc | 103 +++++++----------- 4 files changed, 57 insertions(+), 89 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 14a6f78edb..dd474575ab 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -192,8 +192,7 @@ class Logger derived_context, nostd::shared_ptr(new trace::DefaultSpan(trace::SpanContext( *trace_id_ptr, *span_id_ptr, - trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, - false)))); + trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, false)))); context_ptr = &derived_context; } } diff --git a/api/include/opentelemetry/logs/logger_type_traits.h b/api/include/opentelemetry/logs/logger_type_traits.h index ec5fb7c7ef..93a8ed16d0 100644 --- a/api/include/opentelemetry/logs/logger_type_traits.h +++ b/api/include/opentelemetry/logs/logger_type_traits.h @@ -299,8 +299,7 @@ template ::type, trace::SpanContext>::value, int>::type = 0> -inline const trace::SpanContext *FindSpanContextInArgs(First && /*first*/, - Rest &&...rest) noexcept +inline const trace::SpanContext *FindSpanContextInArgs(First && /*first*/, Rest &&...rest) noexcept { return FindSpanContextInArgs(std::forward(rest)...); } @@ -317,11 +316,11 @@ inline const trace::TraceId *FindTraceIdInArgs(const trace::TraceId &trace_id, return &trace_id; } -template ::type, trace::TraceId>::value, - int>::type = 0> +template < + class First, + class... Rest, + typename std::enable_if::type, trace::TraceId>::value, + int>::type = 0> inline const trace::TraceId *FindTraceIdInArgs(First && /*first*/, Rest &&...rest) noexcept { return FindTraceIdInArgs(std::forward(rest)...); @@ -339,11 +338,11 @@ inline const trace::SpanId *FindSpanIdInArgs(const trace::SpanId &span_id, return &span_id; } -template ::type, trace::SpanId>::value, - int>::type = 0> +template < + class First, + class... Rest, + typename std::enable_if::type, trace::SpanId>::value, + int>::type = 0> inline const trace::SpanId *FindSpanIdInArgs(First && /*first*/, Rest &&...rest) noexcept { return FindSpanIdInArgs(std::forward(rest)...); @@ -366,8 +365,7 @@ template ::type, trace::TraceFlags>::value, int>::type = 0> -inline const trace::TraceFlags *FindTraceFlagsInArgs(First && /*first*/, - Rest &&...rest) noexcept +inline const trace::TraceFlags *FindTraceFlagsInArgs(First && /*first*/, Rest &&...rest) noexcept { return FindTraceFlagsInArgs(std::forward(rest)...); } diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index c3d6baf3fb..b72b63ecb8 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -579,13 +579,12 @@ TEST(Logger, EmitLogRecordWithSpanContextInArgsSynthesizesContextForFilter) { EnablementAwareTestLogger logger(Severity::kTrace); - const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, + const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; const uint8_t span_id_bytes[trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; const trace::SpanContext span_context(trace::TraceId(trace_id_bytes), trace::SpanId(span_id_bytes), - trace::TraceFlags{trace::TraceFlags::kIsSampled}, - false); + trace::TraceFlags{trace::TraceFlags::kIsSampled}, false); logger.EmitLogRecord(Severity::kInfo, span_context, nostd::string_view{"emitted"}); @@ -599,14 +598,13 @@ TEST(Logger, EmitLogRecordWithTracePartsInArgsSynthesizesContextForFilter) { EnablementAwareTestLogger logger(Severity::kTrace); - const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, + const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; const uint8_t span_id_bytes[trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; - logger.EmitLogRecord(Severity::kInfo, trace::TraceId(trace_id_bytes), - trace::SpanId(span_id_bytes), - trace::TraceFlags{trace::TraceFlags::kIsSampled}, - nostd::string_view{"emitted"}); + logger.EmitLogRecord( + Severity::kInfo, trace::TraceId(trace_id_bytes), trace::SpanId(span_id_bytes), + trace::TraceFlags{trace::TraceFlags::kIsSampled}, nostd::string_view{"emitted"}); EXPECT_EQ(logger.enabled_calls_, 1u); EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index a976df5222..80fc72aef6 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -68,6 +68,42 @@ bool IsAllowedByTraceBasedFiltering(const context::Context &context, return span_context.trace_flags().IsSampled(); } + +void StampSpanContextFromContext(const context::Context &context, Recordable &recordable) noexcept +{ + if (!context.HasKey(trace_api::kSpanKey)) + { + return; + } + + const context::ContextValue context_value = context.GetValue(trace_api::kSpanKey); + + const trace_api::SpanContext span_context = [&context_value]() { + // Get the span metadata from the active span in the context + if (const nostd::shared_ptr *maybe_span = + nostd::get_if>(&context_value)) + { + const nostd::shared_ptr &span = *maybe_span; + return span->GetContext(); + } + // Get the span metadata directly from a SpanContext in the context. + // TODO: This path is unused and may be removed in the future. + else if (const nostd::shared_ptr *maybe_span_context = + nostd::get_if>(&context_value)) + { + const nostd::shared_ptr &span_context = *maybe_span_context; + return *span_context; + } + return trace_api::SpanContext::GetInvalid(); + }(); + + if (span_context.IsValid()) + { + recordable.SetTraceId(span_context.trace_id()); + recordable.SetTraceFlags(span_context.trace_flags()); + recordable.SetSpanId(span_context.span_id()); + } +} } // namespace opentelemetry::logs::NoopLogger Logger::kNoopLogger = opentelemetry::logs::NoopLogger(); @@ -109,39 +145,7 @@ opentelemetry::nostd::unique_ptr Logger::CreateL recordable->SetObservedTimestamp(std::chrono::system_clock::now()); - // Get the current span metadata from the runtime context - const auto current_context = context::RuntimeContext::GetCurrent(); - - if (current_context.HasKey(trace_api::kSpanKey)) - { - const context::ContextValue context_value = current_context.GetValue(trace_api::kSpanKey); - - const trace_api::SpanContext span_context = [&context_value]() { - // Get the span metadata from the active span in the runtime context - if (const nostd::shared_ptr *maybe_span = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span = *maybe_span; - return span->GetContext(); - } - // Get the span metadata directly from a SpanContext in the runtime context. - // TODO: This path is unused and may be removed in the future. - else if (const nostd::shared_ptr *maybe_span_context = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span_context = *maybe_span_context; - return *span_context; - } - return trace_api::SpanContext::GetInvalid(); - }(); - - if (span_context.IsValid()) - { - recordable->SetTraceId(span_context.trace_id()); - recordable->SetTraceFlags(span_context.trace_flags()); - recordable->SetSpanId(span_context.span_id()); - } - } + StampSpanContextFromContext(context::RuntimeContext::GetCurrent(), *recordable); return opentelemetry::nostd::unique_ptr(recordable.release()); } @@ -159,38 +163,7 @@ opentelemetry::nostd::unique_ptr Logger::CreateL recordable->SetObservedTimestamp(std::chrono::system_clock::now()); - // Get the span metadata from the supplied context - if (context.HasKey(trace_api::kSpanKey)) - { - const opentelemetry::context::ContextValue context_value = - context.GetValue(trace_api::kSpanKey); - - const trace_api::SpanContext span_context = [&context_value]() { - // Get the span metadata from the active span in the supplied context - if (const nostd::shared_ptr *maybe_span = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span = *maybe_span; - return span->GetContext(); - } - // Get the span metadata directly from a SpanContext in the supplied context. - // TODO: This path is unused and may be removed in the future. - else if (const nostd::shared_ptr *maybe_span_context = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span_context = *maybe_span_context; - return *span_context; - } - return trace_api::SpanContext::GetInvalid(); - }(); - - if (span_context.IsValid()) - { - recordable->SetTraceId(span_context.trace_id()); - recordable->SetTraceFlags(span_context.trace_flags()); - recordable->SetSpanId(span_context.span_id()); - } - } + StampSpanContextFromContext(context, *recordable); return opentelemetry::nostd::unique_ptr(recordable.release()); } From e717aab9a1d918a4af487fdb85e160b4b79b2c48 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Tue, 19 May 2026 19:33:23 +0900 Subject: [PATCH 13/26] refactor: change default to false --- api/include/opentelemetry/logs/logger.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index dd474575ab..a2d007c6c1 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -658,11 +658,15 @@ class Logger mutable uint8_t minimum_severity_{kMaxSeverity}; // - // extended_enabled_required_ defaults to 1 (full chain required) so subclasses that override - // EnabledImplementation keep their existing semantics until they explicitly opt out via - // SetExtendedEnabledRequired(false). Same atomicity rationale as minimum_severity_. + // Controls whether the EmitLogRecord(args...) template calls the + // EnabledImplementation virtual in addition to the cheap atomic + // minimum_severity_ check. When 0, the template trusts the atomic check + // as the complete enabled decision and skips the virtual dispatch. When + // 1, the template also consults EnabledImplementation, which lets a + // Logger subclass apply richer filtering (trace-based, processor-level + // Enabled, custom predicates). // - mutable uint8_t extended_enabled_required_{1}; + mutable uint8_t extended_enabled_required_{0}; }; } // namespace logs OPENTELEMETRY_END_NAMESPACE From 8a18dc7954a5c10358c64ce24cc5fcdc4df6a11d Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Wed, 20 May 2026 14:29:21 +0900 Subject: [PATCH 14/26] refactor: use span context and context --- .github/workflows/clang-tidy.yaml | 4 +- CHANGELOG.md | 10 ++ api/include/opentelemetry/logs/logger.h | 142 ++++++++++-------- api/test/logs/logger_test.cc | 122 +++++++++++---- sdk/include/opentelemetry/sdk/logs/logger.h | 14 +- .../sdk/logs/multi_log_record_processor.h | 3 +- .../opentelemetry/sdk/logs/processor.h | 10 +- sdk/src/logs/logger.cc | 116 ++++++++------ sdk/src/logs/multi_log_record_processor.cc | 5 +- sdk/test/logs/logger_sdk_test.cc | 25 ++- .../logs/simple_log_record_processor_test.cc | 19 ++- 11 files changed, 308 insertions(+), 162 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 19ec7f1573..2f923fa7cf 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 389 + warning_limit: 387 - cmake_options: all-options-abiv2-preview - warning_limit: 395 + warning_limit: 392 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/CHANGELOG.md b/CHANGELOG.md index 36dc7b0d2f..dc7ea19420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,16 @@ Increment the: should similarly override `HasEnabledFilter()` to return `false` to enable the cheap path. SDK ABI break. +* [API/SDK] Replace `Context`-only signatures on + `LogRecordProcessor::Enabled`, + `LogRecordProcessor::EnabledImplementation`, + `Logger::EnabledImplementation` (v2), and `Logger::CreateLogRecord` (v2) + with `nostd::variant`. The + `EmitLogRecord(args...)` template now builds the variant in place from + trace parts in args, avoiding the per-emit `DefaultSpan` heap allocation + that the previous synthesis required. Same precedent as + `trace::StartSpanOptions::parent`. SDK ABI break. + ## [1.27.0] 2026-05-13 * [RELEASE] Bump main branch to 1.27.0-dev diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index a2d007c6c1..8b9332aa8e 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -16,10 +16,7 @@ #include "opentelemetry/version.h" #if OPENTELEMETRY_ABI_VERSION_NO >= 2 -# include "opentelemetry/nostd/shared_ptr.h" -# include "opentelemetry/trace/context.h" -# include "opentelemetry/trace/default_span.h" -# include "opentelemetry/trace/span.h" +# include "opentelemetry/nostd/variant.h" # include "opentelemetry/trace/span_context.h" # include "opentelemetry/trace/trace_flags.h" #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -61,13 +58,16 @@ class Logger #if OPENTELEMETRY_ABI_VERSION_NO >= 2 /** - * Create a Log Record object using given context. + * Create a Log Record object using either a Context or a SpanContext. * - * @param context Context which carries execution-scoped values across execution unit. + * @param context_or_span Variant carrying either a full Context or just a + * SpanContext. Avoids allocating a Context purely to + * propagate trace identity. * @return nostd::unique_ptr */ virtual nostd::unique_ptr CreateLogRecord( - const opentelemetry::context::Context & /*context*/) noexcept + const nostd::variant & + /*context_or_span*/) noexcept { return CreateLogRecord(); } @@ -153,48 +153,45 @@ class Logger * span> -> attributes(return type of MakeAttributes) * Context (v2 only) -> filter + trace stamp (recommended: pass last) * - * When a @c Context is included, the filter chain uses - * @c Enabled(context, severity, ...) and the record is created via - * @c CreateLogRecord(context). When no @c Context is supplied but trace - * parts (@c SpanContext, or @c TraceId + @c SpanId [+ @c TraceFlags]) are - * in args, a @c Context is synthesized with those trace fields so the - * filter evaluates against the trace this record is for instead of the - * implicit runtime context. + * When a @c Context or trace parts (@c SpanContext, or @c TraceId + + * @c SpanId [+ @c TraceFlags]) are in args, the filter chain uses + * @c Enabled(context_or_span, severity, ...) and the record is created + * via @c CreateLogRecord(context_or_span), so the filter evaluates + * against the trace this record is for instead of the implicit runtime + * context. The trace data is threaded as a + * @c nostd::variant built in place + * from args — no Context allocation when only trace parts are supplied. */ template void EmitLogRecord(ArgumentType &&...args) { #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - const opentelemetry::context::Context *context_ptr = detail::FindContextInArgs(args...); - // If no full Context is in args but trace parts are (SpanContext, or - // TraceId + SpanId [+ TraceFlags]), synthesize a Context with that span - // attached so the filter chain evaluates against the trace this record is - // actually for — not the implicit runtime context, which may be unrelated. - opentelemetry::context::Context derived_context; - if (context_ptr == nullptr) + nostd::variant context_or_span = + trace::SpanContext::GetInvalid(); + bool has_context_or_span_arg = false; + + if (const opentelemetry::context::Context *ctx = detail::FindContextInArgs(args...)) + { + context_or_span = *ctx; + has_context_or_span_arg = true; + } + else if (const trace::SpanContext *sc = detail::FindSpanContextInArgs(args...)) + { + context_or_span = *sc; + has_context_or_span_arg = true; + } + else { - const trace::SpanContext *span_context_ptr = detail::FindSpanContextInArgs(args...); - if (span_context_ptr != nullptr) + const trace::TraceId *trace_id_ptr = detail::FindTraceIdInArgs(args...); + const trace::SpanId *span_id_ptr = detail::FindSpanIdInArgs(args...); + if (trace_id_ptr != nullptr && span_id_ptr != nullptr) { - derived_context = trace::SetSpan( - derived_context, - nostd::shared_ptr(new trace::DefaultSpan(*span_context_ptr))); - context_ptr = &derived_context; - } - else - { - const trace::TraceId *trace_id_ptr = detail::FindTraceIdInArgs(args...); - const trace::SpanId *span_id_ptr = detail::FindSpanIdInArgs(args...); - if (trace_id_ptr != nullptr && span_id_ptr != nullptr) - { - const trace::TraceFlags *trace_flags_ptr = detail::FindTraceFlagsInArgs(args...); - derived_context = trace::SetSpan( - derived_context, - nostd::shared_ptr(new trace::DefaultSpan(trace::SpanContext( - *trace_id_ptr, *span_id_ptr, - trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, false)))); - context_ptr = &derived_context; - } + const trace::TraceFlags *trace_flags_ptr = detail::FindTraceFlagsInArgs(args...); + context_or_span = + trace::SpanContext(*trace_id_ptr, *span_id_ptr, + trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, + /*is_remote=*/false); + has_context_or_span_arg = true; } } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -211,12 +208,29 @@ class Logger { const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - const bool extended_enabled = - context_ptr - ? (event_id_ptr ? EnabledImplementation(*context_ptr, arg_severity, *event_id_ptr) - : EnabledImplementation(*context_ptr, arg_severity)) - : (event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) - : EnabledImplementation(arg_severity, static_cast(0))); + bool extended_enabled; + if (has_context_or_span_arg) + { + if (event_id_ptr) + { + extended_enabled = EnabledImplementation(context_or_span, arg_severity, *event_id_ptr); + } + else + { + extended_enabled = EnabledImplementation(context_or_span, arg_severity); + } + } + else + { + if (event_id_ptr) + { + extended_enabled = EnabledImplementation(arg_severity, *event_id_ptr); + } + else + { + extended_enabled = EnabledImplementation(arg_severity, static_cast(0)); + } + } #else const bool extended_enabled = event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) @@ -231,7 +245,7 @@ class Logger #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::unique_ptr log_record = - context_ptr ? CreateLogRecord(*context_ptr) : CreateLogRecord(); + has_context_or_span_arg ? CreateLogRecord(context_or_span) : CreateLogRecord(); #else nostd::unique_ptr log_record = CreateLogRecord(); #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -388,25 +402,27 @@ class Logger // #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - inline bool Enabled(const opentelemetry::context::Context &context, - Severity severity = Severity::kInvalid) const noexcept + inline bool Enabled( + const nostd::variant &context_or_span, + Severity severity = Severity::kInvalid) const noexcept { if OPENTELEMETRY_LIKELY_CONDITION (!Enabled(severity)) { return false; } - return EnabledImplementation(context, severity); + return EnabledImplementation(context_or_span, severity); } - inline bool Enabled(const opentelemetry::context::Context &context, - Severity severity, - const EventId &event_id) const noexcept + inline bool Enabled( + const nostd::variant &context_or_span, + Severity severity, + const EventId &event_id) const noexcept { if OPENTELEMETRY_LIKELY_CONDITION (!Enabled(severity)) { return false; } - return EnabledImplementation(context, severity, event_id); + return EnabledImplementation(context_or_span, severity, event_id); } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -620,15 +636,19 @@ class Logger } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - virtual bool EnabledImplementation(const opentelemetry::context::Context & /*context*/, - Severity /*severity*/) const noexcept + virtual bool EnabledImplementation( + const nostd::variant & + /*context_or_span*/, + Severity /*severity*/) const noexcept { return false; } - virtual bool EnabledImplementation(const opentelemetry::context::Context & /*context*/, - Severity /*severity*/, - const EventId & /*event_id*/) const noexcept + virtual bool EnabledImplementation( + const nostd::variant & + /*context_or_span*/, + Severity /*severity*/, + const EventId & /*event_id*/) const noexcept { return false; } diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index b72b63ecb8..17dfd07528 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -340,7 +340,8 @@ class EnablementAwareTestLogger : public Logger #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::unique_ptr CreateLogRecord( - const context::Context & /*context*/) noexcept override + const nostd::variant & /*context_or_span*/) noexcept + override { ++create_log_record_context_calls_; return CreateLogRecord(); @@ -376,47 +377,58 @@ class EnablementAwareTestLogger : public Logger nostd::unique_ptr last_emitted_record_; #if OPENTELEMETRY_ABI_VERSION_NO >= 2 size_t create_log_record_context_calls_{0}; + mutable bool last_variant_holds_span_context_{false}; + mutable bool last_variant_holds_context_{false}; + mutable trace::SpanContext last_span_context_{trace::SpanContext::GetInvalid()}; #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 protected: #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - bool EnabledImplementation(const context::Context &context, - Severity severity) const noexcept override + void CaptureContextOrSpan( + const nostd::variant &context_or_span) const noexcept { - ++enabled_calls_; - last_enabled_severity_ = severity; - auto value = context.GetValue("test-key"); - if (const bool *maybe_value = nostd::get_if(&value)) + last_variant_holds_span_context_ = false; + last_variant_holds_context_ = false; + last_span_context_ = trace::SpanContext::GetInvalid(); + last_enabled_context_has_test_key_ = false; + last_enabled_context_test_key_value_ = false; + + if (const trace::SpanContext *sc = nostd::get_if(&context_or_span)) { - last_enabled_context_has_test_key_ = true; - last_enabled_context_test_key_value_ = *maybe_value; + last_variant_holds_span_context_ = true; + last_span_context_ = *sc; } - else + else if (const context::Context *ctx = nostd::get_if(&context_or_span)) { - last_enabled_context_has_test_key_ = false; - last_enabled_context_test_key_value_ = false; + last_variant_holds_context_ = true; + auto value = ctx->GetValue("test-key"); + if (const bool *maybe_value = nostd::get_if(&value)) + { + last_enabled_context_has_test_key_ = true; + last_enabled_context_test_key_value_ = *maybe_value; + } } + } + + bool EnabledImplementation( + const nostd::variant &context_or_span, + Severity severity) const noexcept override + { + ++enabled_calls_; + last_enabled_severity_ = severity; + CaptureContextOrSpan(context_or_span); return true; } - bool EnabledImplementation(const context::Context &context, - Severity severity, - const EventId &event_id) const noexcept override + bool EnabledImplementation( + const nostd::variant &context_or_span, + Severity severity, + const EventId &event_id) const noexcept override { ++enabled_with_event_id_calls_; last_enabled_severity_ = severity; last_enabled_event_id_ = event_id.id_; - auto value = context.GetValue("test-key"); - if (const bool *maybe_value = nostd::get_if(&value)) - { - last_enabled_context_has_test_key_ = true; - last_enabled_context_test_key_value_ = *maybe_value; - } - else - { - last_enabled_context_has_test_key_ = false; - last_enabled_context_test_key_value_ = false; - } + CaptureContextOrSpan(context_or_span); return event_id_enabled_; } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -496,6 +508,7 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsBelowMinimumSeverity) TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowed) { EnablementAwareTestLogger logger(Severity::kTrace, true); + logger.SetExtendedEnabledRequired(true); logger.Info(nostd::string_view{"emitted"}); @@ -508,6 +521,7 @@ TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowe TEST(Logger, EmitLogRecordTemplateShortCircuitsWhenEnabledImplementationReturnsFalse) { EnablementAwareTestLogger logger(Severity::kTrace, false); + logger.SetExtendedEnabledRequired(true); logger.Info(nostd::string_view{"filtered"}); @@ -545,9 +559,10 @@ TEST(Logger, EmitLogRecordTemplateSkipsEnabledImplementationWhenExtendedEnabledN } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 -TEST(Logger, EmitLogRecordWithContextInArgsRoutesThroughContextAwareEnabledAndEmits) +TEST(Logger, EmitLogRecordWithContextInArgsRoutesContextVariantToEnabledAndEmits) { EnablementAwareTestLogger logger(Severity::kTrace, true); + logger.SetExtendedEnabledRequired(true); context::Context test_context{"test-key", true}; @@ -557,6 +572,8 @@ TEST(Logger, EmitLogRecordWithContextInArgsRoutesThroughContextAwareEnabledAndEm EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); EXPECT_EQ(logger.create_log_record_context_calls_, 1u); EXPECT_EQ(logger.emit_log_record_calls_, 1u); + EXPECT_TRUE(logger.last_variant_holds_context_); + EXPECT_FALSE(logger.last_variant_holds_span_context_); EXPECT_TRUE(logger.last_enabled_context_has_test_key_); EXPECT_TRUE(logger.last_enabled_context_test_key_value_); } @@ -564,6 +581,7 @@ TEST(Logger, EmitLogRecordWithContextInArgsRoutesThroughContextAwareEnabledAndEm TEST(Logger, EmitLogRecordWithContextInArgsShortCircuitsWhenEnabledImplementationReturnsFalse) { EnablementAwareTestLogger logger(Severity::kTrace, false); + logger.SetExtendedEnabledRequired(true); context::Context test_context{"test-key", true}; @@ -575,9 +593,10 @@ TEST(Logger, EmitLogRecordWithContextInArgsShortCircuitsWhenEnabledImplementatio EXPECT_EQ(logger.emit_log_record_calls_, 0u); } -TEST(Logger, EmitLogRecordWithSpanContextInArgsSynthesizesContextForFilter) +TEST(Logger, EmitLogRecordWithSpanContextInArgsRoutesSpanContextVariantToEnabled) { EnablementAwareTestLogger logger(Severity::kTrace); + logger.SetExtendedEnabledRequired(true); const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; @@ -592,23 +611,62 @@ TEST(Logger, EmitLogRecordWithSpanContextInArgsSynthesizesContextForFilter) EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); EXPECT_EQ(logger.create_log_record_context_calls_, 1u); EXPECT_EQ(logger.emit_log_record_calls_, 1u); + EXPECT_TRUE(logger.last_variant_holds_span_context_); + EXPECT_FALSE(logger.last_variant_holds_context_); + EXPECT_EQ(logger.last_span_context_.trace_id(), span_context.trace_id()); + EXPECT_EQ(logger.last_span_context_.span_id(), span_context.span_id()); + EXPECT_EQ(logger.last_span_context_.trace_flags(), span_context.trace_flags()); +} + +TEST(Logger, EmitLogRecordWithSpanContextInArgsAndEventIdRoutesVariantWithEventId) +{ + EnablementAwareTestLogger logger(Severity::kTrace, true); + logger.SetExtendedEnabledRequired(true); + + const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}; + const uint8_t span_id_bytes[trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; + const trace::SpanContext span_context(trace::TraceId(trace_id_bytes), + trace::SpanId(span_id_bytes), + trace::TraceFlags{trace::TraceFlags::kIsSampled}, false); + + logger.EmitLogRecord(Severity::kInfo, EventId{0x42, "info"}, span_context, + nostd::string_view{"emitted"}); + + EXPECT_EQ(logger.enabled_calls_, 0u); + EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); + EXPECT_EQ(logger.create_log_record_context_calls_, 1u); + EXPECT_EQ(logger.emit_log_record_calls_, 1u); + EXPECT_TRUE(logger.last_variant_holds_span_context_); + EXPECT_FALSE(logger.last_variant_holds_context_); + EXPECT_EQ(logger.last_span_context_.trace_id(), span_context.trace_id()); + EXPECT_EQ(logger.last_span_context_.span_id(), span_context.span_id()); + EXPECT_EQ(logger.last_enabled_event_id_, 0x42); } -TEST(Logger, EmitLogRecordWithTracePartsInArgsSynthesizesContextForFilter) +TEST(Logger, EmitLogRecordWithTracePartsInArgsRoutesSpanContextVariantToEnabled) { EnablementAwareTestLogger logger(Severity::kTrace); + logger.SetExtendedEnabledRequired(true); const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; const uint8_t span_id_bytes[trace::SpanId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8}; + const trace::TraceId trace_id(trace_id_bytes); + const trace::SpanId span_id(span_id_bytes); + const trace::TraceFlags trace_flags{trace::TraceFlags::kIsSampled}; - logger.EmitLogRecord( - Severity::kInfo, trace::TraceId(trace_id_bytes), trace::SpanId(span_id_bytes), - trace::TraceFlags{trace::TraceFlags::kIsSampled}, nostd::string_view{"emitted"}); + logger.EmitLogRecord(Severity::kInfo, trace_id, span_id, trace_flags, + nostd::string_view{"emitted"}); EXPECT_EQ(logger.enabled_calls_, 1u); EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); EXPECT_EQ(logger.create_log_record_context_calls_, 1u); EXPECT_EQ(logger.emit_log_record_calls_, 1u); + EXPECT_TRUE(logger.last_variant_holds_span_context_); + EXPECT_FALSE(logger.last_variant_holds_context_); + EXPECT_EQ(logger.last_span_context_.trace_id(), trace_id); + EXPECT_EQ(logger.last_span_context_.span_id(), span_id); + EXPECT_EQ(logger.last_span_context_.trace_flags(), trace_flags); } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 diff --git a/sdk/include/opentelemetry/sdk/logs/logger.h b/sdk/include/opentelemetry/sdk/logs/logger.h index 098b044df0..8be05720a1 100644 --- a/sdk/include/opentelemetry/sdk/logs/logger.h +++ b/sdk/include/opentelemetry/sdk/logs/logger.h @@ -17,6 +17,11 @@ #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/version.h" +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 +# include "opentelemetry/nostd/variant.h" +# include "opentelemetry/trace/span_context.h" +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 + OPENTELEMETRY_BEGIN_NAMESPACE namespace sdk { @@ -47,7 +52,8 @@ class Logger final : public opentelemetry::logs::Logger #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::unique_ptr CreateLogRecord( - const opentelemetry::context::Context &context) noexcept override; + const nostd::variant + &context_or_span) noexcept override; #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 using opentelemetry::logs::Logger::EmitLogRecord; @@ -74,10 +80,12 @@ class Logger final : public opentelemetry::logs::Logger int64_t event_id) const noexcept override; #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - bool EnabledImplementation(const opentelemetry::context::Context &context, + bool EnabledImplementation(const nostd::variant &context_or_span, opentelemetry::logs::Severity severity) const noexcept override; - bool EnabledImplementation(const opentelemetry::context::Context &context, + bool EnabledImplementation(const nostd::variant &context_or_span, opentelemetry::logs::Severity severity, const opentelemetry::logs::EventId &event_id) const noexcept override; #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 diff --git a/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h b/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h index 020ee0840a..8158a2dbf6 100644 --- a/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h +++ b/sdk/include/opentelemetry/sdk/logs/multi_log_record_processor.h @@ -84,7 +84,8 @@ class MultiLogRecordProcessor : public LogRecordProcessor std::chrono::microseconds timeout = (std::chrono::microseconds::max)()) noexcept; bool EnabledImplementation( - const opentelemetry::context::Context &context, + const opentelemetry::nostd::variant &context_or_span, const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope, opentelemetry::logs::Severity severity, opentelemetry::nostd::string_view event_name) const noexcept override; diff --git a/sdk/include/opentelemetry/sdk/logs/processor.h b/sdk/include/opentelemetry/sdk/logs/processor.h index bdd695d5bc..557db92250 100644 --- a/sdk/include/opentelemetry/sdk/logs/processor.h +++ b/sdk/include/opentelemetry/sdk/logs/processor.h @@ -9,6 +9,8 @@ #include "opentelemetry/context/context.h" #include "opentelemetry/logs/severity.h" #include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/trace/span_context.h" #include "opentelemetry/version.h" OPENTELEMETRY_BEGIN_NAMESPACE @@ -63,12 +65,13 @@ class LogRecordProcessor * The default implementation is permissive and returns true. */ bool Enabled( - const opentelemetry::context::Context &context, + const opentelemetry::nostd::variant &context_or_span, const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope, opentelemetry::logs::Severity severity, opentelemetry::nostd::string_view event_name = {}) const noexcept { - return EnabledImplementation(context, instrumentation_scope, severity, event_name); + return EnabledImplementation(context_or_span, instrumentation_scope, severity, event_name); } /** @@ -99,7 +102,8 @@ class LogRecordProcessor protected: virtual bool EnabledImplementation( - const opentelemetry::context::Context & /*context*/, + const opentelemetry::nostd::variant & /*context_or_span*/, const opentelemetry::sdk::instrumentationscope::InstrumentationScope & /*instrumentation_scope*/, opentelemetry::logs::Severity /*severity*/, diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 8a9c630f08..4aa57a79e5 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -25,7 +25,6 @@ #include "opentelemetry/sdk/logs/logger_context.h" #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/recordable.h" -#include "opentelemetry/trace/context.h" #include "opentelemetry/trace/span.h" #include "opentelemetry/trace/span_context.h" #include "opentelemetry/trace/span_id.h" @@ -50,52 +49,71 @@ nostd::string_view GetEventName(const opentelemetry::logs::EventId &event_id) no : nostd::string_view{}; } -bool IsAllowedByTraceBasedFiltering(const context::Context &context, - const LoggerConfig &logger_config) noexcept +trace_api::SpanContext ExtractSpanContextFromContext(const context::Context &context) noexcept { - if (!logger_config.IsTraceBased()) + if (!context.HasKey(trace_api::kSpanKey)) { - return true; + return trace_api::SpanContext::GetInvalid(); } - const trace_api::SpanContext span_context = trace_api::GetSpan(context)->GetContext(); + const context::ContextValue context_value = context.GetValue(trace_api::kSpanKey); - if (!span_context.span_id().IsValid()) + // Get the span metadata from the active span in the context + if (const nostd::shared_ptr *maybe_span = + nostd::get_if>(&context_value)) { - return true; + const nostd::shared_ptr &span = *maybe_span; + return span->GetContext(); + } + // Get the span metadata directly from a SpanContext in the context. + // TODO: This path is unused and may be removed in the future. + if (const nostd::shared_ptr *maybe_span_context = + nostd::get_if>(&context_value)) + { + const nostd::shared_ptr &span_context = *maybe_span_context; + return *span_context; } + return trace_api::SpanContext::GetInvalid(); +} - return span_context.trace_flags().IsSampled(); +trace_api::SpanContext ExtractSpanContext( + const nostd::variant &context_or_span) noexcept +{ + if (const trace_api::SpanContext *sc = nostd::get_if(&context_or_span)) + { + return *sc; + } + if (const context::Context *ctx = nostd::get_if(&context_or_span)) + { + return ExtractSpanContextFromContext(*ctx); + } + return trace_api::SpanContext::GetInvalid(); } -void StampSpanContextFromContext(const context::Context &context, Recordable &recordable) noexcept +bool IsAllowedByTraceBasedFiltering( + const nostd::variant &context_or_span, + const LoggerConfig &logger_config) noexcept { - if (!context.HasKey(trace_api::kSpanKey)) + if (!logger_config.IsTraceBased()) { - return; + return true; } - const context::ContextValue context_value = context.GetValue(trace_api::kSpanKey); + const trace_api::SpanContext span_context = ExtractSpanContext(context_or_span); - const trace_api::SpanContext span_context = [&context_value]() { - // Get the span metadata from the active span in the context - if (const nostd::shared_ptr *maybe_span = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span = *maybe_span; - return span->GetContext(); - } - // Get the span metadata directly from a SpanContext in the context. - // TODO: This path is unused and may be removed in the future. - else if (const nostd::shared_ptr *maybe_span_context = - nostd::get_if>(&context_value)) - { - const nostd::shared_ptr &span_context = *maybe_span_context; - return *span_context; - } - return trace_api::SpanContext::GetInvalid(); - }(); + if (!span_context.span_id().IsValid()) + { + return true; + } + return span_context.trace_flags().IsSampled(); +} + +void StampSpanContextFromVariant( + const nostd::variant &context_or_span, + Recordable &recordable) noexcept +{ + const trace_api::SpanContext span_context = ExtractSpanContext(context_or_span); if (span_context.IsValid()) { recordable.SetTraceId(span_context.trace_id()); @@ -144,14 +162,18 @@ opentelemetry::nostd::unique_ptr Logger::CreateL recordable->SetObservedTimestamp(std::chrono::system_clock::now()); - StampSpanContextFromContext(context::RuntimeContext::GetCurrent(), *recordable); + StampSpanContextFromVariant( + nostd::variant{ + context::RuntimeContext::GetCurrent()}, + *recordable); return opentelemetry::nostd::unique_ptr(recordable.release()); } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 opentelemetry::nostd::unique_ptr Logger::CreateLogRecord( - const opentelemetry::context::Context &context) noexcept + const nostd::variant + &context_or_span) noexcept { if (!logger_config_.IsEnabled()) { @@ -162,7 +184,7 @@ opentelemetry::nostd::unique_ptr Logger::CreateL recordable->SetObservedTimestamp(std::chrono::system_clock::now()); - StampSpanContextFromContext(context, *recordable); + StampSpanContextFromVariant(context_or_span, *recordable); return opentelemetry::nostd::unique_ptr(recordable.release()); } @@ -195,7 +217,8 @@ void Logger::EmitLogRecord( bool Logger::EnabledImplementation(opentelemetry::logs::Severity severity, const opentelemetry::logs::EventId &event_id) const noexcept { - const auto ¤t = context::RuntimeContext::GetCurrent(); + const nostd::variant current{ + context::RuntimeContext::GetCurrent()}; if (!IsAllowedByTraceBasedFiltering(current, logger_config_)) { return false; @@ -208,7 +231,8 @@ bool Logger::EnabledImplementation(opentelemetry::logs::Severity severity, bool Logger::EnabledImplementation(opentelemetry::logs::Severity severity, int64_t /*event_id*/) const noexcept { - const auto ¤t = context::RuntimeContext::GetCurrent(); + const nostd::variant current{ + context::RuntimeContext::GetCurrent()}; if (!IsAllowedByTraceBasedFiltering(current, logger_config_)) { return false; @@ -218,27 +242,29 @@ bool Logger::EnabledImplementation(opentelemetry::logs::Severity severity, } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 -bool Logger::EnabledImplementation(const opentelemetry::context::Context &context, - opentelemetry::logs::Severity severity) const noexcept +bool Logger::EnabledImplementation( + const nostd::variant &context_or_span, + opentelemetry::logs::Severity severity) const noexcept { - if (!IsAllowedByTraceBasedFiltering(context, logger_config_)) + if (!IsAllowedByTraceBasedFiltering(context_or_span, logger_config_)) { return false; } - return context_->GetProcessor().Enabled(context, GetInstrumentationScope(), severity); + return context_->GetProcessor().Enabled(context_or_span, GetInstrumentationScope(), severity); } -bool Logger::EnabledImplementation(const opentelemetry::context::Context &context, - opentelemetry::logs::Severity severity, - const opentelemetry::logs::EventId &event_id) const noexcept +bool Logger::EnabledImplementation( + const nostd::variant &context_or_span, + opentelemetry::logs::Severity severity, + const opentelemetry::logs::EventId &event_id) const noexcept { - if (!IsAllowedByTraceBasedFiltering(context, logger_config_)) + if (!IsAllowedByTraceBasedFiltering(context_or_span, logger_config_)) { return false; } - return context_->GetProcessor().Enabled(context, GetInstrumentationScope(), severity, + return context_->GetProcessor().Enabled(context_or_span, GetInstrumentationScope(), severity, GetEventName(event_id)); } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 diff --git a/sdk/src/logs/multi_log_record_processor.cc b/sdk/src/logs/multi_log_record_processor.cc index 19ac44ddc6..17826bcf43 100644 --- a/sdk/src/logs/multi_log_record_processor.cc +++ b/sdk/src/logs/multi_log_record_processor.cc @@ -75,7 +75,8 @@ void MultiLogRecordProcessor::OnEmit(std::unique_ptr &&record) noexc } bool MultiLogRecordProcessor::EnabledImplementation( - const opentelemetry::context::Context &context, + const opentelemetry::nostd::variant &context_or_span, const opentelemetry::sdk::instrumentationscope::InstrumentationScope &instrumentation_scope, opentelemetry::logs::Severity severity, opentelemetry::nostd::string_view event_name) const noexcept @@ -88,7 +89,7 @@ bool MultiLogRecordProcessor::EnabledImplementation( for (const auto &processor : processors_) { if (processor != nullptr && - processor->Enabled(context, instrumentation_scope, severity, event_name)) + processor->Enabled(context_or_span, instrumentation_scope, severity, event_name)) { return true; } diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 460d7a86f2..bd77fb11ec 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -309,21 +309,30 @@ class EnablementAwareProcessor final : public LogRecordProcessor bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; } protected: - bool EnabledImplementation(const context::Context &context, - const InstrumentationScope &instrumentation_scope, - logs_api::Severity severity, - nostd::string_view event_name) const noexcept override + bool EnabledImplementation( + const nostd::variant &context_or_span, + const InstrumentationScope &instrumentation_scope, + logs_api::Severity severity, + nostd::string_view event_name) const noexcept override { call_state_->severity = severity; call_state_->event_name = std::string(event_name); call_state_->scope_name = instrumentation_scope.GetName(); call_state_->call_count++; - auto value = context.GetValue("test-key"); - if (const bool *maybe_value = nostd::get_if(&value)) + if (const context::Context *ctx = nostd::get_if(&context_or_span)) { - call_state_->context_has_test_key = true; - call_state_->context_test_key_value = *maybe_value; + auto value = ctx->GetValue("test-key"); + if (const bool *maybe_value = nostd::get_if(&value)) + { + call_state_->context_has_test_key = true; + call_state_->context_test_key_value = *maybe_value; + } + else + { + call_state_->context_has_test_key = false; + call_state_->context_test_key_value = false; + } } else { diff --git a/sdk/test/logs/simple_log_record_processor_test.cc b/sdk/test/logs/simple_log_record_processor_test.cc index 2f9f0ca510..1716ba52ef 100644 --- a/sdk/test/logs/simple_log_record_processor_test.cc +++ b/sdk/test/logs/simple_log_record_processor_test.cc @@ -24,6 +24,7 @@ #include "opentelemetry/sdk/logs/processor.h" #include "opentelemetry/sdk/logs/recordable.h" #include "opentelemetry/sdk/logs/simple_log_record_processor.h" +#include "opentelemetry/trace/span_context.h" using namespace opentelemetry::sdk::logs; using namespace opentelemetry::sdk::common; @@ -292,14 +293,22 @@ class EnabledProcessor final : public LogRecordProcessor bool Shutdown(std::chrono::microseconds /* timeout */) noexcept override { return true; } protected: - bool EnabledImplementation(const context::Context &context, - const instrumentation_scope::InstrumentationScope &scope, - logs_api::Severity severity, - nostd::string_view event_name) const noexcept override + bool EnabledImplementation( + const nostd::variant &context_or_span, + const instrumentation_scope::InstrumentationScope &scope, + logs_api::Severity severity, + nostd::string_view event_name) const noexcept override { if (call_state_ != nullptr) { - call_state_->context = context; + if (const context::Context *ctx = nostd::get_if(&context_or_span)) + { + call_state_->context = *ctx; + } + else + { + call_state_->context = context::Context{}; + } call_state_->scope_name = scope.GetName(); call_state_->severity = severity; call_state_->event_name = std::string(event_name); From d719d999c8d6e674b1e05f153463792b08060b63 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Wed, 20 May 2026 15:14:12 +0900 Subject: [PATCH 15/26] style: follow lint --- api/include/opentelemetry/logs/logger.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 8b9332aa8e..2126331b58 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -158,9 +158,7 @@ class Logger * @c Enabled(context_or_span, severity, ...) and the record is created * via @c CreateLogRecord(context_or_span), so the filter evaluates * against the trace this record is for instead of the implicit runtime - * context. The trace data is threaded as a - * @c nostd::variant built in place - * from args — no Context allocation when only trace parts are supplied. + * context. */ template void EmitLogRecord(ArgumentType &&...args) @@ -187,10 +185,9 @@ class Logger if (trace_id_ptr != nullptr && span_id_ptr != nullptr) { const trace::TraceFlags *trace_flags_ptr = detail::FindTraceFlagsInArgs(args...); - context_or_span = - trace::SpanContext(*trace_id_ptr, *span_id_ptr, - trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, - /*is_remote=*/false); + context_or_span = trace::SpanContext( + *trace_id_ptr, *span_id_ptr, + trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, false); has_context_or_span_arg = true; } } From fc0c4cc040cb25d1a1ff5458a9e9131a4556af91 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 23 May 2026 17:29:10 +0900 Subject: [PATCH 16/26] doc: concise changelog --- CHANGELOG.md | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74cd5073b0..dfc5a0bb09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,37 +28,28 @@ Increment the: [#4085](https://github.com/open-telemetry/opentelemetry-cpp/pull/4085) * [API] `Logger::EmitLogRecord(...)` templates now apply the `Enabled` filter - chain when a `Severity` is in args, - so the `Trace`/`Debug`/`Info`/`Warn`/`Error`/`Fatal` helpers honor the `Enabled()` - flag transparently. Closes the second half of #2667. + chain when a `Severity` is in args. + [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) * [API/SDK] (ABI v2) Add `Logger::CreateLogRecord(const Context &)` virtual for explicit-context record creation. `Logger::EmitLogRecord(args...)` - also detects a `Context` in args and routes filtering through - `Enabled(context, severity, ...)` plus trace stamping through - `CreateLogRecord(context)`. When trace parts (`SpanContext`, or - `TraceId` + `SpanId` [+ `TraceFlags`]) are in args without a `Context`, - the template synthesizes a `Context` with the span attached so the filter - evaluates against the trace the record is for. + also detects a `Context`, `SpanContext` + or `TraceId` + `SpanId` [+ `TraceFlags`] in args and routes filtering. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) * [SDK] Add `LogRecordProcessor::HasEnabledFilter()` so the SDK Logger can include processor-level filtering in its extended-enabled cache. Defaults - to `true` (conservative). Built-in `SimpleLogRecordProcessor` and + to `true`. Built-in `SimpleLogRecordProcessor` and `BatchLogRecordProcessor` override to `false` since they use the default - Enabled. Custom processors that do not override `EnabledImplementation` - should similarly override `HasEnabledFilter()` to return `false` to enable - the cheap path. SDK ABI break. + Enabled. + [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) * [API/SDK] Replace `Context`-only signatures on `LogRecordProcessor::Enabled`, `LogRecordProcessor::EnabledImplementation`, `Logger::EnabledImplementation` (v2), and `Logger::CreateLogRecord` (v2) - with `nostd::variant`. The - `EmitLogRecord(args...)` template now builds the variant in place from - trace parts in args, avoiding the per-emit `DefaultSpan` heap allocation - that the previous synthesis required. Same precedent as - `trace::StartSpanOptions::parent`. SDK ABI break. + with `nostd::variant`. + [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) ## [1.27.0] 2026-05-13 From c6d568428d0c6303fb4e392b1adb47265c7cccd7 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 28 May 2026 00:01:05 +0900 Subject: [PATCH 17/26] perf: use current context once --- api/include/opentelemetry/logs/logger.h | 42 +++++++------------------ api/test/logs/logger_test.cc | 24 +++++++------- 2 files changed, 23 insertions(+), 43 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 2126331b58..56a4ed83ef 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -16,6 +16,7 @@ #include "opentelemetry/version.h" #if OPENTELEMETRY_ABI_VERSION_NO >= 2 +# include "opentelemetry/context/runtime_context.h" # include "opentelemetry/nostd/variant.h" # include "opentelemetry/trace/span_context.h" # include "opentelemetry/trace/trace_flags.h" @@ -166,17 +167,14 @@ class Logger #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::variant context_or_span = trace::SpanContext::GetInvalid(); - bool has_context_or_span_arg = false; if (const opentelemetry::context::Context *ctx = detail::FindContextInArgs(args...)) { - context_or_span = *ctx; - has_context_or_span_arg = true; + context_or_span = *ctx; } else if (const trace::SpanContext *sc = detail::FindSpanContextInArgs(args...)) { - context_or_span = *sc; - has_context_or_span_arg = true; + context_or_span = *sc; } else { @@ -188,7 +186,10 @@ class Logger context_or_span = trace::SpanContext( *trace_id_ptr, *span_id_ptr, trace_flags_ptr != nullptr ? *trace_flags_ptr : trace::TraceFlags{}, false); - has_context_or_span_arg = true; + } + else + { + context_or_span = opentelemetry::context::RuntimeContext::GetCurrent(); } } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -205,29 +206,9 @@ class Logger { const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - bool extended_enabled; - if (has_context_or_span_arg) - { - if (event_id_ptr) - { - extended_enabled = EnabledImplementation(context_or_span, arg_severity, *event_id_ptr); - } - else - { - extended_enabled = EnabledImplementation(context_or_span, arg_severity); - } - } - else - { - if (event_id_ptr) - { - extended_enabled = EnabledImplementation(arg_severity, *event_id_ptr); - } - else - { - extended_enabled = EnabledImplementation(arg_severity, static_cast(0)); - } - } + const bool extended_enabled = + event_id_ptr ? EnabledImplementation(context_or_span, arg_severity, *event_id_ptr) + : EnabledImplementation(context_or_span, arg_severity); #else const bool extended_enabled = event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) @@ -241,8 +222,7 @@ class Logger } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 - nostd::unique_ptr log_record = - has_context_or_span_arg ? CreateLogRecord(context_or_span) : CreateLogRecord(); + nostd::unique_ptr log_record = CreateLogRecord(context_or_span); #else nostd::unique_ptr log_record = CreateLogRecord(); #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 17dfd07528..a5c64fedd6 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -324,8 +324,8 @@ class EnablementAwareTestLogger : public Logger { public: explicit EnablementAwareTestLogger(Severity minimum_severity, - bool event_id_enabled = false) noexcept - : event_id_enabled_(event_id_enabled) + bool enabled_impl_result = false) noexcept + : enabled_impl_result_(enabled_impl_result) { SetMinimumSeverity(static_cast(minimum_severity)); } @@ -362,7 +362,7 @@ class EnablementAwareTestLogger : public Logger } } - void SetEventIdEnabled(bool enabled) noexcept { event_id_enabled_ = enabled; } + void SetEnabledImplResult(bool enabled) noexcept { enabled_impl_result_ = enabled; } using Logger::SetExtendedEnabledRequired; @@ -417,7 +417,7 @@ class EnablementAwareTestLogger : public Logger ++enabled_calls_; last_enabled_severity_ = severity; CaptureContextOrSpan(context_or_span); - return true; + return enabled_impl_result_; } bool EnabledImplementation( @@ -429,7 +429,7 @@ class EnablementAwareTestLogger : public Logger last_enabled_severity_ = severity; last_enabled_event_id_ = event_id.id_; CaptureContextOrSpan(context_or_span); - return event_id_enabled_; + return enabled_impl_result_; } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -438,7 +438,7 @@ class EnablementAwareTestLogger : public Logger ++enabled_with_event_id_calls_; last_enabled_severity_ = severity; last_enabled_event_id_ = event_id.id_; - return event_id_enabled_; + return enabled_impl_result_; } bool EnabledImplementation(Severity severity, int64_t event_id) const noexcept override @@ -447,7 +447,7 @@ class EnablementAwareTestLogger : public Logger } private: - bool event_id_enabled_; + bool enabled_impl_result_; }; } // namespace @@ -482,7 +482,7 @@ TEST(Logger, PushLoggerImplementation) #if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(Logger, EnabledWithExplicitContextUsesContextAwareImplementation) { - EnablementAwareTestLogger logger(Severity::kTrace); + EnablementAwareTestLogger logger(Severity::kTrace, true); context::Context test_context{"test-key", true}; @@ -512,7 +512,7 @@ TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowe logger.Info(nostd::string_view{"emitted"}); - EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); + EXPECT_EQ(logger.enabled_calls_ + logger.enabled_with_event_id_calls_, 1u); EXPECT_EQ(logger.create_log_record_calls_, 1u); EXPECT_EQ(logger.emit_log_record_calls_, 1u); EXPECT_EQ(logger.last_enabled_severity_, Severity::kInfo); @@ -525,7 +525,7 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsWhenEnabledImplementationReturnsF logger.Info(nostd::string_view{"filtered"}); - EXPECT_EQ(logger.enabled_with_event_id_calls_, 1u); + EXPECT_EQ(logger.enabled_calls_ + logger.enabled_with_event_id_calls_, 1u); EXPECT_EQ(logger.create_log_record_calls_, 0u); EXPECT_EQ(logger.emit_log_record_calls_, 0u); } @@ -595,7 +595,7 @@ TEST(Logger, EmitLogRecordWithContextInArgsShortCircuitsWhenEnabledImplementatio TEST(Logger, EmitLogRecordWithSpanContextInArgsRoutesSpanContextVariantToEnabled) { - EnablementAwareTestLogger logger(Severity::kTrace); + EnablementAwareTestLogger logger(Severity::kTrace, true); logger.SetExtendedEnabledRequired(true); const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, @@ -646,7 +646,7 @@ TEST(Logger, EmitLogRecordWithSpanContextInArgsAndEventIdRoutesVariantWithEventI TEST(Logger, EmitLogRecordWithTracePartsInArgsRoutesSpanContextVariantToEnabled) { - EnablementAwareTestLogger logger(Severity::kTrace); + EnablementAwareTestLogger logger(Severity::kTrace, true); logger.SetExtendedEnabledRequired(true); const uint8_t trace_id_bytes[trace::TraceId::kSize] = {1, 2, 3, 4, 5, 6, 7, 8, From 7ca49f2a775ccb3d2b50db04a8c26cdaa48027ea Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 28 May 2026 00:24:51 +0900 Subject: [PATCH 18/26] fix: avoiding hided methods --- api/include/opentelemetry/logs/noop.h | 2 ++ api/test/logs/logger_test.cc | 2 ++ .../etw/include/opentelemetry/exporters/etw/etw_logger.h | 2 ++ sdk/test/logs/log_record_test.cc | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/api/include/opentelemetry/logs/noop.h b/api/include/opentelemetry/logs/noop.h index 275b58e604..8608bb514d 100644 --- a/api/include/opentelemetry/logs/noop.h +++ b/api/include/opentelemetry/logs/noop.h @@ -34,6 +34,8 @@ class NoopLogger final : public Logger public: const nostd::string_view GetName() noexcept override { return "noop logger"; } + using Logger::CreateLogRecord; + nostd::unique_ptr CreateLogRecord() noexcept override { /* diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index a5c64fedd6..7d0f2454c9 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -265,6 +265,8 @@ class TestLogger : public Logger { const nostd::string_view GetName() noexcept override { return "test logger"; } + using Logger::CreateLogRecord; + nostd::unique_ptr CreateLogRecord() noexcept override { return nullptr; diff --git a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h index 14076a8b3e..58ecac86a8 100644 --- a/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h +++ b/exporters/etw/include/opentelemetry/exporters/etw/etw_logger.h @@ -197,6 +197,8 @@ class Logger : public opentelemetry::logs::Logger provHandle(initProvHandle()) {} + using opentelemetry::logs::Logger::CreateLogRecord; + nostd::unique_ptr CreateLogRecord() noexcept { nostd::unique_ptr log_record(new LogRecord()); diff --git a/sdk/test/logs/log_record_test.cc b/sdk/test/logs/log_record_test.cc index 4f9d7addc2..02c0921fe5 100644 --- a/sdk/test/logs/log_record_test.cc +++ b/sdk/test/logs/log_record_test.cc @@ -96,6 +96,8 @@ class TestBodyLogger : public opentelemetry::logs::Logger const nostd::string_view GetName() noexcept override { return "test body logger"; } + using opentelemetry::logs::Logger::CreateLogRecord; + nostd::unique_ptr CreateLogRecord() noexcept override { return nostd::unique_ptr(new ReadWriteLogRecord()); @@ -118,6 +120,8 @@ class TestBodyLogger : public opentelemetry::logs::Logger } protected: + using opentelemetry::logs::Logger::EnabledImplementation; + bool EnabledImplementation(opentelemetry::logs::Severity /*severity*/, int64_t /*event_id*/) const noexcept override { From ce5d3e061b8f696a613f358ac3513d99980d6daf Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Thu, 28 May 2026 01:23:02 +0900 Subject: [PATCH 19/26] test: add SDK test for context EmitLogRecord --- .github/workflows/clang-tidy.yaml | 2 +- sdk/test/logs/logger_sdk_test.cc | 89 +++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 076063ac3e..c00abe7ae5 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -19,7 +19,7 @@ jobs: - cmake_options: all-options-abiv1-preview warning_limit: 387 - cmake_options: all-options-abiv2-preview - warning_limit: 392 + warning_limit: 391 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index bd77fb11ec..7330c28dc6 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -667,6 +667,95 @@ TEST(LoggerSDK, LoggerTraceBasedConfigAllowsSampledExplicitContextWithNamedEvent EXPECT_TRUE(call_state->context_test_key_value); EXPECT_EQ(call_state->call_count, 1U); } + +TEST(LoggerSDK, EmitLogRecordWithExplicitContextStampsTraceFieldsFromContext) +{ + auto api_lp = std::shared_ptr(new LoggerProvider()); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = api_lp->GetLogger("logger", "opentelemetry_library", "", schema_url); + + auto shared_recordable = std::shared_ptr(new MockLogRecordable()); + auto lp = static_cast(api_lp.get()); + lp->AddProcessor(std::unique_ptr(new MockProcessor(shared_recordable))); + + nostd::shared_ptr runtime_span; + { + std::vector> sp; + auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); + runtime_span = tp->GetTracer("runtime")->StartSpan("runtime"); + } + opentelemetry::trace::Scope runtime_scope{runtime_span}; + + auto explicit_span = MakeTestSpan(/*sampled=*/true); + context::Context explicit_context; + explicit_context = opentelemetry::trace::SetSpan(explicit_context, explicit_span); + const auto explicit_span_ctx = explicit_span->GetContext(); + + logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_context); + + EXPECT_EQ(shared_recordable->GetTraceId(), explicit_span_ctx.trace_id()); + EXPECT_EQ(shared_recordable->GetSpanId(), explicit_span_ctx.span_id()); + EXPECT_EQ(shared_recordable->GetTraceFlags(), explicit_span_ctx.trace_flags()); + EXPECT_NE(shared_recordable->GetTraceId(), runtime_span->GetContext().trace_id()); +} + +TEST(LoggerSDK, EmitLogRecordWithExplicitSpanContextStampsTraceFields) +{ + auto api_lp = std::shared_ptr(new LoggerProvider()); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = api_lp->GetLogger("logger", "opentelemetry_library", "", schema_url); + + auto shared_recordable = std::shared_ptr(new MockLogRecordable()); + auto lp = static_cast(api_lp.get()); + lp->AddProcessor(std::unique_ptr(new MockProcessor(shared_recordable))); + + nostd::shared_ptr runtime_span; + { + std::vector> sp; + auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); + runtime_span = tp->GetTracer("runtime")->StartSpan("runtime"); + } + opentelemetry::trace::Scope runtime_scope{runtime_span}; + + const auto explicit_span_ctx = MakeTestSpan(/*sampled=*/true)->GetContext(); + + logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_span_ctx); + + EXPECT_EQ(shared_recordable->GetTraceId(), explicit_span_ctx.trace_id()); + EXPECT_EQ(shared_recordable->GetSpanId(), explicit_span_ctx.span_id()); + EXPECT_EQ(shared_recordable->GetTraceFlags(), explicit_span_ctx.trace_flags()); + EXPECT_NE(shared_recordable->GetTraceId(), runtime_span->GetContext().trace_id()); +} + +TEST(LoggerSDK, EmitLogRecordWithExplicitTracePartsStampsTraceFields) +{ + auto api_lp = std::shared_ptr(new LoggerProvider()); + const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; + auto logger = api_lp->GetLogger("logger", "opentelemetry_library", "", schema_url); + + auto shared_recordable = std::shared_ptr(new MockLogRecordable()); + auto lp = static_cast(api_lp.get()); + lp->AddProcessor(std::unique_ptr(new MockProcessor(shared_recordable))); + + nostd::shared_ptr runtime_span; + { + std::vector> sp; + auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); + runtime_span = tp->GetTracer("runtime")->StartSpan("runtime"); + } + opentelemetry::trace::Scope runtime_scope{runtime_span}; + + const auto explicit_span_ctx = MakeTestSpan(/*sampled=*/true)->GetContext(); + + logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, + explicit_span_ctx.trace_id(), explicit_span_ctx.span_id(), + explicit_span_ctx.trace_flags()); + + EXPECT_EQ(shared_recordable->GetTraceId(), explicit_span_ctx.trace_id()); + EXPECT_EQ(shared_recordable->GetSpanId(), explicit_span_ctx.span_id()); + EXPECT_EQ(shared_recordable->GetTraceFlags(), explicit_span_ctx.trace_flags()); + EXPECT_NE(shared_recordable->GetTraceId(), runtime_span->GetContext().trace_id()); +} #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 static std::unique_ptr create_mock_log_recordable( From 956f820c9d3c12faea40a702d35eddb7e5cdd17f Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 30 May 2026 00:57:17 +0900 Subject: [PATCH 20/26] fix: gate v2 flag --- api/include/opentelemetry/logs/logger.h | 24 +++++++++++++++++------- api/test/logs/logger_test.cc | 8 +++++++- sdk/src/logs/logger.cc | 2 ++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 56a4ed83ef..f96922d100 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -202,23 +202,27 @@ class Logger return; } + const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 if (ExtendedEnabledRequired()) { - const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 const bool extended_enabled = event_id_ptr ? EnabledImplementation(context_or_span, arg_severity, *event_id_ptr) : EnabledImplementation(context_or_span, arg_severity); -#else - const bool extended_enabled = - event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) - : EnabledImplementation(arg_severity, static_cast(0)); -#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 if (!extended_enabled) { return; } } +#else + const bool extended_enabled = + event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) + : EnabledImplementation(arg_severity, static_cast(0)); + if (!extended_enabled) + { + return; + } +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 } #if OPENTELEMETRY_ABI_VERSION_NO >= 2 @@ -426,10 +430,12 @@ class Logger return static_cast(severity) >= OPENTELEMETRY_ATOMIC_READ_8(&minimum_severity_); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 inline bool ExtendedEnabledRequired() const noexcept { return OPENTELEMETRY_ATOMIC_READ_8(&extended_enabled_required_) != 0; } +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 /** * Log an event @@ -636,11 +642,13 @@ class Logger OPENTELEMETRY_ATOMIC_WRITE_8(&minimum_severity_, severity_or_max); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 void SetExtendedEnabledRequired(bool required) noexcept { OPENTELEMETRY_ATOMIC_WRITE_8(&extended_enabled_required_, static_cast(required ? 1 : 0)); } +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 private: template @@ -654,6 +662,7 @@ class Logger // mutable uint8_t minimum_severity_{kMaxSeverity}; +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 // // Controls whether the EmitLogRecord(args...) template calls the // EnabledImplementation virtual in addition to the cheap atomic @@ -664,6 +673,7 @@ class Logger // Enabled, custom predicates). // mutable uint8_t extended_enabled_required_{0}; +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 }; } // namespace logs OPENTELEMETRY_END_NAMESPACE diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 7d0f2454c9..66b2bf5ce9 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -366,7 +366,9 @@ class EnablementAwareTestLogger : public Logger void SetEnabledImplResult(bool enabled) noexcept { enabled_impl_result_ = enabled; } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 using Logger::SetExtendedEnabledRequired; +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 size_t create_log_record_calls_{0}; size_t emit_log_record_calls_{0}; @@ -510,7 +512,9 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsBelowMinimumSeverity) TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowed) { EnablementAwareTestLogger logger(Severity::kTrace, true); +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.SetExtendedEnabledRequired(true); +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.Info(nostd::string_view{"emitted"}); @@ -523,7 +527,9 @@ TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowe TEST(Logger, EmitLogRecordTemplateShortCircuitsWhenEnabledImplementationReturnsFalse) { EnablementAwareTestLogger logger(Severity::kTrace, false); +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.SetExtendedEnabledRequired(true); +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.Info(nostd::string_view{"filtered"}); @@ -548,6 +554,7 @@ TEST(Logger, EmitLogRecordWithRecordBypassesFiltering) EXPECT_EQ(logger.emit_log_record_calls_, 1u); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(Logger, EmitLogRecordTemplateSkipsEnabledImplementationWhenExtendedEnabledNotRequired) { EnablementAwareTestLogger logger(Severity::kTrace, false); @@ -560,7 +567,6 @@ TEST(Logger, EmitLogRecordTemplateSkipsEnabledImplementationWhenExtendedEnabledN EXPECT_EQ(logger.emit_log_record_calls_, 1u); } -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(Logger, EmitLogRecordWithContextInArgsRoutesContextVariantToEnabledAndEmits) { EnablementAwareTestLogger logger(Severity::kTrace, true); diff --git a/sdk/src/logs/logger.cc b/sdk/src/logs/logger.cc index 4aa57a79e5..5c643da7b6 100644 --- a/sdk/src/logs/logger.cc +++ b/sdk/src/logs/logger.cc @@ -138,8 +138,10 @@ Logger::Logger( ? static_cast(logger_config_.GetMinimumSeverity()) : opentelemetry::logs::kMaxSeverity); +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 SetExtendedEnabledRequired(logger_config_.IsTraceBased() || context_->GetProcessor().HasEnabledFilter()); +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 } const opentelemetry::nostd::string_view Logger::GetName() noexcept From f5e855857b2fb54b6083814f1f269c059dc6aab8 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 30 May 2026 01:02:39 +0900 Subject: [PATCH 21/26] doc: remove unnecessary comments --- api/include/opentelemetry/logs/logger.h | 3 +-- sdk/test/logs/logger_sdk_test.cc | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index f96922d100..591453812f 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -62,8 +62,7 @@ class Logger * Create a Log Record object using either a Context or a SpanContext. * * @param context_or_span Variant carrying either a full Context or just a - * SpanContext. Avoids allocating a Context purely to - * propagate trace identity. + * SpanContext. * @return nostd::unique_ptr */ virtual nostd::unique_ptr CreateLogRecord( diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index 7330c28dc6..a869c0ea7c 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -717,7 +717,7 @@ TEST(LoggerSDK, EmitLogRecordWithExplicitSpanContextStampsTraceFields) } opentelemetry::trace::Scope runtime_scope{runtime_span}; - const auto explicit_span_ctx = MakeTestSpan(/*sampled=*/true)->GetContext(); + const auto explicit_span_ctx = MakeTestSpan(true)->GetContext(); logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_span_ctx); @@ -745,7 +745,7 @@ TEST(LoggerSDK, EmitLogRecordWithExplicitTracePartsStampsTraceFields) } opentelemetry::trace::Scope runtime_scope{runtime_span}; - const auto explicit_span_ctx = MakeTestSpan(/*sampled=*/true)->GetContext(); + const auto explicit_span_ctx = MakeTestSpan(true)->GetContext(); logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_span_ctx.trace_id(), explicit_span_ctx.span_id(), From 8cae2952550b26a666648d954f22765348e83356 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Wed, 3 Jun 2026 01:05:19 +0900 Subject: [PATCH 22/26] perf: use hot-path more --- .github/workflows/clang-tidy.yaml | 4 ++-- api/include/opentelemetry/logs/logger.h | 24 ++++++------------------ api/test/logs/logger_test.cc | 6 ++---- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/.github/workflows/clang-tidy.yaml b/.github/workflows/clang-tidy.yaml index 6ddb9d6dde..3acfd0744b 100644 --- a/.github/workflows/clang-tidy.yaml +++ b/.github/workflows/clang-tidy.yaml @@ -17,9 +17,9 @@ jobs: matrix: include: - cmake_options: all-options-abiv1-preview - warning_limit: 385 + warning_limit: 383 - cmake_options: all-options-abiv2-preview - warning_limit: 391 + warning_limit: 387 env: CC: /usr/bin/clang-22 CXX: /usr/bin/clang++-22 diff --git a/api/include/opentelemetry/logs/logger.h b/api/include/opentelemetry/logs/logger.h index 591453812f..b99293286f 100644 --- a/api/include/opentelemetry/logs/logger.h +++ b/api/include/opentelemetry/logs/logger.h @@ -163,6 +163,12 @@ class Logger template void EmitLogRecord(ArgumentType &&...args) { + const Severity arg_severity = detail::FindSeverityInArgs(args...); + if (arg_severity != Severity::kInvalid && !Enabled(arg_severity)) + { + return; + } + #if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::variant context_or_span = trace::SpanContext::GetInvalid(); @@ -191,18 +197,10 @@ class Logger context_or_span = opentelemetry::context::RuntimeContext::GetCurrent(); } } -#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 - const Severity arg_severity = detail::FindSeverityInArgs(args...); if (arg_severity != Severity::kInvalid) { - if (!Enabled(arg_severity)) - { - return; - } - const EventId *event_id_ptr = detail::FindEventIdInArgs(args...); -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 if (ExtendedEnabledRequired()) { const bool extended_enabled = @@ -213,18 +211,8 @@ class Logger return; } } -#else - const bool extended_enabled = - event_id_ptr ? EnabledImplementation(arg_severity, *event_id_ptr) - : EnabledImplementation(arg_severity, static_cast(0)); - if (!extended_enabled) - { - return; - } -#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 } -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 nostd::unique_ptr log_record = CreateLogRecord(context_or_span); #else nostd::unique_ptr log_record = CreateLogRecord(); diff --git a/api/test/logs/logger_test.cc b/api/test/logs/logger_test.cc index 66b2bf5ce9..599be5967e 100644 --- a/api/test/logs/logger_test.cc +++ b/api/test/logs/logger_test.cc @@ -509,12 +509,11 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsBelowMinimumSeverity) EXPECT_EQ(logger.enabled_with_event_id_calls_, 0u); } +#if OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowed) { EnablementAwareTestLogger logger(Severity::kTrace, true); -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.SetExtendedEnabledRequired(true); -#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.Info(nostd::string_view{"emitted"}); @@ -527,9 +526,7 @@ TEST(Logger, EmitLogRecordTemplateInvokesEnabledImplementationAndEmitsWhenAllowe TEST(Logger, EmitLogRecordTemplateShortCircuitsWhenEnabledImplementationReturnsFalse) { EnablementAwareTestLogger logger(Severity::kTrace, false); -#if OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.SetExtendedEnabledRequired(true); -#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 logger.Info(nostd::string_view{"filtered"}); @@ -537,6 +534,7 @@ TEST(Logger, EmitLogRecordTemplateShortCircuitsWhenEnabledImplementationReturnsF EXPECT_EQ(logger.create_log_record_calls_, 0u); EXPECT_EQ(logger.emit_log_record_calls_, 0u); } +#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 TEST(Logger, EmitLogRecordWithRecordBypassesFiltering) { From 5081253aa71b2b5c608c0b6c5f8522eb8376694c Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 6 Jun 2026 21:28:31 +0900 Subject: [PATCH 23/26] refactor: set lifetimebound --- .../opentelemetry/logs/logger_type_traits.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/api/include/opentelemetry/logs/logger_type_traits.h b/api/include/opentelemetry/logs/logger_type_traits.h index 93a8ed16d0..25f312c7a9 100644 --- a/api/include/opentelemetry/logs/logger_type_traits.h +++ b/api/include/opentelemetry/logs/logger_type_traits.h @@ -243,7 +243,9 @@ inline const EventId *FindEventIdInArgs() noexcept } template -inline const EventId *FindEventIdInArgs(const EventId &event_id, Rest &&.../*rest*/) noexcept +inline const EventId *FindEventIdInArgs(const EventId &event_id + OPENTELEMETRY_ATTRIBUTE_LIFETIME_BOUND, + Rest &&.../*rest*/) noexcept { return &event_id; } @@ -265,7 +267,7 @@ inline const opentelemetry::context::Context *FindContextInArgs() noexcept template inline const opentelemetry::context::Context *FindContextInArgs( - const opentelemetry::context::Context &context, + const opentelemetry::context::Context &context OPENTELEMETRY_ATTRIBUTE_LIFETIME_BOUND, Rest &&.../*rest*/) noexcept { return &context; @@ -288,7 +290,8 @@ inline const trace::SpanContext *FindSpanContextInArgs() noexcept } template -inline const trace::SpanContext *FindSpanContextInArgs(const trace::SpanContext &span_context, +inline const trace::SpanContext *FindSpanContextInArgs(const trace::SpanContext &span_context + OPENTELEMETRY_ATTRIBUTE_LIFETIME_BOUND, Rest &&.../*rest*/) noexcept { return &span_context; @@ -310,7 +313,8 @@ inline const trace::TraceId *FindTraceIdInArgs() noexcept } template -inline const trace::TraceId *FindTraceIdInArgs(const trace::TraceId &trace_id, +inline const trace::TraceId *FindTraceIdInArgs(const trace::TraceId &trace_id + OPENTELEMETRY_ATTRIBUTE_LIFETIME_BOUND, Rest &&.../*rest*/) noexcept { return &trace_id; @@ -332,7 +336,8 @@ inline const trace::SpanId *FindSpanIdInArgs() noexcept } template -inline const trace::SpanId *FindSpanIdInArgs(const trace::SpanId &span_id, +inline const trace::SpanId *FindSpanIdInArgs(const trace::SpanId &span_id + OPENTELEMETRY_ATTRIBUTE_LIFETIME_BOUND, Rest &&.../*rest*/) noexcept { return &span_id; @@ -354,7 +359,8 @@ inline const trace::TraceFlags *FindTraceFlagsInArgs() noexcept } template -inline const trace::TraceFlags *FindTraceFlagsInArgs(const trace::TraceFlags &trace_flags, +inline const trace::TraceFlags *FindTraceFlagsInArgs(const trace::TraceFlags &trace_flags + OPENTELEMETRY_ATTRIBUTE_LIFETIME_BOUND, Rest &&.../*rest*/) noexcept { return &trace_flags; From b293548a8b42b6dd6af26b6ecb9e3d23482a7e53 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 6 Jun 2026 21:37:10 +0900 Subject: [PATCH 24/26] test: change to fixture --- sdk/test/logs/logger_sdk_test.cc | 109 +++++++++++++------------------ 1 file changed, 45 insertions(+), 64 deletions(-) diff --git a/sdk/test/logs/logger_sdk_test.cc b/sdk/test/logs/logger_sdk_test.cc index a869c0ea7c..b83df11396 100644 --- a/sdk/test/logs/logger_sdk_test.cc +++ b/sdk/test/logs/logger_sdk_test.cc @@ -668,93 +668,74 @@ TEST(LoggerSDK, LoggerTraceBasedConfigAllowsSampledExplicitContextWithNamedEvent EXPECT_EQ(call_state->call_count, 1U); } -TEST(LoggerSDK, EmitLogRecordWithExplicitContextStampsTraceFieldsFromContext) +class LoggerEmitWithExplicitTraceTest : public ::testing::Test { - auto api_lp = std::shared_ptr(new LoggerProvider()); - const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; - auto logger = api_lp->GetLogger("logger", "opentelemetry_library", "", schema_url); +protected: + void SetUp() override + { + api_lp_ = std::shared_ptr(new LoggerProvider()); + logger_ = api_lp_->GetLogger("logger", "opentelemetry_library", "", + "https://opentelemetry.io/schemas/1.11.0"); - auto shared_recordable = std::shared_ptr(new MockLogRecordable()); - auto lp = static_cast(api_lp.get()); - lp->AddProcessor(std::unique_ptr(new MockProcessor(shared_recordable))); + shared_recordable_ = std::shared_ptr(new MockLogRecordable()); + auto *sdk_lp = static_cast(api_lp_.get()); + sdk_lp->AddProcessor( + std::unique_ptr(new MockProcessor(shared_recordable_))); - nostd::shared_ptr runtime_span; - { - std::vector> sp; - auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); - runtime_span = tp->GetTracer("runtime")->StartSpan("runtime"); + { + std::vector> sp; + auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); + runtime_span_ = tp->GetTracer("runtime")->StartSpan("runtime"); + } + runtime_scope_.reset(new opentelemetry::trace::Scope(runtime_span_)); } - opentelemetry::trace::Scope runtime_scope{runtime_span}; + std::shared_ptr api_lp_; + nostd::shared_ptr logger_; + std::shared_ptr shared_recordable_; + nostd::shared_ptr runtime_span_; + std::unique_ptr runtime_scope_; +}; + +TEST_F(LoggerEmitWithExplicitTraceTest, ExplicitContextStampsTraceFieldsFromContext) +{ auto explicit_span = MakeTestSpan(/*sampled=*/true); context::Context explicit_context; explicit_context = opentelemetry::trace::SetSpan(explicit_context, explicit_span); const auto explicit_span_ctx = explicit_span->GetContext(); - logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_context); + logger_->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_context); - EXPECT_EQ(shared_recordable->GetTraceId(), explicit_span_ctx.trace_id()); - EXPECT_EQ(shared_recordable->GetSpanId(), explicit_span_ctx.span_id()); - EXPECT_EQ(shared_recordable->GetTraceFlags(), explicit_span_ctx.trace_flags()); - EXPECT_NE(shared_recordable->GetTraceId(), runtime_span->GetContext().trace_id()); + EXPECT_EQ(shared_recordable_->GetTraceId(), explicit_span_ctx.trace_id()); + EXPECT_EQ(shared_recordable_->GetSpanId(), explicit_span_ctx.span_id()); + EXPECT_EQ(shared_recordable_->GetTraceFlags(), explicit_span_ctx.trace_flags()); + EXPECT_NE(shared_recordable_->GetTraceId(), runtime_span_->GetContext().trace_id()); } -TEST(LoggerSDK, EmitLogRecordWithExplicitSpanContextStampsTraceFields) +TEST_F(LoggerEmitWithExplicitTraceTest, ExplicitSpanContextStampsTraceFields) { - auto api_lp = std::shared_ptr(new LoggerProvider()); - const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; - auto logger = api_lp->GetLogger("logger", "opentelemetry_library", "", schema_url); - - auto shared_recordable = std::shared_ptr(new MockLogRecordable()); - auto lp = static_cast(api_lp.get()); - lp->AddProcessor(std::unique_ptr(new MockProcessor(shared_recordable))); - - nostd::shared_ptr runtime_span; - { - std::vector> sp; - auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); - runtime_span = tp->GetTracer("runtime")->StartSpan("runtime"); - } - opentelemetry::trace::Scope runtime_scope{runtime_span}; - const auto explicit_span_ctx = MakeTestSpan(true)->GetContext(); - logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_span_ctx); + logger_->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, explicit_span_ctx); - EXPECT_EQ(shared_recordable->GetTraceId(), explicit_span_ctx.trace_id()); - EXPECT_EQ(shared_recordable->GetSpanId(), explicit_span_ctx.span_id()); - EXPECT_EQ(shared_recordable->GetTraceFlags(), explicit_span_ctx.trace_flags()); - EXPECT_NE(shared_recordable->GetTraceId(), runtime_span->GetContext().trace_id()); + EXPECT_EQ(shared_recordable_->GetTraceId(), explicit_span_ctx.trace_id()); + EXPECT_EQ(shared_recordable_->GetSpanId(), explicit_span_ctx.span_id()); + EXPECT_EQ(shared_recordable_->GetTraceFlags(), explicit_span_ctx.trace_flags()); + EXPECT_NE(shared_recordable_->GetTraceId(), runtime_span_->GetContext().trace_id()); } -TEST(LoggerSDK, EmitLogRecordWithExplicitTracePartsStampsTraceFields) +TEST_F(LoggerEmitWithExplicitTraceTest, ExplicitTracePartsStampsTraceFields) { - auto api_lp = std::shared_ptr(new LoggerProvider()); - const std::string schema_url{"https://opentelemetry.io/schemas/1.11.0"}; - auto logger = api_lp->GetLogger("logger", "opentelemetry_library", "", schema_url); - - auto shared_recordable = std::shared_ptr(new MockLogRecordable()); - auto lp = static_cast(api_lp.get()); - lp->AddProcessor(std::unique_ptr(new MockProcessor(shared_recordable))); - - nostd::shared_ptr runtime_span; - { - std::vector> sp; - auto tp = opentelemetry::sdk::trace::TracerProviderFactory::Create(std::move(sp)); - runtime_span = tp->GetTracer("runtime")->StartSpan("runtime"); - } - opentelemetry::trace::Scope runtime_scope{runtime_span}; - const auto explicit_span_ctx = MakeTestSpan(true)->GetContext(); - logger->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, - explicit_span_ctx.trace_id(), explicit_span_ctx.span_id(), - explicit_span_ctx.trace_flags()); + logger_->EmitLogRecord(logs_api::Severity::kInfo, nostd::string_view{"msg"}, + explicit_span_ctx.trace_id(), explicit_span_ctx.span_id(), + explicit_span_ctx.trace_flags()); - EXPECT_EQ(shared_recordable->GetTraceId(), explicit_span_ctx.trace_id()); - EXPECT_EQ(shared_recordable->GetSpanId(), explicit_span_ctx.span_id()); - EXPECT_EQ(shared_recordable->GetTraceFlags(), explicit_span_ctx.trace_flags()); - EXPECT_NE(shared_recordable->GetTraceId(), runtime_span->GetContext().trace_id()); + EXPECT_EQ(shared_recordable_->GetTraceId(), explicit_span_ctx.trace_id()); + EXPECT_EQ(shared_recordable_->GetSpanId(), explicit_span_ctx.span_id()); + EXPECT_EQ(shared_recordable_->GetTraceFlags(), explicit_span_ctx.trace_flags()); + EXPECT_NE(shared_recordable_->GetTraceId(), runtime_span_->GetContext().trace_id()); } #endif // OPENTELEMETRY_ABI_VERSION_NO >= 2 From af777f491e27e074fdd14abd39b598ff8ea2a4b2 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 6 Jun 2026 21:39:59 +0900 Subject: [PATCH 25/26] doc: fix wrong docs --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32adaa5dbb..b1dbdb5f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,9 +49,10 @@ Increment the: chain when a `Severity` is in args. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) -* [API/SDK] (ABI v2) Add `Logger::CreateLogRecord(const Context &)` virtual - for explicit-context record creation. `Logger::EmitLogRecord(args...)` - also detects a `Context`, `SpanContext` +* [API/SDK] (ABI v2) Add + `Logger::CreateLogRecord(const nostd::variant &)` + virtual for explicit-context record creation. + `Logger::EmitLogRecord(args...)` also detects a `Context`, `SpanContext` or `TraceId` + `SpanId` [+ `TraceFlags`] in args and routes filtering. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) From 5b48020f1bc037d1528a41483236e0bf0648d665 Mon Sep 17 00:00:00 2001 From: lani_karrot Date: Sat, 6 Jun 2026 22:03:18 +0900 Subject: [PATCH 26/26] doc: follow lint --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01688214b1..1aa92e15d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,10 +58,10 @@ Increment the: chain when a `Severity` is in args. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667) -* [API/SDK] (ABI v2) Add - `Logger::CreateLogRecord(const nostd::variant &)` - virtual for explicit-context record creation. - `Logger::EmitLogRecord(args...)` also detects a `Context`, `SpanContext` +* [API/SDK] (ABI v2) Add `Logger::CreateLogRecord` virtual taking + `const nostd::variant &` + for explicit-context record creation. `Logger::EmitLogRecord(args...)` + also detects a `Context`, `SpanContext` or `TraceId` + `SpanId` [+ `TraceFlags`] in args and routes filtering. [#2667](https://github.com/open-telemetry/opentelemetry-cpp/issues/2667)