burn_telemetry 0.1.0

burn training logger
Documentation
//! Demonstrates logging to OpenTelemetry and Rerun simultaneously via `MultiLogger`.

use burn::train::{logger::MetricLogger, metric::MetricEntry};
use burn_telemetry::MultiLogger;

#[cfg(all(feature = "otel", feature = "rerun"))]
use std::{env, sync::Arc, time::Duration};

#[cfg(all(feature = "otel", feature = "rerun"))]
use anyhow::{anyhow, Result};

#[cfg(all(feature = "otel", feature = "rerun"))]
use burn_telemetry::{
    otel::{OpenTelemetryConfig, OpenTelemetryLogger},
    rerun::{RerunConfig, RerunLogger},
};

const EPOCHS: usize = 3;
const STEPS_PER_EPOCH: usize = 4;

#[cfg(not(all(feature = "otel", feature = "rerun")))]
fn main() {
    eprintln!(
        "Enable both `otel` and `rerun` features to run this example:\n  \
         cargo run --example multi_logger --features otel,rerun"
    );
}

#[cfg(all(feature = "otel", feature = "rerun"))]
#[tokio::main]
async fn main() -> Result<()> {
    let mut logger = build_multi_logger()?;

    println!("Logging to OpenTelemetry + Rerun");
    println!("================================\n");

    for epoch in 1..=EPOCHS {
        println!("Epoch {epoch}/{EPOCHS}");
        for step in 0..STEPS_PER_EPOCH {
            emit_train_metrics(&mut logger, epoch, step);
            tokio::time::sleep(Duration::from_millis(200)).await;
        }
        logger.end_epoch(epoch);
        println!();
    }

    println!("Flushing exporters (give the backends a second)...");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("Done! Inspect your OTLP backend and the Rerun viewer.");

    Ok(())
}

#[cfg(all(feature = "otel", feature = "rerun"))]
fn build_multi_logger() -> Result<MultiLogger> {
    let otel_logger = OpenTelemetryLogger::new(otel_config(), "multi-logger-job", Some("device-0"))
        .map_err(|err| anyhow!(err))?;

    let rerun_logger =
        RerunLogger::new(rerun_config(), "multi-logger-job").map_err(|err| anyhow!(err))?;

    Ok(MultiLogger::new()
        .add_logger(otel_logger)
        .add_logger(rerun_logger))
}

#[cfg(all(feature = "otel", feature = "rerun"))]
fn otel_config() -> OpenTelemetryConfig {
    let mut config = OpenTelemetryConfig::new("burn-telemetry-multi")
        .with_export_interval(Duration::from_secs(2))
        .with_export_timeout(Duration::from_secs(5))
        .with_max_batch_size(8)
        .with_async_export(true)
        .with_resource_attribute("example", "multi_logger");

    if let Ok(endpoint) = env::var("OTEL_EXPORTER_OTLP_ENDPOINT") {
        config = config.with_endpoint(endpoint);
    }

    config
}

#[cfg(all(feature = "otel", feature = "rerun"))]
fn rerun_config() -> RerunConfig {
    RerunConfig::new("burn-telemetry-multi")
        .with_spawn_viewer(true)
        .with_default_timeline("epoch")
}

#[cfg(all(feature = "otel", feature = "rerun"))]
fn emit_train_metrics(logger: &mut MultiLogger, epoch: usize, step: usize) {
    let global_step = (epoch - 1) * STEPS_PER_EPOCH + step + 1;
    let loss_value = 1.0 / global_step as f64;
    let accuracy_value = (global_step as f64 * 5.0).min(100.0) / 100.0;

    logger.log(&metric(
        "train/loss",
        format!("{loss_value:.4}"),
        format!("{loss_value}"),
    ));

    logger.log(&metric(
        "train/accuracy",
        format!("{:.2}%", accuracy_value * 100.0),
        format!("{accuracy_value}"),
    ));
}

#[cfg(all(feature = "otel", feature = "rerun"))]
fn metric(name: &str, formatted: String, serialized: String) -> MetricEntry {
    MetricEntry::new(Arc::new(name.to_string()), formatted, serialized)
}