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 if let Ok(mut file) = self.file.lock() {
67 let _ = file.write_all(line.as_bytes());
68 let _ = file.flush();
69 }
70 }
71}
72
73#[derive(Default)]
75struct StringVisitor(String);
76
77impl tracing::field::Visit for StringVisitor {
78 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
79 if field.name() == "message" {
80 self.0 = format!("{:?}", value);
81 }
82 }
83
84 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
85 if field.name() == "message" {
86 self.0 = value.to_string();
87 }
88 }
89}