Attribute Macro main
#[main]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 returningstd::io::Result<dial9::AttachedRuntime>: a recorder paired with a runtime attached to it viaDial9HandleTokioExt::attach_tokio_runtime. The macro panics if it is anErr. Usedial9::recorder_from_envfor the env-driven setup. Required.graceful_shutdown— the drain deadline (aDuration); 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() {
/* ... */
}