fresh/services/
tracing_setup.rs1use std::fs::File;
7use std::path::Path;
8use std::sync::Arc;
9use tracing_subscriber::prelude::*;
10use tracing_subscriber::{fmt, EnvFilter};
11
12use super::status_log::{StatusLogHandle, StatusLogLayer};
13use super::warning_log::{WarningLogHandle, WarningLogLayer};
14
15pub struct TracingHandles {
17 pub warning: WarningLogHandle,
18 pub status: StatusLogHandle,
19}
20
21pub fn init_global(log_file_path: &Path) -> Option<TracingHandles> {
31 let (warning_layer, warning_handle) = super::warning_log::create().ok()?;
32 let (status_layer, status_handle) = super::status_log::create().ok()?;
33 let log_file = File::create(log_file_path).ok()?;
34
35 let subscriber = build_subscriber(log_file, Some(warning_layer), Some(status_layer));
36 subscriber.init();
37
38 Some(TracingHandles {
39 warning: warning_handle,
40 status: status_handle,
41 })
42}
43
44pub fn build_subscriber(
48 log_file: File,
49 warning_layer: Option<WarningLogLayer>,
50 status_layer: Option<StatusLogLayer>,
51) -> impl tracing::Subscriber + Send + Sync {
52 let env_filter = EnvFilter::from_default_env()
53 .add_directive(tracing::Level::DEBUG.into())
54 .add_directive("swc_ecma_transforms_base=info".parse().unwrap())
56 .add_directive("swc_common=info".parse().unwrap());
57
58 let span_events = if std::env::var("FRESH_LOG_SPANS").is_ok() {
59 fmt::format::FmtSpan::CLOSE
60 } else {
61 fmt::format::FmtSpan::NONE
62 };
63
64 let fmt_layer = fmt::layer()
65 .with_writer(Arc::new(log_file))
66 .with_span_events(span_events);
67
68 tracing_subscriber::registry()
69 .with(fmt_layer)
70 .with(env_filter)
71 .with(warning_layer)
72 .with(status_layer)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use std::time::Duration;
79 use tempfile::{NamedTempFile, TempPath};
80
81 struct TestSubscriber {
82 subscriber: Box<dyn tracing::Subscriber + Send + Sync>,
83 warning_handle: WarningLogHandle,
84 _log_file: NamedTempFile,
86 _warning_log_path: TempPath,
87 _status_log_path: TempPath,
88 }
89
90 fn create_test_subscriber() -> TestSubscriber {
91 let log_file = NamedTempFile::new().unwrap();
92 let warning_log_file = NamedTempFile::new().unwrap();
93 let warning_log_path = warning_log_file.into_temp_path();
94 let status_log_file = NamedTempFile::new().unwrap();
95 let status_log_path = status_log_file.into_temp_path();
96
97 let (warning_layer, warning_handle) =
98 super::super::warning_log::create_with_path(warning_log_path.to_path_buf()).unwrap();
99 let (status_layer, _status_handle) =
100 super::super::status_log::create_with_path(status_log_path.to_path_buf()).unwrap();
101
102 let subscriber = build_subscriber(
103 log_file.reopen().unwrap(),
104 Some(warning_layer),
105 Some(status_layer),
106 );
107
108 TestSubscriber {
109 subscriber: Box::new(subscriber),
110 warning_handle,
111 _log_file: log_file,
112 _warning_log_path: warning_log_path,
113 _status_log_path: status_log_path,
114 }
115 }
116
117 #[test]
118 fn test_warning_log_captures_warn_level() {
119 let test = create_test_subscriber();
120 let path = test.warning_handle.path.clone();
121
122 tracing::subscriber::with_default(test.subscriber, || {
123 tracing::warn!("Test warning message");
124 });
125
126 let result = test
127 .warning_handle
128 .receiver
129 .recv_timeout(Duration::from_secs(1));
130 assert!(result.is_ok(), "Should receive notification for WARN");
131
132 let contents = std::fs::read_to_string(&path).expect("Failed to read log");
133 assert!(contents.contains("WARN"), "Log should contain WARN level");
134 assert!(
135 contents.contains("Test warning message"),
136 "Log should contain message"
137 );
138 }
139
140 #[test]
141 fn test_warning_log_captures_error_level() {
142 let test = create_test_subscriber();
143 let path = test.warning_handle.path.clone();
144
145 tracing::subscriber::with_default(test.subscriber, || {
146 tracing::error!("Test error message");
147 });
148
149 let result = test
150 .warning_handle
151 .receiver
152 .recv_timeout(Duration::from_secs(1));
153 assert!(result.is_ok(), "Should receive notification for ERROR");
154
155 let contents = std::fs::read_to_string(&path).expect("Failed to read log");
156 assert!(contents.contains("ERROR"), "Log should contain ERROR level");
157 assert!(
158 contents.contains("Test error message"),
159 "Log should contain message"
160 );
161 }
162
163 #[test]
164 fn test_warning_log_ignores_info_level() {
165 let test = create_test_subscriber();
166 let path = test.warning_handle.path.clone();
167
168 tracing::subscriber::with_default(test.subscriber, || {
169 tracing::info!("Test info message");
170 });
171
172 let result = test
173 .warning_handle
174 .receiver
175 .recv_timeout(Duration::from_millis(100));
176 assert!(result.is_err(), "Should NOT receive notification for INFO");
177
178 let contents = std::fs::read_to_string(&path).unwrap_or_default();
179 assert!(
180 !contents.contains("Test info message"),
181 "Log should NOT contain INFO message"
182 );
183 }
184
185 #[test]
186 fn test_warning_log_ignores_debug_level() {
187 let test = create_test_subscriber();
188
189 tracing::subscriber::with_default(test.subscriber, || {
190 tracing::debug!("Test debug message");
191 });
192
193 let result = test
194 .warning_handle
195 .receiver
196 .recv_timeout(Duration::from_millis(100));
197 assert!(result.is_err(), "Should NOT receive notification for DEBUG");
198 }
199
200 #[test]
201 fn test_warning_log_multiple_warnings() {
202 let test = create_test_subscriber();
203 let path = test.warning_handle.path.clone();
204
205 tracing::subscriber::with_default(test.subscriber, || {
206 tracing::warn!("First warning");
207 tracing::error!("An error");
208 tracing::warn!("Second warning");
209 });
210
211 for i in 0..3 {
212 let result = test
213 .warning_handle
214 .receiver
215 .recv_timeout(Duration::from_secs(1));
216 assert!(result.is_ok(), "Should receive notification {}", i + 1);
217 }
218
219 let contents = std::fs::read_to_string(&path).expect("Failed to read log");
220 assert!(
221 contents.contains("First warning"),
222 "Log should contain first warning"
223 );
224 assert!(contents.contains("An error"), "Log should contain error");
225 assert!(
226 contents.contains("Second warning"),
227 "Log should contain second warning"
228 );
229 }
230}