tracing-better-stack 0.1.0

A tracing-subscriber layer for Better Stack (Logtail) logging
Documentation
//! A [`tracing-subscriber`](https://github.com/tokio-rs/tracing) layer for sending logs to [Better Stack](https://betterstack.com).
//!
//! This crate provides a seamless integration between Rust's `tracing` ecosystem and Better Stack's
//! log management platform. It automatically collects, batches, and sends your application logs
//! to Better Stack for centralized logging, monitoring, and analysis.
//!
//! # Features
//!
//! - **🚀 Asynchronous & Non-blocking**: Logs are sent in background tasks without blocking your application
//! - **📦 Automatic Batching**: Efficiently batches logs to minimize HTTP requests and improve performance
//! - **🔄 Retry Logic**: Built-in exponential backoff retry mechanism for handling transient failures
//! - **🎯 Structured Logging**: Full support for structured fields, span context, and nested spans
//! - **⚡ Lazy Initialization**: Gracefully handles tokio runtime initialization and shutdown
//! - **🛡️ Production Ready**: Comprehensive error handling with fail-open behavior to never crash your app
//! - **🗜️ Multiple Formats**: Choose between JSON and MessagePack serialization formats
//!
//! # Quick Start
//!
//! ```no_run
//! use tracing::{info, error};
//! use tracing_better_stack::{BetterStackLayer, BetterStackConfig};
//! use tracing_subscriber::prelude::*;
//!
//! #[tokio::main]
//! async fn main() {
//!     // Configure Better Stack layer with your ingesting host and source token
//!     // Get these from https://logs.betterstack.com/
//!     let layer = BetterStackLayer::new(
//!         BetterStackConfig::builder(
//!             "s1234567.us-east-9.betterstackdata.com",
//!             "your-source-token"
//!         ).build()
//!     );
//!
//!     // Initialize tracing with the Better Stack layer
//!     tracing_subscriber::registry()
//!         .with(layer)
//!         .init();
//!
//!     // Your logs are now being sent to Better Stack!
//!     info!(user_id = 123, "User logged in");
//!     error!(error = "Connection timeout", "Failed to connect to database");
//! }
//! ```
//!
//! # Serialization Formats
//!
//! Better Stack supports both JSON and MessagePack formats. This crate provides both via
//! mutually exclusive feature flags:
//!
//! ## MessagePack (Default)
//!
//! MessagePack is a binary serialization format that's more compact and efficient than JSON:
//!
//! ```toml
//! [dependencies]
//! tracing-better-stack = "0.1"  # Uses MessagePack by default
//! ```
//!
//! ## JSON
//!
//! JSON is human-readable and useful for debugging:
//!
//! ```toml
//! [dependencies]
//! tracing-better-stack = { version = "0.1", default-features = false, features = ["json"] }
//! ```
//!
//! # Configuration
//!
//! The [`BetterStackConfig`] builder provides extensive configuration options:
//!
//! ```no_run
//! use std::time::Duration;
//! use tracing_better_stack::{BetterStackLayer, BetterStackConfig};
//!
//! let layer = BetterStackLayer::new(
//!     BetterStackConfig::builder(
//!         "s1234567.us-east-9.betterstackdata.com",
//!         "your-source-token"
//!     )
//!         .batch_size(200)                                 // Max events per batch
//!         .batch_timeout(Duration::from_secs(10))          // Max time between batches
//!         .max_retries(5)                                  // Retry attempts on failure
//!         .initial_retry_delay(Duration::from_millis(200)) // Initial backoff delay
//!         .max_retry_delay(Duration::from_secs(30))        // Maximum backoff delay
//!         .include_location(true)                          // Include file/line info
//!         .include_spans(true)                             // Include span context
//!         .build()
//! );
//! ```
//!
//! # Structured Logging
//!
//! Take full advantage of tracing's structured logging capabilities:
//!
//! ```
//! use tracing::{info, instrument, warn};
//!
//! #[instrument(fields(request_id = %request_id))]
//! fn process_order(order_id: u64, request_id: &str) {
//!     info!(
//!         order_id,
//!         status = "processing",
//!         amount = 99.99,
//!         currency = "USD",
//!         "Processing payment for order"
//!     );
//!     
//!     // Nested spans are automatically included
//!     process_payment(order_id);
//! }
//!
//! #[instrument]
//! fn process_payment(order_id: u64) {
//!     warn!(order_id, "Payment gateway slow");
//! }
//! ```
//!
//! # Integration with Other Layers
//!
//! Combine with other tracing layers for comprehensive observability:
//!
//! ```no_run
//! use tracing_subscriber::prelude::*;
//! use tracing_subscriber::{fmt, EnvFilter};
//! use tracing_better_stack::{BetterStackLayer, BetterStackConfig};
//!
//! tracing_subscriber::registry()
//!     // Better Stack for production logging
//!     .with(BetterStackLayer::new(
//!         BetterStackConfig::builder("host", "token").build()
//!     ))
//!     // Console output for local development
//!     .with(fmt::layer().pretty())
//!     // Environment-based filtering
//!     .with(EnvFilter::from_default_env())
//!     .init();
//! ```
//!
//! # How It Works
//!
//! 1. **Event Collection**: The layer intercepts tracing events and converts them to log events
//! 2. **Batching**: Events are collected into batches (configurable size and timeout)
//! 3. **Serialization**: Batches are serialized to MessagePack or JSON
//! 4. **Async Sending**: Batches are sent asynchronously to Better Stack's HTTP API
//! 5. **Retry Logic**: Failed requests are retried with exponential backoff
//! 6. **Graceful Degradation**: Errors are logged but never panic or block your application
//!
//! # Performance Considerations
//!
//! - **Batching**: Reduces network overhead by sending multiple logs in a single request
//! - **Async Processing**: All network I/O happens in background tasks
//! - **MessagePack**: ~30-50% more compact than JSON, reducing bandwidth usage
//! - **Bounded Buffers**: Memory usage is controlled via batch size limits
//! - **Fail-Open**: If Better Stack is unreachable, logs are dropped without affecting your app
//!
//! # Error Handling
//!
//! This crate is designed to never panic or interfere with your application:
//!
//! - Network errors are logged to stderr and retried
//! - Serialization errors are logged and the batch is dropped
//! - Full batches trigger an immediate send
//! - The layer continues working even if Better Stack is down

// Compile-time check to ensure only one serialization format is enabled
#[cfg(all(feature = "json", feature = "message_pack"))]
compile_error!(
    "Features 'json' and 'message_pack' are mutually exclusive. \
     Please enable only one serialization format. \
     Use --no-default-features --features json for JSON, \
     or just use the default features for MessagePack."
);

#[cfg(not(any(feature = "json", feature = "message_pack")))]
compile_error!(
    "Either 'json' or 'message_pack' feature must be enabled. \
     Use --features json for JSON format, \
     or --features message_pack for MessagePack format (default)."
);

mod batch;
mod config;
mod error;
mod layer;
mod log_event;
mod sender;

#[doc(inline)]
pub use config::{BetterStackConfig, BetterStackConfigBuilder};
#[doc(inline)]
pub use error::{BetterStackError, Result};
#[doc(inline)]
pub use layer::BetterStackLayer;

#[cfg(test)]
mod tests {
    use super::*;
    use tracing::{debug, error, info, warn};
    use tracing_subscriber::prelude::*;

    #[test]
    fn test_layer_creation() {
        let config =
            BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "test-token")
                .batch_size(50)
                .build();

        assert_eq!(config.batch_size, 50);
        assert_eq!(config.source_token, "test-token");
        assert_eq!(
            config.ingesting_host,
            "s1234567.us-east-9.betterstackdata.com"
        );
    }

    #[tokio::test]
    async fn test_basic_logging() {
        let layer = BetterStackLayer::new(
            BetterStackConfig::builder("localhost:3000", "test-token").build(),
        );

        let subscriber = tracing_subscriber::registry().with(layer);

        tracing::subscriber::with_default(subscriber, || {
            info!("Test info message");
            warn!("Test warning");
            error!("Test error");
            debug!("Test debug");
        });

        // Give some time for async operations
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    }
}