Skip to content

Commit 604c180

Browse files
committed
Move tracing setup to the tracing module
1 parent d403934 commit 604c180

File tree

3 files changed

+304
-298
lines changed

3 files changed

+304
-298
lines changed

src/bootstrap/src/bin/main.rs

Lines changed: 1 addition & 298 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn is_tracing_enabled() -> bool {
2626

2727
fn main() {
2828
#[cfg(feature = "tracing")]
29-
let guard = setup_tracing(is_profiling_enabled());
29+
let guard = bootstrap::setup_tracing(is_profiling_enabled());
3030

3131
let start_time = Instant::now();
3232

@@ -247,300 +247,3 @@ fn check_version(config: &Config) -> Option<String> {
247247

248248
Some(msg)
249249
}
250-
251-
// # Note on `tracing` usage in bootstrap
252-
//
253-
// Due to the conditional compilation via the `tracing` cargo feature, this means that `tracing`
254-
// usages in bootstrap need to be also gated behind the `tracing` feature:
255-
//
256-
// - `tracing` macros with log levels (`trace!`, `debug!`, `warn!`, `info`, `error`) should not be
257-
// used *directly*. You should use the wrapped `tracing` macros which gate the actual invocations
258-
// behind `feature = "tracing"`.
259-
// - `tracing`'s `#[instrument(..)]` macro will need to be gated like `#![cfg_attr(feature =
260-
// "tracing", instrument(..))]`.
261-
#[cfg(feature = "tracing")]
262-
fn setup_tracing(profiling_enabled: bool) -> Option<TracingGuard> {
263-
use std::fmt::Debug;
264-
use std::fs::File;
265-
use std::io::BufWriter;
266-
use std::sync::atomic::{AtomicU32, Ordering};
267-
268-
use bootstrap::STEP_NAME_TARGET;
269-
use chrono::{DateTime, Utc};
270-
use tracing::field::{Field, Visit};
271-
use tracing::{Event, Id, Level, Subscriber};
272-
use tracing_subscriber::layer::{Context, SubscriberExt};
273-
use tracing_subscriber::registry::{LookupSpan, SpanRef};
274-
use tracing_subscriber::{EnvFilter, Layer};
275-
276-
let filter = EnvFilter::from_env("BOOTSTRAP_TRACING");
277-
278-
/// Visitor that extracts both known and unknown field values from events and spans.
279-
#[derive(Default)]
280-
struct FieldValues {
281-
/// Main event message
282-
message: Option<String>,
283-
/// Name of a recorded psna
284-
step_name: Option<String>,
285-
/// The rest of arbitrary event/span fields
286-
fields: Vec<(&'static str, String)>,
287-
}
288-
289-
impl Visit for FieldValues {
290-
/// Record fields if possible using `record_str`, to avoid rendering simple strings with
291-
/// their `Debug` representation, which adds extra quotes.
292-
fn record_str(&mut self, field: &Field, value: &str) {
293-
match field.name() {
294-
"step_name" => {
295-
self.step_name = Some(value.to_string());
296-
}
297-
name => {
298-
self.fields.push((name, value.to_string()));
299-
}
300-
}
301-
}
302-
303-
fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
304-
let formatted = format!("{value:?}");
305-
match field.name() {
306-
"message" => {
307-
self.message = Some(formatted);
308-
}
309-
name => {
310-
self.fields.push((name, formatted));
311-
}
312-
}
313-
}
314-
}
315-
316-
#[derive(Copy, Clone)]
317-
enum SpanAction {
318-
Enter,
319-
}
320-
321-
/// Holds the name of a step, stored in `tracing_subscriber`'s extensions.
322-
struct StepNameExtension(String);
323-
324-
#[derive(Default)]
325-
struct TracingPrinter {
326-
indent: AtomicU32,
327-
span_values: std::sync::Mutex<std::collections::HashMap<tracing::Id, FieldValues>>,
328-
}
329-
330-
impl TracingPrinter {
331-
fn format_header<W: Write>(
332-
&self,
333-
writer: &mut W,
334-
time: DateTime<Utc>,
335-
level: &Level,
336-
) -> std::io::Result<()> {
337-
// Use a fixed-width timestamp without date, that shouldn't be very important
338-
let timestamp = time.format("%H:%M:%S.%3f");
339-
write!(writer, "{timestamp} ")?;
340-
// Make sure that levels are aligned to the same number of characters, in order not to
341-
// break the layout
342-
write!(writer, "{level:>5} ")?;
343-
write!(writer, "{}", " ".repeat(self.indent.load(Ordering::Relaxed) as usize))
344-
}
345-
346-
fn write_event<W: Write>(&self, writer: &mut W, event: &Event<'_>) -> std::io::Result<()> {
347-
let now = Utc::now();
348-
349-
self.format_header(writer, now, event.metadata().level())?;
350-
351-
let mut field_values = FieldValues::default();
352-
event.record(&mut field_values);
353-
354-
if let Some(msg) = &field_values.message {
355-
write!(writer, "{msg}")?;
356-
}
357-
358-
if !field_values.fields.is_empty() {
359-
if field_values.message.is_some() {
360-
write!(writer, " ")?;
361-
}
362-
write!(writer, "[")?;
363-
for (index, (name, value)) in field_values.fields.iter().enumerate() {
364-
write!(writer, "{name} = {value}")?;
365-
if index < field_values.fields.len() - 1 {
366-
write!(writer, ", ")?;
367-
}
368-
}
369-
write!(writer, "]")?;
370-
}
371-
write_location(writer, event.metadata())?;
372-
writeln!(writer)?;
373-
Ok(())
374-
}
375-
376-
fn write_span<W: Write, S>(
377-
&self,
378-
writer: &mut W,
379-
span: SpanRef<'_, S>,
380-
field_values: Option<&FieldValues>,
381-
action: SpanAction,
382-
) -> std::io::Result<()>
383-
where
384-
S: for<'lookup> LookupSpan<'lookup>,
385-
{
386-
let now = Utc::now();
387-
388-
self.format_header(writer, now, span.metadata().level())?;
389-
match action {
390-
SpanAction::Enter => {
391-
write!(writer, "> ")?;
392-
}
393-
}
394-
395-
// We handle steps specially. We instrument them dynamically in `Builder::ensure`,
396-
// and we want to have custom name for each step span. But tracing doesn't allow setting
397-
// dynamic span names. So we detect step spans here and override their name.
398-
if span.metadata().target() == STEP_NAME_TARGET {
399-
let name = field_values.and_then(|v| v.step_name.as_deref()).unwrap_or(span.name());
400-
write!(writer, "{name}")?;
401-
402-
// There should be only one more field called `args`
403-
if let Some(values) = field_values {
404-
let field = &values.fields[0];
405-
write!(writer, " {{{}}}", field.1)?;
406-
}
407-
} else {
408-
write!(writer, "{}", span.name())?;
409-
if let Some(values) = field_values.filter(|v| !v.fields.is_empty()) {
410-
write!(writer, " [")?;
411-
for (index, (name, value)) in values.fields.iter().enumerate() {
412-
write!(writer, "{name} = {value}")?;
413-
if index < values.fields.len() - 1 {
414-
write!(writer, ", ")?;
415-
}
416-
}
417-
write!(writer, "]")?;
418-
}
419-
};
420-
421-
write_location(writer, span.metadata())?;
422-
writeln!(writer)?;
423-
Ok(())
424-
}
425-
}
426-
427-
fn write_location<W: Write>(
428-
writer: &mut W,
429-
metadata: &'static tracing::Metadata<'static>,
430-
) -> std::io::Result<()> {
431-
use std::path::{Path, PathBuf};
432-
433-
if let Some(filename) = metadata.file() {
434-
// Keep only the module name and file name to make it shorter
435-
let filename: PathBuf = Path::new(filename)
436-
.components()
437-
// Take last two path components
438-
.rev()
439-
.take(2)
440-
.collect::<Vec<_>>()
441-
.into_iter()
442-
.rev()
443-
.collect();
444-
445-
write!(writer, " ({}", filename.display())?;
446-
if let Some(line) = metadata.line() {
447-
write!(writer, ":{line}")?;
448-
}
449-
write!(writer, ")")?;
450-
}
451-
Ok(())
452-
}
453-
454-
impl<S> Layer<S> for TracingPrinter
455-
where
456-
S: Subscriber,
457-
S: for<'lookup> LookupSpan<'lookup>,
458-
{
459-
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
460-
let mut writer = std::io::stderr().lock();
461-
self.write_event(&mut writer, event).unwrap();
462-
}
463-
464-
fn on_new_span(&self, attrs: &tracing::span::Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
465-
// Record value of span fields
466-
// Note that we do not implement changing values of span fields after they are created.
467-
// For that we would also need to implement the `on_record` method
468-
let mut field_values = FieldValues::default();
469-
attrs.record(&mut field_values);
470-
471-
// We need to propagate the actual name of the span to the Chrome layer below, because
472-
// it cannot access field values. We do that through extensions.
473-
if let Some(step_name) = field_values.step_name.clone() {
474-
ctx.span(id).unwrap().extensions_mut().insert(StepNameExtension(step_name));
475-
}
476-
self.span_values.lock().unwrap().insert(id.clone(), field_values);
477-
}
478-
479-
fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
480-
if let Some(span) = ctx.span(id) {
481-
let mut writer = std::io::stderr().lock();
482-
let values = self.span_values.lock().unwrap();
483-
let values = values.get(id);
484-
self.write_span(&mut writer, span, values, SpanAction::Enter).unwrap();
485-
}
486-
self.indent.fetch_add(1, Ordering::Relaxed);
487-
}
488-
489-
fn on_exit(&self, _id: &Id, _ctx: Context<'_, S>) {
490-
self.indent.fetch_sub(1, Ordering::Relaxed);
491-
}
492-
}
493-
494-
let registry = tracing_subscriber::registry().with(filter).with(TracingPrinter::default());
495-
496-
let guard = if profiling_enabled {
497-
// When we're creating this layer, we do not yet know the location of the tracing output
498-
// directory, because it is stored in the output directory determined after Config is parsed,
499-
// but we already want to make tracing calls during (and before) config parsing.
500-
// So we store the output into a temporary file, and then move it to the tracing directory
501-
// before bootstrap ends.
502-
let tempdir = tempfile::TempDir::new().expect("Cannot create temporary directory");
503-
let chrome_tracing_path = tempdir.path().join("bootstrap-trace.json");
504-
let file = BufWriter::new(File::create(&chrome_tracing_path).unwrap());
505-
506-
let chrome_layer = tracing_chrome::ChromeLayerBuilder::new()
507-
.writer(file)
508-
.include_args(true)
509-
.name_fn(Box::new(|event_or_span| match event_or_span {
510-
tracing_chrome::EventOrSpan::Event(e) => e.metadata().name().to_string(),
511-
tracing_chrome::EventOrSpan::Span(s) => {
512-
if s.metadata().target() == STEP_NAME_TARGET
513-
&& let Some(extension) = s.extensions().get::<StepNameExtension>()
514-
{
515-
extension.0.clone()
516-
} else {
517-
s.metadata().name().to_string()
518-
}
519-
}
520-
}));
521-
let (chrome_layer, guard) = chrome_layer.build();
522-
523-
tracing::subscriber::set_global_default(registry.with(chrome_layer)).unwrap();
524-
Some(TracingGuard { guard, _tempdir: tempdir, chrome_tracing_path })
525-
} else {
526-
tracing::subscriber::set_global_default(registry).unwrap();
527-
None
528-
};
529-
530-
guard
531-
}
532-
533-
#[cfg(feature = "tracing")]
534-
struct TracingGuard {
535-
guard: tracing_chrome::FlushGuard,
536-
_tempdir: tempfile::TempDir,
537-
chrome_tracing_path: std::path::PathBuf,
538-
}
539-
540-
#[cfg(feature = "tracing")]
541-
impl TracingGuard {
542-
fn copy_to_dir(self, dir: &std::path::Path) {
543-
drop(self.guard);
544-
std::fs::rename(&self.chrome_tracing_path, dir.join("chrome-trace.json")).unwrap();
545-
}
546-
}

src/bootstrap/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ pub use utils::change_tracker::{
5454
CONFIG_CHANGE_HISTORY, find_recent_config_change_ids, human_readable_changes,
5555
};
5656
pub use utils::helpers::{PanicTracker, symlink_dir};
57+
#[cfg(feature = "tracing")]
58+
pub use utils::tracing::setup_tracing;
5759

5860
use crate::core::build_steps::vendor::VENDOR_DIR;
5961

0 commit comments

Comments
 (0)