diff --git a/Cargo.toml b/Cargo.toml index 79b6c0b..9a1b825 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ rust-version = "1.75.0" default = ["tracing-log", "metrics"] # Enables support for exporting OpenTelemetry metrics metrics = ["opentelemetry/metrics", "smallvec"] +valuable = ["dep:valuable", "tracing-core/valuable"] [dependencies] opentelemetry = { version = "0.31.0", default-features = false, features = [ @@ -37,6 +38,8 @@ smallvec = { version = "1.0", optional = true } # Fix minimal-versions lazy_static = { version = "1.0.2", optional = true } +valuable = { version = "0.1.1", optional = true } + [dev-dependencies] criterion = { version = "0.5.1", default-features = false, features = [ "html_reports", @@ -79,6 +82,7 @@ bench = false [lints.rust] unnameable_types = "warn" +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tracing_unstable)"] } [[bench]] name = "trace" diff --git a/src/layer.rs b/src/layer.rs index acf423b..a6b1739 100644 --- a/src/layer.rs +++ b/src/layer.rs @@ -35,6 +35,7 @@ const SPAN_EVENT_COUNT_FIELD: &str = "otel.tracing_event_count"; const EVENT_EXCEPTION_NAME: &str = "exception"; const FIELD_EXCEPTION_MESSAGE: &str = "exception.message"; const FIELD_EXCEPTION_STACKTRACE: &str = "exception.stacktrace"; +const FIELD_EXCEPTION_TYPE: &str = "exception.type"; /// An [OpenTelemetry] propagation layer for use in a project that uses /// [tracing]. @@ -246,6 +247,45 @@ struct SpanEventVisitor<'a, 'b> { } impl field::Visit for SpanEventVisitor<'_, '_> { + #[cfg(all(tracing_unstable, feature = "valuable"))] + #[cfg_attr(docsrs, doc(cfg(all(tracing_unstable, feature = "valuable"))))] + fn record_value(&mut self, field: &field::Field, value: valuable::Value<'_>) { + match field.name() { + "message" => self.event_builder.name = format!("{value:?}").into(), + "error" if self.event_builder.name.is_empty() => { + if self.sem_conv_config.error_events_to_status { + self.span_builder_updates + .get_or_insert_with(SpanBuilderUpdates::default) + .status + .replace(otel::Status::error(format!("{value:?}"))); + } + if self.sem_conv_config.error_events_to_exceptions { + self.event_builder.name = EVENT_EXCEPTION_NAME.into(); + self.event_builder + .attributes + .push(KeyValue::new(FIELD_EXCEPTION_MESSAGE, format!("{value:?}"))); + + let error_type = match value { + valuable::Value::Structable(s) => s.definition().name().to_string(), + valuable::Value::Enumerable(s) => s.definition().name().to_string(), + _ => "unknown".to_string(), + }; + self.event_builder + .attributes + .push(KeyValue::new(FIELD_EXCEPTION_TYPE, error_type)); + } else { + self.event_builder + .attributes + .push(KeyValue::new("error", format!("{value:?}"))); + } + } + // Skip fields that are actually log metadata that have already been handled + #[cfg(feature = "tracing-log")] + name if name.starts_with("log.") => (), + _ => self.record_debug(field, &value), + } + } + /// Record events on the underlying OpenTelemetry [`Span`] from `bool` values. /// /// [`Span`]: opentelemetry::trace::Span