Advanced Features
Deterministic Trace IDs
Section titled “Deterministic Trace IDs”Generate consistent trace IDs from seeds for correlation with external systems:
import { createDeterministicTraceId } from 'autotel/trace-helpers';
const requestId = req.headers['x-request-id'];const traceId = await createDeterministicTraceId(requestId);console.log(`View traces: https://your-backend.com/traces/${traceId}`);Implementation: Uses SHA-256 hashing to generate consistent 128-bit trace IDs. Works in Node.js and edge runtimes (via crypto.subtle).
Use cases:
- Correlate external request IDs with OTel traces
- Link support tickets to trace data
- Associate business entities (orders, sessions) with observability data
Metadata Flattening
Section titled “Metadata Flattening”Automatically flatten nested objects into dot-notation span attributes:
import { flattenMetadata } from 'autotel/trace-helpers';import { trace } from 'autotel';
export const processOrder = trace((ctx) => async (order: Order) => { const metadata = flattenMetadata({ user: { id: order.userId, tier: 'premium' }, payment: { method: 'card', processor: 'stripe' }, items: order.items.length, });
ctx.setAttributes(metadata); // Results in: metadata.user.id, metadata.user.tier, metadata.payment.method, etc.});Features:
- Auto-serializes non-string values to JSON
- Filters out null/undefined values
- Gracefully handles circular references (→
<serialization-failed>) - Customizable prefix (default:
'metadata')
Isolated Tracer Provider
Section titled “Isolated Tracer Provider”For library authors who want to use Autotel without interfering with the application’s global OTel setup:
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';import { setAutotelTracerProvider } from 'autotel/tracer-provider';
// Create isolated provider (don't call provider.register())const exporter = new OTLPTraceExporter({ url: 'https://your-backend.com/v1/traces',});const provider = new NodeTracerProvider();provider.addSpanProcessor(new BatchSpanProcessor(exporter));
// Set as Autotel's provider (isolated from global OTel)setAutotelTracerProvider(provider);
// Now all trace(), span(), instrument() calls use this providerImportant limitations:
- Isolates span processing and export only
- OpenTelemetry context (trace IDs, parent spans) is still shared globally
- Spans from isolated provider may inherit context from global spans
Use cases:
- Library code with embedded Autotel
- SDKs that need observability without forcing users to configure OTel
- Separate span processing for different subsystems
- Testing with isolated trace collection
Semantic Convention Helpers
Section titled “Semantic Convention Helpers”Pre-configured trace helpers following OpenTelemetry semantic conventions:
import { traceDB, traceHTTP, traceMessaging } from 'autotel/semantic-helpers';import { traceGenAI, recordGenAiUsage } from 'autotel-genai/trace';
// LLM operations (Gen AI semantic conventions) — live in autotel-genaiexport const generateText = traceGenAI({ model: 'gpt-4-turbo', operation: 'chat', provider: 'openai',})((ctx) => async (prompt: string) => { const response = await openai.chat.completions.create({ /* ... */ }); // Records gen_ai.usage.input_tokens / gen_ai.usage.output_tokens recordGenAiUsage(ctx, 'gpt-4-turbo', { inputTokens: response.usage.prompt_tokens, outputTokens: response.usage.completion_tokens, }); return response.choices[0].message.content;});
// Database operations (DB semantic conventions)export const getUser = traceDB({ system: 'postgresql', operation: 'SELECT', database: 'app_db', collection: 'users',})((ctx) => async (userId: string) => { const query = 'SELECT * FROM users WHERE id = $1'; ctx.setAttribute('db.statement', query); return await pool.query(query, [userId]);});
// HTTP client operations (HTTP semantic conventions)export const fetchUser = traceHTTP({ method: 'GET', url: 'https://api.example.com/users/:id',})((ctx) => async (userId: string) => { const response = await fetch(`https://api.example.com/users/${userId}`); ctx.setAttribute('http.response.status_code', response.status); return response.json();});
// Messaging operations (Messaging semantic conventions)export const publishEvent = traceMessaging({ system: 'kafka', operation: 'publish', destination: 'user-events',})((ctx) => async (event: Event) => { await producer.send({ topic: 'user-events', messages: [event] }); ctx.setAttribute('messaging.message.id', event.id);});Benefits:
- Automatic semantic attributes following OTel specs
- Type-safe configuration interfaces
- Reduces boilerplate by 60-70%
- Links to official OTel semantic convention docs in JSDoc
Available helpers:
traceGenAI()(fromautotel-genai/trace) — Gen AI operations (chat, completion, embedding)traceDB()— Database operations (SQL, NoSQL, Redis)traceHTTP()— HTTP client requeststraceMessaging()— Queue/messaging operations (Kafka, RabbitMQ, SQS)
Event-Driven Observability
Section titled “Event-Driven Observability”First-class support for message-based systems with traceProducer and traceConsumer helpers:
import { traceProducer, traceConsumer } from 'autotel/messaging';
// Producer - auto-sets SpanKind.PRODUCER and semantic attributesexport const publishEvent = traceProducer({ system: 'kafka', // kafka | sqs | rabbitmq | custom destination: 'user-events', messageIdFrom: (args) => args[0].id, // Extract message ID})((ctx) => async (event: Event) => { const headers = ctx.getTraceHeaders(); // W3C traceparent/tracestate await producer.send({ topic: 'user-events', messages: [{ value: event, headers }], });});
// Consumer - auto-sets SpanKind.CONSUMER, extracts links from headersexport const processEvent = traceConsumer({ system: 'kafka', destination: 'user-events', consumerGroup: 'event-processor', headersFrom: (msg) => msg.headers, // Extract trace headers batchMode: true, // For batch consumers})((ctx) => async (messages) => { // Links to producer spans automatically created for (const msg of messages) await process(msg);});For KafkaJS eachBatch, use withBatchConsumer from autotel-plugins/kafka for batch-level and optional per-message spans with trace continuation from headers.
Key implementation details:
- Uses
SpanKind.PRODUCER/SpanKind.CONSUMERfor proper trace visualization ctx.getTraceHeaders()returns{ traceparent, tracestate? }for header injectionctx.recordDLQ(dlqName, reason)for dead-letter queue tracking- Supports lag metrics via
lagMetrics.getCurrentOffset/getEndOffset - Automatic semantic attributes:
messaging.system,messaging.destination.name,messaging.operation,messaging.consumer.group
Safe Baggage Propagation
Section titled “Safe Baggage Propagation”Type-safe baggage schemas with built-in guardrails:
import { createSafeBaggageSchema, BusinessBaggage,} from 'autotel/business-baggage';
// Pre-built schema for common fieldsBusinessBaggage.set(ctx, { tenantId: 'acme', userId: 'user-123', priority: 'high',});const { tenantId, priority } = BusinessBaggage.get(ctx);
// Custom schema with validation and guardrailsconst OrderBaggage = createSafeBaggageSchema( { orderId: { type: 'string', maxLength: 36 }, customerId: { type: 'string', hash: true }, tier: { type: 'enum', values: ['free', 'pro', 'enterprise'] as const }, }, { prefix: 'order', // Keys: order.orderId, order.tier redactPII: true, // Auto-redact email/phone/SSN patterns hashHighCardinality: true, // Hash UUIDs/timestamps },);Guardrails:
- Size limits:
maxKeyLength(default 64),maxValueLength(default 256) - PII detection: Regex patterns for email, phone, SSN auto-redacted
- High-cardinality hashing: UUIDs and timestamps hashed via FNV-1a
- Enum validation: Rejects values not in the defined set
- Type coercion: Numbers/booleans properly serialized
Workflow & Saga Tracing
Section titled “Workflow & Saga Tracing”Track distributed workflows with compensation support:
import { traceWorkflow, traceStep } from 'autotel/workflow';
export const orderSaga = traceWorkflow({ name: 'OrderSaga', workflowId: (order) => order.id,})((ctx) => async (order) => { await traceStep({ name: 'ReserveInventory', compensate: async (ctx, error) => { await inventoryService.release(order.items); // Rollback }, })((ctx) => async () => { await inventoryService.reserve(order.items); })();
await traceStep({ name: 'ChargePayment', linkToPrevious: true, // Link to ReserveInventory span compensate: async (ctx, error) => { await paymentService.refund(order.id); }, })((ctx) => async () => { await paymentService.charge(order); })();});// If ChargePayment fails, compensations run in reverse orderKey features:
traceWorkflowcreates root span withworkflow.name,workflow.idattributestraceStepcreates child spans withworkflow.step.name,workflow.step.indexlinkToPrevious: truecreates span links for step sequencing- Compensations run in reverse order on failure
ctx.getWorkflowId(),ctx.getWorkflowName(),ctx.getStepIndex()context methods- WeakMap-based state isolation tied to span lifecycle
Distributed Workflows Across Services
Section titled “Distributed Workflows Across Services”autotel/workflow uses AsyncLocalStorage, so it stays inside one process. When a
workflow spans microservices, autotel/workflow-distributed carries the workflow
identity over W3C baggage instead.
import { traceDistributedWorkflow, traceDistributedStep,} from 'autotel/workflow-distributed';
// Service A starts the workflowexport const createOrder = traceDistributedWorkflow({ name: 'OrderFulfillment', workflowIdFrom: (order) => order.id,})((ctx) => async (order) => { await publishToInventory(order); // workflow baggage rides the outbound headers return { workflowId: ctx.workflowId };});
// Service B continues itexport const reserveInventory = traceDistributedStep({ name: 'ReserveInventory', extractBaggage: true,})((ctx) => async (message) => { await reserveItems(message.items); // ctx.workflowId matches Service A});createWorkflowHeaders() and parseWorkflowFromBaggage() give you manual control
when the transport is not HTTP. getWorkflowProgress() and
isInDistributedWorkflow() read the current state.
Async Callbacks: the Parking Lot
Section titled “Async Callbacks: the Parking Lot”A webhook or payment callback can land hours after the request that triggered it.
autotel/webhook stores the originating trace context under a correlation key and
links the later callback span back to it.
import { trace } from 'autotel';import { createParkingLot, InMemoryTraceContextStore } from 'autotel/webhook';
const parkingLot = createParkingLot({ store: new InMemoryTraceContextStore(), defaultTTLMs: 24 * 60 * 60 * 1000,});
export const initiatePayment = trace((ctx) => async (orderId: string) => { await parkingLot.park(`payment:${orderId}`); await stripe.createPaymentIntent({ metadata: { orderId } });});
export const handleStripeWebhook = parkingLot.traceCallback({ name: 'stripe.webhook.payment_intent.succeeded', correlationKeyFrom: (event) => `payment:${event.data.object.metadata.orderId}`,})((ctx) => async (event) => { // ctx.elapsedMs reports how long the callback took to arrive await fulfillOrder(event.data.object);});Swap InMemoryTraceContextStore for a Redis-backed store in production so parked
context survives a restart.
Correlation IDs
Section titled “Correlation IDs”A correlation id is a stable 16-character join key that ties fragmented traces
together. autotel/correlation-id generates one at a request boundary and reads
it back anywhere in the same async scope.
import { generateCorrelationId, getCorrelationId, runWithCorrelationId,} from 'autotel/correlation-id';
await runWithCorrelationId(generateCorrelationId(), async () => { getCorrelationId(); // available to every call in this async scope await handleRequest();});See Configuration for wiring correlation ids into
init().
Class Method Decorators
Section titled “Class Method Decorators”autotel/decorators wraps class methods with tracing using TypeScript 5 decorator
syntax. The span takes the method name by default.
import { Trace } from 'autotel/decorators';
class OrderService { @Trace('order.create', { withMetrics: true }) async createOrder(data: OrderData) { return db.orders.create(data); }
@Trace() // span name: processPayment async processPayment(orderId: string) { return stripe.charge(orderId); }}Request Enrichers
Section titled “Request Enrichers”autotel/enrichers parses request and response headers into normalized span
attributes for user agent, geolocation, and body size.
import { userAgent, geo, requestSize } from 'autotel/enrichers';
const ua = userAgent(req.headers);if (ua) ctx.setAttribute('user_agent.browser', ua['user_agent.browser']);
const location = geo(req.headers);if (location?.['geo.country']) { ctx.setAttribute('geo.country', location['geo.country']);}Drain Pipeline
Section titled “Drain Pipeline”autotel/drain-pipeline batches async work, retries with backoff, and flushes on
an interval. Subscribers and exporters use it internally; reach for it when you
build your own sink.
import { createDrainPipeline } from 'autotel/drain-pipeline';
const send = createDrainPipeline<LogEvent>({ batch: { size: 50, intervalMs: 5000 }, retry: { maxAttempts: 3, backoff: 'exponential' }, maxBufferSize: 1000,})(async (batch) => { await logService.send(batch);});
send(event);await send.flush();await send.shutdown();