1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
//! Authoritative emitter for the `[wt-trace]` log grammar.
//!
//! `[wt-trace]` records are structured single-line `key=value` text emitted on
//! top of `tracing` and parsed downstream by [`super::parse`] and the
//! `wt-perf` binary. This module is the single source of truth for the
//! grammar — any field or formatting change happens here and in `parse.rs`
//! together.
//!
//! # Format
//!
//! ```text
//! [wt-trace] ts=1234567 tid=3 context=worktree cmd="git status" dur_us=12300 ok=true
//! [wt-trace] ts=1234567 tid=3 cmd="gh pr list" dur_us=45200 ok=false
//! [wt-trace] ts=1234567 tid=3 context=main cmd="git merge-base" dur_us=100000 err="fatal: ..."
//! [wt-trace] ts=1234567 tid=3 event="Showed skeleton"
//! [wt-trace] ts=1234567 tid=3 span="build_hook_context" dur_us=8200
//! ```
//!
//! # Emission model
//!
//! Records emit as `tracing` events under [`WT_TRACE_TARGET`] with typed
//! structured fields (`kind`, `ts`, `tid`, `cmd`, `dur_us`, `ok`, `err`,
//! `event`, `span`, `context`). The text grammar is produced downstream by
//! the `trace.log` layer's `FormatEvent` impl in
//! `src/logging.rs::TraceFileFormat`, which reads the structured fields
//! and renders the exact `[wt-trace] key=value …` lines wt-perf and the
//! integration suite parse.
//!
//! This split — structured fields at the emission site, grammar rendering
//! at the layer — means the wire format lives in one place
//! (`logging.rs`) and emit sites carry no string-formatting noise.
//!
//! # The subprocess chokepoint: [`CommandTrace`]
//!
//! Every subprocess command record (`cmd=…`) is emitted by exactly one type:
//! [`CommandTrace`]. The underlying `command_completed` / `command_errored`
//! writers are private to this module, so the *only* way to produce a command
//! record is to construct a `CommandTrace` and resolve it with `complete` /
//! `fail`. This is deliberate: subprocesses are spawned from many places
//! (`shell_exec::Cmd::{run, stream, delayed_stream, pipe_into}`, the
//! concurrent-command runner, pipeline steps, `wt step tether`, the fsmonitor
//! daemon launch), and before this chokepoint each path emitted — or, more
//! often, silently *forgot* to emit — its own record. Routing them all through
//! one guard means a path either traces or doesn't compile a record at all,
//! rather than skipping tracing by omission.
//!
//! `CommandTrace` is `#[must_use]` and asserts on drop that it was resolved
//! (debug builds only), so a future spawn site that constructs a guard but
//! forgets to call `complete`/`fail` trips in tests rather than leaving an
//! unattributed gap in the timeline.
//!
//! # Timing
//!
//! In-process spans (everything that isn't a subprocess) use [`Span`], an
//! RAII guard that captures `ts` at construction and emits the completed
//! record on drop with the elapsed duration. Use it to attribute time spent
//! in code paths subprocess records can't see (config load, repo open,
//! template render).
//!
//! Subprocess timing lives in [`CommandTrace`], which captures
//! `Instant::now()` just before the spawn — `tracing` spans can't carry this
//! across the sync subprocess wait, so the timing stays manual. The duration
//! is snapshotted at the moment `complete`/`fail` is called (the command's
//! resolution point), so a streamed command whose duration isn't known until
//! the stream finishes still records the correct spawn → wait span.
//!
//! # Routing
//!
//! Events emit at `tracing::DEBUG`, so `-vv` or `RUST_LOG=debug` makes them
//! visible. Subprocess stdout/stderr continuations route through separate
//! targets: the full output goes to `subprocess.log`, and a bounded preview
//! shares the routing of all other records — `trace.log` at `-vv`, stderr
//! otherwise — so raw bodies don't spam `-vv`.
use Cow;
use Display;
use OnceLock;
use Instant;
/// Tracing target the `trace.log` layer keys on to render the `[wt-trace]`
/// grammar. Events under any other target fall through to the layer's
/// default message-passing format.
pub const WT_TRACE_TARGET: &str = "worktrunk::wt_trace";
/// Monotonic epoch for trace timestamps. All `ts` fields are microseconds
/// since this point. `Instant` is monotonic even if the system clock steps.
static TRACE_EPOCH: = new;
/// The monotonic epoch all trace timestamps are relative to.
/// Microseconds since [`trace_epoch`]. Use as the `ts` field for records.
/// Numeric thread id, extracted from `ThreadId`'s `Debug` representation.
/// `ThreadId` debug format is `ThreadId(N)`.
/// Emit a completed-command record (`ok=true`/`ok=false`).
///
/// Private: the sole caller is [`CommandTrace::complete`]. Keeping the writer
/// module-private makes [`CommandTrace`] the only way to emit a command
/// record — see the module-level "subprocess chokepoint" note.
/// Emit a failed-command record (the command didn't run to completion).
///
/// Private: the sole caller is [`CommandTrace::fail`]. See [`command_completed`].
/// The single emitter for subprocess command records — every `[wt-trace]`
/// `cmd=…` line comes from here.
///
/// Construct one immediately before spawning a child, then call
/// [`complete`](Self::complete) (the child exited — `ok=` reflects its status)
/// or [`fail`](Self::fail) (the command never ran to completion — spawn error,
/// wait error) exactly once. The record is emitted at that call; the duration
/// is the elapsed time from construction to resolution, so it brackets the
/// real spawn → wait span even for streamed commands whose duration isn't
/// known until the stream finishes.
///
/// # Why a guard rather than a free function
///
/// Subprocesses are spawned from many code paths with incompatible I/O shapes
/// (capture, inherit, buffer-then-stream, two-stage pipe, N concurrent
/// children). They can't share one spawn primitive, but they can share one
/// *trace* primitive. Centralizing emission here — with the writers private to
/// this module — means a path either resolves a `CommandTrace` or emits no
/// record at all, instead of each path re-deriving (and routinely forgetting)
/// the emit logic.
///
/// The `#[must_use]` and the debug-build drop assertion catch the remaining
/// failure mode: a guard that is constructed but never resolved (a spawn site
/// that forgot to wire up `complete`/`fail`) trips a test-time assertion rather
/// than silently producing an unattributed gap in the timeline. The assertion
/// is suppressed while the thread is panicking so an unrelated panic between
/// construction and resolution unwinds cleanly instead of aborting.
/// Emit an instant (milestone) event with no duration. Computes `ts` and
/// `tid` internally — use for one-off markers inside a thread's execution.
///
/// Instant events appear as vertical lines in Chrome Trace Format tools
/// (chrome://tracing, Perfetto).
/// Emit a completed in-process span (a named region of code that ran).
///
/// Spans are the in-process counterpart to `command_completed`: subprocess
/// records cover work in child processes; spans cover everything between and
/// around them (config load, repo open, template render, etc.).
/// RAII guard that times its enclosing scope and emits a span record on drop.
///
/// Construct at the top of a block — `let _span = Span::new("config_load");` —
/// and the span fires when `_span` goes out of scope.
///
/// `name` accepts anything that converts into `Cow<'static, str>`: string
/// literals stay borrowed (allocation-free), and `String` becomes owned —
/// useful when the span name carries dynamic context, e.g.
/// `Span::new(format!("prepare_steps:{}", alias))`.
///
/// The `tracing::enabled!` check happens on drop, not construction. A span
/// constructed before the subscriber is installed (e.g. wrapping the logger
/// init itself) still fires correctly as long as the subscriber is up by the
/// time the span goes out of scope. Construction always pays two
/// `Instant::now()` calls; they're vDSO-fast and the overhead is below noise.