tracing_texray/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(
3    missing_docs,
4    rustdoc::missing_crate_level_docs,
5    missing_debug_implementations,
6    rust_2018_idioms,
7    unreachable_pub
8)]
9
10mod tracked_spans;
11mod tracker;
12
13use parking_lot::Mutex;
14use std::borrow::Cow;
15
16use parking_lot::lock_api::{MutexGuard, RawMutex};
17use std::collections::HashSet;
18use std::fmt::{Debug, Formatter};
19use std::io::{stderr, Write};
20use std::ops::{Deref, DerefMut};
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::Arc;
23use std::time::{Duration, SystemTime};
24
25use tracing::span::{Attributes, Record};
26
27use tracing::subscriber::set_global_default;
28use tracing::{Event as TracingEvent, Id, Span, Subscriber};
29
30use tracing_subscriber::layer::Context;
31use tracing_subscriber::registry::LookupSpan;
32use tracing_subscriber::{Layer, Registry};
33use tracker::{EventInfo, FieldSettings, InterestTracker, RootTracker, SpanInfo, TrackedMetadata};
34
35lazy_static::lazy_static! {
36    static ref GLOBAL_TEXRAY_LAYER: TeXRayLayer = TeXRayLayer::uninitialized();
37}
38
39macro_rules! check_initialized {
40    ($self: expr) => {
41        if !$self.initialized() {
42            return;
43        }
44    };
45}
46
47/// Examine a given span with custom settings
48///
49/// _Note_: A [`TeXRayLayer`] must be installed as a subscriber.
50///
51/// # Examples
52/// ```no_run
53/// use tracing_texray::{Settings, TeXRayLayer};
54/// use tracing_subscriber::Registry;
55/// use tracing::info_span;
56/// use tracing_subscriber::layer::SubscriberExt;
57/// let layer = TeXRayLayer::new().enable_events();
58/// let subscriber = Registry::default().with(layer);
59/// tracing::subscriber::set_global_default(subscriber).unwrap();
60/// tracing_texray::examine_with(info_span!("hello"), Settings::auto().enable_events()).in_scope(|| {
61///   println!("I'm in this span!");
62/// });
63/// ```
64pub fn examine_with(span: Span, local_settings: Settings) -> Span {
65    GLOBAL_TEXRAY_LAYER.dump_on_exit(&span, Some(local_settings.locked()));
66    span
67}
68
69/// Examine a given span with settings from the base `TeXRayLayer`
70///
71/// _Note_: A [`TeXRayLayer`] must be installed as a subscriber.
72///
73/// # Examples
74/// ```no_run
75/// use tracing_texray::{Settings, TeXRayLayer};
76/// use tracing_subscriber::Registry;
77/// use tracing::info_span;
78/// use tracing_subscriber::layer::SubscriberExt;
79/// let layer = TeXRayLayer::new().enable_events();
80/// let subscriber = Registry::default().with(layer);
81/// tracing::subscriber::set_global_default(subscriber).unwrap();
82/// tracing_texray::examine(info_span!("hello")).in_scope(|| {
83///   println!("I'm in this span!");
84/// });
85/// ```
86pub fn examine(span: Span) -> Span {
87    GLOBAL_TEXRAY_LAYER.dump_on_exit(&span, None);
88    span
89}
90
91#[derive(Clone, Debug, PartialEq)]
92struct Types {
93    events: bool,
94    spans: bool,
95}
96
97/// Settings for [`TeXRayLayer`] output
98#[derive(Clone, Debug)]
99pub struct Settings {
100    width: usize,
101    min_duration: Option<Duration>,
102    types: Types,
103    field_filter: FieldFilter,
104    default_output: DynWriter,
105}
106
107impl Settings {
108    fn locked(self) -> SpanSettings {
109        SpanSettings {
110            render: RenderSettings {
111                width: self.width,
112                min_duration: self.min_duration,
113                types: self.types,
114            },
115            fields: FieldSettings::new(self.field_filter),
116            out: self.default_output,
117        }
118    }
119}
120
121/// Wrap a dyn writer to get a Debug implementation
122#[derive(Clone)]
123struct DynWriter {
124    inner: Arc<Mutex<dyn Write + Send>>,
125}
126
127impl Debug for DynWriter {
128    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
129        write!(f, "dyn Writer")
130    }
131}
132
133/// Filter to control which fields are printed along with spans
134#[derive(Clone, Debug)]
135enum FieldFilter {
136    AllowList(HashSet<Cow<'static, str>>),
137    DenyList(HashSet<Cow<'static, str>>),
138}
139
140impl Default for FieldFilter {
141    fn default() -> Self {
142        Self::DenyList(HashSet::new())
143    }
144}
145
146impl FieldFilter {
147    fn should_print(&self, field: &str) -> bool {
148        if field == "message" {
149            return true;
150        }
151        match &self {
152            FieldFilter::DenyList(deny) => !deny.contains(field),
153            FieldFilter::AllowList(allow) => allow.contains(field),
154        }
155    }
156}
157
158impl Default for Settings {
159    fn default() -> Self {
160        Self {
161            width: 120,
162            min_duration: None,
163            types: Types {
164                events: false,
165                spans: true,
166            },
167            field_filter: Default::default(),
168            default_output: DynWriter {
169                inner: Arc::new(Mutex::new(stderr())),
170            },
171        }
172    }
173}
174
175impl Settings {
176    /// Load settings via term-size & defaults
177    ///
178    /// By default:
179    /// - All fields are printed
180    /// - Only spans are printed, events are not
181    /// - Spans of any duration are printed
182    pub fn auto() -> Self {
183        let mut base = Settings::default();
184        if let Some((w, _h)) = term_size::dimensions() {
185            base.width = w;
186        };
187        base
188    }
189
190    /// Set the max-width when printing output
191    #[must_use]
192    pub fn width(mut self, width: usize) -> Self {
193        self.width = width;
194        self
195    }
196
197    /// Overwrite the writer [`TexRayLayer`] will output to
198    pub fn writer<W: Write + Send + 'static>(&mut self, w: W) -> &mut Self {
199        self.default_output = DynWriter {
200            inner: Arc::new(Mutex::new(w)),
201        };
202        self
203    }
204
205    /// Print events in addition to spans
206    pub fn enable_events(mut self) -> Self {
207        self.set_enable_events(true);
208        self
209    }
210
211    fn set_enable_events(&mut self, enabled: bool) -> &mut Self {
212        self.types.events = enabled;
213        self
214    }
215
216    /// When printing spans & events, only render the following fields
217    pub fn only_show_fields(&mut self, fields: &[&'static str]) -> &mut Self {
218        self.field_filter =
219            FieldFilter::AllowList(fields.iter().map(|item| Cow::Borrowed(*item)).collect());
220        self
221    }
222
223    /// When printing spans & events, print all fields, except for these fields
224    #[must_use]
225    pub fn suppress_fields(mut self, fields: &[&'static str]) -> Self {
226        self.field_filter =
227            FieldFilter::DenyList(fields.iter().map(|item| Cow::Borrowed(*item)).collect());
228        self
229    }
230
231    /// Only show spans longer than a minimum duration
232    pub fn min_duration(&mut self, duration: Duration) -> &mut Self {
233        self.min_duration = Some(duration);
234        self
235    }
236}
237
238/// Tracing Layer to display a summary of spans.
239///
240/// _Note:_ This layer does nothing on its own. It must be used in combination with [`examine`] to
241/// print the summary of a specific span.
242#[derive(Debug, Clone)]
243pub struct TeXRayLayer {
244    settings: Arc<Mutex<SettingsContainer>>,
245    initialized: Arc<AtomicBool>,
246    tracker: Arc<RootTracker>,
247}
248
249impl Default for TeXRayLayer {
250    fn default() -> Self {
251        Self::new()
252    }
253}
254
255#[derive(Debug)]
256enum SettingsContainer {
257    Unlocked(Settings),
258    Locked(SpanSettings),
259}
260
261#[derive(Debug, Clone)]
262struct SpanSettings {
263    render: RenderSettings,
264    fields: FieldSettings,
265    out: DynWriter,
266}
267
268#[derive(Debug, Clone)]
269struct RenderSettings {
270    width: usize,
271    min_duration: Option<Duration>,
272    types: Types,
273}
274
275impl Default for RenderSettings {
276    fn default() -> Self {
277        Self {
278            width: 120,
279            min_duration: None,
280            types: Types {
281                events: false,
282                spans: true,
283            },
284        }
285    }
286}
287
288impl SettingsContainer {
289    fn lock_settings(&mut self) -> &SpanSettings {
290        match self {
291            SettingsContainer::Locked(settings) => settings,
292            SettingsContainer::Unlocked(settings) => {
293                let cloned = settings.clone();
294                *self = SettingsContainer::Locked(cloned.locked());
295                self.lock_settings()
296            }
297        }
298    }
299    fn settings_mut(&mut self) -> Option<&mut Settings> {
300        match self {
301            SettingsContainer::Unlocked(settings) => Some(settings),
302            SettingsContainer::Locked(_) => None,
303        }
304    }
305}
306
307impl Default for SettingsContainer {
308    fn default() -> Self {
309        SettingsContainer::Unlocked(Settings::default())
310    }
311}
312
313/// Initialize a default subscriber and install it as the global default
314pub fn init() {
315    let layer = TeXRayLayer::new();
316    use tracing_subscriber::layer::SubscriberExt;
317    let registry = Registry::default().with(layer);
318    set_global_default(registry).expect("failed to install subscriber");
319}
320
321impl TeXRayLayer {
322    fn uninitialized() -> Self {
323        Self {
324            settings: Default::default(),
325            initialized: Arc::new(AtomicBool::new(false)),
326            tracker: Arc::new(RootTracker::new()),
327        }
328    }
329
330    /// Create a new [`TeXRayLayer`] with settings from [`Settings::auto`]
331    pub fn new() -> Self {
332        let dumper = GLOBAL_TEXRAY_LAYER.clone();
333        dumper.initialized.store(true, Ordering::Relaxed);
334        *dumper.settings.lock() = SettingsContainer::Unlocked(Settings::auto());
335        dumper
336    }
337
338    pub(crate) fn settings_mut(&mut self) -> impl DerefMut<Target = Settings> + '_ {
339        struct DerefSettings<'a, R: RawMutex> {
340            target: MutexGuard<'a, R, SettingsContainer>,
341        }
342        impl<'a, R: RawMutex> Deref for DerefSettings<'a, R> {
343            type Target = Settings;
344
345            fn deref(&self) -> &Self::Target {
346                match self.target.deref() {
347                    SettingsContainer::Unlocked(s) => s,
348                    SettingsContainer::Locked(_s) => panic!(),
349                }
350            }
351        }
352        impl<'a, R: RawMutex> DerefMut for DerefSettings<'a, R> {
353            fn deref_mut(&mut self) -> &mut Self::Target {
354                self.target
355                    .deref_mut()
356                    .settings_mut()
357                    .expect("cannot modify settings when already in progress")
358            }
359        }
360        DerefSettings {
361            target: self.settings.lock(),
362        }
363    }
364
365    /// Install [`TexRayLayer`] as the global default
366    pub fn init(self) {
367        let registry = tracing_subscriber::registry().with(self);
368        use tracing_subscriber::layer::SubscriberExt;
369        set_global_default(registry).expect("failed to install subscriber")
370    }
371
372    /// Show events in output in addition to spans
373    pub fn enable_events(mut self) -> Self {
374        self.settings_mut().set_enable_events(true);
375        self
376    }
377
378    /// Override the rendered width
379    ///
380    /// By default, the width is loaded by inspecting the TTY. If a TTY is not available,
381    /// it defaults to 120
382    pub fn width(mut self, width: usize) -> Self {
383        self.settings_mut().width = width;
384        self
385    }
386
387    /// When printing spans & events, only render the following fields
388    pub fn only_show_fields(mut self, fields: &[&'static str]) -> Self {
389        self.settings_mut().only_show_fields(fields);
390        self
391    }
392
393    /// Only render spans longer than `duration`
394    pub fn min_duration(mut self, duration: Duration) -> Self {
395        self.settings_mut().min_duration(duration);
396        self
397    }
398
399    /// Update the settings of this [`TexRayLayer`]
400    pub fn update_settings(mut self, f: impl FnOnce(&mut Settings) -> &mut Settings) -> Self {
401        // TODO: assert!(self.tracked_spans_v2.is_empty());
402        f(self.settings_mut().deref_mut());
403        self
404    }
405
406    /// Updates the `writer` used to dump output
407    pub fn writer(self, writer: impl Write + Send + 'static) -> Self {
408        self.update_settings(move |s| s.writer(writer))
409    }
410
411    fn for_tracker<'a, S>(
412        &self,
413        span: &Id,
414        ctx: &Context<'a, S>,
415        f: impl Fn(&mut InterestTracker, Vec<Id>),
416    ) where
417        S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
418    {
419        if let Some(path) = ctx.span_scope(span) {
420            self.tracker
421                .if_interested(path.from_root().map(|s| s.id()), |tracker, path| {
422                    f(tracker, path.collect::<Vec<_>>())
423                });
424        }
425    }
426
427    fn dump_on_exit(&self, span: &Span, settings: Option<SpanSettings>) {
428        check_initialized!(self);
429        if let Some(id) = span.id() {
430            self.tracker.register_interest(
431                id,
432                settings.unwrap_or(self.settings.lock().lock_settings().clone()),
433            );
434        }
435    }
436
437    fn initialized(&self) -> bool {
438        self.initialized.load(Ordering::Relaxed)
439    }
440}
441
442fn pretty_duration(duration: Duration) -> String {
443    const NANOS_PER_SEC: u128 = 1_000_000_000;
444    let divisors = [
445        ("m ", (60 * NANOS_PER_SEC)),
446        ("s ", NANOS_PER_SEC),
447        ("ms", NANOS_PER_SEC / 1000),
448        ("μs", NANOS_PER_SEC / 1000 / 1000),
449        ("ns", 1),
450    ];
451    let nanos = duration.as_nanos();
452    if nanos == 0 {
453        return "0ns".to_string();
454    }
455    for (unit, div) in divisors {
456        if nanos / div >= 1 {
457            return format!("{}{}", nanos / div, unit);
458        }
459    }
460    unreachable!("{:?}", duration)
461}
462
463impl<S> Layer<S> for TeXRayLayer
464where
465    S: Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
466{
467    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
468        check_initialized!(self);
469        self.for_tracker(id, &ctx, |tracker, path| {
470            tracker.new_span(path).record_metadata(attrs);
471        });
472    }
473
474    fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
475        check_initialized!(self);
476        self.for_tracker(id, &ctx, |tracker, path| {
477            tracker.record_metadata(&path, values)
478        })
479    }
480
481    fn on_event(&self, event: &TracingEvent<'_>, ctx: Context<'_, S>) {
482        check_initialized!(self);
483        if let Some(span) = ctx.current_span().id() {
484            self.for_tracker(span, &ctx, |tracker, path| {
485                let mut metadata = TrackedMetadata::default();
486
487                event.record(&mut tracker.field_recorder(&mut metadata));
488                let tracked_event = EventInfo::now(metadata);
489                tracker.add_event(path, tracked_event);
490            });
491        }
492    }
493
494    fn on_enter(&self, id: &Id, ctx: Context<'_, S>) {
495        check_initialized!(self);
496        self.for_tracker(id, &ctx, |tracker, path| {
497            tracker.open(path, SpanInfo::for_span(id, &ctx));
498        });
499    }
500
501    fn on_close(&self, id: Id, ctx: Context<'_, S>) {
502        check_initialized!(self);
503        self.for_tracker(&id, &ctx, |tracker, path| {
504            tracker.exit(path, SystemTime::now());
505            if self.tracker.end_tracking(id.clone()) {
506                let _ = tracker
507                    .dump()
508                    .map_err(|err| eprintln!("failed to dump output: {}", err));
509            }
510        });
511    }
512}