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  ($name:ident) => {
119    paste::paste! {
120      pub(crate) static [<$name:snake:upper _TRACER>]: std::sync::LazyLock<opentelemetry::global::BoxedTracer> =
121        std::sync::LazyLock::new(|| {
122          use opentelemetry::trace::TracerProvider;
123
124          opentelemetry::global::tracer_provider().tracer_with_scope(
125            opentelemetry::InstrumentationScope::builder(concat!(
126              env!("CARGO_PKG_NAME"),
127              ".", stringify!([<$name:snake>])
128            ))
129            .with_version(env!("CARGO_PKG_VERSION"))
130            .with_schema_url(format!("https://opentelemetry.io/schemas/{}", $crate::OTEL_SPEC_VERSION))
131            .build(),
132          )
133        });
134    }
135  };
136
137  () => {
138    paste::paste! {
139      pub(crate) static TRACER: std::sync::LazyLock<opentelemetry::global::BoxedTracer> =
140        std::sync::LazyLock::new(|| {
141          use opentelemetry::trace::TracerProvider;
142
143          opentelemetry::global::tracer_provider().tracer_with_scope(
144            opentelemetry::InstrumentationScope::builder(concat!(
145              env!("CARGO_PKG_NAME"),
146            ))
147            .with_version(env!("CARGO_PKG_VERSION"))
148            .with_schema_url(format!("https://opentelemetry.io/schemas/{}", $crate::OTEL_SPEC_VERSION))
149            .build(),
150          )
151        });
152    }
153  };
154}
155
156/// Creates an OpenTelemetry span for tracing execution.
157///
158/// This macro creates a span with automatic code location attributes and returns
159/// both the span's [`Context`] and a [`ContextGuard`] that keeps the span active.
160///
161/// # Return Value
162///
163/// Returns `(Context, ContextGuard)`:
164///
165/// - **Context**: The OpenTelemetry context containing the active span. Clone this
166///   to propagate through async boundaries or pass to child operations.
167/// - **ContextGuard**: RAII guard that keeps the context attached to the current thread.
168///   When dropped, the span ends and is exported.
169///
170/// **Important**: The span is only active while the guard is held. Dropping the guard
171/// ends the span, so keep it in scope for the duration of the traced operation.
172///
173/// # Syntax Variants
174///
175/// | Syntax | Returns | Use Case |
176/// |--------|---------|----------|
177/// | `span!("name")` | `()` | Default tracer, creates internal guard |
178/// | `span!("name", "k" = v, ...)` | `()` | With custom attributes, internal guard |
179/// | `span!("name", in \|cx\| { ... })` | `T` | With closure (auto-managed lifetime) |
180/// | `span!("name", "k" = v, in \|cx\| { ... })` | `T` | Closure + attributes |
181/// | `span!(@TRACER, "name")` | `()` | Explicit tracer, internal guard |
182/// | `span!(@TRACER, "name", "k" = v)` | `()` | Explicit tracer + attributes, internal guard |
183/// | `span!(@TRACER, "name", in \|cx\| { ... })` | `T` | Explicit tracer with closure |
184/// | `span!(@TRACER, "name", "k" = v, in \|cx\| { ... })` | `T` | Explicit tracer + attributes + closure |
185/// | `span!(^ "name")` | `Context` | Detached span (no automatic guard) |
186/// | `span!(^ "name", "k" = v)` | `Context` | Detached with attributes |
187/// | `span!(^ @TRACER, "name")` | `Context` | Detached with explicit tracer |
188///
189/// # Automatic Attributes
190///
191/// All spans automatically include:
192///
193/// | Attribute | Description |
194/// |-----------|-------------|
195/// | `code.file.path` | Source file (workspace-relative via [`relative_filepath`]) |
196/// | `code.line.number` | Line number where span was created |
197/// | `code.column.number` | Column number |
198/// | `thread.id` | Current thread ID |
199/// | `thread.name` | Current thread name (or "unnamed") |
200///
201/// # Synchronous Usage
202///
203/// ## Pattern 1: Statement form (automatic guard management)
204///
205/// The simplest form creates an internal guard that manages span lifetime automatically:
206///
207/// ```ignore
208/// fn process_item(item: &Item) {
209///     otel::span!("item.process", "item.id" = item.id);
210///
211///     validate(item);
212///     transform(item);
213///     save(item);
214///
215///     // Span ends automatically at end of scope
216/// }
217/// ```
218///
219/// Nested spans automatically form parent-child relationships:
220///
221/// ```ignore
222/// fn process_batch(items: &[Item]) {
223///     otel::span!("batch.process", "count" = items.len() as i64);
224///
225///     for item in items {
226///         // Automatically becomes a child of "batch.process"
227///         otel::span!("item.process", "item.id" = item.id);
228///         process_item(item);
229///     }
230/// }
231/// ```
232///
233/// ## Pattern 2: Closure-based spans
234///
235/// Use `in` closure syntax for automatic span lifetime management:
236///
237/// ```ignore
238/// use crate::TRACER;
239///
240/// fn process_item(item: &Item) -> Result<ProcessedItem> {
241///     otel::span!("item.process", "item.id" = item.id, in |cx| {
242///         validate(item)?;
243///         let transformed = transform(item)?;
244///         save(&transformed)?;
245///         Ok(transformed)
246///     })
247/// }
248/// ```
249///
250/// The closure receives the `Context` as a parameter and can return any value.
251/// The span ends automatically when the closure completes or panics:
252///
253/// ```ignore
254/// // Simple computation
255/// let result = otel::span!("compute", in |cx| {
256///     expensive_calculation()
257/// });
258///
259/// // With attributes
260/// let user = otel::span!("db.fetch", "user.id" = user_id, in |cx| {
261///     db.get_user(user_id)
262/// })?;
263///
264/// // Explicit tracer
265/// let data = otel::span!(@CUSTOM_TRACER, "custom.operation", in |cx| {
266///     do_work()
267/// });
268/// ```
269///
270/// # Asynchronous Usage
271///
272/// OpenTelemetry context is stored in thread-local storage. Since async tasks can
273/// migrate between threads at `.await` points, you must explicitly propagate context
274/// using [`FutureExt::with_context`].
275///
276/// ## Pattern 1: Closure form for single async operation
277///
278/// ```ignore
279/// use opentelemetry::trace::FutureExt;
280///
281/// async fn fetch_user(id: u64) -> Result<User> {
282///     otel::span!("user.fetch", "user.id" = id as i64, in |cx| {
283///         db.get_user(id)
284///             .with_context(cx)
285///     }).await
286/// }
287/// ```
288///
289/// ## Pattern 2: Detached form for sequential awaits
290///
291/// Use the detached form (`@detached`) to get a context variable for multiple await points:
292///
293/// ```ignore
294/// use opentelemetry::trace::FutureExt;
295///
296/// async fn process_order(order_id: u64) -> Result<()> {
297///     let cx = otel::span!(^ "order.process", "order.id" = order_id as i64);
298///
299///     let order = fetch_order(order_id)
300///         .with_context(cx.clone())
301///         .await?;
302///
303///     validate_order(&order)
304///         .with_context(cx.clone())
305///         .await?;
306///
307///     submit_order(&order)
308///         .with_context(cx)  // Last use, no clone needed
309///         .await
310/// }
311/// ```
312///
313/// ## Pattern 3: Concurrent/spawned tasks
314///
315/// Use detached form and clone the context for each spawned task:
316///
317/// ```ignore
318/// use opentelemetry::trace::FutureExt;
319///
320/// async fn fetch_all(ids: Vec<u64>) -> Vec<Result<Item>> {
321///     let cx = otel::span!(^ "items.fetch_all", "count" = ids.len() as i64);
322///
323///     let futures: Vec<_> = ids
324///         .into_iter()
325///         .map(|id| {
326///             let cx = cx.clone();
327///             async move {
328///                 fetch_item(id)
329///                     .with_context(cx)
330///                     .await
331///             }
332///         })
333///         .collect();
334///
335///     futures::future::join_all(futures).await
336/// }
337/// ```
338///
339/// ## Pattern 4: Child spans in async blocks
340///
341/// Use detached form for parent span, then create child spans inside async blocks:
342///
343/// ```ignore
344/// use opentelemetry::trace::FutureExt;
345///
346/// async fn pipeline() -> Result<()> {
347///     let cx = otel::span!(^ "pipeline.run");
348///
349///     async {
350///         otel::span!("pipeline.phase1");
351///         do_phase1().await
352///     }
353///     .with_context(cx.clone())
354///     .await?;
355///
356///     async {
357///         otel::span!("pipeline.phase2");
358///         do_phase2().await
359///     }
360///     .with_context(cx)
361///     .await
362/// }
363/// ```
364///
365/// # Detached Spans
366///
367/// Use `@detached` to create a span that returns only the `Context`, without a guard:
368///
369/// ```ignore
370/// let cx = otel::span!(^ "background.task", "priority" = "low");
371/// ```
372///
373/// The span remains open until explicitly ended or the underlying span object is dropped.
374/// Use detached spans when:
375///
376/// - Passing context to a spawned task that will manage its own lifetime
377/// - You need manual control over context attachment
378/// - The span lifetime doesn't match lexical scope
379///
380/// ```ignore
381/// async fn spawn_background_work() {
382///     let cx = otel::span!(^ "background.work");
383///
384///     tokio::spawn(async move {
385///         // Attach context in the spawned task
386///         let _guard = cx.attach();
387///         do_background_work().await;
388///         // Span ends when _guard drops here
389///     });
390/// }
391/// ```
392///
393/// # Span Naming Conventions
394///
395/// Use dot-separated hierarchical names describing the component and operation:
396///
397/// ```text
398/// component.operation          → scheduler.run, cache.get, db.connect
399/// component.subcomponent.op    → lsp.request.send, http.client.fetch
400/// noun.verb                    → file.read, user.authenticate, order.submit
401/// ```
402///
403/// # Common Mistakes
404///
405/// ## Forgetting to propagate context in async code
406///
407/// ```ignore
408/// // WRONG: Context lost at await point
409/// async fn bad_example() {
410///     otel::span!("operation");
411///     do_work().await;  // Span may not be active here!
412/// }
413///
414/// // CORRECT: Use closure form or detached form for async
415/// async fn good_example() {
416///     otel::span!("operation", in |cx| {
417///         do_work().with_context(cx)
418///     }).await;
419/// }
420///
421/// // ALSO CORRECT: Detached form for multiple awaits
422/// async fn also_good() {
423///     let cx = otel::span!(^ "operation");
424///     do_work().with_context(cx).await;
425/// }
426/// ```
427///
428/// ## Using statement form in expression position
429///
430/// ```ignore
431/// // WRONG: Statement form returns (), not a value
432/// fn bad_example() -> Result<Data> {
433///     let result = otel::span!("operation");  // result = ()
434///     get_data()
435/// }
436///
437/// // CORRECT: Use closure form to return values
438/// fn good_example() -> Result<Data> {
439///     otel::span!("operation", in |cx| {
440///         get_data()
441///     })
442/// }
443/// ```
444///
445/// ## Moving context without cloning for concurrent use
446///
447/// ```ignore
448/// // WRONG: cx moved into first iteration
449/// async fn bad_example(ids: Vec<u64>) {
450///     let cx = otel::span!(^ "fetch_all");
451///     for id in ids {
452///         tokio::spawn(fetch(id).with_context(cx));  // cx moved on first iteration!
453///     }
454/// }
455///
456/// // CORRECT: Clone context for each task
457/// async fn good_example(ids: Vec<u64>) {
458///     let cx = otel::span!(^ "fetch_all");
459///     for id in ids {
460///         let cx = cx.clone();
461///         tokio::spawn(async move {
462///             fetch(id).with_context(cx).await
463///         });
464///     }
465/// }
466/// ```
467///
468/// # Requirements
469///
470/// - For variants without `@tracer`: `TRACER` must be in scope (`use crate::TRACER;`)
471/// - For `@tracer` variants: The named tracer must be in scope
472/// - A tracer must have been declared with [`tracer!`]
473#[macro_export]
474macro_rules! span {
475    // -------------------------------------------------------------------------
476    // @build rules - 3 variants for context handling
477    // -------------------------------------------------------------------------
478    //
479    // Keyword order: kind → context → links → attrs
480    //
481    // Note: We need 3 @build rules because $($ctx:tt)+ is greedy and would
482    // consume tokens beyond the context expression. Using $ctx:expr is bounded.
483
484    // @build with context = None (literal match, must come first)
485    (@build $tracer:expr, $name:expr
486        $(, kind = $kind:expr)?
487        , context = None
488        $(, links = [$($link:expr),* $(,)?])?
489        $(, $($key:literal = $value:expr),+)?) => {{
490        use opentelemetry::trace::{Tracer as _, TraceContextExt};
491        let thread = std::thread::current();
492        let parent_cx = opentelemetry::Context::new();
493
494        TraceContextExt::with_span(
495            &parent_cx,
496            $tracer
497                .span_builder($name)
498                $(.with_kind($kind))?
499                $(.with_links(vec![$($link),*]))?
500                .with_attributes([
501                    opentelemetry::KeyValue::new("code.namespace", file!()),
502                    opentelemetry::KeyValue::new("code.lineno", line!() as i64),
503                    opentelemetry::KeyValue::new("code.column", column!() as i64),
504                    opentelemetry::KeyValue::new("thread.name", format!("{}", thread.name().unwrap_or("unnamed"))),
505                    $($(opentelemetry::KeyValue::new($key, $value)),+)?
506                ])
507                .start_with_context(&*$tracer, &parent_cx)
508        )
509    }};
510
511    // @build with context = $ctx:expr (expression match)
512    (@build $tracer:expr, $name:expr
513        $(, kind = $kind:expr)?
514        , context = $ctx:expr
515        $(, links = [$($link:expr),* $(,)?])?
516        $(, $($key:literal = $value:expr),+)?) => {{
517        use opentelemetry::trace::{Tracer as _, TraceContextExt};
518        let thread = std::thread::current();
519        let parent_cx = $ctx;
520
521        TraceContextExt::with_span(
522            &parent_cx,
523            $tracer
524                .span_builder($name)
525                $(.with_kind($kind))?
526                $(.with_links(vec![$($link),*]))?
527                .with_attributes([
528                    opentelemetry::KeyValue::new("code.namespace", file!()),
529                    opentelemetry::KeyValue::new("code.lineno", line!() as i64),
530                    opentelemetry::KeyValue::new("code.column", column!() as i64),
531                    opentelemetry::KeyValue::new("thread.name", format!("{}", thread.name().unwrap_or("unnamed"))),
532                    $($(opentelemetry::KeyValue::new($key, $value)),+)?
533                ])
534                .start_with_context(&*$tracer, &parent_cx)
535        )
536    }};
537
538    // @build without context (uses current context)
539    (@build $tracer:expr, $name:expr
540        $(, kind = $kind:expr)?
541        $(, links = [$($link:expr),* $(,)?])?
542        $(, $($key:literal = $value:expr),+)?) => {{
543        use opentelemetry::trace::{Tracer as _, TraceContextExt};
544        let thread = std::thread::current();
545        let parent_cx = opentelemetry::Context::current();
546
547        TraceContextExt::with_span(
548            &parent_cx,
549            $tracer
550                .span_builder($name)
551                $(.with_kind($kind))?
552                $(.with_links(vec![$($link),*]))?
553                .with_attributes([
554                    opentelemetry::KeyValue::new("code.namespace", file!()),
555                    opentelemetry::KeyValue::new("code.lineno", line!() as i64),
556                    opentelemetry::KeyValue::new("code.column", column!() as i64),
557                    opentelemetry::KeyValue::new("thread.name", format!("{}", thread.name().unwrap_or("unnamed"))),
558                    $($(opentelemetry::KeyValue::new($key, $value)),+)?
559                ])
560                .start_with_context(&*$tracer, &parent_cx)
561        )
562    }};
563
564    // -------------------------------------------------------------------------
565    // CONSOLIDATED USER-FACING RULES
566    // -------------------------------------------------------------------------
567    //
568    // Keyword order: kind → context → links → attrs
569    //
570    // Note: Closure forms need separate rules for context = None, context = $expr,
571    // and no context, to avoid ambiguity between $($ctx:tt)+ and ", in |...|"
572    // -------------------------------------------------------------------------
573
574    // -------------------------------------------------------------------------
575    // CLOSURE FORMS - with context = None (literal match, must come first)
576    // -------------------------------------------------------------------------
577
578    // Default TRACER with context = None
579    ($name:expr
580        $(, kind = $kind:expr)?
581        , context = None
582        $(, links = [$($link:expr),* $(,)?])?
583        $(, $($key:literal = $value:expr),+)?
584        , in |$($cx_var:ident)? $(,$guard:ident)?| $body:expr) => {{
585        let cx = $crate::span!(@build TRACER, $name
586            $(, kind = $kind)?
587            , context = None
588            $(, links = [$($link),*])?
589            $(, $($key = $value),+)?);
590        let _guard = cx.clone().attach();
591        $(let $cx_var = cx.clone();)?
592        $(let $guard = _guard;)?
593        $body
594    }};
595
596    // Explicit @TRACER with context = None
597    (@$tracer:ident, $name:expr
598        $(, kind = $kind:expr)?
599        , context = None
600        $(, links = [$($link:expr),* $(,)?])?
601        $(, $($key:literal = $value:expr),+)?
602        , in |$($cx_var:ident)? $(,$guard:ident)?| $body:expr) => {{
603        let cx = $crate::span!(@build $tracer, $name
604            $(, kind = $kind)?
605            , context = None
606            $(, links = [$($link),*])?
607            $(, $($key = $value),+)?);
608        let _guard = cx.clone().attach();
609        $(let $cx_var = cx.clone();)?
610        $(let $guard = _guard;)?
611        $body
612    }};
613
614    // -------------------------------------------------------------------------
615    // CLOSURE FORMS - with explicit context expression
616    // -------------------------------------------------------------------------
617
618    // Default TRACER with explicit context
619    ($name:expr
620        $(, kind = $kind:expr)?
621        , context = $ctx:expr
622        $(, links = [$($link:expr),* $(,)?])?
623        $(, $($key:literal = $value:expr),+)?
624        , in |$($cx_var:ident)? $(,$guard:ident)?| $body:expr) => {{
625        let cx = $crate::span!(@build TRACER, $name
626            $(, kind = $kind)?
627            , context = $ctx
628            $(, links = [$($link),*])?
629            $(, $($key = $value),+)?);
630        let _guard = cx.clone().attach();
631        $(let $cx_var = cx.clone();)?
632        $(let $guard = _guard;)?
633        $body
634    }};
635
636    // Explicit @TRACER with explicit context
637    (@$tracer:ident, $name:expr
638        $(, kind = $kind:expr)?
639        , context = $ctx:expr
640        $(, links = [$($link:expr),* $(,)?])?
641        $(, $($key:literal = $value:expr),+)?
642        , in |$($cx_var:ident)? $(,$guard:ident)?| $body:expr) => {{
643        let cx = $crate::span!(@build $tracer, $name
644            $(, kind = $kind)?
645            , context = $ctx
646            $(, links = [$($link),*])?
647            $(, $($key = $value),+)?);
648        let _guard = cx.clone().attach();
649        $(let $cx_var = cx.clone();)?
650        $(let $guard = _guard;)?
651        $body
652    }};
653
654    // -------------------------------------------------------------------------
655    // CLOSURE FORMS - without context (uses current)
656    // -------------------------------------------------------------------------
657
658    // Default TRACER without context
659    ($name:expr
660        $(, kind = $kind:expr)?
661        $(, links = [$($link:expr),* $(,)?])?
662        $(, $($key:literal = $value:expr),+)?
663        , in |$($cx_var:ident)? $(,$guard:ident)?| $body:expr) => {{
664        let cx = $crate::span!(@build TRACER, $name
665            $(, kind = $kind)?
666            $(, links = [$($link),*])?
667            $(, $($key = $value),+)?);
668        let _guard = cx.clone().attach();
669        $(let $cx_var = cx.clone();)?
670        $(let $guard = _guard;)?
671        $body
672    }};
673
674    // Explicit @TRACER without context
675    (@$tracer:ident, $name:expr
676        $(, kind = $kind:expr)?
677        $(, links = [$($link:expr),* $(,)?])?
678        $(, $($key:literal = $value:expr),+)?
679        , in |$($cx_var:ident)? $(,$guard:ident)?| $body:expr) => {{
680        let cx = $crate::span!(@build $tracer, $name
681            $(, kind = $kind)?
682            $(, links = [$($link),*])?
683            $(, $($key = $value),+)?);
684        let _guard = cx.clone().attach();
685        $(let $cx_var = cx.clone();)?
686        $(let $guard = _guard;)?
687        $body
688    }};
689
690    // -------------------------------------------------------------------------
691    // DETACHED FORM - context = None (literal match, must come first)
692    // -------------------------------------------------------------------------
693
694    // Default TRACER with context = None
695    (^ $name:expr
696        $(, kind = $kind:expr)?
697        , context = None
698        $(, links = [$($link:expr),* $(,)?])?
699        $(, $($key:literal = $value:expr),+)?
700        $(,)?) => {
701        $crate::span!(@build TRACER, $name
702            $(, kind = $kind)?
703            , context = None
704            $(, links = [$($link),*])?
705            $(, $($key = $value),+)?)
706    };
707
708    // Explicit @TRACER with context = None
709    (^ @$tracer:ident, $name:expr
710        $(, kind = $kind:expr)?
711        , context = None
712        $(, links = [$($link:expr),* $(,)?])?
713        $(, $($key:literal = $value:expr),+)?
714        $(,)?) => {
715        $crate::span!(@build $tracer, $name
716            $(, kind = $kind)?
717            , context = None
718            $(, links = [$($link),*])?
719            $(, $($key = $value),+)?)
720    };
721
722    // -------------------------------------------------------------------------
723    // DETACHED FORM - context = $ctx:expr (expression match)
724    // -------------------------------------------------------------------------
725
726    // Default TRACER with explicit context
727    (^ $name:expr
728        $(, kind = $kind:expr)?
729        , context = $ctx:expr
730        $(, links = [$($link:expr),* $(,)?])?
731        $(, $($key:literal = $value:expr),+)?
732        $(,)?) => {
733        $crate::span!(@build TRACER, $name
734            $(, kind = $kind)?
735            , context = $ctx
736            $(, links = [$($link),*])?
737            $(, $($key = $value),+)?)
738    };
739
740    // Explicit @TRACER with explicit context
741    (^ @$tracer:ident, $name:expr
742        $(, kind = $kind:expr)?
743        , context = $ctx:expr
744        $(, links = [$($link:expr),* $(,)?])?
745        $(, $($key:literal = $value:expr),+)?
746        $(,)?) => {
747        $crate::span!(@build $tracer, $name
748            $(, kind = $kind)?
749            , context = $ctx
750            $(, links = [$($link),*])?
751            $(, $($key = $value),+)?)
752    };
753
754    // -------------------------------------------------------------------------
755    // DETACHED FORM - without context (uses current)
756    // -------------------------------------------------------------------------
757
758    // Default TRACER without context
759    (^ $name:expr
760        $(, kind = $kind:expr)?
761        $(, links = [$($link:expr),* $(,)?])?
762        $(, $($key:literal = $value:expr),+)?
763        $(,)?) => {
764        $crate::span!(@build TRACER, $name
765            $(, kind = $kind)?
766            $(, links = [$($link),*])?
767            $(, $($key = $value),+)?)
768    };
769
770    // Explicit @TRACER without context
771    (^ @$tracer:ident, $name:expr
772        $(, kind = $kind:expr)?
773        $(, links = [$($link:expr),* $(,)?])?
774        $(, $($key:literal = $value:expr),+)?
775        $(,)?) => {
776        $crate::span!(@build $tracer, $name
777            $(, kind = $kind)?
778            $(, links = [$($link),*])?
779            $(, $($key = $value),+)?)
780    };
781
782    // -------------------------------------------------------------------------
783    // STATEMENT FORM - context = None (literal match, must come first)
784    // -------------------------------------------------------------------------
785
786    // Default TRACER with context = None
787    ($name:expr
788        $(, kind = $kind:expr)?
789        , context = None
790        $(, links = [$($link:expr),* $(,)?])?
791        $(, $($key:literal = $value:expr),+)?
792        $(,)?) => {
793        let cx = $crate::span!(@build TRACER, $name
794            $(, kind = $kind)?
795            , context = None
796            $(, links = [$($link),*])?
797            $(, $($key = $value),+)?);
798        let _otel_guard = cx.clone().attach();
799    };
800
801    // Explicit @TRACER with context = None
802    (@$tracer:ident, $name:expr
803        $(, kind = $kind:expr)?
804        , context = None
805        $(, links = [$($link:expr),* $(,)?])?
806        $(, $($key:literal = $value:expr),+)?
807        $(,)?) => {
808        let cx = $crate::span!(@build $tracer, $name
809            $(, kind = $kind)?
810            , context = None
811            $(, links = [$($link),*])?
812            $(, $($key = $value),+)?);
813        let _otel_guard = cx.clone().attach();
814    };
815
816    // =-------------------------------------------------------------------------    // STATEMENT FORM - context = $ctx:expr (expression match)
817    // -------------------------------------------------------------------------
818
819    // Default TRACER with explicit context
820    ($name:expr
821        $(, kind = $kind:expr)?
822        , context = $ctx:expr
823        $(, links = [$($link:expr),* $(,)?])?
824        $(, $($key:literal = $value:expr),+)?
825        $(,)?) => {
826        let cx = $crate::span!(@build TRACER, $name
827            $(, kind = $kind)?
828            , context = $ctx
829            $(, links = [$($link),*])?
830            $(, $($key = $value),+)?);
831        let _otel_guard = cx.clone().attach();
832    };
833
834    // Explicit @TRACER with explicit context
835    (@$tracer:ident, $name:expr
836        $(, kind = $kind:expr)?
837        , context = $ctx:expr
838        $(, links = [$($link:expr),* $(,)?])?
839        $(, $($key:literal = $value:expr),+)?
840        $(,)?) => {
841        let cx = $crate::span!(@build $tracer, $name
842            $(, kind = $kind)?
843            , context = $ctx
844            $(, links = [$($link),*])?
845            $(, $($key = $value),+)?);
846        let _otel_guard = cx.clone().attach();
847    };
848
849    // -------------------------------------------------------------------------
850    // STATEMENT FORM - without context (uses current)
851    // -------------------------------------------------------------------------
852
853    // Default TRACER without context
854    ($name:expr
855        $(, kind = $kind:expr)?
856        $(, links = [$($link:expr),* $(,)?])?
857        $(, $($key:literal = $value:expr),+)?
858        $(,)?) => {
859        let cx = $crate::span!(@build TRACER, $name
860            $(, kind = $kind)?
861            $(, links = [$($link),*])?
862            $(, $($key = $value),+)?);
863        let _otel_guard = cx.clone().attach();
864    };
865
866    // Explicit @TRACER without context
867    (@$tracer:ident, $name:expr
868        $(, kind = $kind:expr)?
869        $(, links = [$($link:expr),* $(,)?])?
870        $(, $($key:literal = $value:expr),+)?
871        $(,)?) => {
872        let cx = $crate::span!(@build $tracer, $name
873            $(, kind = $kind)?
874            $(, links = [$($link),*])?
875            $(, $($key = $value),+)?);
876        let _otel_guard = cx.clone().attach();
877    };
878}
879
880/// Records an exception event on the current span and returns an error value.
881///
882/// This macro adds an exception event to the current OpenTelemetry span following
883/// the semantic conventions, then evaluates to the provided error value.
884///
885/// # Syntax
886///
887/// ```ignore
888/// // In closures (ok_or_else, map_err, etc.)
889/// .ok_or_else(|| otel::exception!("invalid_params", Error::new("msg")))?
890///
891/// // In function bodies with explicit return
892/// return Err(otel::exception!("invalid_params", Error::new("msg")));
893///
894/// // With additional attributes
895/// otel::exception!("source_not_found", error, "uri" => uri.to_string())
896/// ```
897///
898/// # Parameters
899///
900/// - `exception_type`: String literal for the exception.type attribute
901/// - `error_value`: The error value to return (must implement Display for exception.message)
902/// - Additional key-value pairs: Optional extra attributes to add to the exception event
903///
904/// # Examples
905///
906/// ```ignore
907/// // In ok_or_else closure
908/// let arg = arguments.first().ok_or_else(|| {
909///     otel::exception!("invalid_params",
910///         jsonrpc::Error::invalid_params("Missing arguments"))
911/// })?;
912///
913/// // In map_err closure
914/// let uri = Uri::parse(uri_str).map_err(|e| {
915///     otel::exception!("invalid_uri",
916///         jsonrpc::Error::invalid_params(format!("Invalid URI: {}", e)),
917///         "uri_str" => uri_str.to_string()
918///     )
919/// })?;
920///
921/// // Direct return
922/// if cache.is_empty() {
923///     return Err(otel::exception!("cache_empty",
924///         jsonrpc::Error::internal_error("Cache is empty"),
925///         "operation" => "lookup"
926///     ));
927/// }
928/// ```
929///
930/// # OpenTelemetry Semantic Conventions
931///
932/// The macro follows the [OpenTelemetry semantic conventions for exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/):
933///
934/// - Event name: `"exception"`
935/// - Required attributes:
936///   - `exception.type`: The type/category of the exception
937///   - `exception.message`: The error message extracted via Display
938/// - Additional attributes: Any extra key-value pairs provided
939#[macro_export]
940macro_rules! exception {
941    ($exception_type:expr, $error:expr $(, $key:literal = $value:expr)* $(,)?) => {{
942        use opentelemetry::trace::TraceContextExt as _;
943        let error_value = $error;
944        let error_message = format!("{}", error_value);
945
946        opentelemetry::Context::current().span().add_event(
947            "exception",
948            vec![
949                opentelemetry::KeyValue::new("exception.type", $exception_type),
950                opentelemetry::KeyValue::new("exception.message", error_message),
951                $(opentelemetry::KeyValue::new($key, $value)),*
952            ],
953        );
954
955        error_value
956    }};
957}
958
959/// Records an error event on the current span and returns an error value.
960///
961/// This macro adds an error event to the current OpenTelemetry span following
962/// the semantic conventions, then evaluates to the provided error value.
963///
964/// # Syntax
965///
966/// ```ignore
967/// // In closures (ok_or_else, map_err, etc.)
968/// .ok_or_else(|| otel::error!("timeout", Error::new("msg")))?
969///
970/// // In function bodies with explicit return
971/// return Err(otel::error!("validation_failed", Error::new("msg")));
972///
973/// // With additional attributes
974/// otel::error!("database_unavailable", error, "retry_count" = 3)
975/// ```
976///
977/// # Parameters
978///
979/// - `error_type`: String literal for the error.type attribute
980/// - `error_value`: The error value to return (must implement Display for error.message)
981/// - Additional key-value pairs: Optional extra attributes to add to the error event
982///
983/// # Examples
984///
985/// ```ignore
986/// // In ok_or_else closure
987/// let config = load_config().ok_or_else(|| {
988///     otel::error!("config_not_found",
989///         jsonrpc::Error::internal_error("Configuration file not found"))
990/// })?;
991///
992/// // In map_err closure
993/// let connection = establish_connection().map_err(|e| {
994///     otel::error!("connection_failed",
995///         jsonrpc::Error::internal_error(format!("Failed to connect: {}", e)),
996///         "host" = host.to_string(),
997///         "port" = port as i64
998///     )
999/// })?;
1000///
1001/// // Direct return
1002/// if !is_valid {
1003///     return Err(otel::error!("invalid_state",
1004///         jsonrpc::Error::internal_error("System is in an invalid state"),
1005///         "state" = current_state
1006///     ));
1007/// }
1008/// ```
1009///
1010/// # OpenTelemetry Semantic Conventions
1011///
1012/// The macro follows the [OpenTelemetry semantic conventions for errors](https://opentelemetry.io/docs/specs/semconv/attributes-registry/error/):
1013///
1014/// - Event name: `"error"`
1015/// - Required attributes:
1016///   - `error.type`: Describes the class of error the operation ended with
1017///   - `error.message`: A human-readable message providing more detail about the error
1018/// - Additional attributes: Any extra key-value pairs provided
1019///
1020/// # Difference from `exception!`
1021///
1022/// While both macros record error-related events, they follow different semantic conventions:
1023///
1024/// - `error!`: For general error conditions (uses `error.type` and `error.message`)
1025/// - `exception!`: For programming exceptions with stack traces (uses `exception.type` and `exception.message`)
1026///
1027/// Use `error!` for application-level errors and `exception!` for exception handling.
1028#[macro_export]
1029macro_rules! error {
1030    ($error_type:expr, $error:expr $(, $key:literal = $value:expr)* $(,)?) => {{
1031        use opentelemetry::trace::TraceContextExt as _;
1032        let error_value = $error;
1033        let error_message = format!("{}", error_value);
1034
1035        opentelemetry::Context::current().span().add_event(
1036            "error",
1037            vec![
1038                opentelemetry::KeyValue::new("error.type", $error_type),
1039                opentelemetry::KeyValue::new("error.message", error_message),
1040                $(opentelemetry::KeyValue::new($key, $value)),*
1041            ],
1042        );
1043
1044        error_value
1045    }};
1046}
1047
1048/// Adds an event to the current span with optional attributes.
1049///
1050/// # Examples
1051///
1052/// ```ignore
1053/// // Simple event
1054/// otel::event!("query.start");
1055///
1056/// // Event with attributes
1057/// otel::event!("query.result",
1058///   "record_count" = 42,
1059///   "partition_key" = "users"
1060/// );
1061/// ```
1062#[macro_export]
1063macro_rules! event {
1064    ($event_name:expr $(, $key:literal = $value:expr)* $(,)?) => {{
1065        #[allow(unused)]
1066        use opentelemetry::trace::TraceContextExt as _;
1067        opentelemetry::Context::current().span().add_event(
1068            $event_name,
1069            vec![
1070                $(opentelemetry::KeyValue::new($key, $value)),*
1071            ],
1072        );
1073    }};
1074}
1075
1076/// Creates a span [`Link`](opentelemetry::trace::Link) from a context for use with `span!` macro's `links` parameter.
1077///
1078/// Links are used to associate spans across trace boundaries when there's a causal
1079/// relationship but not a parent-child relationship. Common use cases include:
1080///
1081/// - Background tasks spawned from a request (linked to spawning context)
1082/// - Batch processing where one span triggers multiple independent operations
1083/// - Fan-out/fan-in patterns
1084///
1085/// # Syntax
1086///
1087/// ```ignore
1088/// // Link from current context
1089/// let link = otel::link!();
1090///
1091/// // Link from explicit context
1092/// let link = otel::link!(parent_cx);
1093///
1094/// // Link with attributes
1095/// let link = otel::link!(parent_cx, "link.type" = "spawned_from");
1096/// ```
1097///
1098/// # Examples
1099///
1100/// ## Background task with link to spawner
1101///
1102/// ```ignore
1103/// fn spawn_background_task(data: Data) {
1104///     // Capture link BEFORE spawning (while still in parent context)
1105///     let parent_link = otel::link!("link.type" = "spawned_from");
1106///
1107///     tokio::spawn(async move {
1108///         // New root trace, linked back to spawner
1109///         otel::span!("background.task", context = None, links = [parent_link], in |_cx| {
1110///             process(data).await
1111///         })
1112///     });
1113/// }
1114/// ```
1115///
1116/// ## Explicit context with attributes
1117///
1118/// ```ignore
1119/// fn process_batch(items: Vec<Item>, trigger_cx: Context) {
1120///     let trigger_link = otel::link!(trigger_cx, "link.type" = "batch_trigger");
1121///
1122///     for item in items {
1123///         // Each item gets its own trace, linked to the batch trigger
1124///         otel::span!("item.process", context = None, links = [trigger_link.clone()], in |_cx| {
1125///             process_item(item)
1126///         });
1127///     }
1128/// }
1129/// ```
1130///
1131/// # OpenTelemetry Semantic Conventions
1132///
1133/// Links follow the [OpenTelemetry link specification](https://opentelemetry.io/docs/concepts/signals/traces/#span-links).
1134/// The `link.type` attribute is commonly used to describe the relationship.
1135#[macro_export]
1136macro_rules! link {
1137    // Link from current context (no attributes)
1138    () => {{
1139        use opentelemetry::trace::TraceContextExt as _;
1140        let cx = opentelemetry::Context::current();
1141        opentelemetry::trace::Link::new(cx.span().span_context().clone(), vec![], 0)
1142    }};
1143
1144    // Link from current context with attributes
1145    ($($key:literal = $value:expr),+ $(,)?) => {{
1146        use opentelemetry::trace::TraceContextExt as _;
1147        let cx = opentelemetry::Context::current();
1148        opentelemetry::trace::Link::new(
1149            cx.span().span_context().clone(),
1150            vec![$(opentelemetry::KeyValue::new($key, $value)),+],
1151            0
1152        )
1153    }};
1154
1155    // Link from explicit context (no attributes)
1156    ($cx:expr) => {{
1157        use opentelemetry::trace::TraceContextExt as _;
1158        opentelemetry::trace::Link::new($cx.span().span_context().clone(), vec![], 0)
1159    }};
1160
1161    // Link from explicit context with attributes
1162    ($cx:expr, $($key:literal = $value:expr),+ $(,)?) => {{
1163        use opentelemetry::trace::TraceContextExt as _;
1164        opentelemetry::trace::Link::new(
1165            $cx.span().span_context().clone(),
1166            vec![$(opentelemetry::KeyValue::new($key, $value)),+],
1167            0
1168        )
1169    }};
1170}
1171
1172#[cfg(test)]
1173mod tests;