From 84c5b4dbe7edb88c6f4d57937fc0919f68014d69 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Fri, 13 Mar 2026 21:50:26 +0100 Subject: [PATCH 1/6] initial --- Cargo.lock | 22 ++++++++++++++++++++++ Cargo.toml | 2 +- rootcause-opentelemetry/Cargo.toml | 8 ++++++++ rootcause-opentelemetry/src/lib.rs | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 rootcause-opentelemetry/Cargo.toml create mode 100644 rootcause-opentelemetry/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f091a13..f80df83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,6 +767,20 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1030,6 +1044,14 @@ dependencies = [ "triomphe", ] +[[package]] +name = "rootcause-opentelemetry" +version = "0.1.0" +dependencies = [ + "opentelemetry", + "rootcause", +] + [[package]] name = "rootcause-tracing" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 80c1f02..67d4adf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/rootcause" rust-version = "1.89" [workspace] -members = ["rootcause-internals", "rootcause-backtrace", "rootcause-tracing"] +members = ["rootcause-internals", "rootcause-backtrace", "rootcause-tracing", "rootcause-opentelemetry"] [features] default = [] diff --git a/rootcause-opentelemetry/Cargo.toml b/rootcause-opentelemetry/Cargo.toml new file mode 100644 index 0000000..0d284e8 --- /dev/null +++ b/rootcause-opentelemetry/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rootcause-opentelemetry" +version = "0.1.0" +edition = "2024" + +[dependencies] +opentelemetry = "0.31" +rootcause = { path='../', version = } \ No newline at end of file diff --git a/rootcause-opentelemetry/src/lib.rs b/rootcause-opentelemetry/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rootcause-opentelemetry/src/lib.rs @@ -0,0 +1 @@ + From 1061a37aaa658cff831e529b70b9901a085466a4 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Fri, 13 Mar 2026 22:34:22 +0100 Subject: [PATCH 2/6] . --- rootcause-opentelemetry/Cargo.toml | 2 +- rootcause-opentelemetry/src/lib.rs | 192 +++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/rootcause-opentelemetry/Cargo.toml b/rootcause-opentelemetry/Cargo.toml index 0d284e8..79292e7 100644 --- a/rootcause-opentelemetry/Cargo.toml +++ b/rootcause-opentelemetry/Cargo.toml @@ -5,4 +5,4 @@ edition = "2024" [dependencies] opentelemetry = "0.31" -rootcause = { path='../', version = } \ No newline at end of file +rootcause = { path = "../", version = "=0.12.1" } \ No newline at end of file diff --git a/rootcause-opentelemetry/src/lib.rs b/rootcause-opentelemetry/src/lib.rs index 8b13789..b0d9a87 100644 --- a/rootcause-opentelemetry/src/lib.rs +++ b/rootcause-opentelemetry/src/lib.rs @@ -1 +1,193 @@ +use core::fmt; +use std::fmt::write; +use opentelemetry::{Context, SpanId, TraceFlags, TraceId, trace::{SpanContext, TraceContextExt, TraceState}}; +use rootcause::{ + ReportMut, + handlers::{ + AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler, Display, FormattingFunction + }, + hooks::report_creation::ReportCreationHook, + markers::{Dynamic, Local, SendSync}, +}; + +struct TraceContext; + +impl AttachmentHandler for TraceContext { + fn display(value: &SpanContext, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + + write!( + formatter, + "Traceparent: 00-{:x}-{:x}-{:02x}", + value.trace_id(), + value.span_id(), + value.trace_flags(), + )?; + + if value.trace_state() != &TraceState::NONE { + write!(formatter, + "\nTracestate: {}", + value.trace_state().header() + )?; + } + + match (value.is_remote(), value.is_sampled()) { + (true, true) => write!(formatter, "\n(remote & sampled)"), + (true, false) => write!(formatter, "\n(remote)"), + (false, true) => write!(formatter, "\n(sampled)"), + (false, false) => Ok(()) + } + } + + fn debug(value: &SpanContext, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &SpanContext, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + AttachmentFormattingStyle { + function, + placement: if value.is_valid() { + AttachmentFormattingPlacement::InlineWithHeader { + header: "Trace context:", + } + } else { + AttachmentFormattingPlacement::Hidden + }, + priority: 8, // just slightly below tracing spans (9) + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraceParent { + trace_id: TraceId, + span_id: SpanId, + trace_flags: TraceFlags +} + +impl TraceParent { + pub fn is_valid(&self) -> bool { + self.trace_id != TraceId::INVALID && self.span_id != SpanId::INVALID + } + + pub fn trace_id(&self) -> TraceId { + self.trace_id + } + + pub fn span_id(&self) -> SpanId { + self.span_id + } + + pub fn trace_flags(&self) -> TraceFlags { + self.trace_flags + } +} + +impl fmt::Display for TraceParent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "00-{:x}-{:x}-{:02x}", + self.trace_id, + self.span_id, + self.trace_flags, + ) + } +} + +impl AttachmentHandler for TraceContext { + fn display(value: &TraceParent, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(value, formatter) + } + + fn debug(value: &TraceParent, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &TraceParent, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + AttachmentFormattingStyle { + function, + placement: if value.is_valid() { + AttachmentFormattingPlacement::InlineWithHeader { header: "Traceparent:" } + } else { + AttachmentFormattingPlacement::Hidden + }, + priority: 7 + } + } +} + +impl AttachmentHandler for TraceContext { + fn display(value: &TraceState, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str( + &value.header() + ) + } + + fn debug(value: &TraceState, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &TraceState, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + let _ = value; + AttachmentFormattingStyle { + function, + placement: if value == &TraceState::NONE { + AttachmentFormattingPlacement::Hidden + } else { + AttachmentFormattingPlacement::InlineWithHeader { header: "Tracestate:" } + }, + priority: 6 + } + } +} + +struct SpanContextCollector; + +impl ReportCreationHook for SpanContextCollector { + fn on_local_creation(&self, report: ReportMut<'_, Dynamic, Local>) { + if Context::current().has_active_span() { + let context = Context::current().span().span_context() + if context.is_valid() { + let mut context = context.clone(); + let tracestate = context.trace_state().clone(); + let traceparent = TraceParent { + trace_id: todo!(), + span_id: todo!(), + trace_flags: todo!(), + } + } + } + } + + fn on_sendsync_creation(&self, report: ReportMut<'_, Dynamic, SendSync>) { + todo!() + } +} + +struct TraceContextCollector; + +impl ReportCreationHook for TraceContextCollector { + fn on_local_creation(&self, report: ReportMut<'_, Dynamic, Local>) { + if Context::current().has_active_span() { + let context = Context::current().span().span_context() + if context.is_valid() { + let mut context = context.clone(); + let tracestate = context.tra + } + } + } + + fn on_sendsync_creation(&self, report: ReportMut<'_, Dynamic, SendSync>) { + todo!() + } +} \ No newline at end of file From 577e2b4dc21707308d83869dbd291df38baceceb Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Sat, 14 Mar 2026 12:40:49 +0100 Subject: [PATCH 3/6] Full design --- .tombi.toml | 4 + rootcause-opentelemetry/src/lib.rs | 216 +++--------------------- rootcause-opentelemetry/src/separate.rs | 135 +++++++++++++++ rootcause-opentelemetry/src/single.rs | 97 +++++++++++ rootcause-opentelemetry/src/types.rs | 111 ++++++++++++ 5 files changed, 370 insertions(+), 193 deletions(-) create mode 100644 .tombi.toml create mode 100644 rootcause-opentelemetry/src/separate.rs create mode 100644 rootcause-opentelemetry/src/single.rs create mode 100644 rootcause-opentelemetry/src/types.rs diff --git a/.tombi.toml b/.tombi.toml new file mode 100644 index 0000000..b0c0630 --- /dev/null +++ b/.tombi.toml @@ -0,0 +1,4 @@ +[lint] +[lint.rules] +tables-out-of-order = "off" +dotted-keys-out-of-order = "off" \ No newline at end of file diff --git a/rootcause-opentelemetry/src/lib.rs b/rootcause-opentelemetry/src/lib.rs index b0d9a87..8a9560f 100644 --- a/rootcause-opentelemetry/src/lib.rs +++ b/rootcause-opentelemetry/src/lib.rs @@ -1,193 +1,23 @@ -use core::fmt; -use std::fmt::write; - -use opentelemetry::{Context, SpanId, TraceFlags, TraceId, trace::{SpanContext, TraceContextExt, TraceState}}; -use rootcause::{ - ReportMut, - handlers::{ - AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler, Display, FormattingFunction - }, - hooks::report_creation::ReportCreationHook, - markers::{Dynamic, Local, SendSync}, -}; - -struct TraceContext; - -impl AttachmentHandler for TraceContext { - fn display(value: &SpanContext, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - - write!( - formatter, - "Traceparent: 00-{:x}-{:x}-{:02x}", - value.trace_id(), - value.span_id(), - value.trace_flags(), - )?; - - if value.trace_state() != &TraceState::NONE { - write!(formatter, - "\nTracestate: {}", - value.trace_state().header() - )?; - } - - match (value.is_remote(), value.is_sampled()) { - (true, true) => write!(formatter, "\n(remote & sampled)"), - (true, false) => write!(formatter, "\n(remote)"), - (false, true) => write!(formatter, "\n(sampled)"), - (false, false) => Ok(()) - } - } - - fn debug(value: &SpanContext, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(value, formatter) - } - - fn preferred_formatting_style( - value: &SpanContext, - function: FormattingFunction, - ) -> AttachmentFormattingStyle { - AttachmentFormattingStyle { - function, - placement: if value.is_valid() { - AttachmentFormattingPlacement::InlineWithHeader { - header: "Trace context:", - } - } else { - AttachmentFormattingPlacement::Hidden - }, - priority: 8, // just slightly below tracing spans (9) - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TraceParent { - trace_id: TraceId, - span_id: SpanId, - trace_flags: TraceFlags -} - -impl TraceParent { - pub fn is_valid(&self) -> bool { - self.trace_id != TraceId::INVALID && self.span_id != SpanId::INVALID - } - - pub fn trace_id(&self) -> TraceId { - self.trace_id - } - - pub fn span_id(&self) -> SpanId { - self.span_id - } - - pub fn trace_flags(&self) -> TraceFlags { - self.trace_flags - } -} - -impl fmt::Display for TraceParent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "00-{:x}-{:x}-{:02x}", - self.trace_id, - self.span_id, - self.trace_flags, - ) - } -} - -impl AttachmentHandler for TraceContext { - fn display(value: &TraceParent, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - fmt::Display::fmt(value, formatter) - } - - fn debug(value: &TraceParent, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - fmt::Debug::fmt(value, formatter) - } - - fn preferred_formatting_style( - value: &TraceParent, - function: FormattingFunction, - ) -> AttachmentFormattingStyle { - AttachmentFormattingStyle { - function, - placement: if value.is_valid() { - AttachmentFormattingPlacement::InlineWithHeader { header: "Traceparent:" } - } else { - AttachmentFormattingPlacement::Hidden - }, - priority: 7 - } - } -} - -impl AttachmentHandler for TraceContext { - fn display(value: &TraceState, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str( - &value.header() - ) - } - - fn debug(value: &TraceState, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(value, formatter) - } - - fn preferred_formatting_style( - value: &TraceState, - function: FormattingFunction, - ) -> AttachmentFormattingStyle { - let _ = value; - AttachmentFormattingStyle { - function, - placement: if value == &TraceState::NONE { - AttachmentFormattingPlacement::Hidden - } else { - AttachmentFormattingPlacement::InlineWithHeader { header: "Tracestate:" } - }, - priority: 6 - } - } -} - -struct SpanContextCollector; - -impl ReportCreationHook for SpanContextCollector { - fn on_local_creation(&self, report: ReportMut<'_, Dynamic, Local>) { - if Context::current().has_active_span() { - let context = Context::current().span().span_context() - if context.is_valid() { - let mut context = context.clone(); - let tracestate = context.trace_state().clone(); - let traceparent = TraceParent { - trace_id: todo!(), - span_id: todo!(), - trace_flags: todo!(), - } - } - } - } - - fn on_sendsync_creation(&self, report: ReportMut<'_, Dynamic, SendSync>) { - todo!() - } -} - -struct TraceContextCollector; - -impl ReportCreationHook for TraceContextCollector { - fn on_local_creation(&self, report: ReportMut<'_, Dynamic, Local>) { - if Context::current().has_active_span() { - let context = Context::current().span().span_context() - if context.is_valid() { - let mut context = context.clone(); - let tracestate = context.tra - } - } - } - - fn on_sendsync_creation(&self, report: ReportMut<'_, Dynamic, SendSync>) { - todo!() - } -} \ No newline at end of file +//! # Open telemetry tracing contexts for Rootcause reports. +//! +//! This crate provides report creation hooks for adding the +//! [Tracing Context][Tracing Context] provided by Open Telemetry to +//! reports. +//! +//! Two collectors are provided, one which collects the whole +//! [`SpanContext`][SpanContext] as a single attachment, and +//! one which splits it up into three separate attachments, +//! for the purpose of providing a slightly prettier formatting. +//! +//! [Tracing Context]: https://www.w3.org/TR/trace-context/ +//! [SpanContext]: opentelemetry::trace::SpanContext + +mod separate; +mod single; +mod types; +pub use opentelemetry::trace::TraceState; +pub use separate::TraceContextCollector; +pub use single::SpanContextCollector; +pub use types::TraceContextAttachment; +pub use types::TraceParent; +pub use types::TraceSettings; diff --git a/rootcause-opentelemetry/src/separate.rs b/rootcause-opentelemetry/src/separate.rs new file mode 100644 index 0000000..ed05eee --- /dev/null +++ b/rootcause-opentelemetry/src/separate.rs @@ -0,0 +1,135 @@ +use std::fmt; + +use opentelemetry::{ + Context, + trace::{TraceContextExt, TraceState}, +}; +use rootcause::{ + ReportMut, + handlers::{ + AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler, + FormattingFunction, + }, + hooks::report_creation::ReportCreationHook, + markers::{Dynamic, Local, ObjectMarkerFor, SendSync}, +}; + +use crate::types::{TraceContextAttachment, TraceParent, TraceSettings}; + +impl AttachmentHandler for TraceContextAttachment { + fn display(value: &TraceParent, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(value, formatter) + } + + fn debug(value: &TraceParent, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &TraceParent, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + AttachmentFormattingStyle { + function, + placement: if value.is_valid() { + AttachmentFormattingPlacement::InlineWithHeader { + header: "Traceparent:", + } + } else { + AttachmentFormattingPlacement::Hidden + }, + priority: -5, + } + } +} + +impl AttachmentHandler for TraceContextAttachment { + fn display(value: &TraceState, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(&value.header()) + } + + fn debug(value: &TraceState, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &TraceState, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + let _ = value; + AttachmentFormattingStyle { + function, + placement: if value == &TraceState::NONE { + AttachmentFormattingPlacement::Hidden + } else { + AttachmentFormattingPlacement::InlineWithHeader { + header: "Tracestate:", + } + }, + priority: -5, + } + } +} + +impl AttachmentHandler for TraceContextAttachment { + fn display(value: &TraceSettings, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(value, formatter) + } + + fn debug(value: &TraceSettings, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &TraceSettings, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + let _ = value; + AttachmentFormattingStyle { + function, + placement: AttachmentFormattingPlacement::Inline, + priority: -5, + } + } +} + +/// Collector for the [`TraceParent`], [`TraceState`], and [`TraceSettings`] +/// objects available at time of report creation, if any. +/// +/// This collector creates three separate attachments. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraceContextCollector; + +impl ReportCreationHook for TraceContextCollector { + fn on_local_creation(&self, report: ReportMut<'_, Dynamic, Local>) { + on_creation(report); + } + + fn on_sendsync_creation(&self, report: ReportMut<'_, Dynamic, SendSync>) { + on_creation(report); + } +} + +fn on_creation(report: ReportMut<'_, Dynamic, T>) +where + TraceSettings: ObjectMarkerFor, + TraceState: ObjectMarkerFor, + TraceParent: ObjectMarkerFor, +{ + if Context::current().has_active_span() { + let context = Context::current(); + let span_ref = context.span(); + let span_context_ref = span_ref.span_context(); + if span_context_ref.is_valid() { + let tracestate = span_context_ref.trace_state().clone(); + + let traceparent = TraceParent::from(span_context_ref); + let trace_settings = TraceSettings::from(span_context_ref); + + let _ = report + .attach_custom::(traceparent) + .attach_custom::(tracestate) + .attach_custom::(trace_settings); + } + } +} diff --git a/rootcause-opentelemetry/src/single.rs b/rootcause-opentelemetry/src/single.rs new file mode 100644 index 0000000..8bff4ce --- /dev/null +++ b/rootcause-opentelemetry/src/single.rs @@ -0,0 +1,97 @@ +use std::fmt; + +use opentelemetry::{ + Context, + trace::{SpanContext, TraceContextExt, TraceState}, +}; +use rootcause::{ + ReportMut, + handlers::{ + AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler, + FormattingFunction, + }, + hooks::report_creation::ReportCreationHook, + markers::{Dynamic, ObjectMarkerFor}, +}; + +use crate::types::TraceContextAttachment; + +impl AttachmentHandler for TraceContextAttachment { + fn display(value: &SpanContext, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + formatter, + "Traceparent: 00-{:x}-{:x}-{:02x}", + value.trace_id(), + value.span_id(), + value.trace_flags(), + )?; + + if value.trace_state() != &TraceState::NONE { + write!(formatter, "\nTracestate: {}", value.trace_state().header())?; + } + + match (value.is_remote(), value.is_sampled()) { + (true, true) => write!(formatter, "\n(remote & sampled)"), + (true, false) => write!(formatter, "\n(remote)"), + (false, true) => write!(formatter, "\n(sampled)"), + (false, false) => Ok(()), + } + } + + fn debug(value: &SpanContext, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(value, formatter) + } + + fn preferred_formatting_style( + value: &SpanContext, + function: FormattingFunction, + ) -> AttachmentFormattingStyle { + AttachmentFormattingStyle { + function, + placement: if value.is_valid() { + AttachmentFormattingPlacement::InlineWithHeader { + header: "Trace context:", + } + } else { + AttachmentFormattingPlacement::Hidden + }, + priority: -5, + } + } +} + +/// Collector for the [`SpanContext`] object available at time +/// of report creation, if any. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SpanContextCollector; + +impl ReportCreationHook for SpanContextCollector { + fn on_local_creation( + &self, + report: rootcause::ReportMut<'_, rootcause::markers::Dynamic, rootcause::markers::Local>, + ) { + on_creation(report); + } + + fn on_sendsync_creation( + &self, + report: rootcause::ReportMut<'_, rootcause::markers::Dynamic, rootcause::markers::SendSync>, + ) { + on_creation(report); + } +} + +fn on_creation(report: ReportMut<'_, Dynamic, T>) +where + SpanContext: ObjectMarkerFor, +{ + if Context::current().has_active_span() { + let context = Context::current(); + let span_ref = context.span(); + let span_context_ref = span_ref.span_context(); + if span_context_ref.is_valid() { + let span_context = span_context_ref.clone(); + let _ = report.attach_custom::(span_context); + } + } +} diff --git a/rootcause-opentelemetry/src/types.rs b/rootcause-opentelemetry/src/types.rs new file mode 100644 index 0000000..a33309d --- /dev/null +++ b/rootcause-opentelemetry/src/types.rs @@ -0,0 +1,111 @@ +use std::fmt; + +use opentelemetry::{SpanId, TraceFlags, TraceId, trace::SpanContext}; + +/// Attachment handler for trace context items. +/// +/// (Yes, the name contains 'Context', it is confusing.) +pub struct TraceContextAttachment; + +/// Struct containing the Open Telemetry trace settings, +/// i.e. whether the current context is a remote trace, +/// and whether it is a sampled trace. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraceSettings { + remote: bool, + sampled: bool, +} + +impl TraceSettings { + pub fn new(remote: bool, sampled: bool) -> Self { + Self { remote, sampled } + } + + pub fn is_remote(&self) -> bool { + self.remote + } + + pub fn is_sampled(&self) -> bool { + self.sampled + } +} + +impl From<&SpanContext> for TraceSettings { + fn from(value: &SpanContext) -> Self { + TraceSettings::new(value.is_remote(), value.is_sampled()) + } +} + +impl fmt::Display for TraceSettings { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let place = if self.is_remote() { "remote" } else { "local" }; + let sampling = if self.is_sampled() { + "sampled" + } else { + "non-sampled" + }; + + write!(f, "{place} & {sampling} trace") + } +} + +/// The `traceparent` HTTP header. +/// +/// In the W3C specification this takes the form of four hexadecimal +/// numbers separated by hyphens, in digit groups of 2, 32, 16, and 2. +/// +/// It consists of the [`trace_id`](SpanContext::trace_id), +/// [`span_id`](SpanContext::span_id), and +/// [`trace_flags`](SpanContext::trace_flags) of the +/// [`SpanContext`]. +/// +/// The corresponding `tracestate` HTTP header is provided as-is +/// by Open Telemetry in [`TraceSettings`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraceParent { + trace_id: TraceId, + span_id: SpanId, + trace_flags: TraceFlags, +} + +impl TraceParent { + pub fn new(trace_id: TraceId, span_id: SpanId, trace_flags: TraceFlags) -> Self { + Self { + trace_id, + span_id, + trace_flags, + } + } + + pub fn is_valid(&self) -> bool { + self.trace_id != TraceId::INVALID && self.span_id != SpanId::INVALID + } + + pub fn trace_id(&self) -> TraceId { + self.trace_id + } + + pub fn span_id(&self) -> SpanId { + self.span_id + } + + pub fn trace_flags(&self) -> TraceFlags { + self.trace_flags + } +} + +impl From<&SpanContext> for TraceParent { + fn from(value: &SpanContext) -> Self { + TraceParent::new(value.trace_id(), value.span_id(), value.trace_flags()) + } +} + +impl fmt::Display for TraceParent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "00-{:x}-{:x}-{:02x}", + self.trace_id, self.span_id, self.trace_flags, + ) + } +} From 3272ddf1c830eb6a97d15a168e5ae638f5daa8a6 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Sat, 14 Mar 2026 12:48:59 +0100 Subject: [PATCH 4/6] no tombi --- .tombi.toml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .tombi.toml diff --git a/.tombi.toml b/.tombi.toml deleted file mode 100644 index b0c0630..0000000 --- a/.tombi.toml +++ /dev/null @@ -1,4 +0,0 @@ -[lint] -[lint.rules] -tables-out-of-order = "off" -dotted-keys-out-of-order = "off" \ No newline at end of file From 80786a6bff68f46586a804113cb7cc4572611687 Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Sat, 14 Mar 2026 12:56:04 +0100 Subject: [PATCH 5/6] Update cargo toml --- rootcause-opentelemetry/Cargo.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rootcause-opentelemetry/Cargo.toml b/rootcause-opentelemetry/Cargo.toml index 79292e7..98135d2 100644 --- a/rootcause-opentelemetry/Cargo.toml +++ b/rootcause-opentelemetry/Cargo.toml @@ -2,7 +2,16 @@ name = "rootcause-opentelemetry" version = "0.1.0" edition = "2024" +license = "MIT/Apache-2.0" +categories = ["rust-patterns", "development-tools::debugging"] +keywords = ["error", "error-handling", "tracing", "spans", "observability", "opentelemetry"] +description = "Open Telemetry tracing context support for the rootcause error reporting library" +repository = "https://github.com/rootcause-rs/rootcause" +documentation = "https://docs.rs/rootcause-opentelemetry" +rust-version = "1.89" [dependencies] opentelemetry = "0.31" + +# Internal rootcause = { path = "../", version = "=0.12.1" } \ No newline at end of file From 994b5944cc3138111df21fca578425638e8d844b Mon Sep 17 00:00:00 2001 From: Kile Asmussen Date: Sat, 14 Mar 2026 12:56:21 +0100 Subject: [PATCH 6/6] Cargo toml update --- rootcause-opentelemetry/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rootcause-opentelemetry/Cargo.toml b/rootcause-opentelemetry/Cargo.toml index 98135d2..8540823 100644 --- a/rootcause-opentelemetry/Cargo.toml +++ b/rootcause-opentelemetry/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" license = "MIT/Apache-2.0" categories = ["rust-patterns", "development-tools::debugging"] -keywords = ["error", "error-handling", "tracing", "spans", "observability", "opentelemetry"] +keywords = ["error", "error-handling", "observability", "opentelemetry", "web-standards"] description = "Open Telemetry tracing context support for the rootcause error reporting library" repository = "https://github.com/rootcause-rs/rootcause" documentation = "https://docs.rs/rootcause-opentelemetry"