Skip to main content

main

Attribute Macro main 

#[main]
Available on crate feature dial9 only.
Expand description

Instrument an async main function with dial9 telemetry.

This macro is a replacement for #[tokio::main], not a complement — do not use both attributes on the same function. Your config yields a dial9::Recorder and its instrumented tokio::runtime::Runtime; the macro runs the body on that runtime as a spawned task via dial9::block_on (polled directly under Runtime::block_on it would be invisible to the poll hooks), then drops the runtime (so workers flush) and drains the recorder.

Spawn instrumented sub-tasks from the body with dial9::spawn.

§Arguments

  • config — a zero-argument function path or closure returning std::io::Result<dial9::AttachedRuntime>: a recorder paired with a runtime attached to it via Dial9HandleTokioExt::attach_tokio_runtime. The macro panics if it is an Err. Use dial9::recorder_from_env for the env-driven setup. Required.
  • graceful_shutdown — the drain deadline (a Duration); defaults to 1s.
  • disable_graceful_shutdown — skip the drain; the recorder is just dropped.

§Graceful shutdown

After the async body returns, the macro drops the runtime (so Tokio worker threads exit and flush their thread-local buffers) and then drains the recorder’s background worker so the final segment is symbolized, compressed, and uploaded before the process exits.

The implicit drain only runs when the body returns normally. If the body panics, the panic propagates and the recorder’s Drop still flushes and seals the final segment, but the background worker is not drained — so a panicking program may not symbolize or upload its last segment.

§Examples

From the environment (the common production path):

#[dial9::main(config = dial9::recorder_from_env)]
async fn main() {
    dial9::spawn(async { /* instrumented sub-task */ }).await.unwrap();
}

A named config that builds its own recorder and runtime:

use std::io;
use dial9::{AttachedRuntime, Dial9HandleTokioExt, DiskBuffer, TokioAttachOptions};

fn my_config() -> io::Result<AttachedRuntime> {
    let writer = DiskBuffer::builder()
        .base_path("/tmp/traces")
        .max_total_size(16 * 1024 * 1024)
        .build()
        .expect("writer build failed");
    let recorder = dial9::recorder(writer).build();

    let mut builder = tokio::runtime::Builder::new_multi_thread();
    builder.enable_all().worker_threads(4);
    let runtime = recorder
        .handle()
        .attach_tokio_runtime(builder, TokioAttachOptions::default())?;

    Ok((recorder, runtime))
}

#[dial9::main(config = my_config, graceful_shutdown = std::time::Duration::from_secs(5))]
async fn main() {
    /* ... */
}

Disabled (no telemetry, plain tokio runtime — useful for toggling dial9 off via a feature flag or env var without removing the macro):

use dial9::{Dial9HandleTokioExt, TokioAttachOptions};

#[dial9::main(config = || {
    let recorder = dial9::recorder_disabled();
    let mut builder = tokio::runtime::Builder::new_multi_thread();
    builder.enable_all();
    let runtime = recorder
        .handle()
        .attach_tokio_runtime(builder, TokioAttachOptions::default())?;
    Ok((recorder, runtime))
})]
async fn main() {
    /* ... */
}

In-memory writer (nothing on local disk). With no disk writeback, pair it with a pipeline that ships the buffered segments somewhere — e.g. .with_s3_uploader(..) or .with_custom_pipeline(..).

use dial9::{Dial9HandleTokioExt, TokioAttachOptions};

#[dial9::main(config = || {
    let writer = dial9::MemoryBuffer::builder()
        .max_total_size(16 * 1024 * 1024)
        .build()
        .expect("writer build failed");
    let recorder = dial9::recorder(writer).build();
    let mut builder = tokio::runtime::Builder::new_multi_thread();
    builder.enable_all();
    let runtime = recorder
        .handle()
        .attach_tokio_runtime(builder, TokioAttachOptions::default())?;
    Ok((recorder, runtime))
})]
async fn main() {
    /* ... */
}