fresh/services/
status_log.rs1use std::fs::File;
8use std::io::Write;
9use std::path::PathBuf;
10use std::sync::{Arc, Mutex};
11use tracing_subscriber::layer::Context;
12use tracing_subscriber::registry::LookupSpan;
13use tracing_subscriber::Layer;
14
15pub struct StatusLogLayer {
17 file: Arc<Mutex<File>>,
18}
19
20pub struct StatusLogHandle {
22 pub path: PathBuf,
24}
25
26pub fn create() -> std::io::Result<(StatusLogLayer, StatusLogHandle)> {
30 create_with_path(super::log_dirs::status_log_path())
31}
32
33pub fn create_with_path(path: PathBuf) -> std::io::Result<(StatusLogLayer, StatusLogHandle)> {
35 let file = File::create(&path)?;
36
37 let layer = StatusLogLayer {
38 file: Arc::new(Mutex::new(file)),
39 };
40
41 let handle = StatusLogHandle { path };
42
43 Ok((layer, handle))
44}
45
46impl<S> Layer<S> for StatusLogLayer
47where
48 S: tracing::Subscriber + for<'a> LookupSpan<'a>,
49{
50 fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
51 let target = event.metadata().target();
53 if target != "status" {
54 return;
55 }
56
57 let mut visitor = StringVisitor::default();
59 event.record(&mut visitor);
60
61 let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
63 let line = format!("{} {}\n", timestamp, visitor.0);
64
65 #[allow(clippy::let_underscore_must_use)]
67 if let Ok(mut file) = self.file.lock() {
68 let _ = file.write_all(line.as_bytes());
69 let _ = file.flush();
70 }
71 }
72}
73
74#[derive(Default)]
76struct StringVisitor(String);
77
78impl tracing::field::Visit for StringVisitor {
79 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
80 if field.name() == "message" {
81 self.0 = format!("{:?}", value);
82 }
83 }
84
85 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
86 if field.name() == "message" {
87 self.0 = value.to_string();
88 }
89 }
90}