Skip to content

Recommend log_bridge.name and log_bridge.version Instrumentation Scope attributes for Log Bridges#5089

Closed
cijothomas wants to merge 5 commits into
open-telemetry:mainfrom
cijothomas:cijothomas/log-bridge-identification
Closed

Recommend log_bridge.name and log_bridge.version Instrumentation Scope attributes for Log Bridges#5089
cijothomas wants to merge 5 commits into
open-telemetry:mainfrom
cijothomas:cijothomas/log-bridge-identification

Conversation

@cijothomas

@cijothomas cijothomas commented May 12, 2026

Copy link
Copy Markdown
Member

Draft. Adds bridge-author guidance for the in-progress semantic convention proposed in open-telemetry/semantic-conventions#3716 (issue: open-telemetry/semantic-conventions#1550). Recommends Log Bridges identify themselves via log_bridge.name / log_bridge.version Instrumentation Scope attributes, distinct from the producer of the log call (reported as the scope name). Held in draft until the convention lands.

Revising the existing "avoid setting any scope attributes" advice

The existing guidance tells appenders to set no scope attributes at all, to prevent scope identity ambiguity — the same logger name producing multiple Loggers that differ only in attributes (e.g. an appender attaching per-request data, or two appenders attaching inconsistent metadata for the same name). That was the right blanket rule when there was no convention for what an appender could legitimately put on the scope.

log_bridge.* doesn't trip this concern because:

  1. Values are constant per bridgelog_bridge.name/.version never vary per record, per request, or per anything dynamic. No cardinality blowup, no identity drift over time.
  2. When two bridges produce the same scope name with different log_bridge.* values, the difference is correct. They really did come from different bridges, and the attributes are doing identity work, not muddying it. That's the disambiguation the original warning was protecting, not violating.

This PR narrows the existing ban to its actual intent — don't put producer-derived or per-event data on the scope — while permitting the one well-defined scope-attribute set that's now standardized.

Appenders SHOULD however identify themselves by setting the
`log_bridge.name` and `log_bridge.version` Instrumentation Scope attributes
(defined in the [Semantic Conventions for Log Bridges][semconv-log-bridge])
on the `Logger`(s) they create. These attributes describe the appender

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks all for discussing this today. I was in listen-only mode in car.

@jack-berg When the bridge has to look up a logger, it can do so with just the logger name alone. This is adding the exact same scope-attributes to every scope (bridge don't change). I think @tigrannajaryan covered this in the call well. So this should not make worse.

Another note - The "lookup" optimization - Fully agree with Jack it has to be extremely performant! OTel Rust hit a big blocker here, and we decided to NOT do any lookup. We store the Logger name (it's referred to as target in Rust world) as a top-level field in the LogRecord, and then fix the scope/logger in the background thread!
https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/design/logs.md#performance

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.

When the bridge has to look up a logger, it can do so with just the logger name alone. This is adding the exact same scope-attributes to every scope (bridge don't change).

Yes the scope id is a composite of name + version + schemaUrl + attributes. In this proposal, attributes is static and name is the only thing that changes. In java, our implementation has an optimization for the case where only name is provided. So even the act of switching from name to name + attributes will change the performance profile, even with attributes being static.

I'm not sure if the performance change is enough to do something about, but there is a change.

We store the Logger name (it's referred to as target in Rust world) as a top-level field in the LogRecord, and then fix the scope/logger in the background thread!

I've wondered about this type of design. Should we adjust to log API spec to indicate that emit can optionally also exist as an operation on LoggerProvider and accept all the existing emit params + Scope (name, version, schemaUrl, attributes)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should we adjust to log API spec to indicate that emit can optionally also exist as an operation on LoggerProvider and accept all the existing emit params + Scope (name, version, schemaUrl, attributes)?

That is a good idea! OTel Rust did it as there is no other way it could make things performant! (There is no ConcurrenthashMap in rust std library, so rw lock protected hashmap was our only option and locks would kill throughput). I can open a separate issue to track it - it'll require some discussions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@jack-berg curiosity question to help me understand the Java side better — is the actual logging in Java a 2-step flow from the bridge's point of view? i.e. step 1: obtain a Logger from the LoggerProvider (by name); step 2: call logRecordBuilder().emit() on that Logger for each record. That's the shape Go and .NET have. In that model the per-record path isn't a lookup at all — the bridge just reuses the Logger reference it already has.

Rust was the odd one — its appender API does it in a single call (the user-facing macro takes the name and the record together), which is what forced us to store the scope name on the LogRecord and resolve the scope on a background thread.

If Java is also two-step, then bridges can hold one Logger per app-logger-name, supply the (constant) log_bridge.* scope attributes once at Logger construction, and the per-record hot path stays exactly as today — just logger.logRecordBuilder()...emit() on a cached reference, with no scope-attribute hashing or extra allocation per event. Want to make sure I'm not missing something Java-specific before going further.

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.

is the actual logging in Java a 2-step flow from the bridge's point of view? i.e. step 1: obtain a Logger from the LoggerProvider (by name); step 2: call logRecordBuilder().emit() on that Logger for each record. That's the shape Go and .NET have.

Yes, here's an example: https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/51a1cd7da9ab80908181a66c3a9a4ccba0881173/instrumentation/logback/logback-appender-1.0/library/src/main/java/io/opentelemetry/instrumentation/logback/appender/v1_0/internal/LoggingEventMapper.java#L118-L127

In that model the per-record path isn't a lookup at all — the bridge just reuses the Logger reference it already has.

The bridge has to lookup the logger by name for each record bridged.

If Java is also two-step, then bridges can hold one Logger per app-logger-name, supply the (constant) log_bridge.* scope attributes once at Logger construction, and the per-record hot path stays exactly as today — just logger.logRecordBuilder()...emit() on a cached reference,

Something like the following??

Map<String, io.opentelemetry.api.logs.Logger> otelLoggersByName = new ConcurrentHashMap<>();
Attributes logAppenderScopeAttributes = Attributes.of("log_bridge.*, ...);
io.opentelemetry.api.logs.LoggerProvider otelLoggerProvider = ...

...

String appLoggerName = event.getLoggerName();
io.opentelemetry.api.logs.Logger otelLogger = otelLoggersByName.computeIfAbsent(appLoggerName, appLoggerName1 -> loggerProvider.loggerBuilder(appLoggerName1).setScopeAttributes(logAppenderScopeAttributes).build());
// continue to map / emit log record

So there's still a lookup but its in log appender itself and is a fast lookup because its keyed by app logger name.

If that's what you mean, the ergonomics aren't great, but its workable.

@jack-berg

Copy link
Copy Markdown
Member

This seems like the most correct way / place to encode this information, but has performance implications on log appenders. Log appenders are special - unlike tracing and metrics where Tracer and Meter instances are established infrequently and normally at the beginning of the app lifecycle, logger instances need to be established for each and every log record which is bridged.

In the java implementation:

  • We LoggerProvider has a overloads to get a logger with just a name, or with a version and schemaUrl (and in the future scope attributes - not implemented today) using a builder pattern. I.e. provider.get("logger-name") vs provider.loggerBuilder("logger-name").setScopeAttributes(Attributes.of("log_bridge.name", "log4j2-appender", "log_brdige.version", "1.x")).build().
  • The Logger get(String name) method is used by log appenders. The implementation has some performance optimizations reflecting that this is the hot path, avoiding allocations and only requiring a map lookup to resolve the corresponding logger instance.

Off the top of my head, I'm not sure how I would keep things performant if the appenders needed to be adjusted to include scope attributes on the hot path. Maybe we could add a Logger get(String name, Attributes scopeAttributes) overload, which whille inconsistent with our builder pattern could at least avoid allocations? But the map lookup would still be more expensive than previously.

Signed-off-by: cijothomas <cijo.thomas@gmail.com>
@github-actions

Copy link
Copy Markdown

This PR was marked stale. It will be closed in 14 days without additional activity.

@github-actions github-actions Bot added the Stale label May 30, 2026
@github-actions

Copy link
Copy Markdown

Closed as inactive. Feel free to reopen if this PR is still being worked on.

@github-actions github-actions Bot closed this Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants