#[doc(hidden)]
#[macro_export]
macro_rules! __attr_to_option {
($value:literal) => {
Some($crate::__private::opentelemetry::Value::from($value))
};
($value:expr) => {
$crate::ToValue::to_value(&$value)
};
}
#[macro_export]
macro_rules! span_attr {
($($key:literal = $value:expr),+ $(,)?) => {{
use $crate::__private::opentelemetry::trace::{TraceContextExt as _};
$(
if let Some(__attr_val) = $crate::ToValue::to_value(&$value) {
$crate::__private::opentelemetry::Context::current().span().set_attribute(
$crate::__private::opentelemetry::KeyValue::new($key, __attr_val),
);
}
)+
}};
}
#[macro_export]
macro_rules! span_err {
($($arg:tt)*) => {{
use $crate::__private::opentelemetry::trace::{Status, TraceContextExt as _};
$crate::__private::opentelemetry::Context::current().span().set_status(Status::error(format!($($arg)*)));
}};
}
#[macro_export]
macro_rules! span_ok {
() => {{
use $crate::__private::opentelemetry::trace::{Status, TraceContextExt as _};
$crate::__private::opentelemetry::Context::current()
.span()
.set_status(Status::Ok);
}};
}
#[cfg(test)]
mod tests {
use crate::span;
use apollo_opentelemetry_test::{TelemetryContext, assert_spans_snapshot};
use opentelemetry::trace::SpanKind;
#[test]
fn test_span_simple() {
let ctx = TelemetryContext::new();
drop(span!("simple"));
assert_spans_snapshot!(ctx, @r#"
- name: simple
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_with_kind() {
let ctx = TelemetryContext::new();
drop(span!("with-kind", kind: SpanKind::Server));
assert_spans_snapshot!(ctx, @r#"
- name: with-kind
span_kind: Server
is_sampled: true
"#);
}
#[test]
fn test_span_with_attributes() {
let ctx = TelemetryContext::new();
drop(span!("with-attrs", "key" = "value", "count" = 42i64));
assert_spans_snapshot!(ctx, @r#"
- name: with-attrs
span_kind: Internal
is_sampled: true
attributes:
count: "42"
key: value
"#);
}
#[test]
fn test_span_with_kind_and_attributes() {
let ctx = TelemetryContext::new();
drop(span!("full", kind: SpanKind::Client, "method" = "GET"));
assert_spans_snapshot!(ctx, @r#"
- name: full
span_kind: Client
is_sampled: true
attributes:
method: GET
"#);
}
#[test]
fn test_span_expr_name_variable() {
let ctx = TelemetryContext::new();
let name = String::from("expr-name");
drop(span!(name));
assert_spans_snapshot!(ctx, @r#"
- name: expr-name
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_expr_name_format() {
let ctx = TelemetryContext::new();
let id = 42;
drop(span!(format!("span-{}", id)));
assert_spans_snapshot!(ctx, @r#"
- name: span-42
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_expr_name_with_kind() {
let ctx = TelemetryContext::new();
let name = "dynamic-name";
drop(span!(name.to_string(), kind: SpanKind::Server));
assert_spans_snapshot!(ctx, @r#"
- name: dynamic-name
span_kind: Server
is_sampled: true
"#);
}
#[test]
fn test_span_expr_name_with_attributes() {
let ctx = TelemetryContext::new();
let name = String::from("expr-attrs");
drop(span!(name, "key" = "value"));
assert_spans_snapshot!(ctx, @r#"
- name: expr-attrs
span_kind: Internal
is_sampled: true
attributes:
key: value
"#);
}
#[test]
fn test_span_expr_name_closure() {
let ctx = TelemetryContext::new();
let name = String::from("expr-closure");
span!(name, || {
});
assert_spans_snapshot!(ctx, @r#"
- name: expr-closure
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_expr_name_async() {
let ctx = TelemetryContext::new();
let id = 123;
span!(format!("async-{}", id), async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-123
span_kind: Internal
is_sampled: true
"#);
}
fn test_scope() -> &'static opentelemetry::InstrumentationScope {
static SCOPE: std::sync::LazyLock<opentelemetry::InstrumentationScope> =
std::sync::LazyLock::new(|| {
opentelemetry::InstrumentationScope::builder("my-component").build()
});
&SCOPE
}
#[tokio::test]
async fn test_span_in_async() {
let ctx = TelemetryContext::new();
let span = span!("async-operation");
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
drop(span);
assert_spans_snapshot!(ctx, @r#"
- name: async-operation
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_context_propagation() {
use opentelemetry::Context;
use opentelemetry::trace::{FutureExt as _, TraceContextExt};
let ctx = TelemetryContext::new();
let parent_span = span!("parent");
let parent_cx = Context::current_with_span(parent_span);
async {
tokio::spawn(
async {
drop(span!("child"));
}
.with_current_context(),
)
.await
.unwrap();
}
.with_context(parent_cx)
.await;
assert_spans_snapshot!(ctx, @r#"
- name: child
span_kind: Internal
has_parent: true
is_sampled: true
- name: parent
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_closure_simple() {
let ctx = TelemetryContext::new();
span!("closure-simple", || {
});
assert_spans_snapshot!(ctx, @r#"
- name: closure-simple
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_closure_with_kind() {
let ctx = TelemetryContext::new();
span!("closure-kind", kind: SpanKind::Server, || {
});
assert_spans_snapshot!(ctx, @r#"
- name: closure-kind
span_kind: Server
is_sampled: true
"#);
}
#[test]
fn test_span_closure_with_attributes() {
let ctx = TelemetryContext::new();
span!("closure-attrs", "key" = "value", || {
});
assert_spans_snapshot!(ctx, @r#"
- name: closure-attrs
span_kind: Internal
is_sampled: true
attributes:
key: value
"#);
}
#[test]
fn test_span_closure_with_kind_and_attributes() {
let ctx = TelemetryContext::new();
span!("closure-full", kind: SpanKind::Client, "method" = "GET", || {
});
assert_spans_snapshot!(ctx, @r#"
- name: closure-full
span_kind: Client
is_sampled: true
attributes:
method: GET
"#);
}
#[test]
fn test_span_closure_returns_value() {
let ctx = TelemetryContext::new();
let result = span!("closure-return", || 42);
assert_eq!(result, 42);
assert_spans_snapshot!(ctx, @r#"
- name: closure-return
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_move_closure() {
let ctx = TelemetryContext::new();
let value = String::from("captured");
let result = span!("move-closure", move || {
value });
assert_eq!(result, "captured");
assert_spans_snapshot!(ctx, @r#"
- name: move-closure
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_closure_nested() {
let ctx = TelemetryContext::new();
span!("outer", || {
span!("inner", || {
});
});
assert_spans_snapshot!(ctx, @r#"
- name: inner
span_kind: Internal
has_parent: true
is_sampled: true
- name: outer
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_closure_with_span_event() {
use crate::span_event;
let ctx = TelemetryContext::new();
span!("with-event", || {
span_event!("something-happened");
});
assert_spans_snapshot!(ctx, @r#"
- name: with-event
span_kind: Internal
is_sampled: true
events:
- name: something-happened
"#);
}
#[tokio::test]
async fn test_span_async_block_simple() {
let ctx = TelemetryContext::new();
span!("async-simple", async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-simple
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_async_block_with_kind() {
let ctx = TelemetryContext::new();
span!("async-kind", kind: SpanKind::Server, async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-kind
span_kind: Server
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_async_block_with_attributes() {
let ctx = TelemetryContext::new();
span!("async-attrs", "key" = "value", async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-attrs
span_kind: Internal
is_sampled: true
attributes:
key: value
"#);
}
#[tokio::test]
async fn test_span_async_block_with_kind_and_attributes() {
let ctx = TelemetryContext::new();
span!("async-full", kind: SpanKind::Client, "method" = "GET", async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-full
span_kind: Client
is_sampled: true
attributes:
method: GET
"#);
}
#[tokio::test]
async fn test_span_async_block_returns_value() {
let ctx = TelemetryContext::new();
let result = span!("async-return", async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
42
})
.await;
assert_eq!(result, 42);
assert_spans_snapshot!(ctx, @r#"
- name: async-return
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_async_block_nested() {
let ctx = TelemetryContext::new();
span!("async-outer", async {
span!("async-inner", async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-inner
span_kind: Internal
has_parent: true
is_sampled: true
- name: async-outer
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_async_move_block() {
let ctx = TelemetryContext::new();
let value = String::from("captured");
let result = span!("async-move", async move {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
value })
.await;
assert_eq!(result, "captured");
assert_spans_snapshot!(ctx, @r#"
- name: async-move
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_async_block_with_span_event() {
use crate::span_event;
let ctx = TelemetryContext::new();
span!("async-with-event", async {
span_event!("async-event");
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: async-with-event
span_kind: Internal
is_sampled: true
events:
- name: async-event
"#);
}
#[tokio::test]
async fn test_span_async_block_spawned_task() {
let ctx = TelemetryContext::new();
span!("parent", async {
tokio::spawn(span!("spawned-child", async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
}))
.await
.unwrap();
})
.await;
assert_spans_snapshot!(ctx, @r#"
- name: spawned-child
span_kind: Internal
has_parent: true
is_sampled: true
- name: parent
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_with_optional_attribute_some() {
let ctx = TelemetryContext::new();
let value: Option<i64> = Some(42);
drop(span!("opt-some", "count" = value));
assert_spans_snapshot!(ctx, @r#"
- name: opt-some
span_kind: Internal
is_sampled: true
attributes:
count: "42"
"#);
}
#[test]
fn test_span_with_optional_attribute_none() {
let ctx = TelemetryContext::new();
let value: Option<i64> = None;
drop(span!("opt-none", "count" = value));
assert_spans_snapshot!(ctx, @r#"
- name: opt-none
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_with_mixed_optional_attributes() {
let ctx = TelemetryContext::new();
let present: Option<&str> = Some("hello");
let absent: Option<i64> = None;
drop(span!(
"mixed-opts",
"present" = present,
"absent" = absent,
"always" = "here"
));
assert_spans_snapshot!(ctx, @r#"
- name: mixed-opts
span_kind: Internal
is_sampled: true
attributes:
always: here
present: hello
"#);
}
#[test]
fn test_span_attr_single() {
let ctx = TelemetryContext::new();
span!("with-post-attr", || {
span_attr!("post.attr" = 123i64);
});
assert_spans_snapshot!(ctx, @r#"
- name: with-post-attr
span_kind: Internal
is_sampled: true
attributes:
post.attr: "123"
"#);
}
#[test]
fn test_span_attr_multiple() {
let ctx = TelemetryContext::new();
span!("with-multi-attr", || {
span_attr!("key1" = "value1", "key2" = 42i64);
});
assert_spans_snapshot!(ctx, @r#"
- name: with-multi-attr
span_kind: Internal
is_sampled: true
attributes:
key1: value1
key2: "42"
"#);
}
#[test]
fn test_span_attr_none_skipped() {
let ctx = TelemetryContext::new();
span!("with-skipped-attr", || {
span_attr!("skipped" = None::<i64>);
});
assert_spans_snapshot!(ctx, @r#"
- name: with-skipped-attr
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_attr_mixed_some_none() {
let ctx = TelemetryContext::new();
let present: Option<&str> = Some("hello");
let absent: Option<i64> = None;
span!("mixed-attr", || {
span_attr!("present" = present, "absent" = absent, "direct" = "value");
});
assert_spans_snapshot!(ctx, @r#"
- name: mixed-attr
span_kind: Internal
is_sampled: true
attributes:
direct: value
present: hello
"#);
}
#[test]
fn test_span_attr_trailing_comma() {
let ctx = TelemetryContext::new();
span!("trailing-comma", || {
span_attr!("key" = "value",);
});
assert_spans_snapshot!(ctx, @r#"
- name: trailing-comma
span_kind: Internal
is_sampled: true
attributes:
key: value
"#);
}
#[test]
fn test_span_err_simple() {
let ctx = TelemetryContext::new();
span!("with-error", || {
span_err!("something went wrong");
});
assert_spans_snapshot!(ctx, @r#"
- name: with-error
span_kind: Internal
is_sampled: true
status: "Error: something went wrong"
"#);
}
#[test]
fn test_span_err_formatted() {
let ctx = TelemetryContext::new();
let id = 42;
let reason = "not found";
span!("with-formatted-error", || {
span_err!("failed to process id {}: {}", id, reason);
});
assert_spans_snapshot!(ctx, @r#"
- name: with-formatted-error
span_kind: Internal
is_sampled: true
status: "Error: failed to process id 42: not found"
"#);
}
#[test]
fn test_span_ok() {
let ctx = TelemetryContext::new();
span!("with-ok", || {
span_ok!();
});
assert_spans_snapshot!(ctx, @r#"
- name: with-ok
span_kind: Internal
is_sampled: true
status: Ok
"#);
}
#[test]
fn test_span_closure_result_ok() {
let ctx = TelemetryContext::new();
let result: Result<i32, &str> = span!("result-ok", || Ok(42));
assert_eq!(result, Ok(42));
assert_spans_snapshot!(ctx, @r#"
- name: result-ok
span_kind: Internal
is_sampled: true
status: Ok
"#);
}
#[test]
fn test_span_closure_result_err() {
let ctx = TelemetryContext::new();
let result: Result<i32, &str> = span!("result-err", || Err("something failed"));
assert_eq!(result, Err("something failed"));
assert_spans_snapshot!(ctx, @r#"
- name: result-err
span_kind: Internal
is_sampled: true
status: "Error: something failed"
"#);
}
#[test]
fn test_span_closure_non_result() {
let ctx = TelemetryContext::new();
let value: i32 = span!("non-result", || 42);
assert_eq!(value, 42);
assert_spans_snapshot!(ctx, @r#"
- name: non-result
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_closure_custom_result_alias() {
type MyResult<T> = Result<T, Box<dyn std::error::Error>>;
let ctx = TelemetryContext::new();
let result: MyResult<&str> = span!("custom-result", || Ok("success"));
assert!(result.is_ok());
assert_spans_snapshot!(ctx, @r#"
- name: custom-result
span_kind: Internal
is_sampled: true
status: Ok
"#);
}
#[tokio::test]
async fn test_span_async_block_result_ok() {
let ctx = TelemetryContext::new();
let result: Result<i32, &str> = span!("async-result-ok", async { Ok(42) }).await;
assert_eq!(result, Ok(42));
assert_spans_snapshot!(ctx, @r#"
- name: async-result-ok
span_kind: Internal
is_sampled: true
status: Ok
"#);
}
#[tokio::test]
async fn test_span_async_block_result_err() {
let ctx = TelemetryContext::new();
let result: Result<i32, &str> =
span!("async-result-err", async { Err("async failure") }).await;
assert_eq!(result, Err("async failure"));
assert_spans_snapshot!(ctx, @r#"
- name: async-result-err
span_kind: Internal
is_sampled: true
status: "Error: async failure"
"#);
}
#[tokio::test]
async fn test_span_async_block_non_result() {
let ctx = TelemetryContext::new();
let value: i32 = span!("async-non-result", async { 42 }).await;
assert_eq!(value, 42);
assert_spans_snapshot!(ctx, @r#"
- name: async-non-result
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_closure_question_mark_sets_error_status() {
fn fallible() -> Result<(), &'static str> {
Err("inner error")
}
let ctx = TelemetryContext::new();
let result: Result<(), &str> = span!("with-question-mark", || {
fallible()?;
Ok(())
});
assert_eq!(result, Err("inner error"));
assert_spans_snapshot!(ctx, @r#"
- name: with-question-mark
span_kind: Internal
is_sampled: true
status: "Error: inner error"
"#);
}
#[tokio::test]
async fn test_span_async_block_question_mark_sets_error_status() {
async fn fallible() -> Result<(), &'static str> {
Err("async inner error")
}
let ctx = TelemetryContext::new();
let result: Result<(), &str> = span!("async-with-question-mark", async {
fallible().await?;
Ok(())
})
.await;
assert_eq!(result, Err("async inner error"));
assert_spans_snapshot!(ctx, @r#"
- name: async-with-question-mark
span_kind: Internal
is_sampled: true
status: "Error: async inner error"
"#);
}
#[test]
fn test_span_with_explicit_parent_return_form() {
use opentelemetry::Context;
use opentelemetry::trace::TraceContextExt;
let ctx = TelemetryContext::new();
let parent_span = span!("parent");
let parent_cx = Context::current_with_span(parent_span);
drop(span!("child", parent: &parent_cx));
drop(parent_cx);
assert_spans_snapshot!(ctx, @r#"
- name: child
span_kind: Internal
has_parent: true
is_sampled: true
- name: parent
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_with_explicit_parent_closure() {
use opentelemetry::Context;
use opentelemetry::trace::TraceContextExt;
let ctx = TelemetryContext::new();
let parent_span = span!("parent");
let parent_cx = Context::current_with_span(parent_span);
span!("child", parent: &parent_cx, || {
});
drop(parent_cx);
assert_spans_snapshot!(ctx, @r#"
- name: child
span_kind: Internal
has_parent: true
is_sampled: true
- name: parent
span_kind: Internal
is_sampled: true
"#);
}
#[test]
fn test_span_with_explicit_parent_kind_attributes_and_scope() {
use opentelemetry::Context;
use opentelemetry::trace::TraceContextExt;
let ctx = TelemetryContext::new();
let parent_span = span!("parent");
let parent_cx = Context::current_with_span(parent_span);
span!(
"child",
scope: test_scope(),
parent: &parent_cx,
kind: SpanKind::Server,
"http.method" = "GET",
|| {
}
);
drop(parent_cx);
assert_spans_snapshot!(ctx, @r#"
- name: child
span_kind: Server
has_parent: true
is_sampled: true
attributes:
http.method: GET
- name: parent
span_kind: Internal
is_sampled: true
"#);
let spans = ctx.spans();
let child = spans.iter().find(|s| s.name == "child").unwrap();
assert_eq!(child.instrumentation_scope.name(), "my-component");
}
#[tokio::test]
async fn test_span_with_explicit_parent_async() {
use opentelemetry::Context;
use opentelemetry::trace::TraceContextExt;
let ctx = TelemetryContext::new();
let parent_span = span!("parent");
let parent_cx = Context::current_with_span(parent_span);
span!("child", parent: &parent_cx, async {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
})
.await;
drop(parent_cx);
assert_spans_snapshot!(ctx, @r#"
- name: child
span_kind: Internal
has_parent: true
is_sampled: true
- name: parent
span_kind: Internal
is_sampled: true
"#);
}
#[tokio::test]
async fn test_span_with_explicit_parent_spawned_task() {
use opentelemetry::Context;
use opentelemetry::trace::TraceContextExt;
let ctx = TelemetryContext::new();
let parent_span = span!("parent");
let parent_cx = Context::current_with_span(parent_span);
tokio::spawn(span!("spawned-child", parent: &parent_cx, async move {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
}))
.await
.unwrap();
drop(parent_cx);
assert_spans_snapshot!(ctx, @r#"
- name: spawned-child
span_kind: Internal
has_parent: true
is_sampled: true
- name: parent
span_kind: Internal
is_sampled: true
"#);
}
}