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!(@detached, "name")` | `Context` | Detached span (no automatic guard) |
187/// | `span!(@detached, "name", "k" => v)` | `Context` | Detached with attributes |
188/// | `span!(@detached, @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!(@detached, "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!(@detached, "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!(@detached, "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!(@detached, "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!(@detached, "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!(@detached, "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!(@detached, "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!(@detached, "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:expr => $value:expr),+)?) => {
478    let $cx = $crate::span!(@build $tracer, $name $(, $($key => $value),+)?);
479  };
480  (@maybe_cx $tracer:ident, $name:expr $(, $($key:expr => $value:expr),+)?) => {
481    let _cx = $crate::span!(@build $tracer, $name $(, $($key => $value),+)?);
482  };
483
484  (@maybe_guard $cx:ident, $guard:ident) => {
485    let $guard = $cx.clone().attach();
486  };
487  (@maybe_guard $cx:ident,) => {};
488  (@maybe_guard $cx:ident) => {};
489  (@maybe_guard) => {};
490
491
492    // Internal: build span context with automatic attributes and optional custom attributes
493    (@build $tracer:expr, $name:expr $(, $($key:expr => $value:expr),+)?) => {{
494        use opentelemetry::trace::{Tracer as _, TraceContextExt as _};
495        let thread = std::thread::current();
496
497        opentelemetry::Context::current_with_span(
498            $tracer
499                .span_builder($name)
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
505                    // waiting on (feature = "thread_id_value", issue = "67939")
506                    // opentelemetry::KeyValue::new("thread.id", std::thread::current().id()),
507                    opentelemetry::KeyValue::new("thread.name", format!("{}",thread.name().unwrap_or("unnamed"))),
508
509                    $($(opentelemetry::KeyValue::new($key, $value)),+)?
510                ])
511                .start_with_context(&*$tracer, &opentelemetry::Context::current())
512        )
513    }};
514
515    // Default TRACER, no attributes, with closure
516    ($name:expr, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
517        $crate::span!(@maybe_cx $($cx,)? TRACER, $name);
518        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
519
520        $body
521    }};
522
523    // Default TRACER with attributes and closure
524    ($name:expr, $($key:expr => $value:expr),+, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
525        $crate::span!(@maybe_cx $($cx,)? TRACER, $name , $($key => $value),+);
526        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
527        $body
528    }};
529
530    // Explicit tracer, no attributes, with closure
531    (@$tracer:ident, $name:expr, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
532      $crate::span!(@maybe_cx $($cx,)? $tracer, $name);
533      $crate::span!(@maybe_guard $($cx,)? $($guard)?);
534        $body
535    }};
536
537    // Explicit tracer with attributes and closure
538    (@$tracer:ident, $name:expr, $($key:expr => $value:expr),+, in |$($cx:ident)? $(,$guard:ident)?| $body:expr) => {{
539        $crate::span!(@maybe_cx $($cx,)? $tracer, $name , $($key => $value),+);
540        $crate::span!(@maybe_guard $($cx,)? $($guard)?);
541
542        $body
543    }};
544
545    // Default TRACER, no attributes (statement form with internal guard)
546    ($name:expr $(,)?) => {{
547        let cx = $crate::span!(@build TRACER, $name);
548        let _otel_guard = cx.clone().attach();
549    }};
550
551    // Default TRACER with attributes (statement form with internal guard)
552    ($name:expr, $($key:expr => $value:expr),+ $(,)?) => {{
553        let cx = $crate::span!(@build TRACER, $name, $($key => $value),+);
554        let _otel_guard = cx.clone().attach();
555    }};
556
557    // Explicit tracer, no attributes (statement form with internal guard)
558    (@$tracer:ident, $name:expr $(,)?) => {{
559        let cx = $crate::span!(@build $tracer, $name);
560        let _otel_guard = cx.clone().attach();
561    }};
562
563    // Explicit tracer with attributes (statement form with internal guard)
564    (@$tracer:ident, $name:expr, $($key:expr => $value:expr),+ $(,)?) => {{
565        let cx = $crate::span!(@build $tracer, $name, $($key => $value),+);
566        let _otel_guard = cx.clone().attach();
567    }};
568
569    // Detached span (no guard) - default TRACER, no attributes
570    (@detached, $name:expr $(,)?) => {
571        $crate::span!(@build TRACER, $name)
572    };
573
574    // Detached span (no guard) - default TRACER with attributes
575    (@detached, $name:expr, $($key:expr => $value:expr),+ $(,)?) => {
576        $crate::span!(@build TRACER, $name, $($key => $value),+)
577    };
578
579    // Detached span (no guard) - explicit tracer, no attributes
580    (@detached, @$tracer:ident, $name:expr $(,)?) => {
581        $crate::span!(@build $tracer, $name)
582    };
583
584    // Detached span (no guard) - explicit tracer with attributes
585    (@detached, @$tracer:ident, $name:expr, $($key:expr => $value:expr),+ $(,)?) => {
586        $crate::span!(@build $tracer, $name, $($key => $value),+)
587    };
588}
589
590/// Records an exception event on the current span and returns an error value.
591///
592/// This macro adds an exception event to the current OpenTelemetry span following
593/// the semantic conventions, then evaluates to the provided error value.
594///
595/// # Syntax
596///
597/// ```ignore
598/// // In closures (ok_or_else, map_err, etc.)
599/// .ok_or_else(|| otel::exception!("invalid_params", Error::new("msg")))?
600///
601/// // In function bodies with explicit return
602/// return Err(otel::exception!("invalid_params", Error::new("msg")));
603///
604/// // With additional attributes
605/// otel::exception!("source_not_found", error, "uri" => uri.to_string())
606/// ```
607///
608/// # Parameters
609///
610/// - `exception_type`: String literal for the exception.type attribute
611/// - `error_value`: The error value to return (must implement Display for exception.message)
612/// - Additional key-value pairs: Optional extra attributes to add to the exception event
613///
614/// # Examples
615///
616/// ```ignore
617/// // In ok_or_else closure
618/// let arg = arguments.first().ok_or_else(|| {
619///     otel::exception!("invalid_params",
620///         jsonrpc::Error::invalid_params("Missing arguments"))
621/// })?;
622///
623/// // In map_err closure
624/// let uri = Uri::parse(uri_str).map_err(|e| {
625///     otel::exception!("invalid_uri",
626///         jsonrpc::Error::invalid_params(format!("Invalid URI: {}", e)),
627///         "uri_str" => uri_str.to_string()
628///     )
629/// })?;
630///
631/// // Direct return
632/// if cache.is_empty() {
633///     return Err(otel::exception!("cache_empty",
634///         jsonrpc::Error::internal_error("Cache is empty"),
635///         "operation" => "lookup"
636///     ));
637/// }
638/// ```
639///
640/// # OpenTelemetry Semantic Conventions
641///
642/// The macro follows the [OpenTelemetry semantic conventions for exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/):
643///
644/// - Event name: `"exception"`
645/// - Required attributes:
646///   - `exception.type`: The type/category of the exception
647///   - `exception.message`: The error message extracted via Display
648/// - Additional attributes: Any extra key-value pairs provided
649#[macro_export]
650macro_rules! exception {
651    ($exception_type:expr, $error:expr $(, $key:expr => $value:expr)* $(,)?) => {{
652        let error_value = $error;
653        let error_message = format!("{}", error_value);
654
655        opentelemetry::Context::current().span().add_event(
656            "exception",
657            vec![
658                opentelemetry::KeyValue::new("exception.type", $exception_type),
659                opentelemetry::KeyValue::new("exception.message", error_message),
660                $(opentelemetry::KeyValue::new($key, $value)),*
661            ],
662        );
663
664        error_value
665    }};
666}
667
668/// Adds an event to the current span with optional attributes.
669///
670/// # Examples
671///
672/// ```ignore
673/// // Simple event
674/// otel::event!("query.start");
675///
676/// // Event with attributes
677/// otel::event!("query.result",
678///   "record_count" => 42,
679///   "partition_key" => "users"
680/// );
681/// ```
682#[macro_export]
683macro_rules! event {
684    ($event_name:expr $(, $key:expr => $value:expr)* $(,)?) => {
685        opentelemetry::Context::current().span().add_event(
686            $event_name,
687            vec![
688                $(opentelemetry::KeyValue::new($key, $value)),*
689            ],
690        );
691    };
692}
693
694#[cfg(test)]
695mod tests {
696  use super::*;
697
698  tracer!();
699
700  #[test]
701  fn span_with_closure_returns_value() {
702    let result = span!("test.closure", in |_cx| {
703        42
704    });
705    assert_eq!(result, 42);
706  }
707
708  #[test]
709  fn span_with_closure_and_attributes() {
710    let result = span!("test.closure_attrs", "foo" => "bar", in |_cx| {
711        "hello"
712    });
713    assert_eq!(result, "hello");
714  }
715
716  #[test]
717  fn span_with_closure_explicit_tracer() {
718    let result = span!(@TRACER, "test.explicit", in |_cx| {
719        100
720    });
721    assert_eq!(result, 100);
722  }
723
724  #[test]
725  fn span_with_closure_explicit_tracer_and_attributes() {
726    let result = span!(@TRACER, "test.explicit_attrs", "key" => "value", in |_cx| {
727        vec![1, 2, 3]
728    });
729    assert_eq!(result, vec![1, 2, 3]);
730  }
731}