diff --git a/Cargo.toml b/Cargo.toml index 010ce42b48..9e14673e3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,7 +67,6 @@ num_cpus = "1.15.0" opentelemetry = { path = "opentelemetry", version = "0.31", default-features = false } opentelemetry_sdk = { path = "opentelemetry-sdk", version = "0.31", default-features = false } opentelemetry-appender-log = { path = "opentelemetry-appender-log", version = "0.31", default-features = false } -opentelemetry-appender-tracing = { path = "opentelemetry-appender-tracing", version = "0.31.1", default-features = false } opentelemetry-http = { path = "opentelemetry-http", version = "0.31", default-features = false } opentelemetry-jaeger-propagator = { path = "opentelemetry-jaeger-propagator", version = "0.31", default-features = false } opentelemetry-otlp = { path = "opentelemetry-otlp", version = "0.31", default-features = false } @@ -81,7 +80,6 @@ sysinfo = "0.32" tempfile = "3.3.0" testcontainers = "0.23.1" tracing-log = "0.2" -tracing-opentelemetry = "0.32" typed-builder = "0.20" uuid = "1.3" pprof = { version = "0.14", features = ["flamegraph", "criterion"] } diff --git a/examples/logs-basic/Cargo.toml b/examples/logs-basic/Cargo.toml index 025f01197d..4234f95544 100644 --- a/examples/logs-basic/Cargo.toml +++ b/examples/logs-basic/Cargo.toml @@ -15,6 +15,6 @@ bench = false [dependencies] opentelemetry_sdk = { workspace = true, features = ["logs"] } opentelemetry-stdout = { workspace = true, features = ["logs"] } -opentelemetry-appender-tracing = { workspace = true } -tracing = { workspace = true, features = ["std"]} -tracing-subscriber = { workspace = true, features = ["env-filter","registry", "std", "fmt"] } +opentelemetry-appender-log = { workspace = true } +log = { workspace = true, features = ["std"] } +tokio = { workspace = true, features = ["full"] } diff --git a/examples/logs-basic/src/main.rs b/examples/logs-basic/src/main.rs index f681331a0b..a6c6d3b1e7 100644 --- a/examples/logs-basic/src/main.rs +++ b/examples/logs-basic/src/main.rs @@ -1,53 +1,29 @@ -use opentelemetry_appender_tracing::layer; -use opentelemetry_sdk::logs::SdkLoggerProvider; -use opentelemetry_sdk::Resource; -use tracing::error; -use tracing_subscriber::{prelude::*, EnvFilter}; +use log::{error, info, warn, Level}; +use opentelemetry_appender_log::OpenTelemetryLogBridge; +use opentelemetry_sdk::logs::{BatchLogProcessor, SdkLoggerProvider}; +use opentelemetry_stdout::LogExporter; -fn main() { - let exporter = opentelemetry_stdout::LogExporter::default(); - let provider: SdkLoggerProvider = SdkLoggerProvider::builder() - .with_resource( - Resource::builder() - .with_service_name("log-appender-tracing-example") - .build(), - ) - .with_simple_exporter(exporter) +#[tokio::main] +async fn main() { + //Create an exporter that writes to stdout + let exporter = LogExporter::default(); + //Create a LoggerProvider and register the exporter + let logger_provider = SdkLoggerProvider::builder() + .with_log_processor(BatchLogProcessor::builder(exporter).build()) .build(); - // To prevent a telemetry-induced-telemetry loop, OpenTelemetry's own internal - // logging is properly suppressed. However, logs emitted by external components - // (such as reqwest, tonic, etc.) are not suppressed as they do not propagate - // OpenTelemetry context. Until this issue is addressed - // (https://github.com/open-telemetry/opentelemetry-rust/issues/2877), - // filtering like this is the best way to suppress such logs. - // - // The filter levels are set as follows: - // - Allow `info` level and above by default. - // - Completely restrict logs from `hyper`, `tonic`, `h2`, and `reqwest`. - // - // Note: This filtering will also drop logs from these components even when - // they are used outside of the OTLP Exporter. - let filter_otel = EnvFilter::new("info") - .add_directive("hyper=off".parse().unwrap()) - .add_directive("tonic=off".parse().unwrap()) - .add_directive("h2=off".parse().unwrap()) - .add_directive("reqwest=off".parse().unwrap()); - let otel_layer = layer::OpenTelemetryTracingBridge::new(&provider).with_filter(filter_otel); + // Setup Log Appender for the log crate. + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); - // Create a new tracing::Fmt layer to print the logs to stdout. It has a - // default filter of `info` level and above, and `debug` and above for logs - // from OpenTelemetry crates. The filter levels can be customized as needed. - let filter_fmt = EnvFilter::new("info").add_directive("opentelemetry=debug".parse().unwrap()); - let fmt_layer = tracing_subscriber::fmt::layer() - .with_thread_names(true) - .with_filter(filter_fmt); + // Emit logs using macros from the log crate. + let fruit = "apple"; + let price = 2.99; - tracing_subscriber::registry() - .with(otel_layer) - .with(fmt_layer) - .init(); + error!(fruit, price; "hello from {fruit}. My price is {price}"); + warn!("warn!"); + info!("test log!"); - error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io", message = "This is an example message"); - let _ = provider.shutdown(); + let _ = logger_provider.shutdown(); } diff --git a/examples/tracing-http-propagator/Cargo.toml b/examples/tracing-http-propagator/Cargo.toml index 713af58ca9..a8ff53ad42 100644 --- a/examples/tracing-http-propagator/Cargo.toml +++ b/examples/tracing-http-propagator/Cargo.toml @@ -29,6 +29,5 @@ opentelemetry_sdk = { workspace = true } opentelemetry-http = { workspace = true } opentelemetry-stdout = { workspace = true, features = ["trace", "logs"] } opentelemetry-semantic-conventions = { workspace = true } -opentelemetry-appender-tracing = { workspace = true } -tracing = { workspace = true, features = ["std"]} -tracing-subscriber = { workspace = true, features = ["env-filter","registry", "std", "fmt"] } +opentelemetry-appender-log = { workspace = true } +log = { workspace = true, features = ["kv"]} diff --git a/examples/tracing-http-propagator/src/client.rs b/examples/tracing-http-propagator/src/client.rs index dfc6cf50c9..5dda49feea 100644 --- a/examples/tracing-http-propagator/src/client.rs +++ b/examples/tracing-http-propagator/src/client.rs @@ -1,18 +1,17 @@ use http_body_util::Full; use hyper_util::{client::legacy::Client, rt::TokioExecutor}; +use log::{info, Level}; use opentelemetry::{ global, trace::{SpanKind, TraceContextExt, Tracer}, Context, }; -use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry_http::{Bytes, HeaderInjector}; use opentelemetry_sdk::{ logs::SdkLoggerProvider, propagation::TraceContextPropagator, trace::SdkTracerProvider, }; use opentelemetry_stdout::{LogExporter, SpanExporter}; -use tracing::info; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; fn init_tracer() -> SdkTracerProvider { global::set_text_map_propagator(TraceContextPropagator::new()); @@ -32,11 +31,10 @@ fn init_logs() -> SdkLoggerProvider { let logger_provider = SdkLoggerProvider::builder() .with_simple_exporter(LogExporter::default()) .build(); - let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); - tracing_subscriber::registry() - .with(otel_layer) - .with(tracing_subscriber::filter::LevelFilter::INFO) - .init(); + // Setup Log Appender for the log crate. + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); logger_provider } @@ -65,7 +63,7 @@ async fn send_request( .request(req.body(Full::new(Bytes::from(body_content.to_string())))?) .await?; - info!(name: "ResponseReceived", status = res.status().to_string(), message = "Response received"); + info!(name = "ResponseReceived", status:% = res.status(); "Response received"); Ok(()) } diff --git a/examples/tracing-http-propagator/src/server.rs b/examples/tracing-http-propagator/src/server.rs index 49d4a084e7..ee11529628 100644 --- a/examples/tracing-http-propagator/src/server.rs +++ b/examples/tracing-http-propagator/src/server.rs @@ -1,6 +1,7 @@ use http_body_util::{combinators::BoxBody, BodyExt, Full}; use hyper::{body::Incoming, service::service_fn, Request, Response, StatusCode}; use hyper_util::rt::{TokioExecutor, TokioIo}; +use log::{info, Level}; use opentelemetry::{ baggage::BaggageExt, global::{self, BoxedTracer}, @@ -9,7 +10,7 @@ use opentelemetry::{ trace::{FutureExt, Span, SpanKind, TraceContextExt, Tracer}, Context, InstrumentationScope, KeyValue, }; -use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry_http::{Bytes, HeaderExtractor}; use opentelemetry_sdk::{ error::OTelSdkResult, @@ -22,8 +23,6 @@ use opentelemetry_stdout::{LogExporter, SpanExporter}; use std::time::Duration; use std::{convert::Infallible, net::SocketAddr, sync::OnceLock}; use tokio::net::TcpListener; -use tracing::info; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; fn get_tracer() -> &'static BoxedTracer { static TRACER: OnceLock = OnceLock::new(); @@ -46,7 +45,7 @@ async fn handle_health_check( .span_builder("health_check") .with_kind(SpanKind::Internal) .start(tracer); - info!(name: "health_check", message = "Health check endpoint hit"); + info!(name = "health_check"; "Health check endpoint hit"); let res = Response::new( Full::new(Bytes::from_static(b"Server is up and running!")) @@ -66,7 +65,7 @@ async fn handle_echo( .span_builder("echo") .with_kind(SpanKind::Internal) .start(tracer); - info!(name = "echo", message = "Echo endpoint hit"); + info!(name = "echo"; "Echo endpoint hit"); let res = Response::new(req.into_body().boxed()); @@ -86,7 +85,7 @@ async fn router( .with_kind(SpanKind::Server) .start_with_context(tracer, &parent_cx); - info!(name = "router", message = "Dispatching request"); + info!(name = "router"; "Dispatching request"); let cx = parent_cx.with_span(span); match (req.method(), req.uri().path()) { @@ -173,8 +172,10 @@ fn init_logs() -> SdkLoggerProvider { .with_log_processor(EnrichWithBaggageLogProcessor) .with_simple_exporter(LogExporter::default()) .build(); - let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); - tracing_subscriber::registry().with(otel_layer).init(); + // Setup Log Appender for the log crate. + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); logger_provider } diff --git a/opentelemetry-appender-tracing/CHANGELOG.md b/opentelemetry-appender-tracing/CHANGELOG.md deleted file mode 100644 index 3f966ec2e1..0000000000 --- a/opentelemetry-appender-tracing/CHANGELOG.md +++ /dev/null @@ -1,166 +0,0 @@ -# Changelog - -## vNext - -## 0.31.1 - -Released 2025-Oct-1 - -- Bump `tracing-opentelemetry` to 0.32 - -## 0.31.0 - -Released 2025-Sep-25 - -- Updated `opentelemetry` dependency to version 0.31.0. - -## 0.30.1 - -Released 2025-June-05 - -- Bump `tracing-opentelemetry` to 0.31 - -## 0.30.0 - -Released 2025-May-23 - -- Updated `opentelemetry` dependency to version 0.30.0. - - -## 0.29.1 - -Released 2025-Mar-24 - -- Bump `tracing-opentelemetry` to 0.30 - - -## 0.29.0 - -Released 2025-Mar-21 - -Fixes [1682](https://github.com/open-telemetry/opentelemetry-rust/issues/1682). -"spec_unstable_logs_enabled" feature now do not suppress logs for other layers. - -The special treatment of the "message" field has been extended when recording -string values. With this change, when a log is emitted with a field named -"message" (and string value), its value is directly assigned to the LogRecord’s -body rather than being stored as an attribute named "message". This offers a -slight performance improvement over previous. - -For example, the below will now produce LogRecord with the message value -populated as LogRecord's body: - -```rust -error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io", message = "This is an example message"); -``` - -Previously, Body was only populated when the below style was used. - -```rust -error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io", "This is an example message"); -``` - -This style, while slightly slower, should still be used when the value is not a -simple string, but require format arguments as in the below example. - -```rust -error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io", "This is an example message with format arguments {} and {}", "foo", "bar"); -``` - -Fixes [2658](https://github.com/open-telemetry/opentelemetry-rust/issues/2658) -InstrumentationScope(Logger) used by the appender now uses an empty ("") named -Logger. Previously, a Logger with name and version of the crate was used. -Receivers (processors, exporters) are expected to use `LogRecord.target()` as -scope name. This is already done in OTLP Exporters, so this change should be -transparent to most users. - -- Passes event name to the `event_enabled` method on the `Logger`. This allows - implementations (SDK, processor, exporters) to leverage this additional - information to determine if an event is enabled. - -- `u64`, `i128`, `u128` and `usize` values are stored as `opentelemetry::logs::AnyValue::Int` -when conversion is feasible. Otherwise stored as -`opentelemetry::logs::AnyValue::String`. This avoids unnecessary string -allocation when values can be represented in their original types. -- Byte arrays are stored as `opentelemetry::logs::AnyValue::Bytes` instead -of string. -- `Error` fields are reported using attribute named "exception.message". For - example, the below will now report an attribute named "exception.message", - instead of previously reporting the user provided attribute "error". - `error!(....error = &OTelSdkError::AlreadyShutdown as &dyn std::error::Error...)` -- perf - small perf improvement by avoiding string allocation of `target` -- Update `opentelemetry` dependency version to 0.29. - -## 0.28.1 - -Released 2025-Feb-12 - -- New *experimental* feature to use trace_id & span_id from spans created through the [tracing](https://crates.io/crates/tracing) crate (experimental_use_tracing_span_context) [#2438](https://github.com/open-telemetry/opentelemetry-rust/pull/2438) - -## 0.28.0 - -Released 2025-Feb-10 - -- Update `opentelemetry` dependency version to 0.28. -- Bump msrv to 1.75.0. - -## 0.27.0 - -Released 2024-Nov-11 - -- Update `opentelemetry` dependency version to 0.27 - -- Bump MSRV to 1.70 [#2179](https://github.com/open-telemetry/opentelemetry-rust/pull/2179) -- **Breaking** [2291](https://github.com/open-telemetry/opentelemetry-rust/pull/2291) Rename `logs_level_enabled flag` to `spec_unstable_logs_enabled`. Please enable this updated flag if the feature is needed. This flag will be removed once the feature is stabilized in the specifications. - -## v0.26.0 - -Released 2024-Sep-30 - -- Update `opentelemetry` dependency version to 0.26 -- [2101](https://github.com/open-telemetry/opentelemetry-rust/pull/2101) The `log` events emitted via the `tracing` pipeline using the `log-tracing` crate no longer include the target metadata as attributes. Exporters or backends that rely on this attribute should now access the target directly from the `LogRecord::target` field. - -## v0.25.0 - -- Update `opentelemetry` dependency version to 0.25 -- Starting with this version, this crate will align with `opentelemetry` crate - on major,minor versions. -- Reduce heap allocation by using `&'static str` for `SeverityText`. - -## v0.5.0 - -- [1869](https://github.com/open-telemetry/opentelemetry-rust/pull/1869) Utilize the `LogRecord::set_target()` method to pass the tracing target to the SDK. - Exporters might use the target to override the instrumentation scope, which previously contained "opentelemetry-appender-tracing". - -- **Breaking** [1928](https://github.com/open-telemetry/opentelemetry-rust/pull/1928) Insert tracing event name into LogRecord::event_name instead of attributes. - - If using a custom exporter, then they must serialize this field directly from LogRecord::event_name instead of iterating over the attributes. OTLP Exporter is modified to handle this. -- Update `opentelemetry` dependency version to 0.24 - -## v0.4.0 - -- Removed unwanted dependency on opentelemetry-sdk. -- Update `opentelemetry` dependency version to 0.23 - -## v0.3.0 - -### Added - -- New experimental metadata attributes feature (experimental\_metadata\_attributes) [#1380](https://github.com/open-telemetry/opentelemetry-rust/pull/1380) - - Experimental new attributes for tracing metadata - - Fixes the following for events emitted using log crate - - Normalized metadata fields - - Remove redundant metadata - -## v0.2.0 - -### Changed - -- Bump MSRV to 1.65 [#1318](https://github.com/open-telemetry/opentelemetry-rust/pull/1318) - -### Added - -- Add log appender versions to loggers (#1182) - -## v0.1.0 - -Initial crate release diff --git a/opentelemetry-appender-tracing/Cargo.toml b/opentelemetry-appender-tracing/Cargo.toml deleted file mode 100644 index 22284d289a..0000000000 --- a/opentelemetry-appender-tracing/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "opentelemetry-appender-tracing" -version = "0.31.1" -edition = "2021" -description = "An OpenTelemetry log appender for the tracing crate" -homepage = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-appender-tracing" -repository = "https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-appender-tracing" -readme = "README.md" -keywords = ["opentelemetry", "log", "logs", "tracing"] -license = "Apache-2.0" -rust-version = "1.75.0" -autobenches = false - -[dependencies] -log = { workspace = true, optional = true } -opentelemetry = { workspace = true, features = ["logs"] } -tracing = { workspace = true, features = ["std"]} -tracing-core = { workspace = true } -tracing-log = { workspace = true, optional = true } -tracing-subscriber = { workspace = true, features = ["registry", "std"] } -tracing-opentelemetry = { workspace = true, optional = true } - -[dev-dependencies] -log = { workspace = true } -opentelemetry-stdout = { workspace = true, features = ["logs"] } -opentelemetry_sdk = { workspace = true, features = ["logs", "testing", "internal-logs"] } -tracing = { workspace = true, features = ["std"]} -tracing-subscriber = { workspace = true, features = ["env-filter","registry", "std", "fmt"] } -tracing-log = { workspace = true } -criterion = { workspace = true } -#tokio = { workspace = true, features = ["full"]} - -[target.'cfg(not(target_os = "windows"))'.dev-dependencies] -pprof = { workspace = true } - -[features] -default = [] -experimental_metadata_attributes = ["dep:tracing-log"] -spec_unstable_logs_enabled = ["opentelemetry/spec_unstable_logs_enabled"] -experimental_use_tracing_span_context = ["tracing-opentelemetry"] -bench_profiling = [] - - -[[bench]] -name = "logs" -harness = false -required-features = ["spec_unstable_logs_enabled"] - -[[bench]] -name = "log-attributes" -harness = false - -[lib] -bench = false - -[lints] -workspace = true diff --git a/opentelemetry-appender-tracing/README.md b/opentelemetry-appender-tracing/README.md deleted file mode 100644 index b5aa3305dc..0000000000 --- a/opentelemetry-appender-tracing/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# OpenTelemetry Log Appender for `tracing` crate - -![OpenTelemetry — An observability framework for cloud-native software.][splash] - -[splash]: https://raw.githubusercontent.com/open-telemetry/opentelemetry-rust/main/assets/logo-text.png - -This crate contains a [Log -Appender](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#log-appender--bridge) -that bridges logs from the [tracing crate](https://tracing.rs/tracing/#events) -to OpenTelemetry. Note that this is different from the existing -[tracing-opentelemetry](https://github.com/tokio-rs/tracing-opentelemetry) -project, which supports bridging traces and logs from tracing into OpenTelemetry -traces. - -[![Crates.io: opentelemetry-appender-tracing](https://img.shields.io/crates/v/opentelemetry-appender-tracing.svg)](https://crates.io/crates/opentelemetry-appender-tracing) -[![Documentation](https://docs.rs/opentelemetry-appender-tracing/badge.svg)](https://docs.rs/opentelemetry-appender-tracing) -[![LICENSE](https://img.shields.io/crates/l/opentelemetry-appender-tracing)](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-appender-tracing/LICENSE) -[![GitHub Actions CI](https://github.com/open-telemetry/opentelemetry-rust/workflows/CI/badge.svg)](https://github.com/open-telemetry/opentelemetry-rust/actions?query=workflow%3ACI+branch%3Amain) -[![Slack](https://img.shields.io/badge/slack-@cncf/otel/rust-brightgreen.svg?logo=slack)](https://cloud-native.slack.com/archives/C03GDP0H023) - -## OpenTelemetry Overview - -OpenTelemetry is an Observability framework and toolkit designed to create and -manage telemetry data such as traces, metrics, and logs. OpenTelemetry is -vendor- and tool-agnostic, meaning that it can be used with a broad variety of -Observability backends, including open source tools like [Jaeger] and -[Prometheus], as well as commercial offerings. - -OpenTelemetry is *not* an observability backend like Jaeger, Prometheus, or other -commercial vendors. OpenTelemetry is focused on the generation, collection, -management, and export of telemetry. A major goal of OpenTelemetry is that you -can easily instrument your applications or systems, no matter their language, -infrastructure, or runtime environment. Crucially, the storage and visualization -of telemetry is intentionally left to other tools. - -*[Supported Rust Versions](#supported-rust-versions)* - -## Release Notes - -You can find the release notes (changelog) [here](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-appender-tracing/CHANGELOG.md). - -## Supported Rust Versions - -OpenTelemetry is built against the latest stable release. The minimum supported -version is 1.75.0. The current OpenTelemetry version is not guaranteed to build -on Rust versions earlier than the minimum supported version. - -The current stable Rust compiler and the three most recent minor versions -before it will always be supported. For example, if the current stable compiler -version is 1.49, the minimum supported version will not be increased past 1.46, -three minor versions prior. Increasing the minimum supported compiler version -is not considered a semver breaking change as long as doing so complies with -this policy. diff --git a/opentelemetry-appender-tracing/benches/log-attributes.rs b/opentelemetry-appender-tracing/benches/log-attributes.rs deleted file mode 100644 index 6fdbefc69c..0000000000 --- a/opentelemetry-appender-tracing/benches/log-attributes.rs +++ /dev/null @@ -1,273 +0,0 @@ -/* -// Run this benchmark with: - // cargo bench --bench log-attributes - // Adding results in comments for a quick reference. - // Apple M4 Pro - // Total Number of Cores: 14 (10 performance and 4 efficiency) - -| Test | Average time | Increment | -|----------------------|--------------|-----------| -| otel_0_attributes | 72 ns | - | -| otel_1_attributes | 117 ns | +45 ns | -| otel_2_attributes | 155 ns | +38 ns | -| otel_3_attributes | 196 ns | +41 ns | -| otel_4_attributes | 240 ns | +44 ns | -| otel_5_attributes | 278 ns | +38 ns | -| otel_6_attributes | 346 ns | +68 ns | // Array is full. 6th attribute causes vec! to be allocated -| otel_7_attributes | 390 ns | +44 ns | -| otel_8_attributes | 431 ns | +41 ns | -| otel_9_attributes | 480 ns | +49 ns | -| otel_10_attributes | 519 ns | +39 ns | -| otel_11_attributes | 625 ns | +106 ns | // vec! initial capacity is 5. 11th attribute causes vec! to be reallocated -| otel_12_attributes | 676 ns | +51 ns | -*/ - -use criterion::{criterion_group, criterion_main, Criterion}; -use opentelemetry::InstrumentationScope; -use opentelemetry_appender_tracing::layer as tracing_layer; -use opentelemetry_sdk::error::OTelSdkResult; -use opentelemetry_sdk::logs::{LogProcessor, SdkLogRecord, SdkLoggerProvider}; -use opentelemetry_sdk::Resource; -#[cfg(all(not(target_os = "windows"), feature = "bench_profiling"))] -use pprof::criterion::{Output, PProfProfiler}; -use tracing::error; -use tracing_subscriber::prelude::*; -use tracing_subscriber::Registry; - -#[derive(Debug)] -struct NoopProcessor; - -impl LogProcessor for NoopProcessor { - fn emit(&self, _: &mut SdkLogRecord, _: &InstrumentationScope) {} - - fn force_flush(&self) -> OTelSdkResult { - Ok(()) - } -} - -/// Creates a single benchmark for a specific number of attributes -fn create_benchmark(c: &mut Criterion, num_attributes: usize) { - let provider = SdkLoggerProvider::builder() - .with_resource( - Resource::builder_empty() - .with_service_name("benchmark") - .build(), - ) - .with_log_processor(NoopProcessor) - .build(); - - let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider); - let subscriber = Registry::default().with(ot_layer); - - tracing::subscriber::with_default(subscriber, || { - c.bench_function(&format!("otel_{num_attributes}_attributes"), |b| { - b.iter(|| { - // Dynamically generate the error! macro call based on the number of attributes - match num_attributes { - 0 => { - error!( - name : "CheckoutFailed", - message = "Unable to process checkout." - ); - } - 1 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - message = "Unable to process checkout." - ); - } - 2 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - message = "Unable to process checkout." - ); - } - 3 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - message = "Unable to process checkout." - ); - } - 4 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - message = "Unable to process checkout." - ); - } - 5 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - message = "Unable to process checkout." - ); - } - 6 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - message = "Unable to process checkout." - ); - } - 7 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - message = "Unable to process checkout." - ); - } - 8 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - field8 = "field8", - message = "Unable to process checkout." - ); - } - 9 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - field8 = "field8", - field9 = "field9", - message = "Unable to process checkout." - ); - } - 10 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - field8 = "field8", - field9 = "field9", - field10 = "field10", - message = "Unable to process checkout." - ); - } - 11 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - field8 = "field8", - field9 = "field9", - field10 = "field10", - field11 = "field11", - message = "Unable to process checkout." - ); - } - 12 => { - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - field8 = "field8", - field9 = "field9", - field10 = "field10", - field11 = "field11", - field12 = "field12", - message = "Unable to process checkout." - ); - } - _ => { - // Fall back to 10 attributes for any higher number - error!( - name : "CheckoutFailed", - field1 = "field1", - field2 = "field2", - field3 = "field3", - field4 = "field4", - field5 = "field5", - field6 = "field6", - field7 = "field7", - field8 = "field8", - field9 = "field9", - field10 = "field10", - message = "Unable to process checkout." - ); - } - } - }); - }); - }); -} - -fn criterion_benchmark(c: &mut Criterion) { - create_benchmark(c, 2); - // Run benchmarks for 0 to 12 attributes - // for num_attributes in 0..=12 { - // create_benchmark(c, 2); - // } -} - -#[cfg(all(not(target_os = "windows"), feature = "bench_profiling"))] -criterion_group! { - name = benches; - config = Criterion::default() - .warm_up_time(std::time::Duration::from_secs(1)) - .measurement_time(std::time::Duration::from_secs(2)) - .with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = criterion_benchmark -} - -#[cfg(any(target_os = "windows", not(feature = "bench_profiling")))] -criterion_group! { - name = benches; - config = Criterion::default() - .warm_up_time(std::time::Duration::from_secs(1)) - .measurement_time(std::time::Duration::from_secs(2)); - targets = criterion_benchmark -} - -criterion_main!(benches); diff --git a/opentelemetry-appender-tracing/benches/logs.rs b/opentelemetry-appender-tracing/benches/logs.rs deleted file mode 100644 index 3d57df25b7..0000000000 --- a/opentelemetry-appender-tracing/benches/logs.rs +++ /dev/null @@ -1,182 +0,0 @@ -/* - The benchmark results: - criterion = "0.5.1" - OS: Ubuntu 22.04.3 LTS (5.15.146.1-microsoft-standard-WSL2) - Hardware: AMD EPYC 7763 64-Core Processor - 2.44 GHz, 16vCPUs, - RAM: 64.0 GB - | Test | Average time| - |-----------------------------|-------------| - | log_no_subscriber | 313 ps | - | noop_layer_disabled | 12 ns | - | noop_layer_enabled | 25 ns | - | ot_layer_disabled | 19 ns | - | ot_layer_enabled | 155 ns | - - Hardware: Apple M4 Pro - Total Number of Cores: 14 (10 performance and 4 efficiency) - | Test | Average time| - |-----------------------------|-------------| - | log_no_subscriber | 285 ps | - | noop_layer_disabled | 8 ns | - | noop_layer_enabled | 14 ns | - | ot_layer_disabled | 12 ns | - | ot_layer_enabled | 130 ns | -*/ - -use criterion::{criterion_group, criterion_main, Criterion}; -use opentelemetry::InstrumentationScope; -use opentelemetry_appender_tracing::layer as tracing_layer; -use opentelemetry_sdk::error::OTelSdkResult; -use opentelemetry_sdk::logs::{LogProcessor, SdkLogRecord, SdkLoggerProvider}; -use opentelemetry_sdk::Resource; -#[cfg(all(not(target_os = "windows"), feature = "bench_profiling"))] -use pprof::criterion::{Output, PProfProfiler}; -use tracing::error; -use tracing_subscriber::prelude::*; -use tracing_subscriber::Layer; -use tracing_subscriber::Registry; - -#[derive(Debug)] -struct NoopProcessor { - enabled: bool, -} - -impl NoopProcessor { - fn new(enabled: bool) -> Self { - Self { enabled } - } -} - -impl LogProcessor for NoopProcessor { - fn emit(&self, _: &mut SdkLogRecord, _: &InstrumentationScope) {} - - fn force_flush(&self) -> OTelSdkResult { - Ok(()) - } - - fn event_enabled( - &self, - _level: opentelemetry::logs::Severity, - _target: &str, - _name: Option<&str>, - ) -> bool { - self.enabled - } -} - -struct NoOpLogLayer { - enabled: bool, -} - -impl Layer for NoOpLogLayer -where - S: tracing::Subscriber, -{ - fn on_event( - &self, - event: &tracing::Event<'_>, - _ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let mut visitor = NoopEventVisitor; - event.record(&mut visitor); - } - - fn event_enabled( - &self, - _event: &tracing::Event<'_>, - _ctx: tracing_subscriber::layer::Context<'_, S>, - ) -> bool { - self.enabled - } -} - -struct NoopEventVisitor; - -impl tracing::field::Visit for NoopEventVisitor { - fn record_debug(&mut self, _field: &tracing::field::Field, _value: &dyn std::fmt::Debug) {} -} - -fn benchmark_no_subscriber(c: &mut Criterion) { - c.bench_function("log_no_subscriber", |b| { - b.iter(|| { - error!( - name : "CheckoutFailed", - book_id = "12345", - book_title = "Rust Programming Adventures", - message = "Unable to process checkout." - ); - }); - }); -} - -fn benchmark_with_ot_layer(c: &mut Criterion, enabled: bool, bench_name: &str) { - let processor = NoopProcessor::new(enabled); - let provider = SdkLoggerProvider::builder() - .with_resource( - Resource::builder_empty() - .with_service_name("benchmark") - .build(), - ) - .with_log_processor(processor) - .build(); - let ot_layer = tracing_layer::OpenTelemetryTracingBridge::new(&provider); - let subscriber = Registry::default().with(ot_layer); - - tracing::subscriber::with_default(subscriber, || { - c.bench_function(bench_name, |b| { - b.iter(|| { - error!( - name : "CheckoutFailed", - book_id = "12345", - book_title = "Rust Programming Adventures", - message = "Unable to process checkout." - ); - }); - }); - }); -} - -fn benchmark_with_noop_layer(c: &mut Criterion, enabled: bool, bench_name: &str) { - let subscriber = Registry::default().with(NoOpLogLayer { enabled }); - - tracing::subscriber::with_default(subscriber, || { - c.bench_function(bench_name, |b| { - b.iter(|| { - error!( - name : "CheckoutFailed", - book_id = "12345", - book_title = "Rust Programming Adventures", - message = "Unable to process checkout." - ); - }); - }); - }); -} - -fn criterion_benchmark(c: &mut Criterion) { - benchmark_no_subscriber(c); - benchmark_with_ot_layer(c, true, "ot_layer_enabled"); - benchmark_with_ot_layer(c, false, "ot_layer_disabled"); - benchmark_with_noop_layer(c, true, "noop_layer_enabled"); - benchmark_with_noop_layer(c, false, "noop_layer_disabled"); -} - -#[cfg(all(not(target_os = "windows"), feature = "bench_profiling"))] -criterion_group! { - name = benches; - config = Criterion::default() - .warm_up_time(std::time::Duration::from_secs(1)) - .measurement_time(std::time::Duration::from_secs(2)) - .with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); - targets = criterion_benchmark -} - -#[cfg(any(target_os = "windows", not(feature = "bench_profiling")))] -criterion_group! { - name = benches; - config = Criterion::default() - .warm_up_time(std::time::Duration::from_secs(1)) - .measurement_time(std::time::Duration::from_secs(2)); - targets = criterion_benchmark -} -criterion_main!(benches); diff --git a/opentelemetry-appender-tracing/examples/basic.rs b/opentelemetry-appender-tracing/examples/basic.rs deleted file mode 100644 index b1c5427058..0000000000 --- a/opentelemetry-appender-tracing/examples/basic.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! run with `$ cargo run --example basic - -use opentelemetry_appender_tracing::layer; -use opentelemetry_sdk::{logs::SdkLoggerProvider, Resource}; -use tracing::error; -use tracing_subscriber::{prelude::*, EnvFilter}; - -fn main() { - let exporter = opentelemetry_stdout::LogExporter::default(); - let provider: SdkLoggerProvider = SdkLoggerProvider::builder() - .with_resource( - Resource::builder() - .with_service_name("log-appender-tracing-example") - .build(), - ) - .with_simple_exporter(exporter) - .build(); - - // To prevent a telemetry-induced-telemetry loop, OpenTelemetry's own internal - // logging is properly suppressed. However, logs emitted by external components - // (such as reqwest, tonic, etc.) are not suppressed as they do not propagate - // OpenTelemetry context. Until this issue is addressed - // (https://github.com/open-telemetry/opentelemetry-rust/issues/2877), - // filtering like this is the best way to suppress such logs. - // - // The filter levels are set as follows: - // - Allow `info` level and above by default. - // - Completely restrict logs from `hyper`, `tonic`, `h2`, and `reqwest`. - // - // Note: This filtering will also drop logs from these components even when - // they are used outside of the OTLP Exporter. - let filter_otel = EnvFilter::new("info") - .add_directive("hyper=off".parse().unwrap()) - .add_directive("opentelemetry=off".parse().unwrap()) - .add_directive("tonic=off".parse().unwrap()) - .add_directive("h2=off".parse().unwrap()) - .add_directive("reqwest=off".parse().unwrap()); - let otel_layer = layer::OpenTelemetryTracingBridge::new(&provider).with_filter(filter_otel); - - // Create a new tracing::Fmt layer to print the logs to stdout. It has a - // default filter of `info` level and above, and `debug` and above for logs - // from OpenTelemetry crates. The filter levels can be customized as needed. - let filter_fmt = EnvFilter::new("info").add_directive("opentelemetry=debug".parse().unwrap()); - let fmt_layer = tracing_subscriber::fmt::layer() - .with_thread_names(true) - .with_filter(filter_fmt); - - tracing_subscriber::registry() - .with(otel_layer) - .with(fmt_layer) - .init(); - - error!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "otel@opentelemetry.io", message = "This is an example message"); - let _ = provider.shutdown(); -} diff --git a/opentelemetry-appender-tracing/src/layer.rs b/opentelemetry-appender-tracing/src/layer.rs deleted file mode 100644 index 02a04ba023..0000000000 --- a/opentelemetry-appender-tracing/src/layer.rs +++ /dev/null @@ -1,961 +0,0 @@ -use opentelemetry::{ - logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}, - Key, -}; -use tracing_core::Level; -#[cfg(feature = "experimental_metadata_attributes")] -use tracing_core::Metadata; -#[cfg(feature = "experimental_metadata_attributes")] -use tracing_log::NormalizeEvent; -use tracing_subscriber::{registry::LookupSpan, Layer}; - -/// Visitor to record the fields from the event record. -struct EventVisitor<'a, LR: LogRecord> { - log_record: &'a mut LR, -} - -/// Logs from the log crate have duplicated attributes that we removed here. -#[cfg(feature = "experimental_metadata_attributes")] -fn is_duplicated_metadata(field: &'static str) -> bool { - field - .strip_prefix("log.") - .map(|remainder| matches!(remainder, "file" | "line" | "module_path" | "target")) - .unwrap_or(false) -} - -#[cfg(feature = "experimental_metadata_attributes")] -fn get_filename(filepath: &str) -> &str { - if let Some((_, filename)) = filepath.rsplit_once('/') { - return filename; - } - if let Some((_, filename)) = filepath.rsplit_once('\\') { - return filename; - } - filepath -} - -impl<'a, LR: LogRecord> EventVisitor<'a, LR> { - fn new(log_record: &'a mut LR) -> Self { - EventVisitor { log_record } - } - - #[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)); - } - } -} - -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 - // to avoid stringification, unless needed. - 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:?}"))); - } - } - - 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:?}"))); - } - } - - 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:?}"))); - } - } - - // TODO: Remaining field types from AnyValue : Bytes, ListAny, Boolean -} - -pub struct OpenTelemetryTracingBridge -where - P: LoggerProvider + Send + Sync, - L: Logger + Send + Sync, -{ - logger: L, - _phantom: std::marker::PhantomData

, // P is not used. -} - -impl OpenTelemetryTracingBridge -where - P: LoggerProvider + Send + Sync, - L: Logger + Send + Sync, -{ - 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(), - } - } -} - -impl Layer for OpenTelemetryTracingBridge -where - S: tracing::Subscriber + for<'a> LookupSpan<'a>, - P: LoggerProvider + Send + Sync + 'static, - L: Logger + Send + Sync + 'static, -{ - 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(|| { - span.scope().last().and_then(|root_span| { - root_span - .extensions() - .get::() - .and_then(|root_otd| root_otd.trace_id()) - }) - }); - if let Some(trace_id) = opt_trace_id { - // Unable to reliably obtain TraceFlags (old implementation also passed None) - log_record.set_trace_context(trace_id, span_id, None); - } - } - } - } - - //emit record - self.logger.emit(log_record); - } -} - -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, - } -} - -#[cfg(test)] -mod tests { - use crate::layer; - use opentelemetry::logs::Severity; - use opentelemetry::trace::TracerProvider; - use opentelemetry::trace::{TraceContextExt, TraceFlags, Tracer}; - use opentelemetry::InstrumentationScope; - use opentelemetry::{logs::AnyValue, Key}; - use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult}; - use opentelemetry_sdk::logs::{InMemoryLogExporter, LogProcessor}; - use opentelemetry_sdk::logs::{SdkLogRecord, SdkLoggerProvider}; - use opentelemetry_sdk::trace::{Sampler, SdkTracerProvider}; - use tracing::error; - use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; - use tracing_subscriber::Layer; - - pub fn attributes_contains(log_record: &SdkLogRecord, key: &Key, value: &AnyValue) -> bool { - log_record - .attributes_iter() - .any(|(k, v)| k == key && v == value) - } - - #[allow(impl_trait_overcaptures)] // can only be fixed with Rust 1.82+ - 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) - } - - // cargo test --features=testing - #[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()); - - // Validate attributes - #[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()) - )); - // 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"))); - } - - // Test when target, eventname are not explicitly provided - exporter.reset(); - error!( - event_id = 20, - 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 target - tracing defaults to module path - assert_eq!( - log.record.target().expect("target is expected").to_string(), - "opentelemetry_appender_tracing::layer::tests" - ); - // Validate event name - tracing defaults to event followed source & line number - // Assert is doing "contains" check to avoid tests failing when line number changes. - // and also account for the fact that the module path is different on different platforms. - // Ex.: The path will be different on a Windows and Linux machine. - assert!(log - .record - .event_name() - .expect("event_name is expected") - .contains("event opentelemetry-appender-tracing"),); - } - - #[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(); - assert!(attributes_key.contains(&Key::new("code.filepath"))); - assert!(attributes_key.contains(&Key::new("code.lineno"))); - assert!(!attributes_key.contains(&Key::new("log.target"))); - } - } - - #[cfg(feature = "experimental_use_tracing_span_context")] - #[test] - fn tracing_appender_inside_tracing_crate_context() { - 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); - 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); - } - - #[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"))); - } - } - - #[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() - .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 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 - ); - - for attribute in log.record.attributes_iter() { - println!("key: {:?}, value: {:?}", attribute.0, attribute.1); - } - - // 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"))); - } - } - - #[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(()) - } - } - - #[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"); - } -} diff --git a/opentelemetry-appender-tracing/src/lib.rs b/opentelemetry-appender-tracing/src/lib.rs deleted file mode 100644 index bee16d4b9e..0000000000 --- a/opentelemetry-appender-tracing/src/lib.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! # OpenTelemetry-Appender-Tracing -//! -//! This crate provides a bridge between the [`tracing`](https://docs.rs/tracing/latest/tracing/) crate and OpenTelemetry logs. -//! It converts `tracing` events into OpenTelemetry `LogRecords`, allowing applications using `tracing` to seamlessly integrate -//! with OpenTelemetry logging backends. -//! -//! ## Background -//! -//! Unlike traces and metrics, OpenTelemetry does not provide a dedicated logging API for end-users. Instead, it recommends using -//! existing logging libraries and bridging them to OpenTelemetry logs. This crate serves as such a bridge for `tracing` users. -//! -//! ## Features -//! -//! - Converts `tracing` events into OpenTelemetry [`LogRecords`](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#log-and-event-record-definition) -//! - Integrates as a [`Layer`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/trait.Layer.html) -//! from [`tracing-subscriber`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/), allowing -//! to be used alongside other `tracing` layers, such as `fmt` -//! - Automatically attaches OpenTelemetry trace context (`TraceId`, `SpanId`, `TraceFlags`) to logs -//! - Automatically associates OpenTelemetry Resource to logs -//! - Supports exporting logs to OpenTelemetry-compatible backends (OTLP, stdout, etc.) -//! -//! ## Getting Started -//! -//! ### 1. Install Dependencies -//! -//! Add the following dependencies to your `Cargo.toml`: -//! -//! ```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" } -//! ``` -//! -//! ### 2. Set Up the OpenTelemetry Logger Provider -//! -//! Before integrating with `tracing`, create an OpenTelemetry [`SdkLoggerProvider`](https://docs.rs/opentelemetry_sdk/latest/opentelemetry_sdk/logs/struct.SdkLoggerProvider.html): -//! -//! ```rust -//! use opentelemetry_sdk::logs::SdkLoggerProvider; -//! use opentelemetry_stdout::LogExporter; -//! -//! let exporter = LogExporter::default(); -//! let provider = SdkLoggerProvider::builder() -//! .with_simple_exporter(exporter) -//! .build(); -//! ``` -//! -//! In this example, `SdkLoggerProvider` is configured to use the `opentelemetry_stdout` crate to export logs to stdout. You can replace it with any other OpenTelemetry-compatible exporter. -//! Any additional OpenTelemetry configuration (e.g., setting up a resource, additional processors etc.) can be done at this stage. -//! -//! ### 3. Create the OpenTelemetry-Tracing Bridge -//! -//! Create `OpenTelemetryTracingBridge` layer using the `SdkLoggerProvider` created in the previous step. -//! -//! ```rust -//! # use opentelemetry_sdk::logs::SdkLoggerProvider; -//! # use opentelemetry_stdout::LogExporter; -//! # use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; -//! # let exporter = LogExporter::default(); -//! # let provider = SdkLoggerProvider::builder() -//! # .with_simple_exporter(exporter) -//! # .build(); -//! let otel_layer = OpenTelemetryTracingBridge::new(&provider); -//! ``` -//! -//! ### 4. Register the `tracing` Subscriber -//! -//! Since this crate provides a `Layer` for `tracing`, you can register it with the `tracing` subscriber as shown below. -//! -//! ```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(); -//! ``` -//! -//! ### 5. Log Events Using `tracing` -//! -//! ```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"); -//! ``` -//! -//! -//! ## Mapping details -//! -//! Since OpenTelemetry and `tracing` have their own data models, this bridge performs the following mappings: -//! -//! | `tracing` | OpenTelemetry | Notes | -//! |-----------------------|-------------------------|-----------------------------------------------------------------------------------------| -//! | name of the event | `EventName` | OpenTelemetry defines logs with name as Events, so every `tracing` Event is actually an OTel Event | -//! | target | `target` | Groups logs from the same module/crate. At recording time, `target` is stored in a top-level field. But exporters treat this information as OpenTelemetry `InstrumentationScope` | -//! | level of the event | `Severity`, `SeverityText` | | -//! | Fields | `Attributes` | Converted into OpenTelemetry log attributes. Field with "message" as key is specially treated and stored as `LogRecord::Body` | -//! | Message | `Body` | The body/message of the log. This is done only if body was not already populated from "message" field above | -//! -//! ### Data Type Mapping -//! -//! The data types supported by `tracing` and OpenTelemetry are different and the following conversions are applied: -//! -//! | `tracing` Type | OpenTelemetry `AnyValue` Type | -//! |----------------|-------------------------------| -//! | `i64` | `Int` | -//! | `f32`, `f64` | `Double` | -//! | `u64`,`u128` ,`i128` | `Int` (if convertible to `i64` without loss) else `String` | -//! | `&str` | `String` | -//! | `bool` | `Bool` | -//! | `&[u8]` | `Bytes` | -//! | `&dyn Debug` | `String` (via `Debug` formatting) | -//! | `&dyn Error` | `String` (via `Debug` formatting). This is stored into an attribute with key "exception.message", following [OTel conventions](https://opentelemetry.io/docs/specs/semconv/attributes-registry/exception/) | -//! -//! In future, additional types may be supported. -//! -//! > **Note:** This crate does not support `tracing` Spans. One may use [`tracing-opentelemetry`](https://docs.rs/tracing-opentelemetry/latest/tracing_opentelemetry/) to -//! > convert `tracing` spans into OpenTelemetry spans. This is a third-party crate -//! > that is not maintained by the OpenTelemetry project. -//! > `tracing-opentelemetry`: -//! > - Converts `tracing` spans into OpenTelemetry spans -//! > - Converts `tracing` events into OpenTelemetry `SpanEvents` rather than logs -//! > Depending on the outcome of the -//! > [discussion](https://github.com/open-telemetry/opentelemetry-rust/issues/1571), -//! > the OpenTelemetry project may provide direct support to map `tracing` -//! > spans to OpenTelemetry in the future. -//! -//! ## Feature Flags -//! `spec_unstable_logs_enabled`: TODO -//! -//! `experimental_metadata_attributes`: TODO -//! -//! `experimental_use_tracing_span_context`: TODO -//! -//! ## Limitations -//! 1. There is no support for `Valuable` crate. [2819](https://github.com/open-telemetry/opentelemetry-rust/issues/2819) -//! -//! ## Stability Guarantees -//! // TODO -//! -//! ## Further Reading -//! -//! - OpenTelemetry Rust: [opentelemetry-rust](https://github.com/open-telemetry/opentelemetry-rust) -//! - Tracing: [tracing](https://docs.rs/tracing/) -//! - OpenTelemetry Logs: [OpenTelemetry Logging Specification](https://opentelemetry.io/docs/specs/otel/logs/) -pub mod layer; diff --git a/opentelemetry-appender-tracing/tests/test_no_stack_overflow_after_shutdown.rs b/opentelemetry-appender-tracing/tests/test_no_stack_overflow_after_shutdown.rs deleted file mode 100644 index 1e7cf207b6..0000000000 --- a/opentelemetry-appender-tracing/tests/test_no_stack_overflow_after_shutdown.rs +++ /dev/null @@ -1,23 +0,0 @@ -use opentelemetry_appender_tracing::layer; -use opentelemetry_sdk::logs::SdkLoggerProvider; -use tracing::info; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - -#[test] -fn test_logging_after_shutdown_does_not_cause_telemetry_induced_telemetry() { - //! Reproduces [#3161](https://github.com/open-telemetry/opentelemetry-rust/issues/3161) - let exporter = opentelemetry_stdout::LogExporter::default(); - let provider: SdkLoggerProvider = SdkLoggerProvider::builder() - .with_batch_exporter(exporter) - .build(); - - let otel_layer = layer::OpenTelemetryTracingBridge::new(&provider); - - tracing_subscriber::registry().with(otel_layer).init(); - - provider.shutdown().unwrap(); - - // If logging causes telemetry-induced-telemetry after shutting down the provider, then a stack - // overflow may occur. - info!("Don't crash") -} diff --git a/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml b/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml index bdb6ed49dc..3dd2f21c4c 100644 --- a/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml +++ b/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml @@ -20,11 +20,10 @@ reqwest-blocking = ["opentelemetry-otlp/reqwest-blocking-client"] opentelemetry = { workspace = true, features = ["trace", "metrics", "logs"] } opentelemetry_sdk = { workspace = true, features = ["trace", "metrics", "logs"] } opentelemetry-otlp = { workspace = true, features = ["reqwest-blocking-client", "http-proto", "trace", "metrics", "logs", "internal-logs"] } -opentelemetry-appender-tracing = { workspace = true } +opentelemetry-appender-log = { workspace = true } +log = { workspace = true, features = ["kv"] } tokio = { workspace = true, features = ["full"] } -tracing = { workspace = true, features = ["std"]} -tracing-subscriber = { workspace = true, features = ["env-filter","registry", "std", "fmt"] } [lints] workspace = true diff --git a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs index 94c6b75e3e..27264bd6ac 100644 --- a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs @@ -1,9 +1,11 @@ +use log::{info, Level}; use opentelemetry::{ global, trace::{TraceContextExt, Tracer}, InstrumentationScope, KeyValue, }; -use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_appender_log::OpenTelemetryLogBridge; +// use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; use opentelemetry_otlp::WithExportConfig; use opentelemetry_otlp::{LogExporter, MetricExporter, Protocol, SpanExporter}; use opentelemetry_sdk::Resource; @@ -11,9 +13,6 @@ use opentelemetry_sdk::{ logs::SdkLoggerProvider, metrics::SdkMeterProvider, trace::SdkTracerProvider, }; use std::{error::Error, sync::OnceLock}; -use tracing::info; -use tracing_subscriber::prelude::*; -use tracing_subscriber::EnvFilter; fn get_resource() -> Resource { static RESOURCE: OnceLock = OnceLock::new(); @@ -69,46 +68,9 @@ fn init_metrics() -> SdkMeterProvider { async fn main() -> Result<(), Box> { let logger_provider = init_logs(); - // Create a new OpenTelemetryTracingBridge using the above LoggerProvider. - let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); - - // To prevent a telemetry-induced-telemetry loop, OpenTelemetry's own internal - // logging is properly suppressed. However, logs emitted by external components - // (such as reqwest, tonic, etc.) are not suppressed as they do not propagate - // OpenTelemetry context. Until this issue is addressed - // (https://github.com/open-telemetry/opentelemetry-rust/issues/2877), - // filtering like this is the best way to suppress such logs. - // - // The filter levels are set as follows: - // - Allow `info` level and above by default. - // - Completely restrict logs from `hyper`, `tonic`, `h2`, and `reqwest`. - // - // Note: This filtering will also drop logs from these components even when - // they are used outside of the OTLP Exporter. - let filter_otel = EnvFilter::new("info") - .add_directive("hyper=off".parse().unwrap()) - .add_directive("tonic=off".parse().unwrap()) - .add_directive("h2=off".parse().unwrap()) - .add_directive("reqwest=off".parse().unwrap()); - let otel_layer = otel_layer.with_filter(filter_otel); - - // Create a new tracing::Fmt layer to print the logs to stdout. It has a - // default filter of `info` level and above, and `debug` and above for logs - // from OpenTelemetry crates. The filter levels can be customized as needed. - let filter_fmt = EnvFilter::new("info").add_directive("opentelemetry=debug".parse().unwrap()); - let fmt_layer = tracing_subscriber::fmt::layer() - .with_thread_names(true) - .with_filter(filter_fmt); - - // Initialize the tracing subscriber with the OpenTelemetry layer and the - // Fmt layer. - tracing_subscriber::registry() - .with(otel_layer) - .with(fmt_layer) - .init(); - - // At this point Logs (OTel Logs and Fmt Logs) are initialized, which will - // allow internal-logs from Tracing/Metrics initializer to be captured. + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); let tracer_provider = init_traces(); // Set the global tracer provider using a clone of the tracer_provider. diff --git a/opentelemetry-otlp/examples/basic-otlp/Cargo.toml b/opentelemetry-otlp/examples/basic-otlp/Cargo.toml index 0474b8960e..dac8264c15 100644 --- a/opentelemetry-otlp/examples/basic-otlp/Cargo.toml +++ b/opentelemetry-otlp/examples/basic-otlp/Cargo.toml @@ -16,10 +16,10 @@ bench = false opentelemetry = { workspace = true, features = ["trace", "metrics", "logs"] } opentelemetry_sdk = { workspace = true, features = ["trace", "metrics", "logs"] } opentelemetry-otlp = { workspace = true, features = ["grpc-tonic", "trace", "metrics", "logs", "internal-logs"] } +opentelemetry-appender-log = { workspace = true } + +log = { workspace = true, features = ["kv"] } tokio = { workspace = true, features = ["full"] } -opentelemetry-appender-tracing = { workspace = true } -tracing = { workspace = true, features = ["std"]} -tracing-subscriber = { workspace = true, features = ["env-filter","registry", "std", "fmt"] } [lints] workspace = true diff --git a/opentelemetry-otlp/examples/basic-otlp/src/main.rs b/opentelemetry-otlp/examples/basic-otlp/src/main.rs index 1e657863e0..3b4b08c434 100644 --- a/opentelemetry-otlp/examples/basic-otlp/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp/src/main.rs @@ -1,7 +1,8 @@ +use log::{info, Level}; use opentelemetry::trace::{TraceContextExt, Tracer}; use opentelemetry::KeyValue; use opentelemetry::{global, InstrumentationScope}; -use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry_otlp::{LogExporter, MetricExporter, SpanExporter}; use opentelemetry_sdk::logs::SdkLoggerProvider; use opentelemetry_sdk::metrics::SdkMeterProvider; @@ -9,9 +10,6 @@ use opentelemetry_sdk::trace::SdkTracerProvider; use opentelemetry_sdk::Resource; use std::error::Error; use std::sync::OnceLock; -use tracing::info; -use tracing_subscriber::prelude::*; -use tracing_subscriber::EnvFilter; fn get_resource() -> Resource { static RESOURCE: OnceLock = OnceLock::new(); @@ -63,46 +61,9 @@ fn init_logs() -> SdkLoggerProvider { async fn main() -> Result<(), Box> { let logger_provider = init_logs(); - // Create a new OpenTelemetryTracingBridge using the above LoggerProvider. - let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider); - - // To prevent a telemetry-induced-telemetry loop, OpenTelemetry's own internal - // logging is properly suppressed. However, logs emitted by external components - // (such as reqwest, tonic, etc.) are not suppressed as they do not propagate - // OpenTelemetry context. Until this issue is addressed - // (https://github.com/open-telemetry/opentelemetry-rust/issues/2877), - // filtering like this is the best way to suppress such logs. - // - // The filter levels are set as follows: - // - Allow `info` level and above by default. - // - Completely restrict logs from `hyper`, `tonic`, `h2`, and `reqwest`. - // - // Note: This filtering will also drop logs from these components even when - // they are used outside of the OTLP Exporter. - let filter_otel = EnvFilter::new("info") - .add_directive("hyper=off".parse().unwrap()) - .add_directive("tonic=off".parse().unwrap()) - .add_directive("h2=off".parse().unwrap()) - .add_directive("reqwest=off".parse().unwrap()); - let otel_layer = otel_layer.with_filter(filter_otel); - - // Create a new tracing::Fmt layer to print the logs to stdout. It has a - // default filter of `info` level and above, and `debug` and above for logs - // from OpenTelemetry crates. The filter levels can be customized as needed. - let filter_fmt = EnvFilter::new("info").add_directive("opentelemetry=debug".parse().unwrap()); - let fmt_layer = tracing_subscriber::fmt::layer() - .with_thread_names(true) - .with_filter(filter_fmt); - - // Initialize the tracing subscriber with the OpenTelemetry layer and the - // Fmt layer. - tracing_subscriber::registry() - .with(otel_layer) - .with(fmt_layer) - .init(); - - // At this point Logs (OTel Logs and Fmt Logs) are initialized, which will - // allow internal-logs from Tracing/Metrics initializer to be captured. + let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); let tracer_provider = init_traces(); // Set the global tracer provider using a clone of the tracer_provider. @@ -148,7 +109,7 @@ async fn main() -> Result<(), Box> { ); span.set_attribute(KeyValue::new("another.key", "yes")); - info!(name: "my-event-inside-span", target: "my-target", "hello from {}. My price is {}. I am also inside a Span!", "banana", 2.99); + info!(target: "my-target", name = "my-event-inside-span"; "hello from {}. My price is {}. I am also inside a Span!", "banana", 2.99); tracer.in_span("Sub operation...", |cx| { let span = cx.span(); @@ -157,7 +118,7 @@ async fn main() -> Result<(), Box> { }); }); - info!(name: "my-event", target: "my-target", "hello from {}. My price is {}", "apple", 1.99); + info!(target: "my-target", name = "my-event"; "hello from {}. My price is {}", "apple", 1.99); // Collect all shutdown errors let mut shutdown_errors = Vec::new(); diff --git a/opentelemetry-otlp/tests/integration_test/Cargo.toml b/opentelemetry-otlp/tests/integration_test/Cargo.toml index 6b156d18fc..20cdac1c4a 100644 --- a/opentelemetry-otlp/tests/integration_test/Cargo.toml +++ b/opentelemetry-otlp/tests/integration_test/Cargo.toml @@ -21,7 +21,6 @@ tracing-subscriber = { workspace = true, features = ["env-filter","registry", "s tracing = {workspace = true} [target.'cfg(unix)'.dependencies] -opentelemetry-appender-tracing = { path = "../../../opentelemetry-appender-tracing", default-features = false} opentelemetry-otlp = { path = "../../../opentelemetry-otlp", default-features = false } opentelemetry-semantic-conventions = { path = "../../../opentelemetry-semantic-conventions" } @@ -31,7 +30,7 @@ reqwest-client = ["opentelemetry-otlp/reqwest-client", "opentelemetry-otlp/http- reqwest-blocking-client = ["opentelemetry-otlp/reqwest-blocking-client", "opentelemetry-otlp/http-proto", "opentelemetry-otlp/trace","opentelemetry-otlp/logs", "opentelemetry-otlp/metrics", "internal-logs"] tonic-client = ["opentelemetry-otlp/grpc-tonic", "opentelemetry-otlp/trace", "opentelemetry-otlp/logs", "opentelemetry-otlp/metrics", "internal-logs"] internal-logs = ["opentelemetry-otlp/internal-logs"] -experimental_metadata_attributes = ["opentelemetry-appender-tracing/experimental_metadata_attributes"] +experimental_metadata_attributes = [] # Keep tonic as the default client default = ["tonic-client", "internal-logs"] diff --git a/opentelemetry-otlp/tests/integration_test/tests/logs.rs b/opentelemetry-otlp/tests/integration_test/tests/logs.rs deleted file mode 100644 index ccccefdb7b..0000000000 --- a/opentelemetry-otlp/tests/integration_test/tests/logs.rs +++ /dev/null @@ -1,379 +0,0 @@ -#![cfg(unix)] - -use anyhow::Result; -use ctor::dtor; -use integration_test_runner::test_utils; -use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; -use opentelemetry_otlp::LogExporter; -use opentelemetry_sdk::logs::SdkLoggerProvider; -use opentelemetry_sdk::{logs as sdklogs, Resource}; -use std::fs::File; -use std::io::Read; -use std::time::Duration; -use tracing::info; -use tracing_subscriber::layer::SubscriberExt; -use uuid::Uuid; - -fn init_logs(is_simple: bool) -> Result { - let exporter_builder = LogExporter::builder(); - #[cfg(feature = "tonic-client")] - let exporter_builder = exporter_builder.with_tonic(); - #[cfg(not(feature = "tonic-client"))] - #[cfg(any( - feature = "hyper-client", - feature = "reqwest-client", - feature = "reqwest-blocking-client" - ))] - let exporter_builder = exporter_builder.with_http(); - - let exporter = exporter_builder.build()?; - - let mut logger_provider_builder = SdkLoggerProvider::builder(); - if is_simple { - logger_provider_builder = logger_provider_builder.with_simple_exporter(exporter) - } else { - logger_provider_builder = logger_provider_builder.with_batch_exporter(exporter) - }; - - let logger_provider = logger_provider_builder - .with_resource( - Resource::builder_empty() - .with_service_name("logs-integration-test") - .build(), - ) - .build(); - - Ok(logger_provider) -} - -async fn logs_tokio_helper( - is_simple: bool, - log_send_outside_rt: bool, - current_thread: bool, -) -> Result<()> { - use crate::{assert_logs_results_contains, init_logs}; - test_utils::start_collector_container().await?; - - let logger_provider = init_logs(is_simple).unwrap(); - let layer = OpenTelemetryTracingBridge::new(&logger_provider); - // generate a random uuid and store it to expected guid - let expected_uuid = std::sync::Arc::new(Uuid::new_v4().to_string()); - { - let clone_uuid = expected_uuid.clone(); - if log_send_outside_rt { - std::thread::spawn(move || { - let subscriber = tracing_subscriber::registry().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - info!( - target: "my-target", - uuid = clone_uuid.as_str(), - "hello from {}. My price is {}.", - "banana", - 2.99 - ); - }) - .join() - .unwrap(); - } else { - let subscriber = tracing_subscriber::registry().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - info!(target: "my-target", uuid = expected_uuid.as_str(), "hello from {}. My price is {}.", "banana", 2.99); - } - } - if current_thread { - let _res = tokio::runtime::Handle::current() - .spawn_blocking(move || logger_provider.shutdown()) - .await - .unwrap(); - } else { - assert!(logger_provider.shutdown().is_ok()); - } - tokio::time::sleep(Duration::from_secs(5)).await; - assert_logs_results_contains(test_utils::LOGS_FILE, expected_uuid.as_str())?; - Ok(()) -} - -fn logs_non_tokio_helper(is_simple: bool, init_logs_inside_rt: bool) -> Result<()> { - let rt = tokio::runtime::Runtime::new()?; - let logger_provider = if init_logs_inside_rt { - // Initialize the logger provider inside the Tokio runtime - rt.block_on(async { - // Setup the collector container inside Tokio runtime - test_utils::start_collector_container().await?; - init_logs(is_simple) - })? - } else { - // Initialize the logger provider outside the Tokio runtime - rt.block_on(async { - let _ = test_utils::start_collector_container().await; - }); - init_logs(is_simple)? - }; - - let layer = OpenTelemetryTracingBridge::new(&logger_provider); - let subscriber = tracing_subscriber::registry().with(layer); - - // Generate a random UUID and store it to expected guid - let expected_uuid = Uuid::new_v4().to_string(); - { - let _guard = tracing::subscriber::set_default(subscriber); - info!( - target: "my-target", - uuid = expected_uuid, - "hello from {}. My price is {}.", - "banana", - 2.99 - ); - } - - assert!(logger_provider.shutdown().is_ok()); - std::thread::sleep(Duration::from_secs(5)); - assert_logs_results_contains(test_utils::LOGS_FILE, expected_uuid.as_str())?; - Ok(()) -} - -fn assert_logs_results_contains(result: &str, expected_content: &str) -> Result<()> { - let file = File::open(result)?; - let mut contents = String::new(); - let mut reader = std::io::BufReader::new(&file); - reader.read_to_string(&mut contents)?; - assert!(contents.contains(expected_content)); - Ok(()) -} - -#[cfg(test)] -mod logtests { - // The tests in this mod works like below: Emit a log with a UUID, - // then read the logs from the file and check if the UUID is present in the - // logs. This makes it easy to validate with a single collector and its - // output. This is a very simple test but good enough to validate that OTLP - // Exporter did work! - - use super::*; - use integration_test_runner::logs_asserter::{read_logs_from_json, LogsAsserter}; - use std::fs::File; - - #[test] - #[should_panic(expected = "assertion `left == right` failed: body does not match")] - pub fn test_assert_logs_eq_failure() { - let left = read_logs_from_json( - File::open("./expected/logs.json").expect("failed to open expected file"), - ) - .expect("Failed to read logs from expected file"); - - let right = read_logs_from_json( - File::open("./expected/failed_logs.json") - .expect("failed to open expected failed log file"), - ) - .expect("Failed to read logs from expected failed log file"); - LogsAsserter::new(right, left).assert(); - } - - #[test] - pub fn test_assert_logs_eq() -> Result<()> { - let logs = read_logs_from_json(File::open("./expected/logs.json")?)?; - LogsAsserter::new(logs.clone(), logs).assert(); - - Ok(()) - } - - // Batch Processor - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - Tonic, Reqwest-blocking - // Worker threads - 4 - #[tokio::test(flavor = "multi_thread", worker_threads = 4)] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub async fn logs_batch_tokio_multi_thread() -> Result<()> { - logs_tokio_helper(false, false, false).await - } - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - Tonic, Reqwest-blocking - // Worker threads - 1 - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub async fn logs_batch_tokio_multi_with_one_worker() -> Result<()> { - logs_tokio_helper(false, false, false).await - } - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - Tonic, Reqwest-blocking - // current thread - #[tokio::test(flavor = "current_thread")] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub async fn logs_batch_tokio_current() -> Result<()> { - logs_tokio_helper(false, false, true).await - } - - // logger initialization - Inside RT - // Log emission - Outside RT - // Client - Tonic, Reqwest-blocking - // Worker threads - 4 - #[tokio::test(flavor = "multi_thread", worker_threads = 4)] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub async fn logs_batch_tokio_log_outside_rt_multi_thread() -> Result<()> { - logs_tokio_helper(false, true, false).await - } - - // logger initialization - Inside RT - // log emission - Outside RT - // Client - Tonic, Reqwest-blocking - // Worker threads - 1 - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub async fn logs_batch_tokio_log_outside_rt_multi_with_one_worker() -> Result<()> { - logs_tokio_helper(false, true, false).await - } - - // logger initialization - Inside RT - // log emission - Outside RT - // Client - Tonic, Reqwest-blocking - // current thread - #[tokio::test(flavor = "current_thread")] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub async fn logs_batch_tokio_log_outside_rt_current_thread() -> Result<()> { - logs_tokio_helper(false, true, true).await - } - - // logger initialization - Inside RT - // Log emission - Inside RT - // Client - Tonic, Reqwest-blocking - // current thread - #[test] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub fn logs_batch_non_tokio_main_init_logs_inside_rt() -> Result<()> { - logs_non_tokio_helper(false, true) - } - - // logger initialization - Outside RT - // log emission - Outside RT - // Client - Tonic, Reqwest-blocking - // current thread - #[test] - #[cfg(feature = "reqwest-blocking-client")] - #[cfg(not(feature = "tonic-client"))] - pub fn logs_batch_non_tokio_main_with_init_logs_outside_rt() -> Result<()> { - logs_non_tokio_helper(false, false) - } - - // logger initialization - Inside RT - // log emission - Outside RT - // Client - Tonic, Reqwest-blocking - // current thread - #[test] - #[cfg(feature = "reqwest-blocking-client")] - pub fn logs_batch_non_tokio_main_with_init_logs_inside_rt() -> Result<()> { - logs_non_tokio_helper(false, true) - } - - // **Simple Processor** - - // logger initialization - Inside RT - // log emission - Outside RT - // Client - Tonic, Reqwest-blocking - #[test] - #[cfg(any(feature = "tonic-client", feature = "reqwest-blocking-client"))] - pub fn logs_simple_non_tokio_main_with_init_logs_inside_rt_blocking() -> Result<()> { - logs_non_tokio_helper(true, true) - } - - // logger initialization - Inside RT - // log emission - Outside RT - // Client - reqwest, hyper - #[ignore] // request and hyper client does not work without tokio runtime - #[test] - #[cfg(any(feature = "reqwest-client", feature = "hyper-client"))] - pub fn logs_simple_non_tokio_main_with_init_logs_inside_rt() -> Result<()> { - logs_non_tokio_helper(true, true) - } - - // logger initialization - Outside RT - // log emission - Outside RT - // Client - Reqwest-blocking - #[test] - #[cfg(feature = "reqwest-blocking-client")] - #[cfg(not(feature = "tonic-client"))] - pub fn logs_simple_non_tokio_main_with_init_logs_outside_rt_blocking() -> Result<()> { - logs_non_tokio_helper(true, false) - } - - // logger initialization - Outside RT - // log emission - Outside RT - // Client - hyper, tonic, reqwest - #[ignore] // request, tonic and hyper client does not work without tokio runtime - #[test] - #[cfg(any( - feature = "hyper-client", - feature = "tonic-client", - feature = "reqwest-client" - ))] - pub fn logs_simple_non_tokio_main_with_init_logs_outside_rt() -> Result<()> { - logs_non_tokio_helper(true, false) - } - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - reqwest-blocking - // Worker threads - 4 - #[ignore] // request-blocking client does not work with tokio - #[tokio::test(flavor = "multi_thread", worker_threads = 4)] - #[cfg(feature = "reqwest-blocking-client")] - pub async fn logs_simple_tokio_multi_thread_blocking() -> Result<()> { - logs_tokio_helper(true, false, false).await - } - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - Tonic, Reqwest, hyper - // Worker threads - 4 - #[tokio::test(flavor = "multi_thread", worker_threads = 4)] - #[cfg(any( - feature = "tonic-client", - feature = "reqwest-client", - feature = "hyper-client" - ))] - pub async fn logs_simple_tokio_multi_thread() -> Result<()> { - logs_tokio_helper(true, false, false).await - } - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - Tonic, Reqwest, hyper - // Worker threads - 1 - #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - #[cfg(any( - feature = "tonic-client", - feature = "reqwest-client", - feature = "hyper-client" - ))] - pub async fn logs_simple_tokio_multi_with_one_worker() -> Result<()> { - logs_tokio_helper(true, false, false).await - } - - // logger initialization - Inside RT - // log emission - Inside RT - // Client - Tonic, Reqwest, hyper - // Current thread - #[ignore] // https://github.com/open-telemetry/opentelemetry-rust/issues/2539 - #[tokio::test(flavor = "current_thread")] - #[cfg(any( - feature = "tonic-client", - feature = "reqwest-client", - feature = "hyper-client" - ))] - pub async fn logs_simple_tokio_current() -> Result<()> { - logs_tokio_helper(true, false, false).await - } -} -/// -/// Make sure we stop the collector container, otherwise it will sit around hogging our -/// ports and subsequent test runs will fail. -/// -#[dtor] -fn shutdown() { - test_utils::stop_collector_container(); -} diff --git a/opentelemetry-otlp/tests/integration_test/tests/logs_serialize_deserialize.rs b/opentelemetry-otlp/tests/integration_test/tests/logs_serialize_deserialize.rs deleted file mode 100644 index c5fc13dc36..0000000000 --- a/opentelemetry-otlp/tests/integration_test/tests/logs_serialize_deserialize.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![cfg(unix)] - -use anyhow::Result; -use ctor::dtor; -use integration_test_runner::logs_asserter::{read_logs_from_json, LogsAsserter}; -use integration_test_runner::test_utils; -use opentelemetry_appender_tracing::layer; -use opentelemetry_otlp::LogExporter; -use opentelemetry_sdk::logs::SdkLoggerProvider; -use opentelemetry_sdk::Resource; -use std::fs::File; -use std::os::unix::fs::MetadataExt; -use tracing::info; -use tracing_subscriber::layer::SubscriberExt; - -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[cfg(feature = "tonic-client")] -pub async fn test_logs() -> Result<()> { - test_utils::start_collector_container().await?; - test_utils::cleanup_file("./actual/logs.json"); // Ensure logs.json is empty before the test - let exporter_builder = LogExporter::builder().with_tonic(); - let exporter = exporter_builder.build()?; - let mut logger_provider_builder = SdkLoggerProvider::builder(); - logger_provider_builder = logger_provider_builder.with_batch_exporter(exporter); - let logger_provider = logger_provider_builder - .with_resource( - Resource::builder_empty() - .with_service_name("logs-integration-test") - .build(), - ) - .build(); - let layer = layer::OpenTelemetryTracingBridge::new(&logger_provider); - let subscriber = tracing_subscriber::registry().with(layer); - - { - let _guard = tracing::subscriber::set_default(subscriber); - info!(target: "my-target", "hello from {}. My price is {}.", "banana", 2.99); - } - - logger_provider.shutdown().unwrap(); - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - assert_logs_results(test_utils::LOGS_FILE, "expected/logs.json")?; - Ok(()) -} - -fn assert_logs_results(result: &str, expected: &str) -> Result<()> { - let left = read_logs_from_json(File::open(expected)?)?; - let right = read_logs_from_json(File::open(result)?)?; - - LogsAsserter::new(left, right).assert(); - - assert!(File::open(result).unwrap().metadata().unwrap().size() > 0); - Ok(()) -} - -/// -/// Make sure we stop the collector container, otherwise it will sit around hogging our -/// ports and subsequent test runs will fail. -/// -#[dtor] -fn shutdown() { - println!("metrics::shutdown"); - test_utils::stop_collector_container(); -} diff --git a/opentelemetry-stdout/Cargo.toml b/opentelemetry-stdout/Cargo.toml index 3e6f778968..b5239b7a47 100644 --- a/opentelemetry-stdout/Cargo.toml +++ b/opentelemetry-stdout/Cargo.toml @@ -24,15 +24,16 @@ rustdoc-args = ["--cfg", "docsrs"] default = ["trace", "metrics", "logs"] trace = ["opentelemetry/trace", "opentelemetry_sdk/trace"] metrics = ["opentelemetry/metrics", "opentelemetry_sdk/metrics"] -logs = ["opentelemetry/logs", "opentelemetry_sdk/logs", "opentelemetry_sdk/spec_unstable_logs_enabled"] +logs = ["opentelemetry/logs", "opentelemetry_sdk/logs", "opentelemetry_sdk/spec_unstable_logs_enabled", "opentelemetry-appender-log", "log"] [dependencies] chrono = { workspace = true, features = ["now"] } opentelemetry = { workspace = true } opentelemetry_sdk = { workspace = true } +opentelemetry-appender-log = { workspace = true, optional = true } +log = { workspace = true, optional = true } [dev-dependencies] -opentelemetry-appender-tracing = { workspace = true } tracing = { workspace = true, features = ["std"]} tracing-subscriber = { workspace = true, features = ["registry", "std"] } tokio = { workspace = true, features = ["full"] } diff --git a/opentelemetry-stdout/examples/basic.rs b/opentelemetry-stdout/examples/basic.rs index 55ed9bcff3..ecde9552cf 100644 --- a/opentelemetry-stdout/examples/basic.rs +++ b/opentelemetry-stdout/examples/basic.rs @@ -43,17 +43,19 @@ fn init_metrics() -> opentelemetry_sdk::metrics::SdkMeterProvider { #[cfg(feature = "logs")] fn init_logs() -> opentelemetry_sdk::logs::SdkLoggerProvider { - use opentelemetry_appender_tracing::layer; + use log::{self, Level}; + use opentelemetry_appender_log::OpenTelemetryLogBridge; use opentelemetry_sdk::logs::SdkLoggerProvider; - use tracing_subscriber::prelude::*; let exporter = opentelemetry_stdout::LogExporter::default(); let provider: SdkLoggerProvider = SdkLoggerProvider::builder() .with_simple_exporter(exporter) .with_resource(RESOURCE.clone()) .build(); - let layer = layer::OpenTelemetryTracingBridge::new(&provider); - tracing_subscriber::registry().with(layer).init(); + // Setup Log Appender for the log crate. + let otel_log_appender = OpenTelemetryLogBridge::new(&provider); + log::set_boxed_logger(Box::new(otel_log_appender)).unwrap(); + log::set_max_level(Level::Info.to_level_filter()); provider } diff --git a/scripts/integration_tests.sh b/scripts/integration_tests.sh index 377ec7ba62..51aa2b82b9 100755 --- a/scripts/integration_tests.sh +++ b/scripts/integration_tests.sh @@ -28,14 +28,6 @@ if [ -d "$TEST_DIR" ]; then echo #### echo cargo test --no-default-features --features "reqwest-blocking-client" - - # Run tests with the hyper-client feature - echo - echo #### - echo "Integration Tests: Hyper Client (Disabled now)" - echo #### - echo - cargo test --no-default-features --features "hyper-client","internal-logs" --test logs else echo "Directory $TEST_DIR does not exist. Skipping tests." exit 1 diff --git a/stress/Cargo.toml b/stress/Cargo.toml index 9063b75d79..ee0e9049f2 100644 --- a/stress/Cargo.toml +++ b/stress/Cargo.toml @@ -31,12 +31,6 @@ path = "src/metrics_overflow.rs" doc = false bench = false -[[bin]] # Bin to run the logs stress tests -name = "logs" -path = "src/logs.rs" -doc = false -bench = false - [[bin]] # Bin to run the traces stress tests name = "traces" path = "src/traces.rs" @@ -55,7 +49,6 @@ lazy_static = { workspace = true } num_cpus = { workspace = true } opentelemetry = { path = "../opentelemetry", features = ["metrics", "logs", "trace", "spec_unstable_logs_enabled"] } opentelemetry_sdk = { path = "../opentelemetry-sdk", features = ["metrics", "logs", "trace", "spec_unstable_logs_enabled", "experimental_logs_concurrent_log_processor", "experimental_metrics_custom_reader"] } -opentelemetry-appender-tracing = { workspace = true, features = ["spec_unstable_logs_enabled"] } rand = { workspace = true, features = ["small_rng", "os_rng"] } tracing = { workspace = true, features = ["std"]} tracing-subscriber = { workspace = true, features = ["registry", "std"] } diff --git a/stress/src/logs.rs b/stress/src/logs.rs deleted file mode 100644 index 88ec1ee5af..0000000000 --- a/stress/src/logs.rs +++ /dev/null @@ -1,100 +0,0 @@ -/* - Stress test results: - OS: Ubuntu 22.04.4 LTS (5.15.153.1-microsoft-standard-WSL2) - Hardware: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz, 16vCPUs, - RAM: 64.0 GB - ~31 M/sec - - Hardware: AMD EPYC 7763 64-Core Processor - 2.44 GHz, 16vCPUs, - ~44 M /sec - - Hardware: Apple M4 Pro - Total Number of Cores: 14 (10 performance and 4 efficiency) - ~50 M/sec - ~1.1 B/sec (when disabled) - - With existing SimpleLogProcessor: - 3 M/sec (when enabled) (.with_log_processor(SimpleLogProcessor::new(NoopExporter::new(true)))) - 26 M/sec (when disabled) (.with_log_processor(SimpleLogProcessor::new(NoopExporter::new(false))) -*/ -use opentelemetry::Key; -use opentelemetry_appender_tracing::layer; -use opentelemetry_sdk::error::OTelSdkResult; -use opentelemetry_sdk::logs::concurrent_log_processor::SimpleConcurrentLogProcessor; -use opentelemetry_sdk::logs::SdkLoggerProvider; -use opentelemetry_sdk::logs::{LogBatch, LogExporter}; -use opentelemetry_sdk::Resource; -use std::time; -use tracing::error; -use tracing_subscriber::prelude::*; - -mod throughput; - -#[derive(Debug)] -struct NoopExporter { - enabled: bool, - service_name: Option, -} - -impl NoopExporter { - fn new(enabled: bool) -> Self { - Self { - enabled, - service_name: None, - } - } -} - -impl LogExporter for NoopExporter { - async fn export(&self, _: LogBatch<'_>) -> OTelSdkResult { - if let Some(_service_name) = &self.service_name { - // do something with the service name - } - Ok(()) - } - - fn shutdown_with_timeout(&self, _timeout: time::Duration) -> OTelSdkResult { - Ok(()) - } - - fn event_enabled( - &self, - _level: opentelemetry::logs::Severity, - _target: &str, - _name: Option<&str>, - ) -> bool { - self.enabled - } - - fn set_resource(&mut self, res: &Resource) { - self.service_name = res - .get(&Key::from_static_str("service.name")) - .map(|v| v.to_string()); - } -} - -fn main() { - // change this to false to test the throughput when enabled is false. - let enabled = true; - - // LoggerProvider with a no-op processor. - let provider: SdkLoggerProvider = SdkLoggerProvider::builder() - .with_log_processor(SimpleConcurrentLogProcessor::new(NoopExporter::new( - enabled, - ))) - .build(); - - // Use the OpenTelemetryTracingBridge to test the throughput of the appender-tracing. - let layer = layer::OpenTelemetryTracingBridge::new(&provider); - tracing_subscriber::registry().with(layer).init(); - throughput::test_throughput(test_log); -} - -fn test_log() { - error!( - name : "CheckoutFailed", - book_id = "12345", - book_title = "Rust Programming Adventures", - message = "Unable to process checkout." - ); -}