otel/
lib.rs

1#![doc = include_str!("../README.md")]
2
3/// OpenTelemetry specification version used for schema URLs.
4///
5/// This version is included in all tracer configurations to indicate
6/// which OpenTelemetry semantic conventions are being followed.
7pub const OTEL_SPEC_VERSION: &str = "1.38.0";
8
9/// Creates a static OpenTelemetry tracer for instrumentation.
10///
11/// This macro creates a `LazyLock<BoxedTracer>` static that initializes on first use
12/// from the global tracer provider. Declare at crate or module root, then import
13/// where needed.
14///
15/// # Prerequisites
16///
17/// The global tracer provider must be initialized before any spans are created.
18/// Typically done in `main()` before calling into application code:
19///
20/// ```ignore
21/// fn main() {
22///     let provider = opentelemetry_otlp::new_pipeline()
23///         .tracing()
24///         .with_exporter(opentelemetry_otlp::new_exporter().tonic())
25///         .install_batch(opentelemetry_sdk::runtime::Tokio)
26///         .expect("Failed to initialize tracer");
27///
28///     opentelemetry::global::set_tracer_provider(provider);
29///
30///     run_app();  // Now TRACER can be used
31///
32///     opentelemetry::global::shutdown_tracer_provider();
33/// }
34/// ```
35///
36/// # Variants
37///
38/// ## Default tracer (no arguments)
39///
40/// Creates a `TRACER` static using the crate's package name as the instrumentation scope:
41///
42/// ```ignore
43/// // In lib.rs or main.rs
44/// otel::tracer!();
45///
46/// // Creates: pub(crate) static TRACER: LazyLock<BoxedTracer>
47/// // Scope name: env!("CARGO_PKG_NAME")
48/// ```
49///
50/// ## Named tracer
51///
52/// Creates a tracer with a custom name suffix, useful for subsystems:
53///
54/// ```ignore
55/// // In src/client/mod.rs
56/// otel::tracer!(lsp_client);
57///
58/// // Creates: pub(crate) static LSP_CLIENT_TRACER: LazyLock<BoxedTracer>
59/// // Scope name: "{CARGO_PKG_NAME}.lsp_client"
60/// ```
61///
62/// # Tracer Configuration
63///
64/// Each tracer is configured with:
65///
66/// | Property | Value |
67/// |----------|-------|
68/// | Scope name | Crate name, or `{crate}.{subsystem}` for named tracers |
69/// | Version | `CARGO_PKG_VERSION` |
70/// | Schema URL | `https://opentelemetry.io/schemas/{OTEL_SPEC_VERSION}` |
71///
72/// # Multiple Tracers
73///
74/// Use named tracers to separate instrumentation by subsystem. This makes it easy
75/// to filter traces by scope in your observability backend:
76///
77/// ```ignore
78/// // src/lib.rs - default tracer for general use
79/// otel::tracer!();
80///
81/// // src/client/mod.rs - client subsystem
82/// otel::tracer!(client);
83///
84/// // src/server/mod.rs - server subsystem
85/// otel::tracer!(server);
86/// ```
87///
88/// Use explicit tracer syntax in `span!` to select which tracer to use:
89///
90/// ```ignore
91/// use crate::client::CLIENT_TRACER;
92///
93/// fn send_request() {
94///     let (_cx, _guard) = otel::span!(@CLIENT_TRACER, "request.send");
95/// }
96/// ```
97///
98/// # Example: Full Setup
99///
100/// ```ignore
101/// // src/lib.rs
102/// otel::tracer!();
103///
104/// pub mod engine;
105/// ```
106///
107/// ```ignore
108/// // src/engine.rs
109/// use crate::TRACER;
110///
111/// pub fn run() {
112///     let (_cx, _guard) = otel::span!("engine.run");
113///     // ...
114/// }
115/// ```
116#[macro_export]
117macro_rules! tracer {
118
119  ($name:ident) => {
120    paste::paste! {
121      pub(crate) static [<$name:snake:upper _TRACER>]: std::sync::LazyLock<opentelemetry::global::BoxedTracer> =
122        std::sync::LazyLock::new(|| {
123          use opentelemetry::trace::TracerProvider;
124
125          opentelemetry::global::tracer_provider().tracer_with_scope(
126            opentelemetry::InstrumentationScope::builder(concat!(
127              env!("CARGO_PKG_NAME"),
128              ".", stringify!([<$name:snake>])
129            ))
130            .with_version(env!("CARGO_PKG_VERSION"))
131            .with_schema_url(format!("https://opentelemetry.io/schemas/{}", $crate::OTEL_SPEC_VERSION))
132            .build(),
133          )
134        });
135    }
136  };
137
138  () => {
139    paste::paste! {
140      pub(crate) static TRACER: std::sync::LazyLock<opentelemetry::global::BoxedTracer> =
141        std::sync::LazyLock::new(|| {
142          use opentelemetry::trace::TracerProvider;
143
144          opentelemetry::global::tracer_provider().tracer_with_scope(
145            opentelemetry::InstrumentationScope::builder(concat!(
146              env!("CARGO_PKG_NAME"),
147            ))
148            .with_version(env!("CARGO_PKG_VERSION"))
149            .with_schema_url(format!("https://opentelemetry.io/schemas/{}", $crate::OTEL_SPEC_VERSION))
150            .build(),
151          )
152        });
153    }
154  };
155}
156
157/// Creates an OpenTelemetry span for tracing execution.
158///
159/// This macro creates a span with automatic code location attributes and returns
160/// both the span's [`Context`] and a [`ContextGuard`] that keeps the span active.
161///
162/// # Return Value
163///
164/// Returns `(Context, ContextGuard)`:
165///
166/// - **Context**: The OpenTelemetry context containing the active span. Clone this
167///   to propagate through async boundaries or pass to child operations.
168/// - **ContextGuard**: RAII guard that keeps the context attached to the current thread.
169///   When dropped, the span ends and is exported.
170///
171/// **Important**: The span is only active while the guard is held. Dropping the guard
172/// ends the span, so keep it in scope for the duration of the traced operation.
173///
174/// # Syntax Variants
175///
176/// | Syntax | Returns | Use Case |
177/// |--------|---------|----------|
178/// | `span!("name")` | `()` | Default tracer, creates internal guard |
179/// | `span!("name", "k" = v, ...)` | `()` | With custom attributes, internal guard |
180/// | `span!("name", in \|cx\| { ... })` | `T` | With closure (auto-managed lifetime) |
181/// | `span!("name", "k" = v, in \|cx\| { ... })` | `T` | Closure + attributes |
182/// | `span!(@TRACER, "name")` | `()` | Explicit tracer, internal guard |
183/// | `span!(@TRACER, "name", "k" = v)` | `()` | Explicit tracer + attributes, internal guard |
184/// | `span!(@TRACER, "name", in \|cx\| { ... })` | `T` | Explicit tracer with closure |
185/// | `span!(@TRACER, "name", "k" = v, in \|cx\| { ... })` | `T` | Explicit tracer + attributes + closure |
186/// | `span!(^ "name")` | `Context` | Detached span (no automatic guard) |
187/// | `span!(^ "name", "k" = v)` | `Context` | Detached with attributes |
188/// | `span!(^ @TRACER, "name")` | `Context` | Detached with explicit tracer |
189///
190/// # Automatic Attributes
191///
192/// All spans automatically include:
193///
194/// | Attribute | Description |
195/// |-----------|-------------|
196/// | `code.file.path` | Source file (workspace-relative via [`relative_filepath`]) |
197/// | `code.line.number` | Line number where span was created |
198/// | `code.column.number` | Column number |
199/// | `thread.id` | Current thread ID |
200/// | `thread.name` | Current thread name (or "unnamed") |
201///
202/// # Synchronous Usage
203///
204/// ## Pattern 1: Statement form (automatic guard management)
205///
206/// The simplest form creates an internal guard that manages span lifetime automatically:
207///
208/// ```ignore
209/// fn process_item(item: &Item) {
210///     otel::span!("item.process", "item.id" = item.id);
211///
212///     validate(item);
213///     transform(item);
214///     save(item);
215///
216///     // Span ends automatically at end of scope
217/// }
218/// ```
219///
220/// Nested spans automatically form parent-child relationships:
221///
222/// ```ignore
223/// fn process_batch(items: &[Item]) {
224///     otel::span!("batch.process", "count" = items.len() as i64);
225///
226///     for item in items {
227///         // Automatically becomes a child of "batch.process"
228///         otel::span!("item.process", "item.id" = item.id);
229///         process_item(item);
230///     }
231/// }
232/// ```
233///
234/// ## Pattern 2: Closure-based spans
235///
236/// Use `in` closure syntax for automatic span lifetime management:
237///
238/// ```ignore
239/// use crate::TRACER;
240///
241/// fn process_item(item: &Item) -> Result<ProcessedItem> {
242///     otel::span!("item.process", "item.id" = item.id, in |cx| {
243///         validate(item)?;
244///         let transformed = transform(item)?;
245///         save(&transformed)?;
246///         Ok(transformed)
247///     })
248/// }
249/// ```
250///
251/// The closure receives the `Context` as a parameter and can return any value.
252/// The span ends automatically when the closure completes or panics:
253///
254/// ```ignore
255/// // Simple computation
256/// let result = otel::span!("compute", in |cx| {
257///     expensive_calculation()
258/// });
259///
260/// // With attributes
261/// let user = otel::span!("db.fetch", "user.id" = user_id, in |cx| {
262///     db.get_user(user_id)
263/// })?;
264///
265/// // Explicit tracer
266/// let data = otel::span!(@CUSTOM_TRACER, "custom.operation", in |cx| {
267///     do_work()
268/// });
269/// ```
270///
271/// # Asynchronous Usage
272///
273/// OpenTelemetry context is stored in thread-local storage. Since async tasks can
274/// migrate between threads at `.await` points, you must explicitly propagate context
275/// using [`FutureExt::with_context`].
276///
277/// ## Pattern 1: Closure form for single async operation
278///
279/// ```ignore
280/// use opentelemetry::trace::FutureExt;
281///
282/// async fn fetch_user(id: u64) -> Result<User> {
283///     otel::span!("user.fetch", "user.id" = id as i64, in |cx| {
284///         db.get_user(id)
285///             .with_context(cx)
286///     }).await
287/// }
288/// ```
289///
290/// ## Pattern 2: Detached form for sequential awaits
291///
292/// Use the detached form (`@detached`) to get a context variable for multiple await points:
293///
294/// ```ignore
295/// use opentelemetry::trace::FutureExt;
296///
297/// async fn process_order(order_id: u64) -> Result<()> {
298///     let cx = otel::span!(^ "order.process", "order.id" = order_id as i64);
299///
300///     let order = fetch_order(order_id)
301///         .with_context(cx.clone())
302///         .await?;
303///
304///     validate_order(&order)
305///         .with_context(cx.clone())
306///         .await?;
307///
308///     submit_order(&order)
309///         .with_context(cx)  // Last use, no clone needed
310///         .await
311/// }
312/// ```
313///
314/// ## Pattern 3: Concurrent/spawned tasks
315///
316/// Use detached form and clone the context for each spawned task:
317///
318/// ```ignore
319/// use opentelemetry::trace::FutureExt;
320///
321/// async fn fetch_all(ids: Vec<u64>) -> Vec<Result<Item>> {
322///     let cx = otel::span!(^ "items.fetch_all", "count" = ids.len() as i64);
323///
324///     let futures: Vec<_> = ids
325///         .into_iter()
326///         .map(|id| {
327///             let cx = cx.clone();
328///             async move {
329///                 fetch_item(id)
330///                     .with_context(cx)
331///                     .await
332///             }
333///         })
334///         .collect();
335///
336///     futures::future::join_all(futures).await
337/// }
338/// ```
339///
340/// ## Pattern 4: Child spans in async blocks
341///
342/// Use detached form for parent span, then create child spans inside async blocks:
343///
344/// ```ignore
345/// use opentelemetry::trace::FutureExt;
346///
347/// async fn pipeline() -> Result<()> {
348///     let cx = otel::span!(^ "pipeline.run");
349///
350///     async {
351///         otel::span!("pipeline.phase1");
352///         do_phase1().await
353///     }
354///     .with_context(cx.clone())
355///     .await?;
356///
357///     async {
358///         otel::span!("pipeline.phase2");
359///         do_phase2().await
360///     }
361///     .with_context(cx)
362///     .await
363/// }
364/// ```
365///
366/// # Detached Spans
367///
368/// Use `@detached` to create a span that returns only the `Context`, without a guard:
369///
370/// ```ignore
371/// let cx = otel::span!(^ "background.task", "priority" = "low");
372/// ```
373///
374/// The span remains open until explicitly ended or the underlying span object is dropped.
375/// Use detached spans when:
376///
377/// - Passing context to a spawned task that will manage its own lifetime
378/// - You need manual control over context attachment
379/// - The span lifetime doesn't match lexical scope
380///
381/// ```ignore
382/// async fn spawn_background_work() {
383///     let cx = otel::span!(^ "background.work");
384///
385///     tokio::spawn(async move {
386///         // Attach context in the spawned task
387///         let _guard = cx.attach();
388///         do_background_work().await;
389///         // Span ends when _guard drops here
390///     });
391/// }
392/// ```
393///
394/// # Span Naming Conventions
395///
396/// Use dot-separated hierarchical names describing the component and operation:
397///
398/// ```text
399/// component.operation          → scheduler.run, cache.get, db.connect
400/// component.subcomponent.op    → lsp.request.send, http.client.fetch
401/// noun.verb                    → file.read, user.authenticate, order.submit
402/// ```
403///
404/// # Common Mistakes
405///
406/// ## Forgetting to propagate context in async code
407///
408/// ```ignore
409/// // WRONG: Context lost at await point
410/// async fn bad_example() {
411///     otel::span!("operation");
412///     do_work().await;  // Span may not be active here!
413/// }
414///
415/// // CORRECT: Use closure form or detached form for async
416/// async fn good_example() {
417///     otel::span!("operation", in |cx| {
418///         do_work().with_context(cx)
419///     }).await;
420/// }
421///
422/// // ALSO CORRECT: Detached form for multiple awaits
423/// async fn also_good() {
424///     let cx = otel::span!(^ "operation");
425///     do_work().with_context(cx).await;
426/// }
427/// ```
428///
429/// ## Using statement form in expression position
430///
431/// ```ignore
432/// // WRONG: Statement form returns (), not a value
433/// fn bad_example() -> Result<Data> {
434///     let result = otel::span!("operation");  // result = ()
435///     get_data()
436/// }
437///
438/// // CORRECT: Use closure form to return values
439/// fn good_example() -> Result<Data> {
440///     otel::span!("operation", in |cx| {
441///         get_data()
442///     })
443/// }
444/// ```
445///
446/// ## Moving context without cloning for concurrent use
447///
448/// ```ignore
449/// // WRONG: cx moved into first iteration
450/// async fn bad_example(ids: Vec<u64>) {
451///     let cx = otel::span!(^ "fetch_all");
452///     for id in ids {
453///         tokio::spawn(fetch(id).with_context(cx));  // cx moved on first iteration!
454///     }
455/// }
456///
457/// // CORRECT: Clone context for each task
458/// async fn good_example(ids: Vec<u64>) {
459///     let cx = otel::span!(^ "fetch_all");
460///     for id in ids {
461///         let cx = cx.clone();
462///         tokio::spawn(async move {
463///             fetch(id).with_context(cx).await
464///         });
465///     }
466/// }
467/// ```
468///
469/// # Requirements
470///
471/// - For variants without `@tracer`: `TRACER` must be in scope (`use crate::TRACER;`)
472/// - For `@tracer` variants: The named tracer must be in scope
473/// - A tracer must have been declared with [`tracer!`]
474#[macro_export]
475macro_rules! span {
476
477  (@maybe_cx $cx:ident, $tracer:ident, $name:expr $(, $($key:literal = $value:expr),+)?) => {
478    let $cx = $crate::span!(@build $tracer, $name $(, $($key = $value),+)?);
479  };
480
481  (@maybe_cx $tracer:ident, $name:expr $(, $($key:literal = $value:expr),+)?) => {
482    let _cx = $crate::span!(@build $tracer, $name $(, $($key = $value),+)?);
483  };
484
485  (@maybe_cx $cx:ident, $tracer:ident, $name:expr, kind = $kind:expr $(, $($key:literal = $value:expr),+)?) => {
486    let $cx = $crate::span!(@build $tracer, $name, kind = $kind $(, $($key = $value),+)?);
487  };
488
489  (@maybe_cx $tracer:ident, $name:expr, kind = $kind:expr $(, $($key:literal = $value:expr),+)?) => {
490    let _cx = $crate::span!(@build $tracer, $name, kind = $kind $(, $($key = $value),+)?);
491  };
492
493  (@maybe_guard $cx:ident, $guard:ident) => {
494    let $guard = $cx.clone().attach();
495  };
496
497  (@maybe_guard $cx:ident $(,)?) => {
498    let _guard = $cx.clone().attach();
499  };
500
501  (@maybe_guard) => {};
502
503
504    // Internal: build span context with automatic attributes and optional custom attributes
505    (@build $tracer:expr, $name:expr $(, $($key:literal = $value:expr),+)?) => {{
506        use opentelemetry::trace::{Tracer as _, TraceContextExt as _};
507        let thread = std::thread::current();
508
509        opentelemetry::Context::current_with_span(
510            $tracer
511                .span_builder($name)
512                .with_attributes([
513                  opentelemetry::KeyValue::new("code.namespace", file!()),
514                  opentelemetry::KeyValue::new("code.lineno", line!() as i64),
515                  opentelemetry::KeyValue::new("code.column", column!() as i64),
516
517                    // waiting on (feature = "thread_id_value", issue = "67939")
518                    // opentelemetry::KeyValue::new("thread.id", std::thread::current().id()),
519                    opentelemetry::KeyValue::new("thread.name", format!("{}",thread.name().unwrap_or("unnamed"))),
520
521                    $($(opentelemetry::KeyValue::new($key, $value)),+)?
522                ])
523                .start_with_context(&*$tracer, &opentelemetry::Context::current())
524        )
525    }};
526
527    // Internal: build span context with SpanKind
528    (@build $tracer:expr, $name:expr, kind = $kind:expr $(, $($key:literal = $value:expr),+)?) => {{
529        use opentelemetry::trace::{Tracer as _, TraceContextExt as _};
530        let thread = std::thread::current();
531
532        opentelemetry::Context::current_with_span(
533            $tracer
534                .span_builder($name)
535                .with_kind($kind)
536                .with_attributes([
537                  opentelemetry::KeyValue::new("code.namespace", file!()),
538                  opentelemetry::KeyValue::new("code.lineno", line!() as i64),
539                  opentelemetry::KeyValue::new("code.column", column!() as i64),
540
541                    // waiting on (feature = "thread_id_value", issue = "67939")
542                    // opentelemetry::KeyValue::new("thread.id", std::thread::current().id()),
543                    opentelemetry::KeyValue::new("thread.name", format!("{}",thread.name().unwrap_or("unnamed"))),
544
545                    $($(opentelemetry::KeyValue::new($key, $value)),+)?
546                ])
547                .start_with_context(&*$tracer, &opentelemetry::Context::current())
548        )
549    }};
550
551    // Default TRACER with kind and attributes, with closure
552    ($name:expr, kind = $kind:expr, $($key:literal = $value:expr),+, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
553        $crate::span!(@maybe_cx $($cx,)? TRACER, $name, kind = $kind, $($key = $value),+);
554        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
555        $body
556    }};
557
558    // Default TRACER with kind, no attributes, with closure
559    ($name:expr, kind = $kind:expr, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
560        $crate::span!(@maybe_cx $($cx,)? TRACER, $name, kind = $kind);
561        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
562        $body
563    }};
564
565    // Default TRACER, no attributes, with closure
566    ($name:expr, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
567        $crate::span!(@maybe_cx $($cx,)? TRACER, $name);
568        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
569
570        $body
571    }};
572
573    // Default TRACER with attributes and closure
574    ($name:expr, $($key:literal = $value:expr),+, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
575        $crate::span!(@maybe_cx $($cx,)? TRACER, $name , $($key = $value),+);
576        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
577        $body
578    }};
579
580    // Explicit tracer with kind and attributes, with closure
581    (@$tracer:ident, $name:expr, kind = $kind:expr, $($key:literal = $value:expr),+, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
582        $crate::span!(@maybe_cx $($cx,)? $tracer, $name, kind = $kind, $($key = $value),+);
583        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
584        $body
585    }};
586
587    // Explicit tracer with kind, no attributes, with closure
588    (@$tracer:ident, $name:expr, kind = $kind:expr, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
589        $crate::span!(@maybe_cx $($cx,)? $tracer, $name, kind = $kind);
590        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
591        $body
592    }};
593
594    // Explicit tracer, no attributes, with closure
595    (@$tracer:ident, $name:expr, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
596      $crate::span!(@maybe_cx $($cx,)? $tracer, $name);
597      $crate::span!(@maybe_guard $($cx,)? $($guard)?);
598        $body
599    }};
600
601    // Explicit tracer with attributes and closure
602    (@$tracer:ident, $name:expr, $($key:literal = $value:expr),+, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
603        $crate::span!(@maybe_cx $($cx,)? $tracer, $name , $($key = $value),+);
604        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
605
606        $body
607    }};
608
609    // Default TRACER with kind and attributes (statement form with internal guard)
610    ($name:expr, kind = $kind:expr, $($key:literal = $value:expr),+ $(,)?) => {
611        let cx = $crate::span!(@build TRACER, $name, kind = $kind, $($key = $value),+);
612        let _otel_guard = cx.clone().attach();
613    };
614
615    // Default TRACER with kind, no attributes (statement form with internal guard)
616    ($name:expr, kind = $kind:expr $(,)?) => {
617        let cx = $crate::span!(@build TRACER, $name, kind = $kind);
618        let _otel_guard = cx.clone().attach();
619    };
620
621    // Default TRACER, no attributes (statement form with internal guard)
622    ($name:expr $(,)?) => {
623        let cx = $crate::span!(@build TRACER, $name);
624        let _otel_guard = cx.clone().attach();
625    };
626
627    // Default TRACER with attributes (statement form with internal guard)
628    ($name:expr, $($key:literal = $value:expr),+ $(,)?) => {
629        let cx = $crate::span!(@build TRACER, $name, $($key = $value),+);
630        let _otel_guard = cx.clone().attach();
631    };
632
633    // Explicit tracer with kind and attributes (statement form with internal guard)
634    (@$tracer:ident, $name:expr, kind = $kind:expr, $($key:literal = $value:expr),+ $(,)?) => {
635        let cx = $crate::span!(@build $tracer, $name, kind = $kind, $($key = $value),+);
636        let _otel_guard = cx.clone().attach();
637    };
638
639    // Explicit tracer with kind, no attributes (statement form with internal guard)
640    (@$tracer:ident, $name:expr, kind = $kind:expr $(,)?) => {
641        let cx = $crate::span!(@build $tracer, $name, kind = $kind);
642        let _otel_guard = cx.clone().attach();
643    };
644
645    // Explicit tracer, no attributes (statement form with internal guard)
646    (@$tracer:ident, $name:expr $(,)?) => {
647        let cx = $crate::span!(@build $tracer, $name);
648        let _otel_guard = cx.clone().attach();
649    };
650
651    // Explicit tracer with attributes (statement form with internal guard)
652    (@$tracer:ident, $name:expr, $($key:literal = $value:expr),+ $(,)?) => {
653        let cx = $crate::span!(@build $tracer, $name, $($key = $value),+);
654        let _otel_guard = cx.clone().attach();
655    };
656
657    // Detached span (no guard) - default TRACER with kind and attributes
658    (^ $name:expr, kind = $kind:expr, $($key:literal = $value:expr),+ $(,)?) => {
659        $crate::span!(@build TRACER, $name, kind = $kind, $($key = $value),+)
660    };
661
662    // Detached span (no guard) - default TRACER with kind, no attributes
663    (^ $name:expr, kind = $kind:expr $(,)?) => {
664        $crate::span!(@build TRACER, $name, kind = $kind)
665    };
666
667    // Detached span (no guard) - default TRACER, no attributes
668    (^ $name:expr $(,)?) => {
669        $crate::span!(@build TRACER, $name)
670    };
671
672    // Detached span (no guard) - default TRACER with attributes
673    (^ $name:expr, $($key:literal = $value:expr),+ $(,)?) => {
674        $crate::span!(@build TRACER, $name, $($key = $value),+)
675    };
676
677    // Detached span (no guard) - explicit tracer with kind and attributes
678    (^ @$tracer:ident, $name:expr, kind = $kind:expr, $($key:literal = $value:expr),+ $(,)?) => {
679        $crate::span!(@build $tracer, $name, kind = $kind, $($key = $value),+)
680    };
681
682    // Detached span (no guard) - explicit tracer with kind, no attributes
683    (^ @$tracer:ident, $name:expr, kind = $kind:expr $(,)?) => {
684        $crate::span!(@build $tracer, $name, kind = $kind)
685    };
686
687    // Detached span (no guard) - explicit tracer, no attributes
688    (^ @$tracer:ident, $name:expr $(,)?) => {
689        $crate::span!(@build $tracer, $name)
690    };
691
692    // Detached span (no guard) - explicit tracer with attributes
693    (^ @$tracer:ident, $name:expr, $($key:literal = $value:expr),+ $(,)?) => {
694        $crate::span!(@build $tracer, $name, $($key = $value),+)
695    };
696}
697
698/// Records an exception event on the current span and returns an error value.
699///
700/// This macro adds an exception event to the current OpenTelemetry span following
701/// the semantic conventions, then evaluates to the provided error value.
702///
703/// # Syntax
704///
705/// ```ignore
706/// // In closures (ok_or_else, map_err, etc.)
707/// .ok_or_else(|| otel::exception!("invalid_params", Error::new("msg")))?
708///
709/// // In function bodies with explicit return
710/// return Err(otel::exception!("invalid_params", Error::new("msg")));
711///
712/// // With additional attributes
713/// otel::exception!("source_not_found", error, "uri" => uri.to_string())
714/// ```
715///
716/// # Parameters
717///
718/// - `exception_type`: String literal for the exception.type attribute
719/// - `error_value`: The error value to return (must implement Display for exception.message)
720/// - Additional key-value pairs: Optional extra attributes to add to the exception event
721///
722/// # Examples
723///
724/// ```ignore
725/// // In ok_or_else closure
726/// let arg = arguments.first().ok_or_else(|| {
727///     otel::exception!("invalid_params",
728///         jsonrpc::Error::invalid_params("Missing arguments"))
729/// })?;
730///
731/// // In map_err closure
732/// let uri = Uri::parse(uri_str).map_err(|e| {
733///     otel::exception!("invalid_uri",
734///         jsonrpc::Error::invalid_params(format!("Invalid URI: {}", e)),
735///         "uri_str" => uri_str.to_string()
736///     )
737/// })?;
738///
739/// // Direct return
740/// if cache.is_empty() {
741///     return Err(otel::exception!("cache_empty",
742///         jsonrpc::Error::internal_error("Cache is empty"),
743///         "operation" => "lookup"
744///     ));
745/// }
746/// ```
747///
748/// # OpenTelemetry Semantic Conventions
749///
750/// The macro follows the [OpenTelemetry semantic conventions for exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/):
751///
752/// - Event name: `"exception"`
753/// - Required attributes:
754///   - `exception.type`: The type/category of the exception
755///   - `exception.message`: The error message extracted via Display
756/// - Additional attributes: Any extra key-value pairs provided
757#[macro_export]
758macro_rules! exception {
759    ($exception_type:expr, $error:expr $(, $key:literal = $value:expr)* $(,)?) => {{
760        use opentelemetry::trace::TraceContextExt as _;
761        let error_value = $error;
762        let error_message = format!("{}", error_value);
763
764        opentelemetry::Context::current().span().add_event(
765            "exception",
766            vec![
767                opentelemetry::KeyValue::new("exception.type", $exception_type),
768                opentelemetry::KeyValue::new("exception.message", error_message),
769                $(opentelemetry::KeyValue::new($key, $value)),*
770            ],
771        );
772
773        error_value
774    }};
775}
776
777/// Records an error event on the current span and returns an error value.
778///
779/// This macro adds an error event to the current OpenTelemetry span following
780/// the semantic conventions, then evaluates to the provided error value.
781///
782/// # Syntax
783///
784/// ```ignore
785/// // In closures (ok_or_else, map_err, etc.)
786/// .ok_or_else(|| otel::error!("timeout", Error::new("msg")))?
787///
788/// // In function bodies with explicit return
789/// return Err(otel::error!("validation_failed", Error::new("msg")));
790///
791/// // With additional attributes
792/// otel::error!("database_unavailable", error, "retry_count" = 3)
793/// ```
794///
795/// # Parameters
796///
797/// - `error_type`: String literal for the error.type attribute
798/// - `error_value`: The error value to return (must implement Display for error.message)
799/// - Additional key-value pairs: Optional extra attributes to add to the error event
800///
801/// # Examples
802///
803/// ```ignore
804/// // In ok_or_else closure
805/// let config = load_config().ok_or_else(|| {
806///     otel::error!("config_not_found",
807///         jsonrpc::Error::internal_error("Configuration file not found"))
808/// })?;
809///
810/// // In map_err closure
811/// let connection = establish_connection().map_err(|e| {
812///     otel::error!("connection_failed",
813///         jsonrpc::Error::internal_error(format!("Failed to connect: {}", e)),
814///         "host" = host.to_string(),
815///         "port" = port as i64
816///     )
817/// })?;
818///
819/// // Direct return
820/// if !is_valid {
821///     return Err(otel::error!("invalid_state",
822///         jsonrpc::Error::internal_error("System is in an invalid state"),
823///         "state" = current_state
824///     ));
825/// }
826/// ```
827///
828/// # OpenTelemetry Semantic Conventions
829///
830/// The macro follows the [OpenTelemetry semantic conventions for errors](https://opentelemetry.io/docs/specs/semconv/attributes-registry/error/):
831///
832/// - Event name: `"error"`
833/// - Required attributes:
834///   - `error.type`: Describes the class of error the operation ended with
835///   - `error.message`: A human-readable message providing more detail about the error
836/// - Additional attributes: Any extra key-value pairs provided
837///
838/// # Difference from `exception!`
839///
840/// While both macros record error-related events, they follow different semantic conventions:
841///
842/// - `error!`: For general error conditions (uses `error.type` and `error.message`)
843/// - `exception!`: For programming exceptions with stack traces (uses `exception.type` and `exception.message`)
844///
845/// Use `error!` for application-level errors and `exception!` for exception handling.
846#[macro_export]
847macro_rules! error {
848    ($error_type:expr, $error:expr $(, $key:literal = $value:expr)* $(,)?) => {{
849        use opentelemetry::trace::TraceContextExt as _;
850        let error_value = $error;
851        let error_message = format!("{}", error_value);
852
853        opentelemetry::Context::current().span().add_event(
854            "error",
855            vec![
856                opentelemetry::KeyValue::new("error.type", $error_type),
857                opentelemetry::KeyValue::new("error.message", error_message),
858                $(opentelemetry::KeyValue::new($key, $value)),*
859            ],
860        );
861
862        error_value
863    }};
864}
865
866/// Adds an event to the current span with optional attributes.
867///
868/// # Examples
869///
870/// ```ignore
871/// // Simple event
872/// otel::event!("query.start");
873///
874/// // Event with attributes
875/// otel::event!("query.result",
876///   "record_count" = 42,
877///   "partition_key" = "users"
878/// );
879/// ```
880#[macro_export]
881macro_rules! event {
882    ($event_name:expr $(, $key:literal = $value:expr)* $(,)?) => {
883        use opentelemetry::trace::TraceContextExt as _;
884        opentelemetry::Context::current().span().add_event(
885            $event_name,
886            vec![
887                $(opentelemetry::KeyValue::new($key, $value)),*
888            ],
889        );
890    };
891}
892
893#[cfg(test)]
894mod tests {
895  use super::*;
896
897  tracer!();
898
899  #[test]
900  fn span_with_closure_returns_value() {
901    let result = span!("test.closure", in |_cx| {
902        42
903    });
904    assert_eq!(result, 42);
905  }
906
907  #[test]
908  fn span_with_closure_and_attributes() {
909    let result = span!("test.closure_attrs", "foo" = "bar", in |_cx| {
910        "hello"
911    });
912    assert_eq!(result, "hello");
913  }
914
915  #[test]
916  fn span_with_closure_explicit_tracer() {
917    let result = span!(@TRACER, "test.explicit", in |_cx| {
918        100
919    });
920    assert_eq!(result, 100);
921  }
922
923  #[test]
924  fn span_with_closure_explicit_tracer_and_attributes() {
925    let result = span!(@TRACER, "test.explicit_attrs", "key" = "value", in |_cx| {
926        vec![1, 2, 3]
927    });
928    assert_eq!(result, vec![1, 2, 3]);
929  }
930}