|
| 1 | +# Adding New Telemetry Metrics |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Every CLI command emits a `command_run` metric with a command key, exit reason, and command-specific attributes. This |
| 6 | +guide shows how to add telemetry to a new command. |
| 7 | + |
| 8 | +## Step 1: Register the command in `schemas/command-run.ts` |
| 9 | + |
| 10 | +Add an entry to `COMMAND_SCHEMAS`: |
| 11 | + |
| 12 | +```ts |
| 13 | +// No attributes: |
| 14 | +'remove.widget': NoAttrs, |
| 15 | + |
| 16 | +// With attributes: |
| 17 | +'add.widget': safeSchema({ |
| 18 | + widget_type: WidgetType, // z.enum(), z.boolean(), z.number(), or z.literal() only |
| 19 | + count: Count, |
| 20 | +}), |
| 21 | +``` |
| 22 | + |
| 23 | +`safeSchema` enforces allowed field types at compile time. No `z.string()` fields. |
| 24 | + |
| 25 | +## Step 2: Add enums to `schemas/common-shapes.ts` |
| 26 | + |
| 27 | +```ts |
| 28 | +export const WidgetType = z.enum(['basic', 'advanced']); |
| 29 | +``` |
| 30 | + |
| 31 | +Use `standardize()` to normalize input before recording: |
| 32 | + |
| 33 | +```ts |
| 34 | +import { WidgetType, standardize } from '../telemetry/schemas/common-shapes.js'; |
| 35 | + |
| 36 | +const type = standardize(WidgetType, userInput); |
| 37 | +``` |
| 38 | + |
| 39 | +## Step 3: Instrument the command handler |
| 40 | + |
| 41 | +| Helper | Use case | |
| 42 | +| --------------------------------------------- | ----------------------------------- | |
| 43 | +| `runCliCommand(command, json, fn)` | CLI handlers that `process.exit` | |
| 44 | +| `withCommandRunTelemetry(command, attrs, fn)` | CLI/TUI handlers returning `Result` | |
| 45 | +| `withAddTelemetry(command, attrs, fn)` | TUI hooks wrapping `.add()` calls | |
| 46 | + |
| 47 | +**`runCliCommand`** — throw on failure, return attrs on success: |
| 48 | + |
| 49 | +```ts |
| 50 | +await runCliCommand('add.widget', !!options.json, async () => { |
| 51 | + const result = await widgetPrimitive.add(options); |
| 52 | + if (!result.success) throw new Error(result.error); |
| 53 | + console.log(JSON.stringify(result)); |
| 54 | + return { widget_type: standardize(WidgetType, options.type), count: options.items.length }; |
| 55 | +}); |
| 56 | +``` |
| 57 | + |
| 58 | +**`withCommandRunTelemetry`** / **`withAddTelemetry`** — returns `Result` to caller: |
| 59 | + |
| 60 | +```ts |
| 61 | +const result = await withAddTelemetry( |
| 62 | + 'add.widget', |
| 63 | + { widget_type: standardize(WidgetType, config.type), count: config.items.length }, |
| 64 | + () => widgetPrimitive.add(config) |
| 65 | +); |
| 66 | +``` |
| 67 | + |
| 68 | +## Key Points |
| 69 | + |
| 70 | +- Telemetry never crashes the CLI — `standardize()` falls back gracefully, `resilientParse` defaults invalid fields to |
| 71 | + `'unknown'`. |
| 72 | +- `runCliCommand` callback must **throw** on failure. The helper catches, records failure telemetry, prints the error, |
| 73 | + and exits. |
| 74 | +- `withCommandRunTelemetry` and `withAddTelemetry` return the `Result` for further handling. |
0 commit comments