diff --git a/deny.toml b/deny.toml index aa68cf6..e22358b 100644 --- a/deny.toml +++ b/deny.toml @@ -227,6 +227,7 @@ skip = [ "r-efi", # "socket2", # "wasi", + "wit-bindgen", ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive diff --git a/examples/logging/screenshot_grafana.png b/examples/logging/screenshot_grafana.png new file mode 100644 index 0000000..1e4f58d Binary files /dev/null and b/examples/logging/screenshot_grafana.png differ diff --git a/init-tracing-opentelemetry/README.md b/init-tracing-opentelemetry/README.md index 77542a3..400de13 100644 --- a/init-tracing-opentelemetry/README.md +++ b/init-tracing-opentelemetry/README.md @@ -281,10 +281,16 @@ async fn main() -> Result<(), Box> { Log export can be toggled at runtime via `.with_logs(bool)`: ```rust,no_run +use init_tracing_opentelemetry::TracingConfig; +//... TracingConfig::default() .with_logs(false) // disable log export (default: enabled when feature is active) - .init_subscriber()?; + .init_subscriber() + .expect("valid tracing configuration"); ``` +> Traces are automatically attached to logs as well, so if the logs are queried in Grafana (for example), the trace automatically links to the log line. + +![screenshot of grafana logs](https://raw.githubusercontent.com/davidB/tracing-opentelemetry-instrumentation-sdk/refs/heads/main/examples/logging/screenshot_grafana.png) Configure the following environment variables to control the logs exporter (in addition to the shared variables above): diff --git a/init-tracing-opentelemetry/src/config.rs b/init-tracing-opentelemetry/src/config.rs index c4089dc..bca9c28 100644 --- a/init-tracing-opentelemetry/src/config.rs +++ b/init-tracing-opentelemetry/src/config.rs @@ -648,7 +648,7 @@ impl TracingConfig { #[cfg(feature = "logs")] let subscriber = subscriber.with(self.otel_config.logs_enabled.then_some(logs_layer)); #[cfg(feature = "metrics")] - let subscriber = subscriber.with(metrics_layer); + let subscriber = subscriber.with(self.otel_config.metrics_enabled.then_some(metrics_layer)); Ok(( subscriber, OtelGuard { diff --git a/init-tracing-opentelemetry/src/formats.rs b/init-tracing-opentelemetry/src/formats.rs index b691ea0..b2cd78c 100644 --- a/init-tracing-opentelemetry/src/formats.rs +++ b/init-tracing-opentelemetry/src/formats.rs @@ -179,17 +179,15 @@ impl LayerBuilder for LogfmtLayerBuilder { // Note: tracing_logfmt doesn't support the same configuration options // as the standard fmt layer, so we have limited configuration ability - match &config.writer { - WriterConfig::Stderr => { - // For stderr, we need to use the builder pattern since layer() doesn't support with_writer - // However, the current tracing_logfmt version may not support this - // For now, we'll fall back to the basic layer - Ok(Box::new(tracing_logfmt::layer())) - } - _ => { - // Default behavior uses stdout - Ok(Box::new(tracing_logfmt::layer())) - } + if let WriterConfig::Stdout = &config.writer { + // Default behavior uses stdout + Ok(Box::new(tracing_logfmt::layer())) + } else { + // For stderr, we need to use the builder pattern since layer() doesn't support with_writer + // However, the current tracing_logfmt version may not support this + // For now, we'll fall back to the basic layer + tracing::warn!("logfmt only support stdout"); + Ok(Box::new(tracing_logfmt::layer())) } } } diff --git a/init-tracing-opentelemetry/src/otlp/logs.rs b/init-tracing-opentelemetry/src/otlp/logs.rs index 9c25a7b..e59ea24 100644 --- a/init-tracing-opentelemetry/src/otlp/logs.rs +++ b/init-tracing-opentelemetry/src/otlp/logs.rs @@ -1,4 +1,4 @@ -use super::infer_protocol; +use super::infer_protocol_from_env; use opentelemetry_otlp::{ExporterBuildError, LogExporter}; use opentelemetry_sdk::{Resource, logs::LoggerProviderBuilder, logs::SdkLoggerProvider}; #[cfg(feature = "tls")] @@ -16,9 +16,13 @@ pub fn init_loggerprovider( where F: FnOnce(LoggerProviderBuilder) -> LoggerProviderBuilder, { - let (maybe_protocol, maybe_endpoint) = read_protocol_and_endpoint_from_env(); - let protocol = infer_protocol(maybe_protocol.as_deref(), maybe_endpoint.as_deref()); + let protocol = infer_protocol_from_env( + "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + "v1/logs", + ); + // builders used the environment variables to determine the endpoint (but not to setup the protocol) let exporter: Option = match protocol.as_deref() { Some("http/protobuf") => Some(LogExporter::builder().with_http().build()?), #[cfg(feature = "tls")] @@ -50,20 +54,3 @@ where logger_provider = transform(logger_provider); Ok(logger_provider.build()) } - -fn read_protocol_and_endpoint_from_env() -> (Option, Option) { - let maybe_protocol = std::env::var("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL") - .or_else(|_| std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")) - .ok(); - let maybe_endpoint = std::env::var("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT") - .or_else(|_| { - std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").map(|endpoint| match &maybe_protocol { - Some(protocol) if protocol.contains("http") => { - format!("{endpoint}/v1/logs") - } - _ => endpoint, - }) - }) - .ok(); - (maybe_protocol, maybe_endpoint) -} diff --git a/init-tracing-opentelemetry/src/otlp/metrics.rs b/init-tracing-opentelemetry/src/otlp/metrics.rs index cc7ca2d..bebc0cb 100644 --- a/init-tracing-opentelemetry/src/otlp/metrics.rs +++ b/init-tracing-opentelemetry/src/otlp/metrics.rs @@ -1,4 +1,4 @@ -use super::infer_protocol; +use super::infer_protocol_from_env; use opentelemetry_otlp::{ExporterBuildError, MetricExporter, WithExportConfig}; use opentelemetry_sdk::Resource; use opentelemetry_sdk::metrics::{ @@ -21,8 +21,11 @@ pub fn init_meterprovider( where F: FnOnce(MeterProviderBuilder) -> MeterProviderBuilder, { - let (maybe_protocol, maybe_endpoint) = read_protocol_and_endpoint_from_env(); - let protocol = infer_protocol(maybe_protocol.as_deref(), maybe_endpoint.as_deref()); + let protocol = infer_protocol_from_env( + "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "v1/metrics", + ); let timeout = env::var("OTEL_EXPORTER_OTLP_METRICS_TIMEOUT") .ok() .and_then(|var| var.parse::().ok()) @@ -43,6 +46,7 @@ where .and_then(|var| var.parse::().ok()) .map_or(Duration::from_secs(60), Duration::from_millis); + // builders used the environment variables to determine the endpoint (but not to setup the protocol) let exporter = match protocol.as_deref() { Some("http/protobuf") => Some( MetricExporter::builder() @@ -90,20 +94,3 @@ where meter_provider = transform(meter_provider); Ok(meter_provider.build()) } - -fn read_protocol_and_endpoint_from_env() -> (Option, Option) { - let maybe_protocol = std::env::var("OTEL_EXPORTER_OTLP_METRICS_PROTOCOL") - .or_else(|_| std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")) - .ok(); - let maybe_endpoint = std::env::var("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") - .or_else(|_| { - std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").map(|endpoint| match &maybe_protocol { - Some(protocol) if protocol.contains("http") => { - format!("{endpoint}/v1/metrics") - } - _ => endpoint, - }) - }) - .ok(); - (maybe_protocol, maybe_endpoint) -} diff --git a/init-tracing-opentelemetry/src/otlp/mod.rs b/init-tracing-opentelemetry/src/otlp/mod.rs index 7b8ac7e..08ee47e 100644 --- a/init-tracing-opentelemetry/src/otlp/mod.rs +++ b/init-tracing-opentelemetry/src/otlp/mod.rs @@ -66,10 +66,18 @@ impl Drop for OtelGuard { } #[allow(unused_mut)] -pub(crate) fn infer_protocol( - maybe_protocol: Option<&str>, - maybe_endpoint: Option<&str>, +pub(crate) fn infer_protocol_from_env( + protocol_key: &str, + endpoint_key: &str, + endpoint_path: &str, ) -> Option { + let (maybe_protocol, maybe_endpoint) = + read_protocol_and_endpoint_from_env(protocol_key, endpoint_key, endpoint_path); + infer_protocol(maybe_protocol.as_deref(), maybe_endpoint.as_deref()) +} + +#[allow(unused_mut)] +fn infer_protocol(maybe_protocol: Option<&str>, maybe_endpoint: Option<&str>) -> Option { let mut maybe_protocol = match (maybe_protocol, maybe_endpoint) { (Some(protocol), _) => Some(protocol.to_string()), (None, Some(endpoint)) => { @@ -91,6 +99,44 @@ pub(crate) fn infer_protocol( maybe_protocol } +pub fn debug_env() { + const SENSITIVE_KEYS: &[&str] = &[ + "OTEL_EXPORTER_OTLP_HEADERS", + "OTEL_EXPORTER_OTLP_CERTIFICATE", + ]; + std::env::vars() + .filter(|(k, _)| k.starts_with("OTEL_")) + .for_each(|(k, v)| { + let display_value = if SENSITIVE_KEYS.iter().any(|s| k == *s) { + "[redacted]" + } else { + &v + }; + tracing::debug!(target: "otel::setup::env", key = %k, value = %display_value); + }); +} + +fn read_protocol_and_endpoint_from_env( + protocol_key: &str, + endpoint_key: &str, + endpoint_path: &str, +) -> (Option, Option) { + let maybe_protocol = std::env::var(protocol_key) + .or_else(|_| std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")) + .ok(); + let maybe_endpoint = std::env::var(endpoint_key) + .or_else(|_| { + std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").map(|endpoint| match &maybe_protocol { + Some(protocol) if protocol.contains("http") => { + format!("{endpoint}/{endpoint_path}") + } + _ => endpoint, + }) + }) + .ok(); + (maybe_protocol, maybe_endpoint) +} + #[cfg(test)] mod tests { use assert2::assert; diff --git a/init-tracing-opentelemetry/src/otlp/traces.rs b/init-tracing-opentelemetry/src/otlp/traces.rs index 1721c35..d423fce 100644 --- a/init-tracing-opentelemetry/src/otlp/traces.rs +++ b/init-tracing-opentelemetry/src/otlp/traces.rs @@ -1,4 +1,4 @@ -use super::infer_protocol; +use super::infer_protocol_from_env; use opentelemetry_otlp::{ExporterBuildError, SpanExporter}; use opentelemetry_sdk::{Resource, trace::SdkTracerProvider, trace::TracerProviderBuilder}; #[cfg(feature = "tls")] @@ -17,10 +17,14 @@ pub fn init_tracerprovider( where F: FnOnce(TracerProviderBuilder) -> TracerProviderBuilder, { - debug_env(); - let (maybe_protocol, maybe_endpoint) = read_protocol_and_endpoint_from_env(); - let protocol = infer_protocol(maybe_protocol.as_deref(), maybe_endpoint.as_deref()); + super::debug_env(); + let protocol = infer_protocol_from_env( + "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "v1/traces", + ); + // builders used the environment variables to determine the endpoint (but not to setup the protocol) let exporter: Option = match protocol.as_deref() { Some("http/protobuf") => Some(SpanExporter::builder().with_http().build()?), #[cfg(feature = "tls")] @@ -52,26 +56,3 @@ where trace_provider = transform(trace_provider); Ok(trace_provider.build()) } - -pub fn debug_env() { - std::env::vars() - .filter(|(k, _)| k.starts_with("OTEL_")) - .for_each(|(k, v)| tracing::debug!(target: "otel::setup::env", key = %k, value = %v)); -} - -fn read_protocol_and_endpoint_from_env() -> (Option, Option) { - let maybe_protocol = std::env::var("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL") - .or_else(|_| std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL")) - .ok(); - let maybe_endpoint = std::env::var("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") - .or_else(|_| { - std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").map(|endpoint| match &maybe_protocol { - Some(protocol) if protocol.contains("http") => { - format!("{endpoint}/v1/traces") - } - _ => endpoint, - }) - }) - .ok(); - (maybe_protocol, maybe_endpoint) -} diff --git a/init-tracing-opentelemetry/src/resource.rs b/init-tracing-opentelemetry/src/resource.rs index 2e40f92..52294a7 100644 --- a/init-tracing-opentelemetry/src/resource.rs +++ b/init-tracing-opentelemetry/src/resource.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use opentelemetry::KeyValue; // use opentelemetry_resource_detectors::OsResourceDetector; use opentelemetry_sdk::{Resource, resource::ResourceDetector}; @@ -16,8 +18,8 @@ use opentelemetry_semantic_conventions::resource; /// ``` #[derive(Debug, Default, Clone)] pub struct DetectResource { - fallback_service_name: Option<&'static str>, - fallback_service_version: Option<&'static str>, + fallback_service_name: Option>, + fallback_service_version: Option>, } impl DetectResource { @@ -25,8 +27,11 @@ impl DetectResource { /// (in this order) `OTEL_SERVICE_NAME`, `SERVICE_NAME`, `APP_NAME`. /// But a default value can be provided with this method. #[must_use] - pub fn with_fallback_service_name(mut self, fallback_service_name: &'static str) -> Self { - self.fallback_service_name = Some(fallback_service_name); + pub fn with_fallback_service_name( + mut self, + fallback_service_name: impl Into>, + ) -> Self { + self.fallback_service_name = Some(fallback_service_name.into()); self } @@ -34,19 +39,22 @@ impl DetectResource { /// (in this order) `SERVICE_VERSION`, `APP_VERSION`. /// But a default value can be provided with this method. #[must_use] - pub fn with_fallback_service_version(mut self, fallback_service_version: &'static str) -> Self { - self.fallback_service_version = Some(fallback_service_version); + pub fn with_fallback_service_version( + mut self, + fallback_service_version: impl Into>, + ) -> Self { + self.fallback_service_version = Some(fallback_service_version.into()); self } #[must_use] - pub fn build(&mut self) -> Resource { + pub fn build(self) -> Resource { //Box::new(OsResourceDetector), //FIXME enable when available for opentelemetry >= 0.25 //Box::new(ProcessResourceDetector), let rsrc = Resource::builder() .with_detector(Box::new(ServiceInfoDetector { - fallback_service_name: self.fallback_service_name.take(), - fallback_service_version: self.fallback_service_version.take(), + fallback_service_name: self.fallback_service_name, + fallback_service_version: self.fallback_service_version, })) .build(); debug_resource(&rsrc); @@ -62,8 +70,8 @@ pub fn debug_resource(rsrc: &Resource) { #[derive(Debug)] pub struct ServiceInfoDetector { - fallback_service_name: Option<&'static str>, - fallback_service_version: Option<&'static str>, + fallback_service_name: Option>, + fallback_service_version: Option>, } impl ResourceDetector for ServiceInfoDetector { @@ -72,17 +80,15 @@ impl ResourceDetector for ServiceInfoDetector { .or_else(|_| std::env::var("SERVICE_NAME")) .or_else(|_| std::env::var("APP_NAME")) .ok() - .or_else(|| { - self.fallback_service_name - .map(std::string::ToString::to_string) - }) + .or_else(|| self.fallback_service_name.clone().map(|v| v.to_string())) .map(|v| KeyValue::new(resource::SERVICE_NAME, v)); let service_version = std::env::var("SERVICE_VERSION") .or_else(|_| std::env::var("APP_VERSION")) .ok() .or_else(|| { self.fallback_service_version - .map(std::string::ToString::to_string) + .clone() + .map(std::borrow::Cow::into_owned) }) .map(|v| KeyValue::new(resource::SERVICE_VERSION, v)); let mut resource = Resource::builder_empty();