zlayer_observability/
logging.rs1use std::io;
4use tracing_appender::non_blocking::WorkerGuard;
5use tracing_subscriber::{
6 fmt::{self, format::FmtSpan},
7 layer::SubscriberExt,
8 util::SubscriberInitExt,
9 EnvFilter,
10};
11
12use crate::config::{FileLoggingConfig, LogFormat, LoggingConfig, RotationStrategy};
13use crate::error::Result;
14
15pub struct LogGuard {
17 _guard: Option<WorkerGuard>,
18}
19
20impl LogGuard {
21 fn new(guard: Option<WorkerGuard>) -> Self {
22 Self { _guard: guard }
23 }
24}
25
26pub fn init_logging(config: &LoggingConfig) -> Result<LogGuard> {
31 let env_filter = EnvFilter::try_from_default_env()
32 .unwrap_or_else(|_| EnvFilter::new(level_to_string(config.level)));
33
34 let (file_writer, guard) = if let Some(file_config) = &config.file {
36 let (writer, guard) = create_file_writer(file_config)?;
37 (Some(writer), Some(guard))
38 } else {
39 (None, None)
40 };
41
42 match (config.format, file_writer) {
45 (LogFormat::Pretty, Some(file_writer)) => {
46 let console_layer = fmt::layer()
47 .with_writer(io::stdout)
48 .with_target(config.include_target)
49 .with_file(config.include_location)
50 .with_line_number(config.include_location)
51 .with_span_events(FmtSpan::CLOSE)
52 .pretty();
53
54 let file_layer = fmt::layer()
55 .with_writer(file_writer)
56 .with_target(config.include_target)
57 .with_file(config.include_location)
58 .with_line_number(config.include_location)
59 .with_span_events(FmtSpan::CLOSE)
60 .with_ansi(false)
61 .json();
62
63 tracing_subscriber::registry()
64 .with(env_filter)
65 .with(console_layer)
66 .with(file_layer)
67 .init();
68 }
69 (LogFormat::Pretty, None) => {
70 let console_layer = fmt::layer()
71 .with_writer(io::stdout)
72 .with_target(config.include_target)
73 .with_file(config.include_location)
74 .with_line_number(config.include_location)
75 .with_span_events(FmtSpan::CLOSE)
76 .pretty();
77
78 tracing_subscriber::registry()
79 .with(env_filter)
80 .with(console_layer)
81 .init();
82 }
83 (LogFormat::Json, Some(file_writer)) => {
84 let console_layer = fmt::layer()
85 .with_writer(io::stdout)
86 .with_target(config.include_target)
87 .with_file(config.include_location)
88 .with_line_number(config.include_location)
89 .with_span_events(FmtSpan::CLOSE)
90 .json();
91
92 let file_layer = fmt::layer()
93 .with_writer(file_writer)
94 .with_target(config.include_target)
95 .with_file(config.include_location)
96 .with_line_number(config.include_location)
97 .with_span_events(FmtSpan::CLOSE)
98 .with_ansi(false)
99 .json();
100
101 tracing_subscriber::registry()
102 .with(env_filter)
103 .with(console_layer)
104 .with(file_layer)
105 .init();
106 }
107 (LogFormat::Json, None) => {
108 let console_layer = fmt::layer()
109 .with_writer(io::stdout)
110 .with_target(config.include_target)
111 .with_file(config.include_location)
112 .with_line_number(config.include_location)
113 .with_span_events(FmtSpan::CLOSE)
114 .json();
115
116 tracing_subscriber::registry()
117 .with(env_filter)
118 .with(console_layer)
119 .init();
120 }
121 (LogFormat::Compact, Some(file_writer)) => {
122 let console_layer = fmt::layer()
123 .with_writer(io::stdout)
124 .with_target(config.include_target)
125 .with_file(config.include_location)
126 .with_line_number(config.include_location)
127 .with_span_events(FmtSpan::CLOSE)
128 .compact();
129
130 let file_layer = fmt::layer()
131 .with_writer(file_writer)
132 .with_target(config.include_target)
133 .with_file(config.include_location)
134 .with_line_number(config.include_location)
135 .with_span_events(FmtSpan::CLOSE)
136 .with_ansi(false)
137 .json();
138
139 tracing_subscriber::registry()
140 .with(env_filter)
141 .with(console_layer)
142 .with(file_layer)
143 .init();
144 }
145 (LogFormat::Compact, None) => {
146 let console_layer = fmt::layer()
147 .with_writer(io::stdout)
148 .with_target(config.include_target)
149 .with_file(config.include_location)
150 .with_line_number(config.include_location)
151 .with_span_events(FmtSpan::CLOSE)
152 .compact();
153
154 tracing_subscriber::registry()
155 .with(env_filter)
156 .with(console_layer)
157 .init();
158 }
159 }
160
161 Ok(LogGuard::new(guard))
162}
163
164fn level_to_string(level: crate::config::LogLevel) -> String {
165 match level {
166 crate::config::LogLevel::Trace => "trace",
167 crate::config::LogLevel::Debug => "debug",
168 crate::config::LogLevel::Info => "info",
169 crate::config::LogLevel::Warn => "warn",
170 crate::config::LogLevel::Error => "error",
171 }
172 .to_string()
173}
174
175fn create_file_writer(
176 config: &FileLoggingConfig,
177) -> Result<(tracing_appender::non_blocking::NonBlocking, WorkerGuard)> {
178 let file_appender = match config.rotation {
179 RotationStrategy::Daily => {
180 tracing_appender::rolling::daily(&config.directory, &config.prefix)
181 }
182 RotationStrategy::Hourly => {
183 tracing_appender::rolling::hourly(&config.directory, &config.prefix)
184 }
185 RotationStrategy::Never => {
186 tracing_appender::rolling::never(&config.directory, &config.prefix)
187 }
188 };
189
190 Ok(tracing_appender::non_blocking(file_appender))
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_log_level_conversion() {
199 assert_eq!(level_to_string(crate::config::LogLevel::Info), "info");
200 assert_eq!(level_to_string(crate::config::LogLevel::Debug), "debug");
201 assert_eq!(level_to_string(crate::config::LogLevel::Trace), "trace");
202 assert_eq!(level_to_string(crate::config::LogLevel::Warn), "warn");
203 assert_eq!(level_to_string(crate::config::LogLevel::Error), "error");
204 }
205
206 #[test]
207 fn test_log_guard_creation() {
208 let guard = LogGuard::new(None);
209 assert!(guard._guard.is_none());
210 }
211}