Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c7360f5
fix: apply filtering in emit log record
proost May 12, 2026
46c13c8
refactor: filter once
proost May 12, 2026
849c52f
Merge branch 'fix-apply-filtering-emitlogrecord' of https://github.co…
proost May 12, 2026
47d1e37
refactor: enabled filtering on the API level
proost May 13, 2026
a0958a4
feat: use context for EmitLogRecord
proost May 14, 2026
355ac95
Merge branch 'main' of github.com:open-telemetry/opentelemetry-cpp in…
proost May 14, 2026
98b3e9c
doc: update changelog
proost May 14, 2026
05b865b
style: follow doc lint
proost May 14, 2026
8063cb6
doc: follow doc lint
proost May 14, 2026
efa0202
Merge branch 'fix-apply-filtering-emitlogrecord' of https://github.co…
proost May 14, 2026
17de264
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
proost May 14, 2026
b9871a4
style: follow doc lint
proost May 14, 2026
5e67680
perf: cached enabled required
proost May 15, 2026
80051a4
Merge branch 'fix-apply-filtering-emitlogrecord' of github.com:proost…
proost May 15, 2026
ff7e302
Merge branch 'main' into fix-apply-filtering-emitlogrecord
ThomsonTan May 15, 2026
dc306d5
feat: has enabled filter
proost May 16, 2026
1a8111f
Merge branch 'fix-apply-filtering-emitlogrecord' of github.com:proost…
proost May 16, 2026
89c8209
Merge branch 'main' of github.com:open-telemetry/opentelemetry-cpp in…
proost May 17, 2026
d4578ad
feat: use temp context
proost May 17, 2026
d62c8e4
refactor: unify function
proost May 17, 2026
67713e3
Merge branch 'main' of github.com:open-telemetry/opentelemetry-cpp in…
proost May 19, 2026
e717aab
refactor: change default to false
proost May 19, 2026
8a18dc7
refactor: use span context and context
proost May 20, 2026
c1997fe
Merge branch 'main' of github.com:open-telemetry/opentelemetry-cpp in…
proost May 20, 2026
d719d99
style: follow lint
proost May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/clang-tidy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/iwyu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ 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.
* [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

* [RELEASE] Bump main branch to 1.27.0-dev
Expand Down
55 changes: 54 additions & 1 deletion api/include/opentelemetry/logs/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ class Logger
*/
virtual nostd::unique_ptr<LogRecord> 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<LogRecord>
*/
virtual nostd::unique_ptr<LogRecord> CreateLogRecord(
const opentelemetry::context::Context & /*context*/) noexcept
{
return CreateLogRecord();
}
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2

/**
* Emit a Log Record object
*
Expand All @@ -58,7 +72,15 @@ class Logger
virtual void EmitLogRecord(nostd::unique_ptr<LogRecord> &&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.
Expand Down Expand Up @@ -120,11 +142,42 @@ class Logger
* KeyValueIterable -> attributes
* Key value iterable container -> attributes
* span<pair<string_view, AttributeValue>> -> attributes(return type of MakeAttributes)
* Context (v2 only) -> filter + trace stamp (recommended: pass last)
*
*/
template <class... ArgumentType>
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...);
#if OPENTELEMETRY_ABI_VERSION_NO >= 2
const bool is_enabled =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid making every enabled log call pay for the full Enabled(...) chain? The disabled and below-min-severity paths stay cheap, but the normal enabled path now pays virtual/context/processor cost before record creation. It may be better to cache whether full filtering is actually needed, and only call the full chain when trace-based filtering or processor-level filtering is configured.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx, 5e67680

how about go through trace based filtering using current option?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this helps with the trace-based case, but I think the original concern is only partially addressed. We still need the cache to account for processor-level filtering too, otherwise the normal non-trace-based path can skip processor Enabled(...) checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now i understand your intention.
dc306d5

Later, i will add logger configuration.. this option might be needed in the configuration. But this is not in the spec. is ok?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not add a new logger configuration option just for this. The spec defines the observable Enabled behavior, but the C++ SDK can still implement it efficiently.

For performance, I think this should stay as an internal cache/capability decision: full filtering is needed when trace_based is enabled or when a processor actually implements Enabled filtering. Otherwise the normal enabled path should avoid paying the full virtual/context/processor chain.

One thing to watch is that the cache must stay correct if processors are added after a logger is created.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I integrate Context And SpanContext both allowed. 8a18dc7

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<int64_t>(0)));
#else
const bool is_enabled = event_id_ptr ? Enabled(arg_severity, *event_id_ptr)
: Enabled(arg_severity, static_cast<int64_t>(0));
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2
if (!is_enabled)
{
return;
}
}

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
nostd::unique_ptr<LogRecord> log_record =
context_ptr ? CreateLogRecord(*context_ptr) : CreateLogRecord();
#else
nostd::unique_ptr<LogRecord> log_record = CreateLogRecord();
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2

EmitLogRecord(std::move(log_record), std::forward<ArgumentType>(args)...);
}
Expand Down
85 changes: 85 additions & 0 deletions api/include/opentelemetry/logs/logger_type_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -143,6 +147,21 @@ struct LogRecordSetterTrait<common::KeyValueIterable>
}
};

#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<opentelemetry::context::Context>
{
template <class ArgumentType>
inline static LogRecord *Set(LogRecord *log_record, ArgumentType && /*arg*/) noexcept
{
return log_record;
}
};
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2

template <class ValueType>
struct LogRecordSetterTrait
{
Expand Down Expand Up @@ -198,6 +217,72 @@ struct LogRecordHasType<ValueType, TargetType, ArgumentType...>
LogRecordHasType<ValueType, ArgumentType...>>::type
{};

inline Severity FindSeverityInArgs() noexcept
{
return Severity::kInvalid;
}

template <class... Rest>
inline Severity FindSeverityInArgs(Severity severity, Rest &&.../*rest*/) noexcept
{
return severity;
}

template <class First,
class... Rest,
typename std::enable_if<!std::is_same<typename std::decay<First>::type, Severity>::value,
int>::type = 0>
inline Severity FindSeverityInArgs(First && /*first*/, Rest &&...rest) noexcept
{
return FindSeverityInArgs(std::forward<Rest>(rest)...);
}

inline const EventId *FindEventIdInArgs() noexcept
{
return nullptr;
}

template <class... Rest>
inline const EventId *FindEventIdInArgs(const EventId &event_id, Rest &&.../*rest*/) noexcept
{
return &event_id;
}

template <class First,
class... Rest,
typename std::enable_if<!std::is_same<typename std::decay<First>::type, EventId>::value,
int>::type = 0>
inline const EventId *FindEventIdInArgs(First && /*first*/, Rest &&...rest) noexcept
{
return FindEventIdInArgs(std::forward<Rest>(rest)...);
}

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
inline const opentelemetry::context::Context *FindContextInArgs() noexcept
{
return nullptr;
}

template <class... Rest>
inline const opentelemetry::context::Context *FindContextInArgs(
const opentelemetry::context::Context &context,
Rest &&.../*rest*/) noexcept
{
return &context;
}

template <class First,
class... Rest,
typename std::enable_if<!std::is_same<typename std::decay<First>::type,
opentelemetry::context::Context>::value,
int>::type = 0>
inline const opentelemetry::context::Context *FindContextInArgs(First && /*first*/,
Rest &&...rest) noexcept
{
return FindContextInArgs(std::forward<Rest>(rest)...);
}
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2

} // namespace detail

} // namespace logs
Expand Down
104 changes: 100 additions & 4 deletions api/test/logs/logger_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include <vector>

#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"
Expand All @@ -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"
Expand Down Expand Up @@ -278,6 +275,9 @@ class TestLogger : public Logger
}
};

namespace
{

class EnablementAwareTestLogRecord : public opentelemetry::logs::LogRecord
{
public:
Expand Down Expand Up @@ -334,6 +334,15 @@ class EnablementAwareTestLogger : public Logger
return nostd::unique_ptr<opentelemetry::logs::LogRecord>(new EnablementAwareTestLogRecord());
}

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
nostd::unique_ptr<opentelemetry::logs::LogRecord> 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(
Expand All @@ -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<EnablementAwareTestLogRecord> 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
Expand Down Expand Up @@ -420,6 +432,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
{
Expand Down Expand Up @@ -461,3 +475,85 @@ 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);
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<LogRecord>&&, 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);
}

#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)
Comment thread
proost marked this conversation as resolved.
{
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
5 changes: 5 additions & 0 deletions sdk/include/opentelemetry/sdk/logs/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class Logger final : public opentelemetry::logs::Logger

nostd::unique_ptr<opentelemetry::logs::LogRecord> CreateLogRecord() noexcept override;

#if OPENTELEMETRY_ABI_VERSION_NO >= 2
nostd::unique_ptr<opentelemetry::logs::LogRecord> CreateLogRecord(
const opentelemetry::context::Context &context) noexcept override;
#endif // OPENTELEMETRY_ABI_VERSION_NO >= 2

using opentelemetry::logs::Logger::EmitLogRecord;

void EmitLogRecord(
Expand Down
Loading
Loading