tracing-better-stack 0.1.0

A tracing-subscriber layer for Better Stack (Logtail) logging
Documentation
use std::env;
use std::time::Duration;
use tracing::{Level, debug, error, info, span};
use tracing_better_stack::{BetterStackConfig, BetterStackLayer};
use tracing_subscriber::prelude::*;

#[tokio::main]
async fn main() {
    // Get the Better Stack ingesting host from environment variable
    // (Better Stack provides unique hosts for each source, e.g., "s1234567.us-east-9.betterstackdata.com")
    let ingesting_host = env::var("BETTER_STACK_INGESTING_HOST")
        .expect("BETTER_STACK_INGESTING_HOST environment variable must be set");

    // Get the Better Stack source token from environment variable
    let source_token = env::var("BETTER_STACK_SOURCE_TOKEN")
        .expect("BETTER_STACK_SOURCE_TOKEN environment variable must be set");

    // Create a custom configured Better Stack layer
    let better_stack_layer = BetterStackLayer::new(
        BetterStackConfig::builder(ingesting_host, source_token)
            .batch_size(200) // Larger batch size for high volume
            .batch_timeout(Duration::from_secs(10)) // Wait longer before sending
            .max_retries(5) // More aggressive retries
            .initial_retry_delay(Duration::from_millis(200))
            .max_retry_delay(Duration::from_secs(30))
            .include_location(false) // Don't include file/line info
            .include_spans(false) // Don't include span context
            .build(),
    );

    // Initialize tracing with custom configuration
    tracing_subscriber::registry()
        .with(better_stack_layer)
        .with(
            tracing_subscriber::fmt::layer()
                .with_target(true)
                .with_level(true)
                .with_thread_ids(true)
                .with_thread_names(true)
                .pretty(),
        )
        .init();

    // Demonstrate different log levels and features
    info!("Application started with custom configuration");

    // High volume logging example
    for i in 0..50 {
        debug!(iteration = i, "Processing item");
        if i % 10 == 0 {
            info!(progress = i, "Checkpoint reached");
        }
    }

    // Structured logging with custom fields
    let user_id = 12345;
    let session_id = "abc-def-ghi";

    info!(
        user_id = user_id,
        session_id = session_id,
        action = "purchase",
        amount = 99.99,
        currency = "USD",
        items = ?vec!["item1", "item2", "item3"],
        "Order completed successfully"
    );

    // Error handling with context
    match process_payment(user_id, 150.00).await {
        Ok(_) => info!(user_id, "Payment processed successfully"),
        Err(e) => error!(
            user_id,
            error = %e,
            retry_attempts = 3,
            "Payment processing failed"
        ),
    }

    // Nested spans (won't be included due to include_spans=false)
    let outer_span = span!(Level::INFO, "outer_operation", request_id = "req-123");
    let _outer = outer_span.enter();

    info!("Starting outer operation");

    {
        let inner_span = span!(Level::DEBUG, "inner_operation");
        let _inner = inner_span.enter();
        debug!("Performing inner operation");
    }

    info!("Outer operation completed");

    // Wait for final batch to be sent
    tokio::time::sleep(Duration::from_secs(11)).await;

    info!("Application shutting down");
}

async fn process_payment(_user_id: u64, amount: f64) -> Result<(), String> {
    if amount > 100.0 {
        Err("Amount exceeds limit".to_string())
    } else {
        Ok(())
    }
}