| Crates.io | betterstack-tracing |
| lib.rs | betterstack-tracing |
| version | 0.1.0 |
| created_at | 2025-10-11 21:49:36.397402+00 |
| updated_at | 2025-10-11 21:49:36.397402+00 |
| description | A tracing layer for sending logs to Betterstack |
| homepage | |
| repository | https://github.com/gorilla-devs/betterstack-tracing |
| max_upload_size | |
| id | 1878594 |
| size | 136,939 |
A tracing layer for sending logs to Betterstack.
This crate provides a Rust implementation inspired by the slog-betterstack Go library, adapted for Rust's tracing ecosystem.
Add this to your Cargo.toml:
[dependencies]
betterstack-tracing = "0.1"
tracing = "0.1"
tracing-subscriber = "0.3"
tokio = { version = "1", features = ["full"] }
use betterstack_tracing::BetterstackLayer;
use tracing_subscriber::prelude::*;
#[tokio::main]
async fn main() {
// Create the Betterstack layer
let config = BetterstackLayer::builder("your-betterstack-token")
.build()
.expect("failed to create config");
let betterstack_layer = BetterstackLayer::new(config);
// Initialize tracing with the Betterstack layer
tracing_subscriber::registry()
.with(betterstack_layer)
.init();
// Use tracing as normal
tracing::info!("Application started");
tracing::error!(error = "connection refused", "Failed to connect");
// Give logs time to be sent before exiting
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
use std::time::Duration;
let config = BetterstackLayer::builder("your-token")
// Optional: Custom endpoint (default: https://in.logs.betterstack.com/)
.endpoint("https://in.logs.betterstack.com/")
// Optional: HTTP request timeout (default: 10s)
.timeout(Duration::from_secs(10))
// Optional: Batch size (default: 10 logs)
.batch_size(10)
// Optional: Batch delay (default: 2s)
.batch_delay(Duration::from_secs(2))
// Optional: Channel capacity (default: 1000)
.channel_capacity(1000)
// Optional: Include span context (default: true)
.include_span_context(true)
// Optional: Custom logger name (default: "tracing-betterstack")
.logger_name("my-app")
// Optional: Custom logger version (default: crate version)
.logger_version("1.0.0")
// Optional: Error callback
.on_error(|error| {
eprintln!("Betterstack error: {}", error);
})
.build()
.expect("failed to create config");
let layer = BetterstackLayer::new(config);
Logs are automatically batched and sent when either:
batch_size logs, ORbatch_delay time has elapsed since the last sendThis provides a good balance between latency and throughput.
When include_span_context is enabled (default), the layer automatically captures the current span hierarchy and includes it in the log payload:
let span = tracing::info_span!("http_request", request_id = "123");
let _enter = span.enter();
tracing::info!("Processing request");
// This log will include the "http_request" span context
use betterstack_tracing::BetterstackLayer;
use tracing_subscriber::prelude::*;
#[tokio::main]
async fn main() {
let token = std::env::var("BETTERSTACK_TOKEN")
.expect("BETTERSTACK_TOKEN must be set");
let config = BetterstackLayer::builder(token)
.build()
.expect("failed to create config");
let betterstack_layer = BetterstackLayer::new(config);
tracing_subscriber::registry()
.with(betterstack_layer)
.init();
tracing::info!("Hello, Betterstack!");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Combine with the fmt layer to log to both console and Betterstack:
use betterstack_tracing::BetterstackLayer;
use tracing_subscriber::prelude::*;
#[tokio::main]
async fn main() {
let config = BetterstackLayer::builder("your-token")
.build()
.expect("failed to create config");
let betterstack_layer = BetterstackLayer::new(config);
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer()) // Console output
.with(betterstack_layer) // Betterstack
.init();
tracing::info!("This goes to both console and Betterstack");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
use betterstack_tracing::BetterstackLayer;
use tracing_subscriber::prelude::*;
#[tokio::main]
async fn main() {
let config = BetterstackLayer::builder("your-token")
.include_span_context(true)
.build()
.expect("failed to create config");
let betterstack_layer = BetterstackLayer::new(config);
tracing_subscriber::registry()
.with(betterstack_layer)
.init();
let span = tracing::info_span!("http_request",
request_id = "req-123",
method = "POST"
);
let _enter = span.enter();
tracing::info!("Handling request");
// The log will include span context with request_id and method
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
See the examples/ directory for more complete examples.
Logs are sent to Betterstack in the following JSON format:
{
"dt": "2025-10-11T12:34:56.789Z",
"level": "INFO",
"message": "log message",
"logger.name": "betterstack-tracing",
"logger.version": "0.1.0",
"target": "my_app::module",
"file": "src/main.rs",
"line": 42,
"custom_field": "value",
"spans": [
{
"name": "http_request",
"fields": {
"request_id": "req-123"
}
}
]
}
All custom fields and span information are included at the root level of the JSON object, not nested under an "extra" key, in compliance with the Betterstack logs API.
If the log channel is full, new logs are dropped to prevent blocking your application.
By default, send errors are silently ignored. You can provide an error callback:
let config = BetterstackLayer::builder("your-token")
.on_error(|error| {
eprintln!("Failed to send logs to Betterstack: {}", error);
// Or send to metrics, etc.
})
.build()
.expect("failed to create config");
The crate automatically enforces Betterstack API size limits:
These limits help prevent rejected requests and ensure reliable log delivery.
The layer automatically flushes pending logs when dropped, with a 5-second timeout. For explicit control:
layer.flush().await;
Licensed under the MIT license.