use std::io::{self, IsTerminal};
use jammi_db::config::{JammiConfig, LogFormat};
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::EnvFilter;
pub fn init_tracing(config: &JammiConfig) {
install(config, io::stdout, io::stdout().is_terminal());
}
fn install<W>(config: &JammiConfig, writer: W, ansi: bool)
where
W: for<'w> MakeWriter<'w> + Send + Sync + 'static,
{
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new(config.logging.level.clone()));
let builder = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(writer)
.with_ansi(ansi);
match config.logging.format {
LogFormat::Json => builder.json().init(),
LogFormat::Text => builder.init(),
}
}
#[cfg(test)]
mod tests {
use std::io;
use std::sync::{Arc, Mutex};
use jammi_db::config::{JammiConfig, LogFormat};
use tracing::subscriber::DefaultGuard;
use tracing_subscriber::fmt::MakeWriter;
use super::*;
#[derive(Clone)]
struct BufferWriter(Arc<Mutex<Vec<u8>>>);
impl io::Write for BufferWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<'w> MakeWriter<'w> for BufferWriter {
type Writer = BufferWriter;
fn make_writer(&'w self) -> Self::Writer {
self.clone()
}
}
fn capture(format: LogFormat, emit: impl FnOnce()) -> String {
let buffer = Arc::new(Mutex::new(Vec::new()));
let writer = BufferWriter(buffer.clone());
let mut config = JammiConfig::default();
config.logging.level = "info".into();
config.logging.format = format;
let filter = EnvFilter::new(config.logging.level.clone());
let builder = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(writer)
.with_ansi(false);
let _guard: DefaultGuard = match config.logging.format {
LogFormat::Json => tracing::subscriber::set_default(builder.json().finish()),
LogFormat::Text => tracing::subscriber::set_default(builder.finish()),
};
emit();
let bytes = buffer.lock().unwrap().clone();
String::from_utf8(bytes).expect("log output is valid UTF-8")
}
#[test]
fn emits_records_to_a_non_tty_sink_in_text_format() {
let output = capture(LogFormat::Text, || {
tracing::info!(answer = 42, "device selected");
});
assert!(
!output.is_empty(),
"text logging produced no output to a non-TTY sink"
);
assert!(output.contains("device selected"));
assert!(output.contains("answer"));
}
#[test]
fn emits_records_to_a_non_tty_sink_in_json_format() {
let output = capture(LogFormat::Json, || {
tracing::info!(answer = 42, "device selected");
});
assert!(
!output.is_empty(),
"json logging produced no output to a non-TTY sink"
);
assert!(output.contains("\"message\":\"device selected\""));
assert!(output.contains("\"answer\":42"));
assert!(output.contains("\"level\":\"INFO\""));
}
}