diff --git a/opentelemetry-appender-tracing/CHANGELOG.md b/opentelemetry-appender-tracing/CHANGELOG.md index 4b637c7eea..1b40e5b4db 100644 --- a/opentelemetry-appender-tracing/CHANGELOG.md +++ b/opentelemetry-appender-tracing/CHANGELOG.md @@ -2,6 +2,10 @@ ## vNext +- Add custom instrumentation scope attributes support via + `OpenTelemetryTracingBridge::builder_with_scope_attributes(..)`. + [3415](https://github.com/open-telemetry/opentelemetry-rust/issues/3415) + ## 0.32.0 Released 2026-May-08 diff --git a/opentelemetry-appender-tracing/src/layer.rs b/opentelemetry-appender-tracing/src/layer.rs index 91f7d449a1..fb5535473e 100644 --- a/opentelemetry-appender-tracing/src/layer.rs +++ b/opentelemetry-appender-tracing/src/layer.rs @@ -1,6 +1,6 @@ use opentelemetry::{ logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}, - Key, + InstrumentationScope, Key, KeyValue, }; #[cfg(feature = "experimental_span_attributes")] use std::borrow::Cow; @@ -375,10 +375,16 @@ where P: LoggerProvider + Send + Sync, L: Logger + Send + Sync, { + /// Creates a bridge using the default OpenTelemetry logger scope. + /// + /// The default scope uses an empty scope name for the appender logger. + /// Use [`Self::builder_with_scope_attributes`] to provide a + /// custom set of instrumentation scope attributes. pub fn new(provider: &P) -> Self { Self::builder(provider).build() } + /// Creates a builder using the default OpenTelemetry logger scope. pub fn builder(provider: &P) -> OpenTelemetryTracingBridgeBuilder { OpenTelemetryTracingBridgeBuilder { // Using empty scope name. @@ -392,6 +398,23 @@ where span_attributes: None, } } + + /// Creates a builder that uses custom OpenTelemetry instrumentation scope + /// attributes for the appender logger. + pub fn builder_with_scope_attributes( + provider: &P, + attributes: impl IntoIterator, + ) -> OpenTelemetryTracingBridgeBuilder { + let scope = InstrumentationScope::builder("") + .with_attributes(attributes) + .build(); + OpenTelemetryTracingBridgeBuilder { + logger: provider.logger_with_scope(scope), + _phantom: Default::default(), + #[cfg(feature = "experimental_span_attributes")] + span_attributes: None, + } + } } pub struct OpenTelemetryTracingBridgeBuilder @@ -597,7 +620,7 @@ mod tests { use opentelemetry::trace::TracerProvider; use opentelemetry::trace::{TraceContextExt, TraceFlags, Tracer}; use opentelemetry::InstrumentationScope; - use opentelemetry::{logs::AnyValue, Key}; + use opentelemetry::{logs::AnyValue, Key, KeyValue}; use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult}; use opentelemetry_sdk::logs::{InMemoryLogExporter, LogProcessor}; use opentelemetry_sdk::logs::{SdkLogRecord, SdkLoggerProvider}; @@ -919,6 +942,41 @@ mod tests { } } + #[test] + fn tracing_appender_with_custom_scope_attributes() { + let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); + let logger_provider = SdkLoggerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + + let subscriber = tracing_subscriber::registry().with( + layer::OpenTelemetryTracingBridge::builder_with_scope_attributes( + &logger_provider, + [KeyValue::new("scope-key", "scope-value")], + ) + .build(), + ); + + let _guard = tracing::subscriber::set_default(subscriber); + + error!(name: "scoped-event", target: "my-system", event_id = 20); + assert!(logger_provider.force_flush().is_ok()); + + let exported_logs = exporter + .get_emitted_logs() + .expect("Logs are expected to be exported."); + assert_eq!(exported_logs.len(), 1); + let log = exported_logs + .first() + .expect("At least one log is expected to be present."); + + assert!(log + .instrumentation + .attributes() + .eq([KeyValue::new("scope-key", "scope-value")].iter())); + exporter.reset(); + } + #[test] #[ignore = "log tests set a global logger that cannot be unset and affects other tests"] fn tracing_appender_standalone_with_tracing_log() {