1use std::path::PathBuf;
17use std::sync::OnceLock;
18use tracing_subscriber::prelude::*;
19
20struct Guards {
23 #[cfg(feature = "profiling")]
24 _chrome: tracing_chrome::FlushGuard,
25 #[cfg(feature = "flamegraph")]
26 _flame: tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>,
27}
28
29static GUARDS: OnceLock<Guards> = OnceLock::new();
30
31pub fn log_dir() -> PathBuf {
33 dirs::home_dir()
34 .unwrap_or_else(|| PathBuf::from("/tmp"))
35 .join(".cache/zshrs")
36}
37
38pub fn log_path() -> PathBuf {
40 log_dir().join("zshrs.log")
41}
42
43pub fn init() {
49 GUARDS.get_or_init(|| {
50 let dir = log_dir();
51 let _ = std::fs::create_dir_all(&dir);
52 let pid = std::process::id();
53
54 let log_file = std::fs::OpenOptions::new()
58 .create(true)
59 .append(true)
60 .open(dir.join("zshrs.log"))
61 .unwrap_or_else(|_| {
62 std::fs::OpenOptions::new()
63 .create(true)
64 .append(true)
65 .open("/tmp/zshrs.log")
66 .expect("cannot open any log file")
67 });
68 let log_writer = std::sync::Mutex::new(log_file);
69
70 let env_filter = std::env::var("ZSHRS_LOG").unwrap_or_else(|_| "info".to_string());
71
72 let file_layer = tracing_subscriber::fmt::layer()
73 .with_writer(log_writer)
74 .with_ansi(false)
75 .with_target(true)
76 .with_thread_names(true)
77 .compact();
78
79 #[cfg(feature = "profiling")]
81 let (chrome_layer, chrome_guard) = {
82 let trace_path = dir.join(format!("trace-{}.json", pid));
83 let (layer, guard) = tracing_chrome::ChromeLayerBuilder::new()
84 .file(trace_path)
85 .include_args(true)
86 .build();
87 (Some(layer), guard)
88 };
89 #[cfg(not(feature = "profiling"))]
90 let chrome_layer: Option<tracing_subscriber::layer::Identity> = None;
91
92 #[cfg(feature = "flamegraph")]
94 let (flame_layer, flame_guard) = {
95 let flame_path = dir.join(format!("flame-{}.folded", pid));
96 let file =
97 std::fs::File::create(&flame_path).expect("cannot create flamegraph output file");
98 let writer = std::io::BufWriter::new(file);
99 let (layer, guard) = tracing_flame::FlameLayer::with_writer(writer).build();
100 (Some(layer), guard)
101 };
102 #[cfg(not(feature = "flamegraph"))]
103 let flame_layer: Option<tracing_subscriber::layer::Identity> = None;
104
105 #[cfg(feature = "prometheus")]
107 {
108 let builder = metrics_exporter_prometheus::PrometheusBuilder::new();
110 if let Err(e) = builder.with_http_listener(([127, 0, 0, 1], 9090)).install() {
111 eprintln!("zshrs: failed to start prometheus exporter: {}", e);
112 }
113 }
114
115 let subscriber = tracing_subscriber::registry()
117 .with(tracing_subscriber::EnvFilter::new(&env_filter))
118 .with(file_layer)
119 .with(chrome_layer)
120 .with(flame_layer);
121
122 let _ = tracing::subscriber::set_global_default(subscriber);
123
124 Guards {
125 #[cfg(feature = "profiling")]
126 _chrome: chrome_guard,
127 #[cfg(feature = "flamegraph")]
128 _flame: flame_guard,
129 }
130 });
131}
132
133pub fn flush() {
136 std::thread::sleep(std::time::Duration::from_millis(50));
140}
141
142pub fn profiling_enabled() -> bool {
144 cfg!(feature = "profiling")
145}
146
147pub fn flamegraph_enabled() -> bool {
148 cfg!(feature = "flamegraph")
149}
150
151pub fn prometheus_enabled() -> bool {
152 cfg!(feature = "prometheus")
153}