Instrumentation with Autotel

VerifiedSafe

Use autotel to instrument functions with tracing, structured logs, and enriched errors.

Sby Skills Guide Bot
DevOpsIntermediate
7/24/2026
Claude CodeCursorWindsurfCodex
#observability#tracing#logging#structured-errors#opentelemetry

Recommended for

Automated summary

Guides instrumentation of TypeScript/Node.js and edge services with the autotel library: trace/span wrappers, structured errors, request loggers, and canonical log lines.

Strengths

  • Provides concrete API patterns for trace()/span(), distinguishing the factory form (ctx access) from the direct form
  • Lays out a clear error-handling ladder: throw a structured error, attach call-site context and rethrow, or call ctx.recordError()
  • Covers attribute redaction through init({ attributeRedactor }) and preventing re-initialization with lockLogger()
  • Explicitly targets Node.js, Cloudflare Workers, Hono, and Next.js

Limitations

  • The supplied content is truncated: the request logger section cuts off mid-code block
  • Assumes prior familiarity with the autotel library; installation and OTLP backend configuration are not covered
  • Detailed procedures live in references/ files that are not included in this excerpt
When to use it

Use it when adding tracing or structured logging to a Node.js/edge handler, or when reviewing code for observability anti-patterns.

When not to use it

Avoid it if the project is not TypeScript/autotel, or if it relies on a different telemetry stack such as the raw OpenTelemetry SDK or Sentry.

Security analysis

Safe
Quality score85/100

The skill is pure documentation for an observability/tracing library: prose, API reference tables, and illustrative TypeScript snippets. There are no shell one-liners, no network fetches, no credential handling, no obfuscated payloads, and no instructions aimed at the auditor. The only outbound data flow mentioned (OTLP export, subscriber drain callbacks) is the library's user-configured telemetry purpose, and the skill actively recommends PII redaction presets. Declared allowed-tools is empty, so no execution surface is granted.

No concerns found

How this analysis is made

Examples

Convert console.log to wide events
Review src/api/checkout.ts and convert the console.log calls into autotel wide events with a canonical log line per request.
Add structured errors
Add autotel createStructuredError with why, fix, and link fields to the error paths in the payment service.
Set up init with redaction
Set up autotel init() in our Cloudflare Worker with strict attribute redaction and lock the logger in the Hono plugin.

name: autotel description: Use when instrumenting with trace/span/track, reviewing code for logging and observability patterns, converting console.log to wide events, adding structured errors, setting up canonical log lines, configuring init(), adding subscribers, or working in the autotel monorepo.

Autotel

Philosophy: "Write once, observe everywhere" - instrument once, stream to any OTLP-compatible backend.

trace() wraps functions. getRequestLogger() accumulates context. createStructuredError() adds why/fix/link to errors. Canonical log lines emit one wide event per request automatically.

When to Use

  • Instrumenting functions with tracing
  • Code uses console.log / console.error for observability
  • Error handling lacks structured context (no why, fix, or link)
  • Adding tracing to any Node.js or edge runtime handler
  • Reviewing code for observability anti-patterns
  • Setting up observability in Cloudflare Workers, Hono, or Next.js
  • Working in the autotel monorepo

Quick Reference

| Task | Reference | |------|-----------| | Convert console.log to wide events | wide-events.md | | Add structured errors | structured-errors.md | | Accumulate request context | request-logger.md | | Review code for anti-patterns | code-review.md | | Add attribute redaction | (use init({ attributeRedactor: 'default' | 'strict' | 'pci-dss' }) or custom config) | | Lock init from re-initialization | (use lockLogger() in framework plugins) | |

Tracing API

import { trace, span } from 'autotel';

// Factory pattern (receives ctx for attributes)
export const createUser = trace((ctx) => async (data) => {
  ctx.setAttribute('user.id', data.id);
  return await db.users.create(data);
});

// Direct pattern (no ctx needed)
export const getUser = trace(async (id) => {
  return await db.users.findById(id);
});

// Nested span
span('db.insert', async () => {
  await db.insert(record);
});

Recording Errors

Default: throw, don't catch. trace() records status, exception, and structured attributes when the wrapped function rejects.

import { trace, createStructuredError } from 'autotel';

export const charge = trace((ctx) => async (cart) => {
  if (!cart.items.length) {
    throw createStructuredError({
      message: 'Cart is empty',
      why: 'User submitted checkout with no items',
      fix: 'Validate cart on the client before submit',
      link: 'https://docs.example.com/errors/empty-cart',
    });
  }
  return await processCart(cart);
});

Fallbacks, in order:

  1. Attach call-site context, then rethrow — getRequestLogger(ctx).error(err, { step }). Use when the rethrown error needs context only known at the catch site.
  2. Writing instrumentation/middleware that wraps user handlers — ctx.recordError(err) from inside a trace((ctx) => ...) callback. Sets ERROR status, structured error.* attributes, and (during the back-compat window) records the exception. Accepts unknown so no as Error cast is needed in catch blocks. For code that doesn't have a ctx handle, use the standalone form recordStructuredError(ctx, err).
// Inside a trace() callback — instrumentation wrapping a user handler:
return trace({ name }, async (ctx) => {
  try {
    return await userHandler(args);
  } catch (err) {
    ctx.recordError(err); // ergonomic replacement for ctx.recordException
    throw err;
  }
});

ctx.recordException(...) and ctx.addEvent(...) are intentionally hidden from the TraceContext type per OTEP 4430 (March 2026 — Span Event API deprecation). The runtime methods exist for back-compat only; new code MUST go through createStructuredError, ctx.recordError(err) / recordStructuredError(ctx, err), or the request logger.

Request Logger

import { trace, getRequestLogger } from 'autotel';

export const handleOrder = trace((ctx) => async (req) => {
  const log = getRequestLogger(ctx);
  log.set({ feature: 'checkout', tier: req.user.tier });

  const cart = await loadCart(req.cartId);
  log.set({ cart_items: cart.items.length, cart_total: cart.total });

  try {
    const payment = await processPayment(cart);
    log.set({ payment_method: payment.method });
  } catch (error) {
    // Fallback pattern: attach call-site context, then rethrow.
    // Default is to let the error propagate and let trace() record it.
    log.error(error, { step: 'payment' });
    throw error;
  }
});

Event Tracking

import { trace, getEventQueue } from 'autotel';

// Inside trace() — use ctx.track for ergonomic, ctx-bound emission:
export const signup = trace((ctx) => async (data) => {
  ctx.track('user.signup', { userId: data.id, plan: data.plan });
  return await db.users.create(data);
});

// Outside trace() — use the standalone track():
import { track } from 'autotel';
track('user.signup', { userId: '123', plan: 'pro' });

// MUST flush before assertions or shutdown
await getEventQueue()?.flush();

Correlation ID

import { getOrCreateCorrelationId, runWithCorrelationId } from 'autotel';

const correlationId = getOrCreateCorrelationId();
runWithCorrelationId(incomingId, () => handleRequest());

Framework Adapters

// Cloudflare Workers (via autotel/workers)
import { init, wrapModule, trace } from 'autotel/workers';

const processOrder = trace(async (orderId: string, kv: KVNamespace) => {
  return await kv.get(orderId);
});

export default wrapModule(
  { service: { name: 'my-worker' } },
  {
    async fetch(_req, env) {
      return Response.json(await processOrder('123', env.ORDERS_KV));
    },
  },
);
// Next.js
import { withAutotel, useLogger } from 'autotel-adapters/next';

export const POST = withAutotel(async (request) => {
  const log = useLogger(request);
  log.set({ feature: 'checkout' });
  return Response.json({ ok: true });
});
// Hono (with autotel-hono middleware already creating spans)
import { useLogger } from 'autotel-adapters/hono';

app.get('/orders/:id', (c) => {
  const log = useLogger(c);
  log.set({ route: c.req.path });
  return c.json({ ok: true });
});

Anti-Patterns to Detect

| Anti-Pattern | Fix | |---|---| | console.log('user created', userId) | log.set({ user_id: userId }) inside trace() | | catch (e) { throw e } | Delete the catch — trace() records errors automatically. Or log.error(e, { step }); throw e to attach call-site context | | catch (e) { res.json({ error: e.message }) } | parseError(e) for consistent shape | | throw new Error('Payment failed') | createStructuredError({ message, why, fix, link }) | | ctx.recordException(err) / span.recordException(err) | App code: throw createStructuredError(...). Instrumentation: ctx.recordError(err) (or recordStructuredError(ctx, err) if you don't have a ctx handle). Span Event API is deprecated (OTEP 4430) and type-gated out of TraceContext | | ctx.addEvent('name', { ... }) / span.addEvent(...) | Discrete event inside trace(): ctx.track('event.name', { ... }) (or standalone track('event.name', { ... }) when there's no ctx handle). Wide-event attribute: getRequestLogger(ctx).set({ ... }) | | (ctx as any).recordException(err) / as unknown as { recordException } | Don't bypass the type gate — use recordStructuredError(ctx, err) instead | | Manual console.log at start/end of function | trace() wrapper handles lifecycle | | Separate request ID generation | ctx.correlationId provides automatic correlation |

init() Configuration

Signals: Traces, Metrics, Logs

When endpoint is set, traces and metrics are auto-configured by default. Logs are opt-in to avoid unexpected export and preserve OTel SDK OTEL_LOGS_EXPORTER handling:

init({
  service: 'my-app',
  endpoint: 'http://localhost:4318',
  // traces: always on when endpoint is set
  // metrics: true by default (AUTOTEL_METRICS env var override)
  // logs: false by default — opt-in with logs: true (AUTOTEL_LOGS env var override)
  logs: true,  // enable auto OTLP log export
});

Disable any signal explicitly:

init({
  service: 'my-app',
  endpoint: 'http://localhost:4318',
  metrics: false,  // disable auto OTLP metrics
  logs: false,     // disable auto OTLP logs (already the default)
});

Custom logRecordProcessors are additive — they work alongside the auto-configured exporter:

init({
  service: 'my-app',
  endpoint: 'http://localhost:4318',
  logRecordProcessors: [customProcessor], // added alongside auto-configured OTLP exporter
});

Protocol selection (http default, grpc optional) applies to all signals. gRPC exporters are optional peer deps.

Logger Locking

Framework plugins can lock init() to prevent re-initialization:

import { lockLogger, isLoggerLocked } from 'autotel';

lockLogger(); // After framework sets up instrumentation
isLoggerLocked(); // true

Silent Mode

Suppress internal autotel logs while keeping exporters running:

init({
  service: 'my-app',
  silent: true,        // Suppress console output
  minLevel: 'warn',    // Only log warnings/errors
});

Attribute Redaction

Automatically redact PII from span attributes:

init({
  service: 'my-app',
  attributeRedactor: 'default', // 'default' | 'strict' | 'pci-dss'
});
  • default — Emails, phones, SSNs, credit cards (last 4), sensitive keys
  • strict — Plus JWTs, Bearer tokens, IBANs, API keys
  • pci-dss — Focused on payment card data

Custom:

init({
  attributeRedactor: {
    keyPatterns: [/password/i, /secret/i],
    valuePatterns: [{ name: 'customerId', pattern: /CUST-\d{8}/g, replacement: 'CUST-***' }],
    builtins: ['email', 'creditCard'],
  },
});

Sampling

Default: AdaptiveSampler with 10% baseline, 100% for errors and slow requests (>1s). Tail sampling via TailSamplingSpanProcessor defers the decision until span ends.

import { AdaptiveSampler } from 'autotel';

init({
  service: 'my-app',
  endpoint: 'http://localhost:4318',
  sampler: new AdaptiveSampler({
    baselineSampleRate: 0.1,    // 10% of normal requests (default)
    slowThresholdMs: 1000,       // Requests > 1s are "slow" (default)
    alwaysSampleErrors: true,    // Always trace errors (default)
    alwaysSampleSlow: true,      // Always trace slow requests (default)
    linksBased: false,           // Enable for event-driven architectures
  }),
});

The Sampler interface is simple — return true to trace, false to skip:

const sampler: Sampler = {
  shouldSample({ operationName, args, links }) {
    return operationName.startsWith('critical.');
  },
};

Canonical Log Lines

init({
  service: 'my-app',
  canonicalLogLines: {
    enabled: true,
    rootSpansOnly: true,
    keep: [{ status: 500 }, { durationMs: 1000 }],
    pretty: true,
    drain: (event) => sendToAnalytics(event),
  },
});

MUST / SHOULD / NEVER

Init & Module Loading

  • MUST: Keep init() synchronous
  • MUST: Use safeRequire/requireModule from ./node-require for dynamic deps
  • NEVER: Use await import() for optional/lazy dependencies

Tracing

  • MUST: Use trace(), span(), instrument() to wrap business logic
  • MUST: Use factory pattern trace((ctx) => ...) when setting attributes
  • SHOULD: Let trace names infer from const/function names
  • NEVER: Manually start/end spans for app logic (SDK glue only)

Errors & Events

  • MUST: Throw createStructuredError({ message, why, fix, link }) instead of new Error(...) in app code — let trace() record it on span exit
  • MUST: Use ctx.recordError(err) from instrumentation/middleware code that wraps user handlers (or recordStructuredError(ctx, err) if you don't have a ctx handle)
  • SHOULD: Only catch errors when you need to attach call-site context, then getRequestLogger(ctx).error(err, { step }) and rethrow
  • SHOULD: Emit discrete events inside trace() with ctx.track('event.name', { ... }) (or standalone track('event.name', { ... }) outside trace()); emit wide-event attributes with getRequestLogger(ctx).set({ ... })
  • NEVER: Call ctx.recordException(err) or ctx.addEvent(...) — Span Event API is deprecated (OTEP 4430, March 2026) and intentionally type-gated out of TraceContext
  • NEVER: Cast ctx as any or as unknown as { recordException } to bypass the type gate

Event Queue

  • MUST: Call getEventQueue()?.flush() before assertions or shutdown
  • MUST: Forward options.autotel in subscriber payloads (contains trace context)
  • NEVER: Assert on event delivery without flush

Tree-Shaking & Repository

  • MUST: Use explicit exports in package.json for new entry points
  • MUST: Ask before adding new dependencies or modifying build configs
  • MUST: Create changeset for any package changes (pnpm changeset)
  • NEVER: Add barrel re-exports that pull in unused code

Package Layout

| Package | Role | |---------|------| | autotel | Node.js core: init, trace, span, track, event-queue, correlation-id. Also provides autotel/workers and autotel/cloudflare for Cloudflare Workers | | autotel-genai | GenAI/LLM instrumentation: traceGenAI, cost, metrics, events, semconv, ai-sdk bridge, and agent governance (autotel-genai/agent) | | autotel-edge | Edge runtime foundation (alternative to workers for vendor-agnostic edge) | | autotel-cloudflare | Cloudflare Workers implementation (re-exported via autotel/workers) | | autotel-adapters | Framework adapters (Next.js, Hono, Nitro) | | autotel-mcp-instrumentation | MCP instrumentation | | autotel-tanstack | TanStack Start | | autotel-subscribers | Event subscribers (PostHog, Mixpanel, Webhook) |

Each package has a CLAUDE.md for local conventions.

Semantic Helpers

import { traceDB, traceHTTP, traceMessaging } from 'autotel/semantic-helpers';
import { traceGenAI, recordGenAiUsage } from 'autotel-genai/trace';

// GenAI helpers live in the autotel-genai package (not core autotel)
export const generateText = traceGenAI({
  model: 'gpt-4-turbo', operation: 'chat', provider: 'openai',
})((ctx) => async (prompt) => {
  const response = await openai.chat.completions.create({ /* ... */ });
  recordGenAiUsage(ctx, 'gpt-4-turbo', {
    inputTokens: response.usage.prompt_tokens,
    outputTokens: response.usage.completion_tokens,
  });
});

export const getUser = traceDB({
  system: 'postgresql', operation: 'SELECT', collection: 'users',
})((ctx) => async (userId) => { /* ... */ });

Type-Safe Attributes

import { attrs, setUser, safeSetAttributes } from 'autotel/attributes';

ctx.setAttributes(attrs.user.id('user-123'));
setUser(ctx, { id: '123', email: 'user@example.com' });
safeSetAttributes(ctx, attrs.user.data({ email: 'pii@example.com' }), {
  guardrails: { pii: 'hash' },
});

Producer/Consumer Pattern

import { traceProducer, traceConsumer } from 'autotel/messaging';

export const publish = traceProducer({
  system: 'kafka', destination: 'user-events',
  messageIdFrom: (args) => args[0].id,
})((ctx) => async (event) => {
  const headers = ctx.getTraceHeaders();
  await producer.send({ messages: [{ value: event, headers }] });
});

Quick Commands

pnpm build      # Build all packages
pnpm test       # Run all tests
pnpm lint       # Lint
pnpm quality    # build + lint + format + type-check + test
pnpm changeset  # Create changeset for release

Testing

  • Unit tests: *.test.ts / Integration tests: *.integration.test.ts
  • MUST flush before assertions: await getEventQueue()?.flush();
  • Use SubscriberTestHarness for subscriber tests

Loading Reference Files

Advanced Features

See docs/ADVANCED.md for deterministic trace IDs, metadata flattening, isolated tracer providers, safe baggage propagation, and workflow/saga tracing.

From the file to a first result

The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.

View the source and supporting files

This download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.

1. Download the file

Download SKILL.md

From the root of a test project, save the file as .skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.

Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.

mkdir -p '.skill-downloads/instrumentation-with-autotel-mryiqihm' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/instrumentation-with-autotel-mryiqihm/download' --output '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md.part' && mv '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md.part' '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md'

2. Check prerequisites

Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.

Original excerpt: Quick Reference
TaskReference
Convert console.log to wide eventswide-events.md
Add structured errorsstructured-errors.md
Accumulate request contextrequest-logger.md
Review code for anti-patternscode-review.md
Add attribute redaction(use `init({ attributeRedactor: 'default'
Lock init from re-initialization(use lockLogger() in framework plugins)

The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.

3. Load the skill

After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.

Claude Code
test -f '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md' && test -s '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/autotel' && cp -R '.skill-downloads/instrumentation-with-autotel-mryiqihm' '.claude/skills/autotel'

Cursor
test -f '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md' && test -s '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/autotel' && cp -R '.skill-downloads/instrumentation-with-autotel-mryiqihm' '.cursor/skills/autotel'

Codex
test -f '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md' && test -s '.skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/autotel' && cp -R '.skill-downloads/instrumentation-with-autotel-mryiqihm' '.agents/skills/autotel'

OpenSkills

OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.

Official OpenSkills installation
openskills install './.skill-downloads/instrumentation-with-autotel-mryiqihm'

openskills read 'instrumentation-with-autotel-mryiqihm'

Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.

For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.

openskills sync

Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.

4. Try a first task

Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:

Read .skill-downloads/instrumentation-with-autotel-mryiqihm/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.

Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.

Related skills