tsz_cli/tracing_config.rs
1//! Tracing configuration for debugging conformance failures.
2//!
3//! Supports three output formats controlled by `TSZ_LOG_FORMAT`:
4//!
5//! - `text` (default): Standard `tracing-subscriber` flat output
6//! - `tree`: Hierarchical indented output via `tracing-tree` — easy to read,
7//! great for pasting into conversations
8//! - `json`: One JSON object per span/event — machine-readable, also pasteable
9//!
10//! ## Quick start
11//!
12//! ```bash
13//! # Human-readable tree (recommended for debugging conformance)
14//! TSZ_LOG=debug TSZ_LOG_FORMAT=tree tsz file.ts
15//!
16//! # JSON (for tooling or sharing full traces)
17//! TSZ_LOG=debug TSZ_LOG_FORMAT=json tsz file.ts
18//!
19//! # Plain text (classic fmt subscriber)
20//! TSZ_LOG=debug tsz file.ts
21//!
22//! # Fine-grained filtering
23//! TSZ_LOG="wasm::checker=debug,wasm::solver=trace" TSZ_LOG_FORMAT=tree tsz file.ts
24//! ```
25//!
26//! The subscriber is only initialised when `TSZ_LOG` (or `RUST_LOG`) is set,
27//! so there is zero overhead in normal builds.
28//! `TSZ_PERF` also enables a minimal default perf filter (`wasm::perf=info`).
29
30use tracing_subscriber::prelude::*;
31use tracing_subscriber::{EnvFilter, Registry, fmt};
32
33/// Tracing output format.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum LogFormat {
36 /// Standard flat text lines (default).
37 Text,
38 /// Hierarchical indented tree via `tracing-tree`.
39 Tree,
40 /// Newline-delimited JSON objects.
41 Json,
42}
43
44impl LogFormat {
45 /// Parse from the `TSZ_LOG_FORMAT` environment variable.
46 fn from_env() -> Self {
47 match std::env::var("TSZ_LOG_FORMAT")
48 .unwrap_or_default()
49 .to_lowercase()
50 .as_str()
51 {
52 "tree" => Self::Tree,
53 "json" => Self::Json,
54 _ => Self::Text,
55 }
56 }
57}
58
59/// Build an `EnvFilter` from `TSZ_LOG`, falling back to `RUST_LOG`.
60///
61/// `TSZ_LOG` takes precedence when both are set. Values use the same
62/// syntax as `RUST_LOG` (e.g. `debug`, `wasm::checker=trace`).
63fn build_filter() -> EnvFilter {
64 if let Ok(val) = std::env::var("TSZ_LOG") {
65 EnvFilter::builder().parse_lossy(val)
66 } else {
67 // RUST_LOG is set (caller already checked). Use it as-is.
68 EnvFilter::from_default_env()
69 }
70}
71
72/// Initialise the global tracing subscriber.
73///
74/// Does nothing when neither `TSZ_LOG` nor `RUST_LOG` is set, keeping startup
75/// cost at zero for normal usage.
76///
77/// All output goes to stderr so it never interferes with stdout
78/// (compiler diagnostics, `--showConfig`, or LSP JSON-RPC).
79pub fn init_tracing() {
80 // Only pay for tracing when explicitly requested.
81 let has_tsz_log = std::env::var("TSZ_LOG").is_ok();
82 let has_rust_log = std::env::var("RUST_LOG").is_ok();
83 let has_perf = std::env::var_os("TSZ_PERF").is_some();
84 if !has_tsz_log && !has_rust_log && !has_perf {
85 return;
86 }
87
88 let filter = if has_tsz_log || has_rust_log {
89 build_filter()
90 } else {
91 EnvFilter::builder().parse_lossy("wasm::perf=info")
92 };
93 let format = LogFormat::from_env();
94
95 match format {
96 LogFormat::Tree => {
97 let tree_layer = tracing_tree::HierarchicalLayer::default()
98 .with_indent_amount(2)
99 .with_indent_lines(true)
100 .with_deferred_spans(true)
101 .with_span_retrace(true)
102 .with_targets(true);
103
104 Registry::default().with(filter).with(tree_layer).init();
105 }
106 LogFormat::Json => {
107 let json_layer = fmt::layer().json().with_writer(std::io::stderr);
108
109 Registry::default().with(filter).with(json_layer).init();
110 }
111 LogFormat::Text => {
112 tracing_subscriber::fmt()
113 .with_env_filter(filter)
114 .with_writer(std::io::stderr)
115 .init();
116 }
117 }
118}