tracing_better_stack/
lib.rs

1//! A [`tracing-subscriber`](https://github.com/tokio-rs/tracing) layer for sending logs to [Better Stack](https://betterstack.com).
2//!
3//! This crate provides a seamless integration between Rust's `tracing` ecosystem and Better Stack's
4//! log management platform. It automatically collects, batches, and sends your application logs
5//! to Better Stack for centralized logging, monitoring, and analysis.
6//!
7//! # Features
8//!
9//! - **🚀 Asynchronous & Non-blocking**: Logs are sent in background tasks without blocking your application
10//! - **📦 Automatic Batching**: Efficiently batches logs to minimize HTTP requests and improve performance
11//! - **🔄 Retry Logic**: Built-in exponential backoff retry mechanism for handling transient failures
12//! - **🎯 Structured Logging**: Full support for structured fields, span context, and nested spans
13//! - **⚡ Lazy Initialization**: Gracefully handles tokio runtime initialization and shutdown
14//! - **🛡️ Production Ready**: Comprehensive error handling with fail-open behavior to never crash your app
15//! - **🗜️ Multiple Formats**: Choose between JSON and MessagePack serialization formats
16//!
17//! # Quick Start
18//!
19//! ```no_run
20//! use tracing::{info, error};
21//! use tracing_better_stack::{BetterStackLayer, BetterStackConfig};
22//! use tracing_subscriber::prelude::*;
23//!
24//! #[tokio::main]
25//! async fn main() {
26//!     // Configure Better Stack layer with your ingesting host and source token
27//!     // Get these from https://logs.betterstack.com/
28//!     let layer = BetterStackLayer::new(
29//!         BetterStackConfig::builder(
30//!             "s1234567.us-east-9.betterstackdata.com",
31//!             "your-source-token"
32//!         ).build()
33//!     );
34//!
35//!     // Initialize tracing with the Better Stack layer
36//!     tracing_subscriber::registry()
37//!         .with(layer)
38//!         .init();
39//!
40//!     // Your logs are now being sent to Better Stack!
41//!     info!(user_id = 123, "User logged in");
42//!     error!(error = "Connection timeout", "Failed to connect to database");
43//! }
44//! ```
45//!
46//! # Serialization Formats
47//!
48//! Better Stack supports both JSON and MessagePack formats. This crate provides both via
49//! mutually exclusive feature flags:
50//!
51//! ## MessagePack (Default)
52//!
53//! MessagePack is a binary serialization format that's more compact and efficient than JSON:
54//!
55//! ```toml
56//! [dependencies]
57//! tracing-better-stack = "0.1"  # Uses MessagePack by default
58//! ```
59//!
60//! ## JSON
61//!
62//! JSON is human-readable and useful for debugging:
63//!
64//! ```toml
65//! [dependencies]
66//! tracing-better-stack = { version = "0.1", default-features = false, features = ["json"] }
67//! ```
68//!
69//! # Configuration
70//!
71//! The [`BetterStackConfig`] builder provides extensive configuration options:
72//!
73//! ```no_run
74//! use std::time::Duration;
75//! use tracing_better_stack::{BetterStackLayer, BetterStackConfig};
76//!
77//! let layer = BetterStackLayer::new(
78//!     BetterStackConfig::builder(
79//!         "s1234567.us-east-9.betterstackdata.com",
80//!         "your-source-token"
81//!     )
82//!         .batch_size(200)                                 // Max events per batch
83//!         .batch_timeout(Duration::from_secs(10))          // Max time between batches
84//!         .max_retries(5)                                  // Retry attempts on failure
85//!         .initial_retry_delay(Duration::from_millis(200)) // Initial backoff delay
86//!         .max_retry_delay(Duration::from_secs(30))        // Maximum backoff delay
87//!         .include_location(true)                          // Include file/line info
88//!         .include_spans(true)                             // Include span context
89//!         .build()
90//! );
91//! ```
92//!
93//! # Structured Logging
94//!
95//! Take full advantage of tracing's structured logging capabilities:
96//!
97//! ```
98//! use tracing::{info, instrument, warn};
99//!
100//! #[instrument(fields(request_id = %request_id))]
101//! fn process_order(order_id: u64, request_id: &str) {
102//!     info!(
103//!         order_id,
104//!         status = "processing",
105//!         amount = 99.99,
106//!         currency = "USD",
107//!         "Processing payment for order"
108//!     );
109//!     
110//!     // Nested spans are automatically included
111//!     process_payment(order_id);
112//! }
113//!
114//! #[instrument]
115//! fn process_payment(order_id: u64) {
116//!     warn!(order_id, "Payment gateway slow");
117//! }
118//! ```
119//!
120//! # Integration with Other Layers
121//!
122//! Combine with other tracing layers for comprehensive observability:
123//!
124//! ```no_run
125//! use tracing_subscriber::prelude::*;
126//! use tracing_subscriber::{fmt, EnvFilter};
127//! use tracing_better_stack::{BetterStackLayer, BetterStackConfig};
128//!
129//! tracing_subscriber::registry()
130//!     // Better Stack for production logging
131//!     .with(BetterStackLayer::new(
132//!         BetterStackConfig::builder("host", "token").build()
133//!     ))
134//!     // Console output for local development
135//!     .with(fmt::layer().pretty())
136//!     // Environment-based filtering
137//!     .with(EnvFilter::from_default_env())
138//!     .init();
139//! ```
140//!
141//! # How It Works
142//!
143//! 1. **Event Collection**: The layer intercepts tracing events and converts them to log events
144//! 2. **Batching**: Events are collected into batches (configurable size and timeout)
145//! 3. **Serialization**: Batches are serialized to MessagePack or JSON
146//! 4. **Async Sending**: Batches are sent asynchronously to Better Stack's HTTP API
147//! 5. **Retry Logic**: Failed requests are retried with exponential backoff
148//! 6. **Graceful Degradation**: Errors are logged but never panic or block your application
149//!
150//! # Performance Considerations
151//!
152//! - **Batching**: Reduces network overhead by sending multiple logs in a single request
153//! - **Async Processing**: All network I/O happens in background tasks
154//! - **MessagePack**: ~30-50% more compact than JSON, reducing bandwidth usage
155//! - **Bounded Buffers**: Memory usage is controlled via batch size limits
156//! - **Fail-Open**: If Better Stack is unreachable, logs are dropped without affecting your app
157//!
158//! # Error Handling
159//!
160//! This crate is designed to never panic or interfere with your application:
161//!
162//! - Network errors are logged to stderr and retried
163//! - Serialization errors are logged and the batch is dropped
164//! - Full batches trigger an immediate send
165//! - The layer continues working even if Better Stack is down
166
167// Compile-time check to ensure only one serialization format is enabled
168#[cfg(all(feature = "json", feature = "message_pack"))]
169compile_error!(
170    "Features 'json' and 'message_pack' are mutually exclusive. \
171     Please enable only one serialization format. \
172     Use --no-default-features --features json for JSON, \
173     or just use the default features for MessagePack."
174);
175
176#[cfg(not(any(feature = "json", feature = "message_pack")))]
177compile_error!(
178    "Either 'json' or 'message_pack' feature must be enabled. \
179     Use --features json for JSON format, \
180     or --features message_pack for MessagePack format (default)."
181);
182
183mod batch;
184mod config;
185mod error;
186mod layer;
187mod log_event;
188mod sender;
189
190#[doc(inline)]
191pub use config::{BetterStackConfig, BetterStackConfigBuilder};
192#[doc(inline)]
193pub use error::{BetterStackError, Result};
194#[doc(inline)]
195pub use layer::BetterStackLayer;
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use tracing::{debug, error, info, warn};
201    use tracing_subscriber::prelude::*;
202
203    #[test]
204    fn test_layer_creation() {
205        let config =
206            BetterStackConfig::builder("s1234567.us-east-9.betterstackdata.com", "test-token")
207                .batch_size(50)
208                .build();
209
210        assert_eq!(config.batch_size, 50);
211        assert_eq!(config.source_token, "test-token");
212        assert_eq!(
213            config.ingesting_host,
214            "s1234567.us-east-9.betterstackdata.com"
215        );
216    }
217
218    #[tokio::test]
219    async fn test_basic_logging() {
220        let layer = BetterStackLayer::new(
221            BetterStackConfig::builder("localhost:3000", "test-token").build(),
222        );
223
224        let subscriber = tracing_subscriber::registry().with(layer);
225
226        tracing::subscriber::with_default(subscriber, || {
227            info!("Test info message");
228            warn!("Test warning");
229            error!("Test error");
230            debug!("Test debug");
231        });
232
233        // Give some time for async operations
234        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
235    }
236}