Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Attaching properties to spans

Properties captured in the span macros are added to an ambient context and automatically included on any events emitted within that operation:

#![allow(unused)]
fn main() {
extern crate emit;
#[emit::span("wait a bit", sleep_ms)]
fn wait_a_bit(sleep_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(sleep_ms));

    emit::emit!("waiting a bit longer");

    std::thread::sleep(std::time::Duration::from_millis(sleep_ms));
}
}
Event {
    mdl: "my_app",
    tpl: "waiting a bit longer",
    extent: Some(
        "2024-04-27T22:47:34.780288000Z",
    ),
    props: {
        "trace_id": d2a5e592546010570472ac6e6457c086,
        "sleep_ms": 1200,
        "span_id": ee9fde093b6efd78,
    },
}
Event {
    mdl: "my_app",
    tpl: "wait a bit",
    extent: Some(
        "2024-04-27T22:47:33.574839000Z".."2024-04-27T22:47:35.985844000Z",
    ),
    props: {
        "evt_kind": span,
        "span_name": "wait a bit",
        "trace_id": d2a5e592546010570472ac6e6457c086,
        "sleep_ms": 1200,
        "span_id": ee9fde093b6efd78,
    },
}

Any operations started within a span will inherit its identifiers:

#![allow(unused)]
fn main() {
extern crate emit;
#[emit::span("outer span", sleep_ms)]
fn outer_span(sleep_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(sleep_ms));

    inner_span(sleep_ms / 2);
}

#[emit::span("inner span", sleep_ms)]
fn inner_span(sleep_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(sleep_ms));
}
}
Event {
    mdl: "my_app",
    tpl: "inner span",
    extent: Some(
        "2024-04-27T22:50:50.385706000Z".."2024-04-27T22:50:50.994509000Z",
    ),
    props: {
        "evt_kind": span,
        "span_name": "inner span",
        "trace_id": 12b2fde225aebfa6758ede9cac81bf4d,
        "span_parent": 23995f85b4610391,
        "sleep_ms": 600,
        "span_id": fc8ed8f3a980609c,
    },
}
Event {
    mdl: "my_app",
    tpl: "outer span",
    extent: Some(
        "2024-04-27T22:50:49.180025000Z".."2024-04-27T22:50:50.994797000Z",
    ),
    props: {
        "evt_kind": span,
        "span_name": "outer span",
        "sleep_ms": 1200,
        "span_id": 23995f85b4610391,
        "trace_id": 12b2fde225aebfa6758ede9cac81bf4d,
    },
}

Notice the span_parent of inner_span is the same as the span_id of outer_span. That’s because inner_span was called within the execution of outer_span.

Adding properties to a span without making them ambient

You can specify properties that should appear on a span’s completion event, but not be made part of the ambient context, using the evt_props control parameter:

#![allow(unused)]
fn main() {
extern crate emit;
#[emit::span(
    evt_props: emit::props! {
        sleep_ms,
    },
    "wait a bit",
)]
fn wait_a_bit(sleep_ms: u64) {
    std::thread::sleep(std::time::Duration::from_millis(sleep_ms));
}
}
Event {
    mdl: "my_app",
    tpl: "wait a bit",
    extent: Some(
        "2024-04-27T22:47:33.574839000Z".."2024-04-27T22:47:35.985844000Z",
    ),
    props: {
        "evt_kind": span,
        "span_name": "wait a bit",
        "trace_id": d2a5e592546010570472ac6e6457c086,
        "sleep_ms": 1200,
        "span_id": ee9fde093b6efd78,
    },
}

The evt_props control parameter accepts any value that implements Props, such as the props! macro.

Adding properties to a span as it runs

You can bind the implicit SpanGuard created for an instrumented function to an identifier using the guard control parameter. You can then use that identifier in the body of the function to interact with the span before it completes. See Manual span creation for more details.

With a SpanGuard, you can attach additional properties collection to the span:

#![allow(unused)]
fn main() {
extern crate emit;
use std::collections::HashMap;
// This example uses a `HashMap` to store additional properties to include
#[emit::span(guard: span, evt_props: HashMap::new(), "checking a value", i)]
fn check(i: i32) {
    if i > 4 {
        // The type of the span's properties derefence to `HashMap`, so we can insert into them
        span.props_mut().map(|props| props.insert("is_big", true));
    }

    // At this point `span` will be dropped
    // It may or may not carry `is_big`
}
}
Event {
    mdl: "my_app",
    tpl: "checking a value",
    extent: Some(
        "2024-04-27T22:50:49.180025000Z".."2024-04-27T22:50:50.994797000Z",
    ),
    props: {
        "evt_kind": span,
        "span_name": "checking a value",
        "is_big": true,
        "i": 5,
        "span_id": 23995f85b4610391,
        "trace_id": 12b2fde225aebfa6758ede9cac81bf4d,
    },
}

Attaching additional properties to the span guard is preferrable to adding them through the span’s completion. When they’re on the guard they’ll be used even if the function panics.

Visibility of properties on spans

The previous sections show that properties on spans have two visibility levels:

  • Ambient: Added to the ambient context and present on all child events. Properties you add to the #[span] template are shared.
  • Private: Added to the SpanGuard and only present on the span event itself. Properties you add through the evt_props control parameter, and subsequently through the SpanGuard are private.
#![allow(unused)]
fn main() {
extern crate emit;
#[emit::span(
    guard: span,
    evt_props: emit::props! {
        private_1: i,
    },
    "checking {ambient_1: i}",
    ambient_2: i,
)]
fn check(i: i32) {
    let _span = span.push_prop("private_2", i);

    // Your code goes here
}
}

This means you only want to include properties in your #[span] template that are ambiently useful, because they’ll also appear on all child events. It’s generally more efficient to use private properties than shared ones.

To make additional properties ambiently available during the execution of a span, see Context.

Capturing complex values

Properties aren’t limited to strings; they can be arbitrarily complex structured values. See the following sections and Value data model for more details.

Using fmt::Debug

If you want to log a type that implements Debug, you can apply the #[as_debug] attribute to it to capture it with its debug format:

#![allow(unused)]
fn main() {
extern crate emit;
#[derive(Debug)]
struct User<'a> {
    name: &'a str,
}

#[emit::span("greet {user}", #[emit::as_debug] user)]
fn greet(user: &User) {
    println!("Hello, {}", user.name);
}
}

Using serde::Serialize

If you want to log a type that implements Serialize, you can apply the #[as_serde] attribute to it to capture it as a structured value:

#![allow(unused)]
fn main() {
extern crate emit;
#[macro_use] extern crate serde;
#[derive(Serialize)]
struct User<'a> {
    name: &'a str,
}

#[emit::span("greet {user}", #[emit::as_serde] user)]
fn greet(user: &User) {
    println!("Hello, {}", user.name);
}
}