1use crate::{LogLevel, Xlog};
5use std::fmt;
6use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
7use std::sync::Arc;
8use tracing::field::{Field, Visit};
9use tracing::{Event, Level, Metadata, Subscriber};
10use tracing_subscriber::layer::{Context, Layer};
11use tracing_subscriber::registry::LookupSpan;
12
13#[derive(Debug, Clone)]
15pub struct XlogLayerConfig {
16 pub enabled: bool,
18 pub level: LogLevel,
20 pub tag: Option<String>,
22 pub include_spans: bool,
24}
25
26impl XlogLayerConfig {
27 pub fn new(level: LogLevel) -> Self {
30 Self {
31 enabled: true,
32 level,
33 tag: None,
34 include_spans: false,
35 }
36 }
37
38 pub fn enabled(mut self, enabled: bool) -> Self {
40 self.enabled = enabled;
41 self
42 }
43
44 pub fn level(mut self, level: LogLevel) -> Self {
46 self.level = level;
47 self
48 }
49
50 pub fn tag(mut self, tag: impl Into<String>) -> Self {
52 self.tag = Some(tag.into());
53 self
54 }
55
56 pub fn include_spans(mut self, include: bool) -> Self {
58 self.include_spans = include;
59 self
60 }
61}
62
63#[derive(Clone)]
65pub struct XlogLayerHandle {
66 state: Arc<LayerState>,
67}
68
69impl XlogLayerHandle {
70 pub fn set_enabled(&self, enabled: bool) {
72 self.state.enabled.store(enabled, Ordering::Release);
73 }
74
75 pub fn enabled(&self) -> bool {
77 self.state.enabled.load(Ordering::Acquire)
78 }
79
80 pub fn set_level(&self, level: LogLevel) {
82 self.state
83 .level
84 .store(level_to_u8(level), Ordering::Release);
85 }
86
87 pub fn level(&self) -> LogLevel {
89 level_from_u8(self.state.level.load(Ordering::Acquire))
90 }
91}
92
93pub struct XlogLayer {
95 state: Arc<LayerState>,
96 tag: Option<String>,
97 include_spans: bool,
98}
99
100impl XlogLayer {
101 pub fn new(logger: Xlog) -> (Self, XlogLayerHandle) {
103 let config = XlogLayerConfig::new(logger.level());
104 Self::with_config(logger, config)
105 }
106
107 pub fn with_config(logger: Xlog, config: XlogLayerConfig) -> (Self, XlogLayerHandle) {
112 let state = Arc::new(LayerState::new(logger, config.enabled, config.level));
113 let layer = Self {
114 state: Arc::clone(&state),
115 tag: config.tag,
116 include_spans: config.include_spans,
117 };
118 let handle = XlogLayerHandle { state };
119 (layer, handle)
120 }
121
122 pub fn handle(&self) -> XlogLayerHandle {
124 XlogLayerHandle {
125 state: Arc::clone(&self.state),
126 }
127 }
128
129 fn is_enabled_for(&self, level: LogLevel) -> bool {
130 if !self.state.enabled.load(Ordering::Acquire) {
131 return false;
132 }
133 let min_level = level_from_u8(self.state.level.load(Ordering::Acquire));
134 level_rank(level) >= level_rank(min_level)
135 }
136
137 fn is_metadata_enabled(&self, metadata: &Metadata<'_>) -> bool {
138 let level = tracing_level_to_log_level(metadata.level());
139 level != LogLevel::None && self.is_enabled_for(level)
140 }
141}
142
143impl<S> Layer<S> for XlogLayer
144where
145 S: Subscriber + for<'a> LookupSpan<'a>,
146{
147 fn enabled(&self, metadata: &Metadata<'_>, _ctx: Context<'_, S>) -> bool {
148 self.is_metadata_enabled(metadata)
149 }
150
151 fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
152 let metadata = event.metadata();
153 let level = tracing_level_to_log_level(metadata.level());
154 if level == LogLevel::None {
155 return;
156 }
157 if !self.is_enabled_for(level) {
158 return;
159 }
160 if !self.state.logger.is_enabled(level) {
161 return;
162 }
163
164 let mut visitor = EventVisitor::default();
165 event.record(&mut visitor);
166
167 let mut message = visitor.finish();
168 if self.include_spans {
169 if let Some(scope) = ctx.event_scope(event) {
170 let mut spans = String::new();
171 for span in scope.from_root() {
172 if !spans.is_empty() {
173 spans.push_str(" > ");
174 }
175 spans.push_str(span.metadata().name());
176 }
177 if !spans.is_empty() {
178 if message.is_empty() {
179 message = spans;
180 } else {
181 message = format!("[{}] {}", spans, message);
182 }
183 }
184 }
185 }
186 if message.is_empty() {
187 message = metadata.name().to_string();
188 }
189
190 let tag = self.tag.as_deref().unwrap_or_else(|| metadata.target());
191 let file = metadata.file().unwrap_or("<unknown>");
192 let module = metadata.module_path().unwrap_or("<unknown>");
193 let line = metadata.line().unwrap_or(0);
194
195 self.state
196 .logger
197 .write_with_meta(level, Some(tag), file, module, line, &message);
198 }
199}
200
201struct LayerState {
202 enabled: AtomicBool,
203 level: AtomicU8,
204 logger: Xlog,
205}
206
207impl LayerState {
208 fn new(logger: Xlog, enabled: bool, level: LogLevel) -> Self {
209 Self {
210 enabled: AtomicBool::new(enabled),
211 level: AtomicU8::new(level_to_u8(level)),
212 logger,
213 }
214 }
215}
216
217#[derive(Default)]
218struct EventVisitor {
219 message: Option<String>,
220 fields: Vec<(String, String)>,
221}
222
223impl EventVisitor {
224 fn finish(self) -> String {
225 let mut output = String::new();
226 if let Some(message) = self.message {
227 output.push_str(&message);
228 }
229 if !self.fields.is_empty() {
230 if !output.is_empty() {
231 output.push(' ');
232 }
233 output.push('{');
234 for (idx, (name, value)) in self.fields.iter().enumerate() {
235 if idx > 0 {
236 output.push_str(", ");
237 }
238 output.push_str(name);
239 output.push('=');
240 output.push_str(value);
241 }
242 output.push('}');
243 }
244 output
245 }
246
247 fn record_field(&mut self, field: &Field, value: String) {
248 if field.name() == "message" {
249 self.message = Some(value);
250 } else {
251 self.fields.push((field.name().to_string(), value));
252 }
253 }
254}
255
256impl Visit for EventVisitor {
257 fn record_f64(&mut self, field: &Field, value: f64) {
258 self.record_field(field, value.to_string());
259 }
260
261 fn record_i64(&mut self, field: &Field, value: i64) {
262 self.record_field(field, value.to_string());
263 }
264
265 fn record_u64(&mut self, field: &Field, value: u64) {
266 self.record_field(field, value.to_string());
267 }
268
269 fn record_bool(&mut self, field: &Field, value: bool) {
270 self.record_field(field, value.to_string());
271 }
272
273 fn record_str(&mut self, field: &Field, value: &str) {
274 self.record_field(field, value.to_string());
275 }
276
277 fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
278 self.record_field(field, format!("{value:?}"));
279 }
280}
281
282fn tracing_level_to_log_level(level: &Level) -> LogLevel {
283 match *level {
284 Level::TRACE => LogLevel::Verbose,
285 Level::DEBUG => LogLevel::Debug,
286 Level::INFO => LogLevel::Info,
287 Level::WARN => LogLevel::Warn,
288 Level::ERROR => LogLevel::Error,
289 }
290}
291
292fn level_rank(level: LogLevel) -> u8 {
293 match level {
294 LogLevel::Verbose => 0,
295 LogLevel::Debug => 1,
296 LogLevel::Info => 2,
297 LogLevel::Warn => 3,
298 LogLevel::Error => 4,
299 LogLevel::Fatal => 5,
300 LogLevel::None => 6,
301 }
302}
303
304fn level_to_u8(level: LogLevel) -> u8 {
305 level_rank(level)
306}
307
308fn level_from_u8(value: u8) -> LogLevel {
309 match value {
310 0 => LogLevel::Verbose,
311 1 => LogLevel::Debug,
312 2 => LogLevel::Info,
313 3 => LogLevel::Warn,
314 4 => LogLevel::Error,
315 5 => LogLevel::Fatal,
316 _ => LogLevel::None,
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use std::sync::atomic::{AtomicUsize, Ordering};
323
324 use tempfile::TempDir;
325
326 use super::{XlogLayer, XlogLayerConfig};
327 use crate::{LogLevel, Xlog, XlogConfig};
328
329 static NEXT_PREFIX_ID: AtomicUsize = AtomicUsize::new(1);
330
331 fn unique_prefix() -> String {
332 let id = NEXT_PREFIX_ID.fetch_add(1, Ordering::Relaxed);
333 format!("tracing-layer-{}-{id}", std::process::id())
334 }
335
336 #[test]
337 fn with_config_does_not_mutate_logger_level() {
338 let dir = TempDir::new().expect("tempdir");
339 let logger = Xlog::init(
340 XlogConfig::new(dir.path().display().to_string(), unique_prefix()),
341 LogLevel::Info,
342 )
343 .expect("init logger");
344 assert_eq!(logger.level(), LogLevel::Info);
345
346 let (_layer, _handle) =
347 XlogLayer::with_config(logger.clone(), XlogLayerConfig::new(LogLevel::Debug));
348 assert_eq!(logger.level(), LogLevel::Info);
349 }
350
351 #[test]
352 fn handle_set_level_only_updates_layer_filter() {
353 let dir = TempDir::new().expect("tempdir");
354 let logger = Xlog::init(
355 XlogConfig::new(dir.path().display().to_string(), unique_prefix()),
356 LogLevel::Warn,
357 )
358 .expect("init logger");
359 let (_layer, handle) =
360 XlogLayer::with_config(logger.clone(), XlogLayerConfig::new(LogLevel::Info));
361
362 assert_eq!(logger.level(), LogLevel::Warn);
363 assert_eq!(handle.level(), LogLevel::Info);
364
365 handle.set_level(LogLevel::Debug);
366 assert_eq!(handle.level(), LogLevel::Debug);
367 assert_eq!(logger.level(), LogLevel::Warn);
368 }
369}