#![cfg(feature = "logging")]
use std::fs;
use std::path::PathBuf;
use stratify::logging::{ConsoleConfig, FileConfig, FileFormat};
use tracing::subscriber::with_default;
fn scratch(label: &str) -> PathBuf {
std::env::temp_dir().join(format!("lk-format-{}-{label}", std::process::id()))
}
fn write_one(dir: &PathBuf, format: FileFormat) -> String {
let _ = fs::remove_dir_all(dir);
let (subscriber, handle) = stratify::logging::builder()
.with_filter(tracing_subscriber::EnvFilter::new("info"))
.file(FileConfig::new(dir.to_string_lossy().to_string()).with_format(format))
.build()
.expect("the file sink should build");
with_default(subscriber, || {
tracing::info!(request_id = "req-7", "hello from the file sink");
});
handle.flush();
drop(handle);
let entry = fs::read_dir(dir)
.expect("the log directory should exist")
.filter_map(Result::ok)
.next()
.expect("a log file should have been written");
fs::read_to_string(entry.path()).expect("the log file should be readable")
}
#[test]
fn text_format_writes_a_human_readable_line() {
let dir = scratch("text");
let contents = write_one(&dir, FileFormat::Text);
assert!(
!contents.trim_start().starts_with('{'),
"text format must not emit JSON, got: {contents}"
);
assert!(
contents.contains("hello from the file sink"),
"got: {contents}"
);
assert!(contents.contains("INFO"), "got: {contents}");
assert!(
contents.contains("req-7"),
"structured fields survive: {contents}"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn text_format_writes_no_ansi_escapes() {
let dir = scratch("noansi");
let contents = write_one(&dir, FileFormat::Text);
assert!(
!contents.contains('\u{1b}'),
"escape codes reached the file"
);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn json_remains_the_default() {
let dir = scratch("default");
let _ = fs::remove_dir_all(&dir);
let (subscriber, handle) = stratify::logging::builder()
.with_filter(tracing_subscriber::EnvFilter::new("info"))
.file(FileConfig::new(dir.to_string_lossy().to_string()))
.build()
.expect("builds");
with_default(subscriber, || tracing::info!("default format"));
handle.flush();
drop(handle);
let entry = fs::read_dir(&dir)
.expect("directory exists")
.filter_map(Result::ok)
.next()
.expect("a log file was written");
let contents = fs::read_to_string(entry.path()).expect("readable");
assert!(
contents.trim_start().starts_with('{'),
"the default must still be JSON, got: {contents}"
);
assert_eq!(FileFormat::default(), FileFormat::Json);
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn json_format_is_still_available_explicitly() {
let dir = scratch("json");
let contents = write_one(&dir, FileFormat::Json);
assert!(contents.trim_start().starts_with('{'), "got: {contents}");
assert!(contents.contains("\"level\":\"INFO\""), "got: {contents}");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn span_fields_in_the_file_carry_no_ansi_even_with_color_off_everywhere() {
let dir = std::env::temp_dir().join(format!("lk-ansi-off-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
let (subscriber, handle) = stratify::logging::builder()
.with_filter(stratify::logging::EnvFilter::new("info"))
.console(ConsoleConfig::default().with_color(false))
.file(FileConfig::new(dir.to_string_lossy().to_string()).with_format(FileFormat::Text))
.build()
.expect("builds");
with_default(subscriber, || {
let span = tracing::info_span!("http_request", method = "GET", path = "/health");
let _entered = span.enter();
tracing::info!(latency = 3, "finished");
});
handle.flush();
drop(handle);
let entry = fs::read_dir(&dir)
.expect("directory")
.filter_map(Result::ok)
.next()
.expect("a log file");
let body = fs::read_to_string(entry.path()).expect("readable");
let _ = fs::remove_dir_all(&dir);
assert!(body.contains("http_request"), "the span renders: {body}");
assert!(
!body.contains('\u{1b}'),
"no escape codes anywhere in the file: {body:?}"
);
}
#[test]
fn a_colored_console_cannot_bleed_ansi_into_the_file() {
let dir = std::env::temp_dir().join(format!("lk-ansi-bleed-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
let (subscriber, handle) = stratify::logging::builder()
.with_filter(stratify::logging::EnvFilter::new("info"))
.console(ConsoleConfig::default().with_color(true))
.file(FileConfig::new(dir.to_string_lossy().to_string()).with_format(FileFormat::Text))
.build()
.expect("builds");
with_default(subscriber, || {
let span = tracing::info_span!("http_request", method = "GET");
let _entered = span.enter();
tracing::info!("finished");
});
handle.flush();
drop(handle);
let entry = fs::read_dir(&dir)
.expect("directory")
.filter_map(Result::ok)
.next()
.expect("a log file");
let body = fs::read_to_string(entry.path()).expect("readable");
let _ = fs::remove_dir_all(&dir);
assert!(
!body.contains('\u{1b}'),
"console colour must stay out of the file: {body:?}"
);
}