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
47pub 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
69pub 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#[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#[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#[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 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 #[must_use]
192 pub fn width(mut self, width: usize) -> Self {
193 self.width = width;
194 self
195 }
196
197 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 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 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 #[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 pub fn min_duration(&mut self, duration: Duration) -> &mut Self {
233 self.min_duration = Some(duration);
234 self
235 }
236}
237
238#[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
313pub 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 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 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 pub fn enable_events(mut self) -> Self {
374 self.settings_mut().set_enable_events(true);
375 self
376 }
377
378 pub fn width(mut self, width: usize) -> Self {
383 self.settings_mut().width = width;
384 self
385 }
386
387 pub fn only_show_fields(mut self, fields: &[&'static str]) -> Self {
389 self.settings_mut().only_show_fields(fields);
390 self
391 }
392
393 pub fn min_duration(mut self, duration: Duration) -> Self {
395 self.settings_mut().min_duration(duration);
396 self
397 }
398
399 pub fn update_settings(mut self, f: impl FnOnce(&mut Settings) -> &mut Settings) -> Self {
401 f(self.settings_mut().deref_mut());
403 self
404 }
405
406 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}