-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathlib.rs
More file actions
123 lines (106 loc) · 4.64 KB
/
Copy pathlib.rs
File metadata and controls
123 lines (106 loc) · 4.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 2026 (c) Copyright Contributors to the GOSH DAO. All rights reserved.
//
//! Shared tracing setup for the dodex services.
//!
//! Every service logs to stdout, filtered by `RUST_LOG` (default `info`).
//! When the `LOG_DIR` environment variable is set and non-empty, each service
//! ALSO writes human-readable, daily-rotated files named `<service>.log.<date>`
//! into that directory, keeping at most `LOG_MAX_FILES` (default 14) of them.
//!
//! Lives in its own crate — free of the heavy `dodex-infrastructure`
//! dependency graph — so the standalone `market-manager` workspace can reuse
//! it by path, exactly like `dodex-chain`.
use std::env;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_appender::rolling::RollingFileAppender;
use tracing_appender::rolling::Rotation;
use tracing_subscriber::fmt;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
/// Daily log files retained before the oldest is pruned, when `LOG_MAX_FILES`
/// is unset or unparseable.
const DEFAULT_MAX_FILES: usize = 14;
fn env_filter() -> EnvFilter {
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
}
fn max_files() -> usize {
env::var("LOG_MAX_FILES").ok().and_then(|v| v.parse().ok()).unwrap_or(DEFAULT_MAX_FILES)
}
/// Build the daily rolling-file appender for `service` under `dir`, creating
/// the directory if needed. Errors out (rather than panicking) so the caller
/// can fall back to stdout-only.
fn file_appender(dir: &str, service: &str) -> anyhow::Result<RollingFileAppender> {
std::fs::create_dir_all(dir)?;
let appender = RollingFileAppender::builder()
.rotation(Rotation::DAILY)
.filename_prefix(format!("{service}.log"))
.max_log_files(max_files())
.build(dir)?;
Ok(appender)
}
/// Install the global tracing subscriber for `service`.
///
/// Always logs to stdout. If `LOG_DIR` is set and non-empty, also writes
/// daily-rotated `<service>.log.<date>` files there. On a log-dir error, warns
/// loudly to the (already-installed) stdout logger and continues stdout-only.
///
/// Returns the appender guard(s). The caller MUST keep them alive for the
/// lifetime of the process (`let _guards = dodex_logging::init("api");`) — drop
/// them and the background file writer stops flushing.
#[must_use]
pub fn init(service: &str) -> Vec<WorkerGuard> {
let stdout_layer = fmt::layer().with_writer(std::io::stdout);
let log_dir = env::var("LOG_DIR").unwrap_or_default();
if log_dir.is_empty() {
tracing_subscriber::registry().with(env_filter()).with(stdout_layer).init();
return Vec::new();
}
match file_appender(&log_dir, service) {
Ok(appender) => {
let (writer, guard) = tracing_appender::non_blocking(appender);
let file_layer = fmt::layer().with_ansi(false).with_writer(writer);
tracing_subscriber::registry()
.with(env_filter())
.with(stdout_layer)
.with(file_layer)
.init();
vec![guard]
}
Err(err) => {
tracing_subscriber::registry().with(env_filter()).with(stdout_layer).init();
tracing::warn!(
log_dir = %log_dir,
error = %err,
"LOG_DIR set but file logging could not be initialised; continuing with stdout only"
);
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_a_rotated_file_when_dir_is_set() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().to_str().expect("utf-8 path");
let appender = file_appender(path, "testsvc").expect("appender builds");
let (writer, guard) = tracing_appender::non_blocking(appender);
let subscriber =
tracing_subscriber::registry().with(fmt::layer().with_ansi(false).with_writer(writer));
tracing::subscriber::with_default(subscriber, || {
tracing::info!("hello-from-test");
});
// Dropping the guard flushes and joins the background writer thread.
drop(guard);
let log_files: Vec<_> = std::fs::read_dir(dir.path())
.expect("read tempdir")
.filter_map(Result::ok)
.filter(|e| e.file_name().to_string_lossy().starts_with("testsvc.log"))
.collect();
assert_eq!(log_files.len(), 1, "exactly one rotated file is created");
let contents = std::fs::read_to_string(log_files[0].path()).expect("read log file");
assert!(contents.contains("hello-from-test"), "log line written, got: {contents}");
}
}