use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::EnvFilter;
pub fn is_json_format() -> bool {
matches!(std::env::var("LOG_FORMAT").ok().as_deref(), Some("json"))
}
pub fn init_logging() {
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
if is_json_format() {
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.json()
.init();
} else {
tracing_subscriber::fmt().with_env_filter(env_filter).init();
}
}
pub fn json_subscriber_for_writer<W>(writer: W) -> impl tracing::Subscriber + Send + Sync
where
W: for<'a> MakeWriter<'a> + Send + Sync + 'static,
{
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::new("info"))
.json()
.with_writer(writer)
.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use std::sync::{Arc, Mutex};
use tracing_subscriber::fmt::MakeWriter;
#[test]
fn is_json_format_true_when_env_set() {
std::env::set_var("LOG_FORMAT", "json");
assert!(is_json_format());
std::env::remove_var("LOG_FORMAT");
}
#[test]
fn is_json_format_false_when_unset() {
std::env::remove_var("LOG_FORMAT");
assert!(!is_json_format());
}
#[test]
fn is_json_format_false_for_other_values() {
std::env::set_var("LOG_FORMAT", "pretty");
assert!(!is_json_format());
std::env::remove_var("LOG_FORMAT");
}
#[derive(Clone)]
struct BufWriter(Arc<Mutex<Vec<u8>>>);
impl Write for BufWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> MakeWriter<'a> for BufWriter {
type Writer = BufWriter;
fn make_writer(&'a self) -> Self::Writer {
self.clone()
}
}
#[test]
fn json_output_is_parseable_with_required_fields() {
let buf = Arc::new(Mutex::new(Vec::<u8>::new()));
let writer = BufWriter(buf.clone());
let subscriber = json_subscriber_for_writer(writer);
tracing::subscriber::with_default(subscriber, || {
tracing::info!(target: "ac17_test", "hello world");
});
let bytes = buf.lock().unwrap().clone();
let text = String::from_utf8(bytes).expect("utf-8 log output");
assert!(!text.is_empty(), "expected at least one log line");
let mut saw_event = false;
for line in text.lines().filter(|l| !l.trim().is_empty()) {
let v: serde_json::Value =
serde_json::from_str(line).unwrap_or_else(|e| panic!("invalid JSON: {line}: {e}"));
assert!(v.get("timestamp").is_some(), "missing timestamp: {line}");
assert!(v.get("level").is_some(), "missing level: {line}");
assert!(v.get("target").is_some(), "missing target: {line}");
assert!(v.get("fields").is_some(), "missing fields: {line}");
if v["target"] == "ac17_test" {
assert_eq!(v["level"], "INFO");
assert_eq!(v["fields"]["message"], "hello world");
saw_event = true;
}
}
assert!(
saw_event,
"expected to see the emitted test event in JSON output"
);
}
}