Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ Increment the:
* [RELEASE] Bump main branch to 1.28.0-dev
[#4081](https://github.com/open-telemetry/opentelemetry-cpp/pull/4081)

* [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.

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