autotel

Write Once, Observe Anywhere

autotel-core

core
163 lines

When to use trace vs span vs request logger vs events in Autotel. Init once at startup, package exports (autotel, autotel/event, autotel/testing). Use for setup and choosing the right API.

Autotel — Core

OpenTelemetry instrumentation for Node.js and edge. Instrument once; stream to any OTLP backend. Use trace()/span() for spans, getRequestLogger() for one snapshot per request, createStructuredError/parseError for errors, track() for product events.

When to Use What

NeedAPIImport
Wrap a function with a spantrace(fn), span('Name', fn)autotel
Request-scoped attributes + emit oncegetRequestLogger(ctx?).set(), .emitNow()autotel
Throw with why/fix/linkcreateStructuredError({ message, why?, fix?, link?, status? })autotel
Parse API errors (client)parseError(err)message, why, fix, linkautotel
Product/analytics eventstrack(name, attrs) or Event from autotel/eventautotel, autotel/event
Init (once at startup)init({ service, ... })autotel or autotel/instrumentation
TestingcreateTraceCollector(), InMemorySpanExporterautotel/testing, autotel/exporters

Request logger requires an active span. Wrap HTTP handlers with trace() or framework middleware that creates a span, then call getRequestLogger() inside.

Setup

typescript
import { init, trace, getRequestLogger } from 'autotel';

init({ service: 'my-app' });

const handler = trace((ctx) => async (req: Request) => {
  const log = getRequestLogger(ctx);
  log.set({ path: req.url });
  const result = await doWork(req);
  log.emitNow();
  return result;
});

Core Patterns

Factory pattern when you need context (attributes, request logger):

typescript
const createUser = trace((ctx) => async (data: UserInput) => {
  ctx.setAttribute('user.id', data.id);
  const log = getRequestLogger(ctx);
  log.set({ user: { id: data.id } });
  return db.users.create(data);
});

Direct pattern when you don't need context:

typescript
const getUser = trace(async (id: string) => {
  return db.users.findById(id);
});

Structured errors in API routes:

typescript
import { createStructuredError } from 'autotel';
throw createStructuredError({
  message: 'Not found',
  status: 404,
  why: `No user "${id}"`,
  fix: 'Check the ID and try again',
});

Client: parseError for UI:

typescript
import { parseError } from 'autotel';
const e = parseError(err);
toast.error(e.message, { description: e.why });

Common Mistakes

HIGH Call getRequestLogger() without active span

Wrong:

typescript
app.get('/api/x', () => {
  const log = getRequestLogger();
  log.set({ route: 'x' });
});

Correct:

typescript
app.use(autotelMiddleware()); // or wrap route with trace()
app.get('/api/x', () => {
  const log = getRequestLogger();
  log.set({ route: 'x' });
});

getRequestLogger() requires an active span. Register middleware that creates a span per request, or wrap the handler with trace().

Source: packages/autotel/src/request-logger.ts

HIGH Use await import() for init-time optional deps

Wrong:

typescript
const pkg = await import('optional-dep');

Correct:

typescript
import { safeRequire } from 'autotel';
const pkg = safeRequire('optional-dep');

init() must stay synchronous. Use node-require helpers for optional dependencies.

Source: packages/autotel/CLAUDE.md

MEDIUM Use trace() without ctx when you need attributes or request logger

Wrong:

typescript
const handler = trace(async (req) => {
  const log = getRequestLogger(); // may throw if span not set up for request logger
});

Correct:

typescript
const handler = trace((ctx) => async (req) => {
  const log = getRequestLogger(ctx);
  log.set({ route: req.url });
});

Use the factory pattern (ctx) => async (...) when you need to set attributes or use the request logger.

Source: docs/AGENT-GUIDE.md

Version

Targets autotel v2.23.x.

See also: autotel-instrumentation/SKILL.md — init and trace/span in depth. autotel-request-logging/SKILL.md — request logger usage.