feat(appender-tracing): add builder support for custom instrumentation scope#3428
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3428 +/- ##
=====================================
Coverage 83.0% 83.0%
=====================================
Files 130 130
Lines 27952 27992 +40
=====================================
+ Hits 23210 23250 +40
Misses 4742 4742 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Hello @cijothomas, could you please take a look at this PR? It's my first time contributing here, and I'm ready to correct/change if it's required. Many thanks in advance for review. |
|
The new scope support looks fine, but this changes the public builder API in a breaking way. fn make_builder<P, L>(
provider: &P,
) -> opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridgeBuilder<P, L>
where
P: opentelemetry::logs::LoggerProvider<Logger = L> + Send + Sync,
L: opentelemetry::logs::Logger + Send + Sync,
{
opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge::builder(provider)
}Can we keep the builder owned while still adding with_scope()? |
aba679c to
bf413c0
Compare
|
Thank you @lalitb for the review and for highlighting this. You are correct: my original proposal introduced a breaking API change by adding a lifetime to the public My goal was to keep configuration cohesive in the builder ( I have updated the implementation to remove the lifetime and keep the builder owned, as requested. The current trade-off is configuration split across two entry points:
This preserves compatibility, but is less uniform than a fully builder-centric API. From an API stability perspective, I also see a risk in a public Would you prefer one of these directions?
|
|
Thanks for the update and the well-thought-out options @DmitryAstafyev. I'd go with your Option 1 - keep The scope isn't a configuration property of the bridge itself - it's a parameter needed alongside the provider to create the underlying logger. Since it must be known at the same time as the provider, a separate also, note that you need to sign CLA before this can be moved further. |
|
Thanks @lalitb, for your response. I've made the requested changes.
Let me know, please, if I should do something else. |
|
Thanks @DmitryAstafyev LGTM. Will approve/merge this once the CLA is signed. |
6684d49 to
1bef051
Compare
1bef051 to
866bae7
Compare
|
@DmitryAstafyev Can you share bit more about the intended use-case? Since OTLP Exporter override scope-name with target, this may not have the intended effect, so want to make sure we have a good grasp of the scenario. |
Thanks for your question @cijothomas . I hope I've gotten your question in right way. We are interested in not loosing attributes, overwriting scope-name with target is okey. The use case is simple: we need to attach per-invocation metadata to every log record's scope in OTLP output. This metadata is dynamic and cannot live on the Resource level, so it belongs on the If the concern is that scope attributes were being discarded by the OTLP exporter when a target is present - that was indeed a real issue, but it appears to have been fixed by 3332 which was merged recently. With both fixes in place the full use case should work as intended for us. |
Thanks for the explanation @DmitryAstafyev! One clarification — the InstrumentationScope is set once when the bridge is created and stays fixed for its lifetime. So by "per-invocation metadata," do you mean per-process-invocation (e.g., a CLI/batch job that runs and exits)? In a long-running server, the scope attributes wouldn't change across requests — per-request data would need to go as log record attributes instead. Once you share more details, I can tell if there is better way to do it.! If you need per-request metadata in a long-running server, |
Thanks @cijothomas for your question. Yes, our use case is per-invocation, not per-request in a long-running server. Sorry for confusion in my prev-reply. For each function invocation we create a fresh OpenTelemetryTracingBridge with invocation-scoped metadata (e.g. invocation ID etc.). Span attributes on individual log records wouldn't be a fit here, since the metadata describes the bridge's operational context rather than individual log events. A native builder_with_scope would let us drop the wrapper-workaround we currently rely on. |
|
Hello @cijothomas, any news about the progress? |
866bae7 to
d0df76c
Compare
|
Hello @cijothomas, @lalitb, I've rebased PR to master. Could you please take a look. Thanks in advance! |
Apologies for the delay. Before going further on the API, can you share a minimal example of the per-invocation use case? Specifically: Is the process short-lived (one invocation per process), or long-running with many invocations in-process? The reason this matters: as written, builder_with_scope(provider, scope) looks like the supplied scope is used directly, but the bridge uses a single placeholder Logger and the scope name on every record comes from Renaming to https://github.com/open-telemetry/opentelemetry-rust/blob/main/docs/design/logs.md#performance, section 5 explains why we are in this tricky situation. We certainly want to make sure the scenarios is well understood before expanding the API here else this would get more complex. |
|
@cijothomas thanks for your question. I read our previous discussion, and I think I may have confused you and a little messed up things. Sorry if that happened. Let me clarify the lifecycle first:
I also wasn't correct when talking about log record attributes. We do not want to copy these attributes into every log record. What we need is scope attributes under The attributes contain some important metadata for us. With the current With I agree that the method name could be better. I hope I've answered your question more clearly this time :) addon:
I will back with an answer tomorrow. Have to check. |
…elemetryTracingBridge API
e229f3e to
755d247
Compare
|
Given the lifecycle you described (one process per invocation, metadata constant for the whole run), I think Resource might actually be a cleaner fit than Scope here - it's process-scoped, set once at startup, and flows through as ResourceLogs.resource.attributes on every export, which sounds like exactly what you need. (also - what backend are you using? Some might not support scope attributes, and might require Collector massaging to move attributes around to the right place) |
|
Thanks @cijothomas for your response. I took time to check the proposed change and the alternatives against our system. Sorry, I can't share the code, but I'll try to explain. In our model one process = one invocation. On the Resource we keep only stable identity that does not change between invocations (service name, version, etc.). We do not put per-invocation data (like the invocation id and others) there. The Resource is shared by traces and metrics in the same process, so anything we add to it also lands on our metrics and traces. Metrics are aggregated across many invocations on the backend, and the backend groups them by Resource identity. Per-invocation data in the Resource would make every invocation a separate series and affect aggregation. One way around this would be separate Resources per signal. But I would not do it. Resource is the identity of the entity that produces the telemetry, and backends correlate logs, metrics, and traces by matching that identity. In other words, using different Resources for what is actually the same entity does not look right. This is also reflected in the spec, where Another work-around is the per-record option (span attributes on each LogRecord). I mean copying the some data (attrs) into every record. We can do this with a custom logger, but it isn't efficient: the value is constant for the whole run, so the right way is one copy at
This is a data-model question (scope vs Resource vs per-record), independent of the backend. Our backend reads scope-level attributes, so it's not a blocker for us. @cijothomas, with the rename and the use-case clarified, is there any remaining blocker, or are we good to merge? |
|
@dmitryastafyev-dyna Thanks. It's more clear now. Let me re-review the changes with this context. |
https://github.com/open-telemetry/opentelemetry-rust/pull/3428/changes#r3473102342 - Once this is addressed, we can merge. |
Thanks @cijothomas . Done. |
|
@DmitryAstafyev Queue to merge. Thanks for patiently working on this! |
Changes
This PR adds
OpenTelemetryTracingBridgeBuilder::with_scope()to allow configuring the OpenTelemetryInstrumentationScopeused by the appender logger.The implementation also updates
OpenTelemetryTracingBridgeBuilderto hold the logger provider untilbuild(), so the builder can expose a set of configuration methods:with_scope()with_span_attribute_allowlist()Design Notes
This change introduces a lifetime parameter on
OpenTelemetryTracingBridgeBuilder<'a>. That was done to keep the builder aligned with the existing style, where the bridge is constructed from a provider reference rather than from a pre-created logger.In return, this keeps the API shape cleaner and gives us a more natural builder surface with related configuration methods grouped together.
Compatibility
Technically, adding the lifetime to the public builder type is a breaking API change.
However, this builder API has not yet shipped in a released version, so this does not affect production users of published crates. In practice, the change is limited to the unreleased API surface.
Testing
Added a unit test (
tracing_appender_with_custom_scope) coveringwith_scope()and verifying that the configuredInstrumentationScopeis propagated to emitted logs.Related
Merge requirement checklist
CHANGELOG.mdfiles updated for non-trivial, user-facing changes