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
Use it when adding tracing or structured logging to a Node.js/edge handler, or when reviewing code for observability anti-patterns.
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
SafeThe 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 madeExamples
Review src/api/checkout.ts and convert the console.log calls into autotel wide events with a canonical log line per request.Add autotel createStructuredError with why, fix, and link fields to the error paths in the payment service.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.errorfor 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:
- Attach call-site context, then rethrow —
getRequestLogger(ctx).error(err, { step }). Use when the rethrown error needs context only known at the catch site. - Writing instrumentation/middleware that wraps user handlers —
ctx.recordError(err)from inside atrace((ctx) => ...)callback. Sets ERROR status, structurederror.*attributes, and (during the back-compat window) records the exception. Acceptsunknownso noas Errorcast is needed in catch blocks. For code that doesn't have actxhandle, use the standalone formrecordStructuredError(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 keysstrict— Plus JWTs, Bearer tokens, IBANs, API keyspci-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/requireModulefrom./node-requirefor 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 ofnew Error(...)in app code — lettrace()record it on span exit - MUST: Use
ctx.recordError(err)from instrumentation/middleware code that wraps user handlers (orrecordStructuredError(ctx, err)if you don't have actxhandle) - 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()withctx.track('event.name', { ... })(or standalonetrack('event.name', { ... })outsidetrace()); emit wide-event attributes withgetRequestLogger(ctx).set({ ... }) - NEVER: Call
ctx.recordException(err)orctx.addEvent(...)— Span Event API is deprecated (OTEP 4430, March 2026) and intentionally type-gated out ofTraceContext - NEVER: Cast
ctx as anyoras unknown as { recordException }to bypass the type gate
Event Queue
- MUST: Call
getEventQueue()?.flush()before assertions or shutdown - MUST: Forward
options.autotelin subscriber payloads (contains trace context) - NEVER: Assert on event delivery without flush
Tree-Shaking & Repository
- MUST: Use explicit
exportsinpackage.jsonfor 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
SubscriberTestHarnessfor subscriber tests
Loading Reference Files
- Building error handling? Load structured-errors.md
- Adding request logging? Load request-logger.md
- Setting up wide events or querying? Load wide-events.md
- Reviewing existing code? Load code-review.md
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 filesThis 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.mdFrom 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
| 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' |
| 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 installationopenskills 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 syncManual 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.
Configure a Chat Channel with SecretRefs
DevOps
Set up chat channels using non-interactive commands, with secrets only as SecretRefs. Covers gather, mutate, repair, and prove steps for reliable delivery.
Investigate ClickHouse CI Failures
DevOps
Investigate a ClickHouse CI failure end-to-end from a PR or S3 report URL. Fetches failed tests, classifies flaky vs real regression, and checks for existing issues/fixes.
Ansible PR Review
DevOps
Reviews an Ansible PR following the project's standardized process, including changelog checks, test coverage validation, and feedback tracking.