telemetry
Define and collect telemetry events and traces from Sanity CLI, Studio, and other TypeScript apps.
Repository Health
Technical Analysis
@sanity/telemetry is a small TypeScript library for instrumenting applications with typed, versioned telemetry events and multi-step traces. Rather than hard-wiring an app to a specific analytics vendor, it exposes a store you configure with your own consent-resolution and event-delivery strategies, then hands application code a logger with log() and trace() methods to call.
It originated as the shared telemetry layer for the Sanity CLI and Sanity Studio, where the same event needs to be logged consistently from many entry points without each one knowing about consent state, session identity, or batching. The library builds on RxJS internally to buffer events, throttle flush intervals, and retry failed submissions, while keeping the public API deliberately small: defineEvent, defineTrace, createBatchedStore, and a React provider/hook pair for component-level usage.
What You Get
defineEvent()anddefineTrace()helpers for declaring versioned, typed telemetry schemas ahead of time- A
createBatchedStore()factory that buffers events, throttles flushes on a configurable interval, and retries failed submissions - Pluggable
resolveConsentandsendEventsstrategies so the library stays agnostic about the analytics backend and consent UI - Trace contexts via
newContext()that propagate caller information through shared helper functions without those functions needing telemetry awareness - A React integration (
@sanity/telemetry/react) withTelemetryProvideranduseTelemetry()for component-level logging - Browser lifecycle handling (
registerLifecycleEvents,endWithBeacon) to flush pending events before a tab closes or navigates away
Common Use Cases
- Logging feature usage and session events from the Sanity CLI and Sanity Studio
- Tracking multi-step user flows (e.g. search, onboarding) as traces with start/log/complete/error states
- Wrapping a shared helper function (like a login routine) so telemetry context flows through it without coupling the helper to any specific caller
- Collecting client-side usage data in a browser app with consent gating and beacon-based flush on unload
Under The Hood
Architecture
The core is createStore(), a small event-bus built on an RxJS Subject that turns log()/trace() calls into typed TelemetryEvent objects on an observable stream. createBatchedStore() wraps that store with buffering, throttled flush timing, and retry-on-failure logic composed from RxJS operators (throttle, concatMap, mergeMap, catchError), while index.ts and the react/ subdirectory re-export the same store contract for plain TypeScript and React consumers respectively. Data flows one way — call site to Subject to in-memory buffer to periodic submit() to the consumer-supplied sendEvents() — with no dependency-injection framework; composition is purely functional closures. Because both the CLI and the React provider build on the same TelemetryStore shape, changing that core contract would ripple through every consumer at once.
Tech Stack
Written in TypeScript targeting ES2020 with bundler module resolution, the package’s only runtime dependencies are RxJS (event stream plumbing) and typeid-js (trace ID generation); React is an optional peer dependency used solely by the react/ entry point. It’s built with @sanity/pkg-utils into dual CJS/ESM bundles with generated type declarations, tested with Vitest, linted with ESLint/Prettier, managed as a pnpm workspace package, and released via release-please GitHub Actions automation with Renovate keeping dependencies current.
Code Quality
Tests live under src/__test__ using Vitest, exercising createBatchedStore and createDeferredStore end-to-end with mocked sendEvents/resolveConsent callbacks and real timers, favoring integration-style toMatchObject assertions over isolated per-function unit tests. Strict TypeScript settings (strict, strictNullChecks, noImplicitAny) are enabled project-wide, and naming is consistent across factory functions (createXStore, defineX). No dedicated CI test workflow is visible in .github/workflows (only a release-please workflow), and inline documentation comments are sparse and inconsistently applied across exported functions.
API Design
The public surface is deliberately small: defineEvent()/defineTrace() declare typed, versioned schemas ahead of time, and a single logger object exposes log()/trace() for actually recording data. Nothing is batteries-included — integrators must supply their own resolveConsent() and sendEvents() implementations before any event reaches a backend, which adds upfront work but keeps the library vendor-neutral. The React integration (useTelemetry()) mirrors the vanilla logger shape closely, minimizing relearning across environments, and the examples/ directory ships runnable CLI and React sample apps alongside a single README rather than generated API docs.