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")]
52pub const IO_SPAN_TARGET: &str = "IO";
53
54#[macro_export]
58macro_rules! trace_io {
59 ($name:expr, $($args:tt)*) => {
60 ::tracing::trace_span!(
61 target: $crate::utils::tracing::IO_SPAN_TARGET,
62 $name,
63 $($args)*,
64 location = $crate::utils::tracing::format_location(*::std::panic::Location::caller())
65 ).entered()
66 }
67}
68
69pub fn format_location(location: std::panic::Location<'static>) -> String {
70 format!("{}:{}", location.file(), location.line())
71}
72
73#[cfg(feature = "tracing")]
74const COMMAND_SPAN_TARGET: &str = "COMMAND";
75
76#[cfg(feature = "tracing")]
77pub fn trace_cmd(command: &crate::BootstrapCommand) -> tracing::span::EnteredSpan {
78 let fingerprint = command.fingerprint();
79 let location = command.get_created_location();
80 let location = format_location(location);
81
82 tracing::span!(
83 target: COMMAND_SPAN_TARGET,
84 tracing::Level::TRACE,
85 "cmd",
86 cmd_name = fingerprint.program_name().to_string(),
87 cmd = fingerprint.format_short_cmd(),
88 full_cmd = ?command,
89 location
90 )
91 .entered()
92}
93
94#[cfg(feature = "tracing")]
105mod inner {
106 use std::fmt::Debug;
107 use std::fs::File;
108 use std::io::Write;
109 use std::path::{Path, PathBuf};
110 use std::sync::atomic::Ordering;
111
112 use chrono::{DateTime, Utc};
113 use tracing::field::{Field, Visit};
114 use tracing::{Event, Id, Level, Subscriber};
115 use tracing_subscriber::layer::{Context, SubscriberExt};
116 use tracing_subscriber::registry::{LookupSpan, SpanRef};
117 use tracing_subscriber::{EnvFilter, Layer};
118
119 use super::{COMMAND_SPAN_TARGET, IO_SPAN_TARGET};
120 use crate::STEP_SPAN_TARGET;
121
122 pub fn setup_tracing(env_name: &str) -> TracingGuard {
123 let filter = EnvFilter::from_env(env_name);
124
125 let registry = tracing_subscriber::registry().with(filter).with(TracingPrinter::default());
126
127 let tempdir = tempfile::TempDir::new().expect("Cannot create temporary directory");
133 let chrome_tracing_path = tempdir.path().join("bootstrap-trace.json");
134 let file = std::io::BufWriter::new(File::create(&chrome_tracing_path).unwrap());
135
136 let chrome_layer = tracing_chrome::ChromeLayerBuilder::new()
137 .writer(file)
138 .include_args(true)
139 .name_fn(Box::new(|event_or_span| match event_or_span {
140 tracing_chrome::EventOrSpan::Event(e) => e.metadata().name().to_string(),
141 tracing_chrome::EventOrSpan::Span(s) => {
142 if s.metadata().target() == STEP_SPAN_TARGET
143 && let Some(extension) = s.extensions().get::<StepNameExtension>()
144 {
145 extension.0.clone()
146 } else if s.metadata().target() == COMMAND_SPAN_TARGET
147 && let Some(extension) = s.extensions().get::<CommandNameExtension>()
148 {
149 extension.0.clone()
150 } else {
151 s.metadata().name().to_string()
152 }
153 }
154 }));
155 let (chrome_layer, guard) = chrome_layer.build();
156
157 tracing::subscriber::set_global_default(registry.with(chrome_layer)).unwrap();
158 TracingGuard { guard, _tempdir: tempdir, chrome_tracing_path }
159 }
160
161 pub struct TracingGuard {
162 guard: tracing_chrome::FlushGuard,
163 _tempdir: tempfile::TempDir,
164 chrome_tracing_path: std::path::PathBuf,
165 }
166
167 impl TracingGuard {
168 pub fn copy_to_dir(self, dir: &std::path::Path) {
169 drop(self.guard);
170 crate::utils::helpers::move_file(
171 &self.chrome_tracing_path,
172 dir.join("chrome-trace.json"),
173 )
174 .unwrap();
175 }
176 }
177
178 #[derive(Default)]
180 struct FieldValues {
181 message: Option<String>,
183 step_name: Option<String>,
185 cmd_name: Option<String>,
187 fields: Vec<(&'static str, String)>,
189 }
190
191 impl Visit for FieldValues {
192 fn record_str(&mut self, field: &Field, value: &str) {
195 match field.name() {
196 "step_name" => {
197 self.step_name = Some(value.to_string());
198 }
199 "cmd_name" => {
200 self.cmd_name = Some(value.to_string());
201 }
202 name => {
203 self.fields.push((name, value.to_string()));
204 }
205 }
206 }
207
208 fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
209 let formatted = format!("{value:?}");
210 match field.name() {
211 "message" => {
212 self.message = Some(formatted);
213 }
214 name => {
215 self.fields.push((name, formatted));
216 }
217 }
218 }
219 }
220
221 #[derive(Copy, Clone)]
222 enum SpanAction {
223 Enter,
224 }
225
226 struct StepNameExtension(String);
228
229 struct CommandNameExtension(String);
231
232 #[derive(Default)]
233 struct TracingPrinter {
234 indent: std::sync::atomic::AtomicU32,
235 span_values: std::sync::Mutex<std::collections::HashMap<tracing::Id, FieldValues>>,
236 }
237
238 impl TracingPrinter {
239 fn format_header<W: Write>(
240 &self,
241 writer: &mut W,
242 time: DateTime<Utc>,
243 level: &Level,
244 ) -> std::io::Result<()> {
245 let timestamp = time.format("%H:%M:%S.%3f");
247 write!(writer, "{timestamp} ")?;
248 write!(writer, "{level:>5} ")?;
251 write!(writer, "{}", " ".repeat(self.indent.load(Ordering::Relaxed) as usize))
252 }
253
254 fn write_event<W: Write>(&self, writer: &mut W, event: &Event<'_>) -> std::io::Result<()> {
255 let now = Utc::now();
256
257 self.format_header(writer, now, event.metadata().level())?;
258
259 let mut field_values = FieldValues::default();
260 event.record(&mut field_values);
261
262 if let Some(msg) = &field_values.message {
263 write!(writer, "{msg}")?;
264 }
265
266 if !field_values.fields.is_empty() {
267 if field_values.message.is_some() {
268 write!(writer, " ")?;
269 }
270 write!(writer, "[")?;
271 for (index, (name, value)) in field_values.fields.iter().enumerate() {
272 write!(writer, "{name} = {value}")?;
273 if index < field_values.fields.len() - 1 {
274 write!(writer, ", ")?;
275 }
276 }
277 write!(writer, "]")?;
278 }
279 write_location(writer, event.metadata())?;
280 writeln!(writer)?;
281 Ok(())
282 }
283
284 fn write_span<W: Write, S>(
285 &self,
286 writer: &mut W,
287 span: SpanRef<'_, S>,
288 field_values: Option<&FieldValues>,
289 action: SpanAction,
290 ) -> std::io::Result<()>
291 where
292 S: for<'lookup> LookupSpan<'lookup>,
293 {
294 let now = Utc::now();
295
296 self.format_header(writer, now, span.metadata().level())?;
297 match action {
298 SpanAction::Enter => {
299 write!(writer, "> ")?;
300 }
301 }
302
303 fn write_fields<'a, I: IntoIterator<Item = &'a (&'a str, String)>, W: Write>(
304 writer: &mut W,
305 iter: I,
306 ) -> std::io::Result<()> {
307 let items = iter.into_iter().collect::<Vec<_>>();
308 if !items.is_empty() {
309 write!(writer, " [")?;
310 for (index, (name, value)) in items.iter().enumerate() {
311 write!(writer, "{name} = {value}")?;
312 if index < items.len() - 1 {
313 write!(writer, ", ")?;
314 }
315 }
316 write!(writer, "]")?;
317 }
318 Ok(())
319 }
320
321 let write_with_location = |writer: &mut W| -> std::io::Result<()> {
324 if let Some(values) = field_values {
325 write_fields(
326 writer,
327 values.fields.iter().filter(|(name, _)| *name != "location"),
328 )?;
329 let location =
330 &values.fields.iter().find(|(name, _)| *name == "location").unwrap().1;
331 let (filename, line) = location.rsplit_once(':').unwrap();
332 let filename = shorten_filename(filename);
333 write!(writer, " ({filename}:{line})",)?;
334 }
335 Ok(())
336 };
337
338 match span.metadata().target() {
342 STEP_SPAN_TARGET => {
344 let name =
345 field_values.and_then(|v| v.step_name.as_deref()).unwrap_or(span.name());
346 write!(writer, "{name}")?;
347
348 if let Some(values) = field_values {
350 let field = &values.fields[0];
351 write!(writer, " {{{}}}", field.1)?;
352 }
353 write_with_location(writer)?;
354 }
355 COMMAND_SPAN_TARGET => {
357 write!(writer, "{}", span.name())?;
358 write_with_location(writer)?;
359 }
360 IO_SPAN_TARGET => {
361 write!(writer, "{}", span.name())?;
362 write_with_location(writer)?;
363 }
364 _ => {
366 write!(writer, "{}", span.name())?;
367 if let Some(values) = field_values {
368 write_fields(writer, values.fields.iter())?;
369 }
370 write_location(writer, span.metadata())?;
371 }
372 }
373
374 writeln!(writer)?;
375 Ok(())
376 }
377 }
378
379 fn write_location<W: Write>(
380 writer: &mut W,
381 metadata: &'static tracing::Metadata<'static>,
382 ) -> std::io::Result<()> {
383 if let Some(filename) = metadata.file() {
384 let filename = shorten_filename(filename);
385
386 write!(writer, " ({filename}")?;
387 if let Some(line) = metadata.line() {
388 write!(writer, ":{line}")?;
389 }
390 write!(writer, ")")?;
391 }
392 Ok(())
393 }
394
395 fn shorten_filename(filename: &str) -> String {
397 Path::new(filename)
398 .components()
399 .rev()
401 .take(2)
402 .collect::<Vec<_>>()
403 .into_iter()
404 .rev()
405 .collect::<PathBuf>()
406 .display()
407 .to_string()
408 }
409
410 impl<S> Layer<S> for TracingPrinter
411 where
412 S: Subscriber,
413 S: for<'lookup> LookupSpan<'lookup>,
414 {
415 fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
416 let mut field_values = FieldValues::default();
420 attrs.record(&mut field_values);
421
422 if attrs.metadata().target() == STEP_SPAN_TARGET
425 && let Some(step_name) = field_values.step_name.clone()
426 {
427 ctx.span(id).unwrap().extensions_mut().insert(StepNameExtension(step_name));
428 } else if attrs.metadata().target() == COMMAND_SPAN_TARGET
429 && let Some(cmd_name) = field_values.cmd_name.clone()
430 {
431 ctx.span(id).unwrap().extensions_mut().insert(CommandNameExtension(cmd_name));
432 }
433 self.span_values.lock().unwrap().insert(id.clone(), field_values);
434 }
435
436 fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
437 let mut writer = std::io::stderr().lock();
438 self.write_event(&mut writer, event).unwrap();
439 }
440
441 fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
442 if let Some(span) = ctx.span(id) {
443 let mut writer = std::io::stderr().lock();
444 let values = self.span_values.lock().unwrap();
445 let values = values.get(id);
446 self.write_span(&mut writer, span, values, SpanAction::Enter).unwrap();
447 }
448 self.indent.fetch_add(1, Ordering::Relaxed);
449 }
450
451 fn on_exit(&self, _id: &Id, _ctx: Context<'_, S>) {
452 self.indent.fetch_sub(1, Ordering::Relaxed);
453 }
454 }
455}
456
457#[cfg(feature = "tracing")]
458pub use inner::setup_tracing;