ECS (Elastic Common Schema) 8.11 JSON formatter for tracing-subscriber.
Produces JSON logs conforming to the ECS 8.11 specification, suitable for ingestion into Elasticsearch, Kibana, and other ECS-compatible systems.
- ECS 8.11 compliant — Explicitly targets the latest ECS specification
- OpenTelemetry integration — Automatically extracts
trace.idandspan.idfrom OpenTelemetry context - Lightweight — Implements
FormatEventtrait, composes with existingtracing-subscriberlayers - Minimal dependencies — OpenTelemetry support is optional
- Service metadata — Built-in
service.nameandservice.versionfields
[dependencies]
tracing-ecs-formatter = "1"Without OpenTelemetry support (smaller dependency tree):
[dependencies]
tracing-ecs-formatter = { version = "1", default-features = false }use tracing_subscriber::fmt;
use tracing_ecs_formatter::EcsFormatter;
fn main() {
tracing_subscriber::fmt()
.event_format(EcsFormatter::new("my-service", "1.0.0"))
.init();
tracing::info!("Application started");
}Each log event produces a single JSON line:
{"@timestamp":"2024-01-15T10:30:00.000Z","log.level":"INFO","message":"Application started","ecs.version":"8.11","service.name":"my-service","service.version":"1.0.0","log.logger":"my_app"}| Field | Description |
|---|---|
@timestamp |
RFC 3339 timestamp |
log.level |
ERROR, WARN, INFO, DEBUG, or TRACE |
message |
Log message |
ecs.version |
Always "8.11" |
service.name |
Configured service name |
service.version |
Configured service version |
log.logger |
Tracing target (logger instance name) |
trace.id |
OpenTelemetry trace ID (when available) |
span.id |
OpenTelemetry span ID (when available) |
error.type |
Error type (when present) |
error.message |
Error message (when present) |
error.stack_trace |
Stack trace (when present) |
labels.* |
Additional fields from tracing events |
When used with tracing-opentelemetry, trace context is automatically included:
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_ecs_formatter::EcsFormatter;
fn main() {
let tracer = opentelemetry::trace::noop::NoopTracer::new();
let _ = tracing_subscriber::registry()
.with(tracing_opentelemetry::layer().with_tracer(tracer))
.with(
tracing_subscriber::fmt::layer()
.event_format(EcsFormatter::new("my-service", "1.0.0"))
)
.try_init();
}Use dotted field names to populate ECS error fields:
fn main() {
let stacktrace = "at db::connect (db.rs:42)\nat main (main.rs:10)";
tracing::error!(
error.type = "DatabaseError",
error.message = "Connection refused",
error.stack_trace = %stacktrace,
"Failed to connect to database"
);
}Any additional fields are output as labels.*:
fn main() {
tracing::info!(
user_id = 42,
request_id = "abc-123",
"User logged in"
);
}Output:
{"@timestamp":"2024-01-15T10:30:00.000Z","log.level":"INFO","message":"User logged in","ecs.version":"8.11","service.name":"my-service","service.version":"1.0.0","log.logger":"my_app","labels.user_id":"42","labels.request_id":"abc-123"}Works with all field types:
fn main() {
let headers = vec![("Content-Type", "application/json")];
let addr = std::net::Ipv4Addr::new(192, 168, 1, 1);
// Strings
tracing::info!(endpoint = "/api/users", "Request received");
// Numbers (serialized as strings in labels)
tracing::info!(latency_ms = 150, status_code = 200, "Request completed");
// Debug formatting
tracing::info!(?headers, "Processing request");
// Display formatting
tracing::info!(client_ip = %addr, "Connection established");
}| Feature | Default | Description |
|---|---|---|
opentelemetry |
Yes | Enables trace.id and span.id extraction from OpenTelemetry context |
Licensed under Apache License, Version 2.0.