Skip to content

Commit f15a337

Browse files
authored
fix: Fix Logger scope in Tracing Appender (#2735)
1 parent 5a77fb2 commit f15a337

File tree

3 files changed

+96
-14
lines changed

3 files changed

+96
-14
lines changed

docs/design/logs.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,19 @@ convey that decision back to logger, allowing appender to avoid even the cost of
296296
creating a `LogRecord` in the first place if there is no listener. This check is
297297
done for each log emission, and can react dynamically to changes in interest, by
298298
enabling/disabling ETW/user-event listener.
299+
5. `tracing` has a notion of "target", which is expected to be mapped to OTel's
300+
concept of Instrumentation Scope for Logs, when `OpenTelemetry-Tracing-Appender`
301+
bridges `tracing` to OpenTelemetry. Since scopes are tied to Loggers, a naive
302+
approach would require creating a separate logger for each unique target. This
303+
would necessitate an RWLock-protected HashMap lookup, introducing contention and
304+
reducing throughput. To avoid this, `OpenTelemetry-Tracing-Appender` instead
305+
stores the target directly in the LogRecord as a top-level field, ensuring fast
306+
access in the hot path. Components processing the LogRecord can retrieve the
307+
target via LogRecord.target(), treating it as the scope. The OTLP Exporter
308+
already handles this automatically, so end-users will see “target” reflected in
309+
the Instrumentation Scope. An alternative design would be to use thread-local
310+
HashMaps - but it can cause increased memory usage, as there can be 100s of
311+
unique targets. (because `tracing` defaults to using module path as target).
299312

300313
### Perf test - benchmarks
301314

opentelemetry-appender-tracing/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ simple string, but require format arguments as in the below example.
3131
error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "[email protected]", "This is an example message with format arguments {} and {}", "foo", "bar");
3232
```
3333

34+
Fixes [2658](https://github.com/open-telemetry/opentelemetry-rust/issues/2658)
35+
InstrumentationScope(Logger) used by the appender now uses an empty ("") named
36+
Logger. Previously, a Logger with name and version of the crate was used.
37+
Receivers (processors, exporters) are expected to use `LogRecord.target()` as
38+
scope name. This is already done in OTLP Exporters, so this change should be
39+
transparent to most users.
40+
3441
## 0.28.1
3542

3643
Released 2025-Feb-12

opentelemetry-appender-tracing/src/layer.rs

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
use opentelemetry::{
22
logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity},
3-
InstrumentationScope, Key,
3+
Key,
44
};
5-
use std::borrow::Cow;
65
use tracing_core::Level;
76
#[cfg(feature = "experimental_metadata_attributes")]
87
use tracing_core::Metadata;
98
#[cfg(feature = "experimental_metadata_attributes")]
109
use tracing_log::NormalizeEvent;
1110
use tracing_subscriber::{registry::LookupSpan, Layer};
1211

13-
const INSTRUMENTATION_LIBRARY_NAME: &str = "opentelemetry-appender-tracing";
14-
1512
/// Visitor to record the fields from the event record.
1613
struct EventVisitor<'a, LR: LogRecord> {
1714
log_record: &'a mut LR,
@@ -135,12 +132,13 @@ where
135132
L: Logger + Send + Sync,
136133
{
137134
pub fn new(provider: &P) -> Self {
138-
let scope = InstrumentationScope::builder(INSTRUMENTATION_LIBRARY_NAME)
139-
.with_version(Cow::Borrowed(env!("CARGO_PKG_VERSION")))
140-
.build();
141-
142135
OpenTelemetryTracingBridge {
143-
logger: provider.logger_with_scope(scope),
136+
// Using empty scope name.
137+
// The name/version of this library itself can be added
138+
// as a Scope attribute, once a semantic convention is
139+
// defined for the same.
140+
// See https://github.com/open-telemetry/semantic-conventions/issues/1550
141+
logger: provider.logger(""),
144142
_phantom: Default::default(),
145143
}
146144
}
@@ -349,8 +347,18 @@ mod tests {
349347
.expect("Atleast one log is expected to be present.");
350348

351349
// Validate common fields
352-
assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
350+
assert_eq!(log.instrumentation.name(), "");
353351
assert_eq!(log.record.severity_number(), Some(Severity::Error));
352+
// Validate target
353+
assert_eq!(
354+
log.record.target().expect("target is expected").to_string(),
355+
"my-system"
356+
);
357+
// Validate event name
358+
assert_eq!(
359+
log.record.event_name().expect("event_name is expected"),
360+
"my-event-name"
361+
);
354362

355363
// Validate trace context is none.
356364
assert!(log.record.trace_context().is_none());
@@ -399,6 +407,39 @@ mod tests {
399407
assert!(attributes_key.contains(&Key::new("code.lineno")));
400408
assert!(!attributes_key.contains(&Key::new("log.target")));
401409
}
410+
411+
// Test when target, eventname are not explicitly provided
412+
exporter.reset();
413+
error!(
414+
event_id = 20,
415+
user_name = "otel",
416+
user_email = "[email protected]"
417+
);
418+
assert!(logger_provider.force_flush().is_ok());
419+
420+
// Assert TODO: move to helper methods
421+
let exported_logs = exporter
422+
.get_emitted_logs()
423+
.expect("Logs are expected to be exported.");
424+
assert_eq!(exported_logs.len(), 1);
425+
let log = exported_logs
426+
.first()
427+
.expect("Atleast one log is expected to be present.");
428+
429+
// Validate target - tracing defaults to module path
430+
assert_eq!(
431+
log.record.target().expect("target is expected").to_string(),
432+
"opentelemetry_appender_tracing::layer::tests"
433+
);
434+
// Validate event name - tracing defaults to event followed source & line number
435+
// Assert is doing "contains" check to avoid tests failing when line number changes.
436+
// and also account for the fact that the module path is different on different platforms.
437+
// Ex.: The path will be different on a Windows and Linux machine.
438+
assert!(log
439+
.record
440+
.event_name()
441+
.expect("event_name is expected")
442+
.contains("event opentelemetry-appender-tracing"),);
402443
}
403444

404445
#[test]
@@ -443,8 +484,18 @@ mod tests {
443484
.expect("Atleast one log is expected to be present.");
444485

445486
// validate common fields.
446-
assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
487+
assert_eq!(log.instrumentation.name(), "");
447488
assert_eq!(log.record.severity_number(), Some(Severity::Error));
489+
// Validate target
490+
assert_eq!(
491+
log.record.target().expect("target is expected").to_string(),
492+
"my-system"
493+
);
494+
// Validate event name
495+
assert_eq!(
496+
log.record.event_name().expect("event_name is expected"),
497+
"my-event-name"
498+
);
448499

449500
// validate trace context.
450501
assert!(log.record.trace_context().is_some());
@@ -584,7 +635,7 @@ mod tests {
584635
drop(tracing_log::LogTracer::init());
585636

586637
// Act
587-
log::error!(target: "my-system", "log from log crate");
638+
log::error!("log from log crate");
588639
assert!(logger_provider.force_flush().is_ok());
589640

590641
// Assert TODO: move to helper methods
@@ -597,8 +648,19 @@ mod tests {
597648
.expect("Atleast one log is expected to be present.");
598649

599650
// Validate common fields
600-
assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
651+
assert_eq!(log.instrumentation.name(), "");
601652
assert_eq!(log.record.severity_number(), Some(Severity::Error));
653+
// Target and EventName from Log crate are "log" and "log event" respectively.
654+
// Validate target
655+
assert_eq!(
656+
log.record.target().expect("target is expected").to_string(),
657+
"log"
658+
);
659+
// Validate event name
660+
assert_eq!(
661+
log.record.event_name().expect("event_name is expected"),
662+
"log event"
663+
);
602664

603665
// Validate trace context is none.
604666
assert!(log.record.trace_context().is_none());
@@ -676,7 +738,7 @@ mod tests {
676738
.expect("Atleast one log is expected to be present.");
677739

678740
// validate common fields.
679-
assert_eq!(log.instrumentation.name(), "opentelemetry-appender-tracing");
741+
assert_eq!(log.instrumentation.name(), "");
680742
assert_eq!(log.record.severity_number(), Some(Severity::Error));
681743

682744
// validate trace context.

0 commit comments

Comments
 (0)