### Install Dependencies for opentelemetry-appender-tracing Source: https://docs.rs/opentelemetry-appender-tracing/latest/index.html Add these dependencies to your Cargo.toml file to use the opentelemetry-appender-tracing crate. ```toml [dependencies] tracing = ">=0.1.40" tracing-core = { version = ">=0.1.33" } tracing-subscriber = { version = "0.3", features = ["registry", "std", "fmt"] } opentelemetry = { version = "0.31", features = ["logs"] } opentelemetry-sdk = { version = "0.31", features = ["logs"] } opentelemetry-appender-tracing = { version = "0.31.1" } ``` -------------------------------- ### Register Tracing Subscriber with OpenTelemetry Layer Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/lib.rs.html Register the `OpenTelemetryTracingBridge` layer with the `tracing` subscriber. This example also includes the `fmt` layer for console output. ```rust # use opentelemetry_sdk::logs::SdkLoggerProvider; # use opentelemetry_stdout::LogExporter; # let exporter = LogExporter::default(); # let provider = SdkLoggerProvider::builder().with_simple_exporter(exporter).build(); # use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; # let otel_layer = OpenTelemetryTracingBridge::new(&provider); use tracing_subscriber::prelude::* tracing_subscriber::registry() .with(otel_layer) .with(tracing_subscriber::fmt::layer()) // In this example, `fmt` layer is also added. .init(); ``` -------------------------------- ### Layer::on_register_dispatch Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Performs late initialization when the layer is installed as part of a Subscriber. ```rust fn on_register_dispatch(&self, subscriber: &Dispatch) ``` -------------------------------- ### Any::type_id Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Gets the TypeId of the trait object. ```rust fn type_id(&self) -> TypeId ``` -------------------------------- ### Set Up OpenTelemetry Logger Provider Source: https://docs.rs/opentelemetry-appender-tracing/latest/index.html Configure an SdkLoggerProvider using a chosen exporter, such as opentelemetry_stdout. This step is necessary before integrating with tracing. ```rust use opentelemetry_sdk::logs::SdkLoggerProvider; use opentelemetry_stdout::LogExporter; let exporter = LogExporter::default(); let provider = SdkLoggerProvider::builder() .with_simple_exporter(exporter) .build(); ``` -------------------------------- ### OpenTelemetryTracingBridge::new Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Creates a new OpenTelemetryTracingBridge instance. ```APIDOC ## OpenTelemetryTracingBridge::new ### Description Creates a new `OpenTelemetryTracingBridge` instance. ### Signature ```rust pub fn new(provider: &P) -> Self ``` ### Parameters * `provider` - A reference to a `LoggerProvider` that implements `Send` and `Sync`. ``` -------------------------------- ### Standalone Logging with Tracing Appender Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Demonstrates logging using the `log` crate when the tracing appender is configured but not within an active `tracing` span. Verifies that trace context is not present in such logs. ```rust #[test] fn tracing_appender_standalone_with_tracing_log() { // Arrange let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); let logger_provider = SdkLoggerProvider::builder() .with_simple_exporter(exporter.clone()) .build(); let subscriber = create_tracing_subscriber(&logger_provider); // avoiding setting tracing subscriber as global as that does not // play well with unit tests. let _guard = tracing::subscriber::set_default(subscriber); drop(tracing_log::LogTracer::init()); // Act log::error!("log from log crate"); assert!(logger_provider.force_flush().is_ok()); // Assert TODO: move to helper methods 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("Atleast one log is expected to be present."); // Validate common fields assert_eq!(log.instrumentation.name(), ""); assert_eq!(log.record.severity_number(), Some(Severity::Error)); // Target and EventName from Log crate are "log" and "log event" respectively. // Validate target assert_eq!( log.record.target().expect("target is expected").to_string(), "log" ); // Validate event name assert_eq!( log.record.event_name().expect("event_name is expected"), "log event" ); // Validate trace context is none. assert!(log.record.trace_context().is_none()); // Attributes can be polluted when we don't use this feature. #[cfg(feature = "experimental_metadata_attributes")] assert_eq!(log.record.attributes_iter().count(), 4); #[cfg(feature = "experimental_metadata_attributes")] { assert!(attributes_contains( &log.record, &Key::new("code.filename"), &AnyValue::String("layer.rs".into()) )); assert!(attributes_contains( &log.record, &Key::new("code.namespace"), &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into()) )); // The other 3 experimental_metadata_attributes are too unstable to check their value. // Ex.: The path will be different on a Windows and Linux machine. // Ex.: The line can change easily if someone makes changes in this source file. let attributes_key: Vec = log .record .attributes_iter() .map(|(key, _)| key.clone()) .collect(); assert!(attributes_key.contains(&Key::new("code.filepath"))); assert!(attributes_key.contains(&Key::new("code.lineno"))); assert!(!attributes_key.contains(&Key::new("log.target"))); } } ``` -------------------------------- ### Create OpenTelemetry-Tracing Bridge Layer Source: https://docs.rs/opentelemetry-appender-tracing/latest/index.html Instantiate the OpenTelemetryTracingBridge layer using the previously created SdkLoggerProvider. ```rust let otel_layer = OpenTelemetryTracingBridge::new(&provider); ``` -------------------------------- ### Experimental Metadata Visitor Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Visits and records experimental metadata attributes from `tracing_core::Metadata` onto an OpenTelemetry `LogRecord`. This includes module path, file path, filename, and line number. ```rust impl<'a, LR: LogRecord> EventVisitor<'a, LR> { #[cfg(feature = "experimental_metadata_attributes")] fn visit_experimental_metadata(&mut self, meta: &Metadata) { if let Some(module_path) = meta.module_path() { self.log_record.add_attribute( Key::new("code.namespace"), AnyValue::from(module_path.to_owned()), ); } if let Some(filepath) = meta.file() { self.log_record.add_attribute( Key::new("code.filepath"), AnyValue::from(filepath.to_owned()), ); self.log_record.add_attribute( Key::new("code.filename"), AnyValue::from(get_filename(filepath).to_owned()), ); } if let Some(line) = meta.line() { self.log_record .add_attribute(Key::new("code.lineno"), AnyValue::from(line)); } } } ``` -------------------------------- ### From::from Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Returns the argument unchanged. This is a standard blanket implementation. ```rust fn from(t: T) -> T ``` -------------------------------- ### Create Tracing Subscriber with OpenTelemetry Layer Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Sets up a `tracing` subscriber with the `OpenTelemetryTracingBridge` layer. This function configures the layer to capture logs at `WARN` level and above, bridging them to OpenTelemetry. ```rust fn create_tracing_subscriber(logger_provider: &SdkLoggerProvider) -> impl tracing::Subscriber { let level_filter = tracing_subscriber::filter::LevelFilter::WARN; // Capture WARN and ERROR levels let layer = layer::OpenTelemetryTracingBridge::new(logger_provider).with_filter(level_filter); // No filter based on target, only based on log level tracing_subscriber::registry().with(layer) } ``` -------------------------------- ### OpenTelemetryTracingBridge Constructor Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Creates a new `OpenTelemetryTracingBridge` instance. It initializes the logger with an empty scope name, as semantic conventions for library names are still under development. ```rust pub fn new(provider: &P) -> Self { OpenTelemetryTracingBridge { // Using empty scope name. // The name/version of this library itself can be added // as a Scope attribute, once a semantic convention is // defined for the same. // See https://github.com/open-telemetry/semantic-conventions/issues/1550 logger: provider.logger(""), _phantom: Default::default(), } } ``` -------------------------------- ### OpenTelemetryTracingBridge Constructor Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Creates a new instance of OpenTelemetryTracingBridge, taking a reference to a LoggerProvider. ```rust pub fn new(provider: &P) -> Self> ``` -------------------------------- ### Log Events Using Tracing Source: https://docs.rs/opentelemetry-appender-tracing/latest/index.html Emit log events using the tracing crate's macros. These events will be converted into OpenTelemetry LogRecords by the bridge. ```rust use tracing::error; error!(name: "my-event-name1", target: "my-system", event_id = 10, user_name = "otel", user_email = "otel@opentelemetry.io", message = "This is an example message"); ``` -------------------------------- ### Layer::on_layer Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Performs late initialization when attaching a Layer to a Subscriber. ```rust fn on_layer(&mut self, subscriber: &mut S) ``` -------------------------------- ### Logging within a Tracing Context Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Shows how logs generated using the `log` crate inside an active `tracing` span automatically inherit the trace and span context. This ensures logs are correctly associated with ongoing traces. ```rust #[test] fn tracing_appender_inside_tracing_context_with_tracing_log() { // Arrange let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); let logger_provider = SdkLoggerProvider::builder() .with_simple_exporter(exporter.clone()) .build(); let subscriber = create_tracing_subscriber(&logger_provider); // avoiding setting tracing subscriber as global as that does not // play well with unit tests. let _guard = tracing::subscriber::set_default(subscriber); drop(tracing_log::LogTracer::init()); // setup tracing as well. let tracer_provider = SdkTracerProvider::builder() .with_sampler(Sampler::AlwaysOn) .build(); let tracer = tracer_provider.tracer("test-tracer"); // Act let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| { let trace_id = cx.span().span_context().trace_id(); let span_id = cx.span().span_context().span_id(); // logging is done inside span context. log::error!(target: "my-system", "log from log crate"); (trace_id, span_id) }); assert!(logger_provider.force_flush().is_ok()); // Assert TODO: move to helper methods let exported_logs = exporter .get_emitted_logs() ``` -------------------------------- ### OpenTelemetryTracingBridge on_event Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Implements the `on_event` method for the `Layer` trait. This method processes `tracing::Event`s, converts them to OpenTelemetry log records, and adds attributes, including experimental metadata and span context if available. ```rust fn on_event( &self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>, ) { let metadata = event.metadata(); let severity = severity_of_level(metadata.level()); let target = metadata.target(); let name = metadata.name(); #[cfg(feature = "spec_unstable_logs_enabled")] if !self.logger.event_enabled(severity, target, Some(name)) { // TODO: See if we need internal logs or track the count. return; } #[cfg(feature = "experimental_metadata_attributes")] let normalized_meta = event.normalized_metadata(); #[cfg(feature = "experimental_metadata_attributes")] let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata()); let mut log_record = self.logger.create_log_record(); log_record.set_target(target); log_record.set_event_name(name); log_record.set_severity_number(severity); log_record.set_severity_text(metadata.level().as_str()); let mut visitor = EventVisitor::new(&mut log_record); #[cfg(feature = "experimental_metadata_attributes")] visitor.visit_experimental_metadata(meta); // Visit fields. event.record(&mut visitor); #[cfg(feature = "experimental_use_tracing_span_context")] if let Some(span) = _ctx.event_span(event) { use tracing_opentelemetry::OtelData; if let Some(otd) = span.extensions().get::() { if let Some(span_id) = otd.span_id() { // Try the trace_id of the current span first; // If it is not already established (still in the Builder state), try finding the root span. let opt_trace_id = otd.trace_id().or_else(|| { ``` -------------------------------- ### Record u64 Field Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Handles recording of `u64` fields. It attempts to convert the value to `i64` for direct attribute addition. If conversion fails, it formats the `u64` as a string. ```rust fn record_u64(&mut self, field: &tracing::field::Field, value: u64) { #[cfg(feature = "experimental_metadata_attributes")] if is_duplicated_metadata(field.name()) { return; } if let Ok(signed) = i64::try_from(value) { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(signed)); } else { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(format!({"value:?"}))); } } ``` -------------------------------- ### Subscriber Management Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Methods for attaching a `Subscriber` or the current default `Subscriber` to a type, returning a `WithDispatch` wrapper. ```APIDOC ## fn with_subscriber(self, subscriber: S) -> WithDispatch where S: Into, Attaches the provided `Subscriber` to this type, returning a `WithDispatch` wrapper. ### Method `with_subscriber` ### Parameters - **subscriber** (`S` where `S: Into`) - The subscriber to attach. ### Returns - `WithDispatch` - A wrapper type containing the type with the attached subscriber. ``` ```APIDOC ## fn with_current_subscriber(self) -> WithDispatch Attaches the current default `Subscriber` to this type, returning a `WithDispatch` wrapper. ### Method `with_current_subscriber` ### Returns - `WithDispatch` - A wrapper type containing the type with the attached subscriber. ``` -------------------------------- ### Layer::and_then Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Composes this layer around another layer, creating a new Layered struct. ```rust fn and_then(self, layer: L) -> Layered where L: Layer, Self: Sized, ``` -------------------------------- ### Layer Implementations for OpenTelemetryTracingBridge Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html This section details the implementations of the `Layer` trait for `OpenTelemetryTracingBridge`, outlining how it interacts with spans and events within a tracing subscriber. ```APIDOC ## Layer Implementations for OpenTelemetryTracingBridge ### `on_event` Notifies this layer that an event has occurred. ### `on_register_dispatch` Performs late initialization when installing this layer as a `Subscriber`. ### `on_layer` Performs late initialization when attaching a `Layer` to a `Subscriber`. ### `register_callsite` Registers a new callsite with this layer, returning whether or not the layer is interested in being notified about the callsite. ### `enabled` Returns `true` if this layer is interested in a span or event with the given `metadata` in the current `Context`. ### `on_new_span` Notifies this layer that a new span was constructed with the given `Attributes` and `Id`. ### `on_record` Notifies this layer that a span with the given `Id` recorded the given `values`. ### `on_follows_from` Notifies this layer that a span with the ID `span` recorded that it follows from the span with the ID `follows`. ### `event_enabled` Called before `on_event`, to determine if `on_event` should be called. ### `on_enter` Notifies this layer that a span with the given ID was entered. ### `on_exit` Notifies this layer that the span with the given ID was exited. ### `on_close` Notifies this layer that the span with the given ID has been closed. ### `on_id_change` Notifies this layer that a span ID has been cloned, and that the subscriber returned a different ID. ### `and_then` Composes this layer around the given `Layer`, returning a `Layered` struct implementing `Layer`. ### `with_subscriber` Composes this `Layer` with the given `Subscriber`, returning a `Layered` struct that implements `Subscriber`. ### `with_filter` Combines `self` with a `Filter`, returning a `Filtered` layer. ### `boxed` Erases the type of this `Layer`, returning a `Box`ed `dyn Layer` trait object. ``` -------------------------------- ### Standalone Tracing Appender Test Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Tests the `OpenTelemetryTracingBridge` in a standalone scenario. It sets up an in-memory exporter, creates a subscriber, emits a log record with various data types, and asserts the emitted log's properties. ```rust #[test] fn tracing_appender_standalone() { // Arrange let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); let logger_provider = SdkLoggerProvider::builder() .with_simple_exporter(exporter.clone()) .build(); let subscriber = create_tracing_subscriber(&logger_provider); // avoiding setting tracing subscriber as global as that does not // play well with unit tests. let _guard = tracing::subscriber::set_default(subscriber); // Act let small_u64value: u64 = 42; let big_u64value: u64 = u64::MAX; let small_usizevalue: usize = 42; let big_usizevalue: usize = usize::MAX; let small_u128value: u128 = 42; let big_u128value: u128 = u128::MAX; let small_i128value: i128 = 42; let big_i128value: i128 = i128::MAX; error!(name: "my-event-name", target: "my-system", event_id = 20, bytes = &b"abc"[..], error = &OTelSdkError::AlreadyShutdown as &dyn std::error::Error, small_u64value, big_u64value, small_usizevalue, big_usizevalue, small_u128value, big_u128value, small_i128value, big_i128value, user_name = "otel", user_email = "otel@opentelemetry.io"); assert!(logger_provider.force_flush().is_ok()); // Assert TODO: move to helper methods 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("Atleast one log is expected to be present."); // Validate common fields assert_eq!(log.instrumentation.name(), ""); assert_eq!(log.record.severity_number(), Some(Severity::Error)); // Validate target assert_eq!( log.record.target().expect("target is expected").to_string(), "my-system" ); // Validate event name assert_eq!( log.record.event_name().expect("event_name is expected"), "my-event-name" ); // Validate trace context is none. assert!(log.record.trace_context().is_none()); } ``` -------------------------------- ### Layer::on_event Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Handles incoming events within the tracing layer. This method is called when an event occurs. ```rust fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) ``` -------------------------------- ### Borrow::borrow Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Immutably borrows from an owned value. ```rust fn borrow(&self) -> &T ``` -------------------------------- ### Test Default Target and Event Name Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Tests the default behavior of the tracing appender when target and event names are not explicitly provided. It asserts that the target defaults to the module path and the event name defaults to a string including the source file and line number. ```rust exporter.reset(); error!( event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io" ); 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("Atleast one log is expected to be present."); assert_eq!( log.record.target().expect("target is expected").to_string(), "opentelemetry_appender_tracing::layer::tests" ); ``` -------------------------------- ### Helper Function to Check Log Attributes Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html A utility function for tests to verify if a `SdkLogRecord` contains specific attributes. It iterates through the log record's attributes and checks for a matching key-value pair. ```rust pub fn attributes_contains(log_record: &SdkLogRecord, key: &Key, value: &AnyValue) -> bool { log_record .attributes_iter() .any(|(k, v)| k == key && v == value) } ``` -------------------------------- ### Layer::with_subscriber Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Composes this layer with a given subscriber, returning a Layered struct that implements Subscriber. ```rust fn with_subscriber(self, inner: S) -> Layered where Self: Sized, ``` -------------------------------- ### Layer Implementation for OpenTelemetryTracingBridge Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Implements the Layer trait for OpenTelemetryTracingBridge, enabling it to be used within a tracing subscriber. ```rust impl Layer for OpenTelemetryTracingBridge where S: Subscriber + for<'a> LookupSpan<'a>, P: LoggerProvider + Send + Sync + 'static, L: Logger + Send + Sync + 'static, ``` -------------------------------- ### Layer::on_enter Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Notifies the layer when a span is entered. ```rust fn on_enter(&self, _id: &Id, _ctx: Context<'_, S>) ``` -------------------------------- ### Layer::on_follows_from Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Notifies the layer that a span follows from another span. ```rust fn on_follows_from(&self, _span: &Id, _follows: &Id, _ctx: Context<'_, S>) ``` -------------------------------- ### EventVisitor for LogRecord Attributes Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Implements the `tracing::field::Visit` trait for `EventVisitor` to record various data types as attributes on an OpenTelemetry `LogRecord`. Handles debug, error, bytes, string, boolean, float, and integer types. ```rust impl tracing::field::Visit for EventVisitor<'_, LR> { fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { #[cfg(feature = "experimental_metadata_attributes")] if is_duplicated_metadata(field.name()) { return; } if field.name() == "message" { self.log_record.set_body(format!({"value:?"}).into()); } else { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(format!({"value:?"}))); } } fn record_error( &mut self, _field: &tracing_core::Field, value: &(dyn std::error::Error + 'static), ) { self.log_record.add_attribute( Key::new("exception.message"), AnyValue::from(value.to_string()), ); // No ability to get exception.stacktrace or exception.type from the error today. } fn record_bytes(&mut self, field: &tracing_core::Field, value: &[u8]) { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(value)); } fn record_str(&mut self, field: &tracing_core::Field, value: &str) { #[cfg(feature = "experimental_metadata_attributes")] if is_duplicated_metadata(field.name()) { return; } //TODO: Fix heap allocation. Check if lifetime of &str can be used // to optimize sync exporter scenario. if field.name() == "message" { self.log_record.set_body(AnyValue::from(value.to_owned())); } else { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(value.to_owned())); } } fn record_bool(&mut self, field: &tracing_core::Field, value: bool) { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(value)); } fn record_f64(&mut self, field: &tracing::field::Field, value: f64) { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(value)); } fn record_i64(&mut self, field: &tracing::field::Field, value: i64) { #[cfg(feature = "experimental_metadata_attributes")] if is_duplicated_metadata(field.name()) { return; } self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(value)); } // TODO: We might need to do similar for record_i128,record_u128 too } ``` -------------------------------- ### Layer::register_callsite Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Registers a new callsite with the layer and returns whether the layer is interested in notifications from it. ```rust fn register_callsite(&self, metadata: &'static Metadata<'static>) -> Interest ``` -------------------------------- ### OpenTelemetryTracingBridge Struct Definition Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Defines the OpenTelemetryTracingBridge struct, which requires a LoggerProvider and a Logger that are Send and Sync. ```rust pub struct OpenTelemetryTracingBridge where P: LoggerProvider + Send + Sync, L: Logger + Send + Sync, { /* private fields */ } ``` -------------------------------- ### Register Tracing Subscriber Source: https://docs.rs/opentelemetry-appender-tracing/latest/index.html Register the OpenTelemetryTracingBridge layer with the tracing subscriber, optionally including other layers like fmt. ```rust use tracing_subscriber::prelude::*; tracing_subscriber::registry() .with(otel_layer) .with(tracing_subscriber::fmt::layer()) // In this example, `fmt` layer is also added. .init(); ``` -------------------------------- ### Test Tracing Appender Inside Tracing Context Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html This test verifies that logs emitted within a `tracing` span context are correctly captured by the OpenTelemetry appender. It sets up an in-memory exporter, logger provider, and tracing subscriber, then emits a log within a span. Finally, it asserts that the log contains the correct trace context and attributes. ```rust #[test] fn tracing_appender_inside_tracing_context() { // Arrange let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); let logger_provider = SdkLoggerProvider::builder() .with_simple_exporter(exporter.clone()) .build(); let subscriber = create_tracing_subscriber(&logger_provider); // avoiding setting tracing subscriber as global as that does not // play well with unit tests. let _guard = tracing::subscriber::set_default(subscriber); // setup tracing as well. let tracer_provider = SdkTracerProvider::builder() .with_sampler(Sampler::AlwaysOn) .build(); let tracer = tracer_provider.tracer("test-tracer"); // Act let (trace_id_expected, span_id_expected) = tracer.in_span("test-span", |cx| { let trace_id = cx.span().span_context().trace_id(); let span_id = cx.span().span_context().span_id(); // logging is done inside span context. error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io"); (trace_id, span_id) }); assert!(logger_provider.force_flush().is_ok()); // Assert TODO: move to helper methods 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("Atleast one log is expected to be present."); // validate common fields. assert_eq!(log.instrumentation.name(), ""); assert_eq!(log.record.severity_number(), Some(Severity::Error)); // Validate target assert_eq!( log.record.target().expect("target is expected").to_string(), "my-system" ); // Validate event name assert_eq!( log.record.event_name().expect("event_name is expected"), "my-event-name" ); // validate trace context. assert!(log.record.trace_context().is_some()); assert_eq!( log.record.trace_context().unwrap().trace_id, trace_id_expected ); assert_eq!( log.record.trace_context().unwrap().span_id, span_id_expected ); assert_eq!( log.record.trace_context().unwrap().trace_flags.unwrap(), TraceFlags::SAMPLED ); // validate attributes. #[cfg(not(feature = "experimental_metadata_attributes"))] assert_eq!(log.record.attributes_iter().count(), 3); #[cfg(feature = "experimental_metadata_attributes")] assert_eq!(log.record.attributes_iter().count(), 7); assert!(attributes_contains( &log.record, &Key::new("event_id"), &AnyValue::Int(20.into()) )); assert!(attributes_contains( &log.record, &Key::new("user_name"), &AnyValue::String("otel".into()) )); assert!(attributes_contains( &log.record, &Key::new("user_email"), &AnyValue::String("otel@opentelemetry.io".into()) )); #[cfg(feature = "experimental_metadata_attributes")] { assert!(attributes_contains( &log.record, &Key::new("code.filename"), &AnyValue::String("layer.rs".into()) )); assert!(attributes_contains( &log.record, &Key::new("code.namespace"), &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into()) )); // The other 3 experimental_metadata_attributes are too unstable to check their value. // Ex.: The path will be different on a Windows and Linux machine. // Ex.: The line can change easily if someone makes changes in this source file. let attributes_key: Vec = log .record .attributes_iter() .map(|(key, _)| key.clone()) .collect(); ``` -------------------------------- ### Test Tracing Appender Inside Tracing Crate Context Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html This test verifies the `OpenTelemetryTracingBridge` when used within the `tracing` crate's context. It sets up both logging and tracing subscribers, emits logs and spans, and asserts that the trace and span IDs are correctly propagated and associated. ```rust use opentelemetry::{trace::SpanContext, Context, SpanId, TraceId}; use opentelemetry_sdk::trace::InMemorySpanExporterBuilder; use tracing_opentelemetry::OpenTelemetrySpanExt; // Arrange let exporter: InMemoryLogExporter = InMemoryLogExporter::default(); let logger_provider = SdkLoggerProvider::builder() .with_simple_exporter(exporter.clone()) .build(); // setup tracing layer to compare trace/span IDs against let span_exporter = InMemorySpanExporterBuilder::new().build(); let tracer_provider = SdkTracerProvider::builder() .with_simple_exporter(span_exporter.clone()) .build(); let tracer = tracer_provider.tracer("test-tracer"); let level_filter = tracing_subscriber::filter::LevelFilter::ERROR; let log_layer = layer::OpenTelemetryTracingBridge::new(&logger_provider).with_filter(level_filter); let subscriber = tracing_subscriber::registry() .with(log_layer) .with(tracing_opentelemetry::layer().with_tracer(tracer)); // Avoiding global subscriber.init() as that does not play well with unit tests. let _guard = tracing::subscriber::set_default(subscriber); // Act tracing::error_span!("outer-span").in_scope(|| { error!("first-event"); tracing::error_span!("inner-span").in_scope(|| { error!("second-event"); }); }); assert!(logger_provider.force_flush().is_ok()); let logs = exporter.get_emitted_logs().expect("No emitted logs"); assert_eq!(logs.len(), 2, "Expected 2 logs, got: {logs:?}"); let spans = span_exporter.get_finished_spans().unwrap(); assert_eq!(spans.len(), 2); let trace_id = spans[0].span_context.trace_id(); assert_eq!(trace_id, spans[1].span_context.trace_id()); let inner_span_id = spans[0].span_context.span_id(); let outer_span_id = spans[1].span_context.span_id(); assert_eq!(outer_span_id, spans[0].parent_span_id); let trace_ctx0 = logs[0].record.trace_context().unwrap(); let trace_ctx1 = logs[1].record.trace_context().unwrap(); assert_eq!(trace_ctx0.trace_id, trace_id); assert_eq!(trace_ctx1.trace_id, trace_id); assert_eq!(trace_ctx0.span_id, outer_span_id); assert_eq!(trace_ctx1.span_id, inner_span_id); // Set context from remote. let remote_trace_id = TraceId::from(233); let remote_span_id = SpanId::from(2333); let remote_span_context = SpanContext::new( remote_trace_id, remote_span_id, TraceFlags::SAMPLED, true, Default::default(), ); // Act again. { let parent_context = Context::current().with_remote_span_context(remote_span_context); let outer_span = tracing::error_span!("outer-span"); let _ = outer_span.set_parent(parent_context); outer_span.in_scope(|| { error!("first-event"); let inner_span = tracing::error_span!("inner-span"); inner_span.in_scope(|| { error!("second-event"); }); }); } assert!(logger_provider.force_flush().is_ok()); let logs = exporter.get_emitted_logs().expect("No emitted logs"); assert_eq!(logs.len(), 4, "Expected 4 logs, got: {logs:?}"); let logs = &logs[2..]; let spans = span_exporter.get_finished_spans().unwrap(); assert_eq!(spans.len(), 4); let spans = &spans[2..]; let trace_id = spans[0].span_context.trace_id(); assert_eq!(trace_id, remote_trace_id); assert_eq!(trace_id, spans[1].span_context.trace_id()); let inner_span_id = spans[0].span_context.span_id(); let outer_span_id = spans[1].span_context.span_id(); assert_eq!(outer_span_id, spans[0].parent_span_id); let trace_ctx0 = logs[0].record.trace_context().unwrap(); let trace_ctx1 = logs[1].record.trace_context().unwrap(); assert_eq!(trace_ctx0.trace_id, trace_id); ``` -------------------------------- ### Layer::with_filter Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Combines this layer with a filter, returning a Filtered layer. ```rust fn with_filter(self, filter: F) -> Filtered where Self: Sized, F: Filter, ``` -------------------------------- ### Record i128 Field Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Handles recording of `i128` fields. It attempts to convert the value to `i64` for direct attribute addition. If conversion fails, it formats the `i128` as a string. ```rust fn record_i128(&mut self, field: &tracing::field::Field, value: i128) { #[cfg(feature = "experimental_metadata_attributes")] if is_duplicated_metadata(field.name()) { return; } if let Ok(signed) = i64::try_from(value) { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(signed)); } else { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(format!({"value:?"}))); } } ``` -------------------------------- ### BorrowMut::borrow_mut Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Mutably borrows from an owned value. ```rust fn borrow_mut(&mut self) -> &mut T ``` -------------------------------- ### Layer::enabled Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Determines if the layer is interested in a span or event based on metadata and the current context. ```rust fn enabled(&self, metadata: &Metadata<'_>, ctx: Context<'_, S>) -> bool ``` -------------------------------- ### Test Event Enabled Logic Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Tests the `event_enabled` functionality of the tracing subscriber by configuring a custom log processor and emitting an error log. This verifies that the filtering logic correctly identifies events. ```rust #[cfg(feature = "spec_unstable_logs_enabled")] #[test] fn is_enabled() { // Arrange let logger_provider = SdkLoggerProvider::builder() .with_log_processor(LogProcessorWithIsEnabled::new( Severity::Error, "my-event-name".to_string(), "my-system".to_string(), )) .build(); let subscriber = create_tracing_subscriber(&logger_provider); // avoiding setting tracing subscriber as global as that does not // play well with unit tests. let _guard = tracing::subscriber::set_default(subscriber); // Name, Target and Severity are expected to be passed to the IsEnabled check // The validation is done in the LogProcessorWithIsEnabled struct. error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io"); } ``` -------------------------------- ### Layer::on_record Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Notifies the layer when a span records specific values. ```rust fn on_record(&self, _span: &Id, _values: &Record<'_>, _ctx: Context<'_, S>) ``` -------------------------------- ### Record u128 Field Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Handles recording of `u128` fields. It attempts to convert the value to `i64` for direct attribute addition. If conversion fails, it formats the `u128` as a string. ```rust fn record_u128(&mut self, field: &tracing::field::Field, value: u128) { #[cfg(feature = "experimental_metadata_attributes")] if is_duplicated_metadata(field.name()) { return; } if let Ok(signed) = i64::try_from(value) { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(signed)); } else { self.log_record .add_attribute(Key::new(field.name()), AnyValue::from(format!({"value:?"}))); } } ``` -------------------------------- ### Instrumenting Types with Spans Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Methods for instrumenting a type with a `Span` or the current `Span`, returning an `Instrumented` wrapper. ```APIDOC ## fn instrument(self, span: Span) -> Instrumented Instruments this type with the provided `Span`, returning an `Instrumented` wrapper. ### Method `instrument` ### Parameters - **span** (`Span`) - The span to instrument with. ### Returns - `Instrumented` - A wrapper type containing the instrumented self. ``` ```APIDOC ## fn in_current_span(self) -> Instrumented Instruments this type with the current `Span`, returning an `Instrumented` wrapper. ### Method `in_current_span` ### Returns - `Instrumented` - A wrapper type containing the instrumented self. ``` -------------------------------- ### Validate Log Attributes Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Asserts the presence and values of various log attributes, including event ID, user information, exception details, numeric types (u64, usize, u128, i128), and byte arrays. Conditional compilation is used for experimental metadata attributes. ```rust #[cfg(not(feature = "experimental_metadata_attributes"))] assert_eq!(log.record.attributes_iter().count(), 13); #[cfg(feature = "experimental_metadata_attributes")] assert_eq!(log.record.attributes_iter().count(), 17); assert!(attributes_contains( &log.record, &Key::new("event_id"), &AnyValue::Int(20) )); assert!(attributes_contains( &log.record, &Key::new("user_name"), &AnyValue::String("otel".into()) )); assert!(attributes_contains( &log.record, &Key::new("user_email"), &AnyValue::String("otel@opentelemetry.io".into()) )); assert!(attributes_contains( &log.record, &Key::new("exception.message"), &AnyValue::String(OTelSdkError::AlreadyShutdown.to_string().into()) )); assert!(attributes_contains( &log.record, &Key::new("small_u64value"), &AnyValue::Int(42.into()) )); assert!(attributes_contains( &log.record, &Key::new("big_u64value"), &AnyValue::String(format!("{}", u64::MAX).into()) )); assert!(attributes_contains( &log.record, &Key::new("small_usizevalue"), &AnyValue::Int(42.into()) )); assert!(attributes_contains( &log.record, &Key::new("big_usizevalue"), &AnyValue::String(format!("{}", u64::MAX).into()) )); assert!(attributes_contains( &log.record, &Key::new("small_u128value"), &AnyValue::Int(42.into()) )); assert!(attributes_contains( &log.record, &Key::new("big_u128value"), &AnyValue::String(format!("{}", u128::MAX).into()) )); assert!(attributes_contains( &log.record, &Key::new("small_i128value"), &AnyValue::Int(42.into()) )); assert!(attributes_contains( &log.record, &Key::new("big_i128value"), &AnyValue::String(format!("{}", i128::MAX).into()) )); assert!(attributes_contains( &log.record, &Key::new("bytes"), &AnyValue::Bytes(Box::new(b"abc".to_vec())) )); #[cfg(feature = "experimental_metadata_attributes")] { assert!(attributes_contains( &log.record, &Key::new("code.filename"), &AnyValue::String("layer.rs".into()) )); assert!(attributes_contains( &log.record, &Key::new("code.namespace"), &AnyValue::String("opentelemetry_appender_tracing::layer::tests".into()) )); let attributes_key: Vec = log .record .attributes_iter() .map(|(key, _)| key.clone()) .collect(); assert!(attributes_key.contains(&Key::new("code.filepath"))); assert!(attributes_key.contains(&Key::new("code.lineno"))); assert!(!attributes_key.contains(&Key::new("log.target"))); } ``` -------------------------------- ### Layer::on_new_span Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Notifies the layer when a new span is constructed with given attributes and ID. ```rust fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) ``` -------------------------------- ### Custom Log Processor for IsEnabled Check Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Implements a custom `LogProcessor` to verify if events are enabled based on severity, target, and name. This is used in testing to ensure the `event_enabled` logic functions correctly. ```rust #[derive(Debug)] struct LogProcessorWithIsEnabled { severity_level: Severity, name: String, target: String, } impl LogProcessorWithIsEnabled { fn new(severity_level: Severity, name: String, target: String) -> Self { LogProcessorWithIsEnabled { severity_level, name, target, } } } impl LogProcessor for LogProcessorWithIsEnabled { fn emit(&self, _record: &mut SdkLogRecord, _scope: &InstrumentationScope) { // no-op } #[cfg(feature = "spec_unstable_logs_enabled")] fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool { // assert that passed in arguments are same as the ones set in the test. assert_eq!(self.severity_level, level); assert_eq!(self.target, target); assert_eq!( self.name, name.expect("name is expected from tracing appender") ); true } fn force_flush(&self) -> OTelSdkResult { Ok(()) } } ``` -------------------------------- ### Severity Mapping for Log Levels Source: https://docs.rs/opentelemetry-appender-tracing/latest/src/opentelemetry_appender_tracing/layer.rs.html Maps `tracing` log levels to OpenTelemetry `Severity` enum values. This is used internally by the appender to set the severity of the emitted log records. ```rust const fn severity_of_level(level: &Level) -> Severity { match *level { Level::TRACE => Severity::Trace, Level::DEBUG => Severity::Debug, Level::INFO => Severity::Info, Level::WARN => Severity::Warn, Level::ERROR => Severity::Error, } } ``` -------------------------------- ### Layer::on_exit Implementation Source: https://docs.rs/opentelemetry-appender-tracing/latest/opentelemetry_appender_tracing/layer/struct.OpenTelemetryTracingBridge.html Notifies the layer when a span is exited. ```rust fn on_exit(&self, _id: &Id, _ctx: Context<'_, S>) ```