Telemetry

telemetry

Wide-event telemetry for CLIs and automation — evlog's one-event-per-run model for tools on user machines, with privacy-safe flags, consent, outbox, and auto-generated disclosure.

@evlog/telemetry brings evlog's wide-event model to tools that run on other people's machines — CLIs, GitHub Actions, dev scripts, and CI jobs. Same philosophy as HTTP logging: one command execution → one structured event, not a stream of analytics calls.

pnpm add @evlog/telemetry

You get command name, sanitized flags, duration, and outcome automatically. Call telemetry.set() only when you have extra counters — numbers and booleans by default. Raw argv is never read; disclosure is generated from your runtime config so it cannot drift from what you actually collect.

Add anonymous telemetry to my CLI or script

From CLI to your backend

The disk outbox (~/.config/{toolName}/telemetry/outbox.ndjson) is a reliability buffer, not where telemetry lives long-term. Each run appends one event locally, then the HTTP drain POSTs the backlog to your ingestion endpoint. Short-lived CI jobs and offline machines drain on the next invocation.

  1. Runmy-tool doctor executes on the user's machine
  2. Capture@evlog/telemetry sanitizes flags, checks consent, and records one RunEvent
  3. Buffer — the event appends to ~/.config/{toolName}/telemetry/outbox.ndjson
  4. Send — on flush, POST { events: RunEvent[] } to your ingest URL
  5. ValidateparseIngestBody() allowlists tools and custom keys (Ingest)
  6. Store — dedupe on idempotencyKey, then DB / warehouse / evlog drain
  7. Ack — on 2xx, delivered keys are removed from the outbox
CLI → @evlog/telemetry → outbox.ndjson → POST /ingest → validate → store

Nothing is sent until you configure an endpoint and the server returns a success status. Until then, events stay in the outbox (or are purged on opt-out).

Why install it

You can wire anonymous usage telemetry yourself — outbox file, consent flags, flag sanitization, disclosure markdown, ingest validation, first-run notice. Or you add one dependency and get the evlog playbook baked in.

What you get for ~28 KB

Published size~28 KB ESM (@evlog/telemetry), ~8.6 KB gzip — standalone, no dependency on evlog core
Server ingest only@evlog/telemetry/ingest ~5 KB (~1.7 KB gzip) — parseIngestBody() without pulling citty wiring into your API
Runtime depscitty + std-env only — both sideEffects: false, tree-shakeable ESM
Node18+ · ESM · sideEffects: false on the package

Fits wherever your code runs

SurfaceEntryLines of integration
citty CLIwithTelemetry() on the root commandOne wrapper + optional defineTelemetryCommands()
Script / migratorcreateTelemetry() + t.run()Wrap each logical run
GitHub ActionscreateGitHubActionsTelemetry()Same as script; ghaAction / ghaEvent auto-injected
Your API (ingest)parseIngestBody() from @evlog/telemetry/ingestOne validator + one POST route

No framework lock-in. No PostHog / Segment / custom analytics SDK. No second schema to maintain — disclosure is generated from the same collect config the CLI uses at runtime.

What you skip building

  • Privacy-safe flag capture (never reads raw argv)
  • DO_NOT_TRACK / EVLOG_TELEMETRY / persisted opt-out with outbox purge
  • Disk outbox with lockfile, stale-lock recovery, and backlog drain
  • Auto-generated disclosure (TELEMETRY.md + telemetry status)
  • First-run notice with opt-out instructions
  • Typed telemetry.set() with allowlisted string fields
  • Server-side ingest validation aligned with the CLI envelope
  • Hard guarantee: telemetry never throws, never blocks exit

The payoff

Once ingest is live you can answer product questions that CLI authors usually guess at:

  • Which commands are actually used (doctor vs sync vs telemetry status)
  • Where runs fail (outcome, errorCode, version breakdown)
  • How long operations take (durationMs per command)
  • Which versions are still in the wild (tool.version on every event)
  • Whether CI or local dev dominates (env.ci, env.agent, env.tty)

All from one wide event per run — the same mental model as evlog on the server, without bolting a browser analytics stack onto a terminal tool.

Try it locally: examples/telemetry-playgroundpnpm run cli -- doctor, EVLOG_TELEMETRY_DEBUG=1 to inspect payloads, telemetry status for disclosure.

See also

  • Setup — citty, scripts, GitHub Actions, delivery config
  • Ingest — server endpoint, validation, storage
  • Reference — envelope, schema extensions, disclosure, consent
  • Audit — wide events for security-sensitive actions
  • Drain pipeline — forward ingested events into Axiom, Datadog, OTLP, or a custom store