feat(observability): use human-readable log format for terminals
When stdout is connected to a TTY, use tracing_subscriber's pretty format with colors instead of single-line JSON. Non-TTY (container logs, pipes) continue to output JSON for log aggregation. Override auto-detection via APP_LOG_FORMAT=json|pretty. Also adds APP_LOG_PRETTY=true to use serde_json::to_string_pretty for human-readable JSON output (useful for development/debugging).
This commit is contained in:
parent
ecf9f33b26
commit
7d7103e271
@ -156,7 +156,13 @@ where
|
|||||||
ordered.insert(key.clone(), value.clone());
|
ordered.insert(key.clone(), value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(writer, "{}", serde_json::to_string(&ordered).map_err(|_| fmt::Error)?)
|
// Use pretty JSON for human readability in terminals, compact for pipelines
|
||||||
|
let json = if std::env::var("APP_LOG_PRETTY").as_deref() == Ok("true") {
|
||||||
|
serde_json::to_string_pretty(&ordered)
|
||||||
|
} else {
|
||||||
|
serde_json::to_string(&ordered)
|
||||||
|
};
|
||||||
|
write!(writer, "{}", json.map_err(|_| fmt::Error)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
//! Tracing subscriber initialisation with JSON output.
|
//! Tracing subscriber initialisation.
|
||||||
//!
|
//!
|
||||||
//! Uses a custom `MsgJsonFormat` that injects `_msg` as the first field
|
//! Terminal (TTY): human-readable format with colors
|
||||||
//! for VictoriaLogs compatibility.
|
//! Pipeline (non-TTY): JSON output for VictoriaLogs
|
||||||
|
//! Override via `APP_LOG_FORMAT=json|pretty` env var.
|
||||||
|
|
||||||
use crate::msg_json_fmt::MsgJsonFormat;
|
use crate::msg_json_fmt::MsgJsonFormat;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::io::IsTerminal;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
fmt::{self, format::FmtSpan, Layer as FmtLayer},
|
fmt::{self, format::FmtSpan},
|
||||||
layer::SubscriberExt,
|
layer::SubscriberExt,
|
||||||
util::SubscriberInitExt,
|
util::SubscriberInitExt,
|
||||||
EnvFilter,
|
EnvFilter,
|
||||||
};
|
};
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
/// Global instance identifier, resolved once at startup.
|
/// Global instance identifier, resolved once at startup.
|
||||||
/// Priority: `INSTANCE_ID` env var → system hostname → `"unknown"`.
|
/// Priority: `INSTANCE_ID` env var → system hostname → `"unknown"`.
|
||||||
@ -34,23 +37,42 @@ pub fn instance_id() -> String {
|
|||||||
INSTANCE_ID.clone()
|
INSTANCE_ID.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialises the global tracing subscriber with JSON-formatted output to stderr.
|
/// Determines the log format based on environment and TTY detection.
|
||||||
|
fn use_json() -> bool {
|
||||||
|
match std::env::var("APP_LOG_FORMAT").as_deref() {
|
||||||
|
Ok("json") => true,
|
||||||
|
Ok("pretty") => false,
|
||||||
|
_ => !std::io::stdout().is_terminal(), // TTY → pretty, non-TTY → json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialises the global tracing subscriber.
|
||||||
///
|
///
|
||||||
/// Each JSON line includes `_msg` (first field), `timestamp`, `level`, `target`,
|
/// TTY terminals get human-readable output with colors.
|
||||||
/// `file`, `line`, and structured event fields.
|
/// Non-TTY (pipes, container logs) get JSON for log aggregation.
|
||||||
|
/// `APP_LOG_FORMAT=json|pretty` overrides auto-detection.
|
||||||
/// `RUST_LOG` env var controls the log level filter.
|
/// `RUST_LOG` env var controls the log level filter.
|
||||||
///
|
///
|
||||||
/// Pass `defer = true` when OTLP will be initialized afterwards via `init_otlp()`;
|
/// Pass `defer = true` when OTLP will be initialized afterwards via `init_otlp()`.
|
||||||
/// in that case this function only builds the subscriber without calling `try_init()`,
|
|
||||||
/// and the combined (fmt + OTLP) subscriber is installed by `init_otlp()` instead.
|
|
||||||
pub fn init_tracing_subscriber(level: &str, defer: bool) {
|
pub fn init_tracing_subscriber(level: &str, defer: bool) {
|
||||||
let env_filter = EnvFilter::try_from_default_env()
|
let env_filter = EnvFilter::try_from_default_env()
|
||||||
.or_else(|_| EnvFilter::from_str(level))
|
.or_else(|_| EnvFilter::from_str(level))
|
||||||
.expect("invalid log level");
|
.expect("invalid log level");
|
||||||
|
|
||||||
let mut fmt_layer: FmtLayer<_, _, _, _> = fmt::layer()
|
let fmt_layer: Box<dyn Layer<_> + Send + Sync> = if use_json() {
|
||||||
.event_format(MsgJsonFormat);
|
let mut layer = fmt::layer()
|
||||||
fmt_layer.set_span_events(FmtSpan::CLOSE);
|
.event_format(MsgJsonFormat);
|
||||||
|
layer.set_span_events(FmtSpan::CLOSE);
|
||||||
|
<_ as Layer<_>>::boxed(layer)
|
||||||
|
} else {
|
||||||
|
let mut layer = fmt::layer()
|
||||||
|
.with_target(false)
|
||||||
|
.with_level(true)
|
||||||
|
.with_ansi(std::io::stdout().is_terminal())
|
||||||
|
.pretty();
|
||||||
|
layer.set_span_events(FmtSpan::CLOSE);
|
||||||
|
<_ as Layer<_>>::boxed(layer)
|
||||||
|
};
|
||||||
|
|
||||||
let registry = tracing_subscriber::registry()
|
let registry = tracing_subscriber::registry()
|
||||||
.with(env_filter)
|
.with(env_filter)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user