1use parking_lot::Mutex;
2use std::fs::OpenOptions;
3use std::io::{self, Write};
4use tracing_subscriber::{
5 EnvFilter, Registry,
6 fmt::{self, format::FmtSpan},
7 layer::SubscriberExt,
8 util::SubscriberInitExt,
9};
10
11static LOGGING_ENABLED: std::sync::LazyLock<Mutex<bool>> =
12 std::sync::LazyLock::new(|| Mutex::new(false));
13static LOG_FILE: std::sync::LazyLock<Mutex<Option<std::fs::File>>> =
14 std::sync::LazyLock::new(|| Mutex::new(None));
15static LOG_TO_STDOUT: std::sync::LazyLock<Mutex<bool>> =
16 std::sync::LazyLock::new(|| Mutex::new(false));
17static VERBOSE_LOGGING: std::sync::LazyLock<Mutex<bool>> =
18 std::sync::LazyLock::new(|| Mutex::new(false));
19
20#[derive(Clone)]
22struct UnifiedWriter;
23
24impl Write for UnifiedWriter {
25 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
26 if let Some(file) = LOG_FILE.lock().as_mut() {
28 let _ = file.write_all(buf);
29 let _ = file.flush();
30 }
31
32 if *LOG_TO_STDOUT.lock() {
34 let _ = io::stdout().write_all(buf);
35 }
36
37 Ok(buf.len())
38 }
39
40 fn flush(&mut self) -> io::Result<()> {
41 if let Some(file) = LOG_FILE.lock().as_mut() {
42 let _ = file.flush();
43 }
44 if *LOG_TO_STDOUT.lock() {
45 let _ = io::stdout().flush();
46 }
47 Ok(())
48 }
49}
50
51impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for UnifiedWriter {
52 type Writer = UnifiedWriter;
53
54 fn make_writer(&'a self) -> Self::Writer {
55 UnifiedWriter
56 }
57}
58
59pub fn init(debug: bool) -> Result<(), Box<dyn std::error::Error>> {
69 use std::sync::{Once, OnceLock};
70 static INIT: Once = Once::new();
71 static INIT_RESULT: OnceLock<Result<(), String>> = OnceLock::new();
72
73 INIT.call_once(|| {
74 let verbose_from_env = std::env::var("GIT_IRIS_VERBOSE").is_ok()
76 || std::env::var("RUST_LOG").is_ok_and(|v| v.contains("debug") || v.contains("trace"));
77
78 let verbose = debug || verbose_from_env;
79
80 if verbose {
81 set_verbose_logging(true);
82 set_log_to_stdout(true);
83 }
84
85 enable_logging();
87
88 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
90 if verbose {
91 "git_iris=debug,iris=debug,rig=info,warn".into()
92 } else {
93 "warn".into()
95 }
96 });
97
98 let fmt_layer = fmt::Layer::new()
99 .with_target(true)
100 .with_level(true)
101 .with_timer(fmt::time::ChronoUtc::rfc_3339())
102 .with_span_events(FmtSpan::CLOSE)
103 .with_writer(UnifiedWriter);
104
105 let result = Registry::default()
106 .with(env_filter)
107 .with(fmt_layer)
108 .try_init()
109 .map_err(|e| format!("Failed to initialize logging: {e}"));
110
111 let _ = INIT_RESULT.set(result);
112 });
113
114 match INIT_RESULT.get() {
115 Some(Ok(())) => Ok(()),
116 Some(Err(e)) => Err(e.clone().into()),
117 None => Err("Initialization failed unexpectedly".into()),
118 }
119}
120
121pub fn enable_logging() {
122 let mut logging_enabled = LOGGING_ENABLED.lock();
123 *logging_enabled = true;
124}
125
126pub fn disable_logging() {
127 let mut logging_enabled = LOGGING_ENABLED.lock();
128 *logging_enabled = false;
129}
130
131pub fn set_verbose_logging(enabled: bool) {
132 let mut verbose_logging = VERBOSE_LOGGING.lock();
133 *verbose_logging = enabled;
134
135 }
138
139#[must_use]
141pub fn has_log_file() -> bool {
142 LOG_FILE.lock().is_some()
143}
144
145pub fn set_log_file(file_path: &str) -> std::io::Result<()> {
151 let file = OpenOptions::new()
152 .create(true)
153 .append(true)
154 .open(file_path)?;
155
156 let mut log_file = LOG_FILE.lock();
157 *log_file = Some(file);
158 Ok(())
159}
160
161pub fn set_log_to_stdout(enabled: bool) {
162 let mut log_to_stdout = LOG_TO_STDOUT.lock();
163 *log_to_stdout = enabled;
164}
165
166#[macro_export]
168macro_rules! log_debug {
169 ($($arg:tt)*) => {
170 tracing::debug!($($arg)*)
171 };
172}
173
174#[macro_export]
175macro_rules! log_error {
176 ($($arg:tt)*) => {
177 tracing::error!($($arg)*)
178 };
179}
180
181#[macro_export]
182macro_rules! log_info {
183 ($($arg:tt)*) => {
184 tracing::info!($($arg)*)
185 };
186}
187
188#[macro_export]
189macro_rules! log_warn {
190 ($($arg:tt)*) => {
191 tracing::warn!($($arg)*)
192 };
193}
194
195#[macro_export]
197macro_rules! trace_debug {
198 (target: $target:expr, $($arg:tt)*) => {
199 tracing::debug!(target: $target, $($arg)*)
200 };
201 ($($arg:tt)*) => {
202 tracing::debug!($($arg)*)
203 };
204}
205
206#[macro_export]
207macro_rules! trace_info {
208 (target: $target:expr, $($arg:tt)*) => {
209 tracing::info!(target: $target, $($arg)*)
210 };
211 ($($arg:tt)*) => {
212 tracing::info!($($arg)*)
213 };
214}
215
216#[macro_export]
217macro_rules! trace_warn {
218 (target: $target:expr, $($arg:tt)*) => {
219 tracing::warn!(target: $target, $($arg)*)
220 };
221 ($($arg:tt)*) => {
222 tracing::warn!($($arg)*)
223 };
224}
225
226#[macro_export]
227macro_rules! trace_error {
228 (target: $target:expr, $($arg:tt)*) => {
229 tracing::error!(target: $target, $($arg)*)
230 };
231 ($($arg:tt)*) => {
232 tracing::error!($($arg)*)
233 };
234}