1#[macro_export]
12macro_rules! trace {
13 ($($tokens:tt)*) => {
14 #[cfg(feature = "tracing")]
15 ::tracing::trace!($($tokens)*)
16 }
17}
18
19#[macro_export]
20macro_rules! debug {
21 ($($tokens:tt)*) => {
22 #[cfg(feature = "tracing")]
23 ::tracing::debug!($($tokens)*)
24 }
25}
26
27#[macro_export]
28macro_rules! warn {
29 ($($tokens:tt)*) => {
30 #[cfg(feature = "tracing")]
31 ::tracing::warn!($($tokens)*)
32 }
33}
34
35#[macro_export]
36macro_rules! info {
37 ($($tokens:tt)*) => {
38 #[cfg(feature = "tracing")]
39 ::tracing::info!($($tokens)*)
40 }
41}
42
43#[macro_export]
44macro_rules! error {
45 ($($tokens:tt)*) => {
46 #[cfg(feature = "tracing")]
47 ::tracing::error!($($tokens)*)
48 }
49}
50
51#[cfg(feature = "tracing")]
52const COMMAND_SPAN_TARGET: &str = "COMMAND";
53
54#[cfg(feature = "tracing")]
55pub fn trace_cmd(command: &crate::BootstrapCommand) -> tracing::span::EnteredSpan {
56 let fingerprint = command.fingerprint();
57 let location = command.get_created_location();
58 let location = format!("{}:{}", location.file(), location.line());
59
60 tracing::span!(
61 target: COMMAND_SPAN_TARGET,
62 tracing::Level::TRACE,
63 "cmd",
64 cmd_name = fingerprint.program_name().to_string(),
65 cmd = fingerprint.format_short_cmd(),
66 full_cmd = ?command,
67 location
68 )
69 .entered()
70}
71
72#[cfg(feature = "tracing")]
83mod inner {
84 use std::fmt::Debug;
85 use std::fs::File;
86 use std::io::Write;
87 use std::sync::atomic::Ordering;
88
89 use chrono::{DateTime, Utc};
90 use tracing::field::{Field, Visit};
91 use tracing::{Event, Id, Level, Subscriber};
92 use tracing_subscriber::layer::{Context, SubscriberExt};
93 use tracing_subscriber::registry::{LookupSpan, SpanRef};
94 use tracing_subscriber::{EnvFilter, Layer};
95
96 use crate::STEP_SPAN_TARGET;
97 use crate::utils::tracing::COMMAND_SPAN_TARGET;
98
99 pub fn setup_tracing(env_name: &str) -> TracingGuard {
100 let filter = EnvFilter::from_env(env_name);
101
102 let registry = tracing_subscriber::registry().with(filter).with(TracingPrinter::default());
103
104 let tempdir = tempfile::TempDir::new().expect("Cannot create temporary directory");
110 let chrome_tracing_path = tempdir.path().join("bootstrap-trace.json");
111 let file = std::io::BufWriter::new(File::create(&chrome_tracing_path).unwrap());
112
113 let chrome_layer = tracing_chrome::ChromeLayerBuilder::new()
114 .writer(file)
115 .include_args(true)
116 .name_fn(Box::new(|event_or_span| match event_or_span {
117 tracing_chrome::EventOrSpan::Event(e) => e.metadata().name().to_string(),
118 tracing_chrome::EventOrSpan::Span(s) => {
119 if s.metadata().target() == STEP_SPAN_TARGET
120 && let Some(extension) = s.extensions().get::<StepNameExtension>()
121 {
122 extension.0.clone()
123 } else if s.metadata().target() == COMMAND_SPAN_TARGET
124 && let Some(extension) = s.extensions().get::<CommandNameExtension>()
125 {
126 extension.0.clone()
127 } else {
128 s.metadata().name().to_string()
129 }
130 }
131 }));
132 let (chrome_layer, guard) = chrome_layer.build();
133
134 tracing::subscriber::set_global_default(registry.with(chrome_layer)).unwrap();
135 TracingGuard { guard, _tempdir: tempdir, chrome_tracing_path }
136 }
137
138 pub struct TracingGuard {
139 guard: tracing_chrome::FlushGuard,
140 _tempdir: tempfile::TempDir,
141 chrome_tracing_path: std::path::PathBuf,
142 }
143
144 impl TracingGuard {
145 pub fn copy_to_dir(self, dir: &std::path::Path) {
146 drop(self.guard);
147 std::fs::rename(&self.chrome_tracing_path, dir.join("chrome-trace.json")).unwrap();
148 }
149 }
150
151 #[derive(Default)]
153 struct FieldValues {
154 message: Option<String>,
156 step_name: Option<String>,
158 cmd_name: Option<String>,
160 fields: Vec<(&'static str, String)>,
162 }
163
164 impl Visit for FieldValues {
165 fn record_str(&mut self, field: &Field, value: &str) {
168 match field.name() {
169 "step_name" => {
170 self.step_name = Some(value.to_string());
171 }
172 "cmd_name" => {
173 self.cmd_name = Some(value.to_string());
174 }
175 name => {
176 self.fields.push((name, value.to_string()));
177 }
178 }
179 }
180
181 fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
182 let formatted = format!("{value:?}");
183 match field.name() {
184 "message" => {
185 self.message = Some(formatted);
186 }
187 name => {
188 self.fields.push((name, formatted));
189 }
190 }
191 }
192 }
193
194 #[derive(Copy, Clone)]
195 enum SpanAction {
196 Enter,
197 }
198
199 struct StepNameExtension(String);
201
202 struct CommandNameExtension(String);
204
205 #[derive(Default)]
206 struct TracingPrinter {
207 indent: std::sync::atomic::AtomicU32,
208 span_values: std::sync::Mutex<std::collections::HashMap<tracing::Id, FieldValues>>,
209 }
210
211 impl TracingPrinter {
212 fn format_header<W: Write>(
213 &self,
214 writer: &mut W,
215 time: DateTime<Utc>,
216 level: &Level,
217 ) -> std::io::Result<()> {
218 let timestamp = time.format("%H:%M:%S.%3f");
220 write!(writer, "{timestamp} ")?;
221 write!(writer, "{level:>5} ")?;
224 write!(writer, "{}", " ".repeat(self.indent.load(Ordering::Relaxed) as usize))
225 }
226
227 fn write_event<W: Write>(&self, writer: &mut W, event: &Event<'_>) -> std::io::Result<()> {
228 let now = Utc::now();
229
230 self.format_header(writer, now, event.metadata().level())?;
231
232 let mut field_values = FieldValues::default();
233 event.record(&mut field_values);
234
235 if let Some(msg) = &field_values.message {
236 write!(writer, "{msg}")?;
237 }
238
239 if !field_values.fields.is_empty() {
240 if field_values.message.is_some() {
241 write!(writer, " ")?;
242 }
243 write!(writer, "[")?;
244 for (index, (name, value)) in field_values.fields.iter().enumerate() {
245 write!(writer, "{name} = {value}")?;
246 if index < field_values.fields.len() - 1 {
247 write!(writer, ", ")?;
248 }
249 }
250 write!(writer, "]")?;
251 }
252 write_location(writer, event.metadata())?;
253 writeln!(writer)?;
254 Ok(())
255 }
256
257 fn write_span<W: Write, S>(
258 &self,
259 writer: &mut W,
260 span: SpanRef<'_, S>,
261 field_values: Option<&FieldValues>,
262 action: SpanAction,
263 ) -> std::io::Result<()>
264 where
265 S: for<'lookup> LookupSpan<'lookup>,
266 {
267 let now = Utc::now();
268
269 self.format_header(writer, now, span.metadata().level())?;
270 match action {
271 SpanAction::Enter => {
272 write!(writer, "> ")?;
273 }
274 }
275
276 fn write_fields<'a, I: IntoIterator<Item = &'a (&'a str, String)>, W: Write>(
277 writer: &mut W,
278 iter: I,
279 ) -> std::io::Result<()> {
280 let items = iter.into_iter().collect::<Vec<_>>();
281 if !items.is_empty() {
282 write!(writer, " [")?;
283 for (index, (name, value)) in items.iter().enumerate() {
284 write!(writer, "{name} = {value}")?;
285 if index < items.len() - 1 {
286 write!(writer, ", ")?;
287 }
288 }
289 write!(writer, "]")?;
290 }
291 Ok(())
292 }
293
294 match span.metadata().target() {
298 STEP_SPAN_TARGET => {
300 let name =
301 field_values.and_then(|v| v.step_name.as_deref()).unwrap_or(span.name());
302 write!(writer, "{name}")?;
303
304 if let Some(values) = field_values {
306 let field = &values.fields[0];
307 write!(writer, " {{{}}}", field.1)?;
308 }
309 write_location(writer, span.metadata())?;
310 }
311 COMMAND_SPAN_TARGET => {
313 write!(writer, "{}", span.name())?;
314 if let Some(values) = field_values {
315 write_fields(
316 writer,
317 values.fields.iter().filter(|(name, _)| *name != "location"),
318 )?;
319 write!(
320 writer,
321 " ({})",
322 values.fields.iter().find(|(name, _)| *name == "location").unwrap().1
323 )?;
324 }
325 }
326 _ => {
328 write!(writer, "{}", span.name())?;
329 if let Some(values) = field_values {
330 write_fields(writer, values.fields.iter())?;
331 }
332 write_location(writer, span.metadata())?;
333 }
334 }
335
336 writeln!(writer)?;
337 Ok(())
338 }
339 }
340
341 fn write_location<W: Write>(
342 writer: &mut W,
343 metadata: &'static tracing::Metadata<'static>,
344 ) -> std::io::Result<()> {
345 use std::path::{Path, PathBuf};
346
347 if let Some(filename) = metadata.file() {
348 let filename: PathBuf = Path::new(filename)
350 .components()
351 .rev()
353 .take(2)
354 .collect::<Vec<_>>()
355 .into_iter()
356 .rev()
357 .collect();
358
359 write!(writer, " ({}", filename.display())?;
360 if let Some(line) = metadata.line() {
361 write!(writer, ":{line}")?;
362 }
363 write!(writer, ")")?;
364 }
365 Ok(())
366 }
367
368 impl<S> Layer<S> for TracingPrinter
369 where
370 S: Subscriber,
371 S: for<'lookup> LookupSpan<'lookup>,
372 {
373 fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
374 let mut field_values = FieldValues::default();
378 attrs.record(&mut field_values);
379
380 if attrs.metadata().target() == STEP_SPAN_TARGET
383 && let Some(step_name) = field_values.step_name.clone()
384 {
385 ctx.span(id).unwrap().extensions_mut().insert(StepNameExtension(step_name));
386 } else if attrs.metadata().target() == COMMAND_SPAN_TARGET
387 && let Some(cmd_name) = field_values.cmd_name.clone()
388 {
389 ctx.span(id).unwrap().extensions_mut().insert(CommandNameExtension(cmd_name));
390 }
391 self.span_values.lock().unwrap().insert(id.clone(), field_values);
392 }
393
394 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
395 let mut writer = std::io::stderr().lock();
396 self.write_event(&mut writer, event).unwrap();
397 }
398
399 fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
400 if let Some(span) = ctx.span(id) {
401 let mut writer = std::io::stderr().lock();
402 let values = self.span_values.lock().unwrap();
403 let values = values.get(id);
404 self.write_span(&mut writer, span, values, SpanAction::Enter).unwrap();
405 }
406 self.indent.fetch_add(1, Ordering::Relaxed);
407 }
408
409 fn on_exit(&self, _id: &Id, _ctx: Context<'_, S>) {
410 self.indent.fetch_sub(1, Ordering::Relaxed);
411 }
412 }
413}
414
415#[cfg(feature = "tracing")]
416pub use inner::setup_tracing;