OpenTelemetry
First-class OpenTelemetry metrics from the workflow event stream.
Autotel adapter
Section titled “Autotel adapter”Create an adapter that tracks metrics and optionally creates spans:
import { ok, err, type Result } from 'awaitly';import { createWorkflow } from 'awaitly/workflow';import { createAutotelAdapter } from 'awaitly/otel';
// Define your dependencies with Result-returning functionstype UserNotFound = { type: 'USER_NOT_FOUND'; id: string };type CardDeclined = { type: 'CARD_DECLINED'; reason: string };
const deps = { fetchUser: async (id: string): Promise<Result<User, UserNotFound>> => { const user = await db.users.find(id); return user ? ok(user) : err({ type: 'USER_NOT_FOUND', id }); }, chargeCard: async (amount: number): Promise<Result<Charge, CardDeclined>> => { const result = await paymentGateway.charge(amount); return result.success ? ok(result.charge) : err({ type: 'CARD_DECLINED', reason: result.error }); },};
const autotel = createAutotelAdapter({ serviceName: 'checkout-service', createStepSpans: true, // Create spans for each step recordMetrics: true, // Record step metrics recordRetryEvents: true, // Record retry events markErrorsOnSpan: true, // Mark errors on spans defaultAttributes: { // Custom attributes for all spans environment: 'production', },});
// Use with workflowconst workflow = createWorkflow('workflow', deps, { onEvent: autotel.handleEvent,});
await workflow.run(async ({ step, deps }) => { const user = await step('fetch-user', () => deps.fetchUser(id)); const charge = await step('charge-card', () => deps.chargeCard(100)); return { user, charge };});Access metrics
Section titled “Access metrics”const metrics = autotel.getMetrics();
console.log(metrics.stepDurations);// [{ name: 'fetch-user', durationMs: 45, success: true }, ...]
console.log(metrics.retryCount); // Total retry countconsole.log(metrics.errorCount); // Total error countconsole.log(metrics.cacheHits); // Cache hit countconsole.log(metrics.cacheMisses); // Cache miss countSimple event handler
Section titled “Simple event handler”For debug logging without full metrics collection:
import { createWorkflow } from 'awaitly/workflow';import { createAutotelEventHandler } from 'awaitly/otel';
const workflow = createWorkflow('workflow', deps, { onEvent: createAutotelEventHandler({ serviceName: 'checkout', includeStepDetails: true, }),});
// Set AUTOTEL_DEBUG=true to see console outputWith autotel tracing
Section titled “With autotel tracing”Wrap workflows with actual OpenTelemetry spans:
import { withAutotelTracing } from 'awaitly/otel';import { trace } from 'autotel';
const traced = withAutotelTracing(trace, { serviceName: 'checkout' });
const result = await traced('process-order', async () => { return workflow.run(async ({ step, deps }) => { const user = await step('fetch-user', () => deps.fetchUser(id)); const charge = await step('charge', () => deps.chargeCard(100)); return { user, charge }; });}, { orderId: '123' }); // Optional attributesConfiguration options
Section titled “Configuration options”{ serviceName: string; // Required: identifies the service createStepSpans?: boolean; // Create spans for steps (default: false) recordMetrics?: boolean; // Collect metrics (default: true) recordRetryEvents?: boolean; // Track retries (default: true) markErrorsOnSpan?: boolean; // Mark errors on spans (default: true) defaultAttributes?: Record<string, string>; // Added to all spans}Span attributes
Section titled “Span attributes”When createStepSpans is enabled, spans include:
| Attribute | Description |
|---|---|
workflow.step.name | Step name from options |
workflow.step.key | Step cache key (if set) |
workflow.step.cached | Whether result was cached |
workflow.step.retry_count | Number of retries |
workflow.step.duration_ms | Step duration |
workflow.step.success | Whether step succeeded |
workflow.step.error | Error type (if failed) |
Multiple workflows
Section titled “Multiple workflows”Create separate adapters for different workflows:
const checkoutTelemetry = createAutotelAdapter({ serviceName: 'checkout-service', defaultAttributes: { workflow: 'checkout' },});
const inventoryTelemetry = createAutotelAdapter({ serviceName: 'inventory-service', defaultAttributes: { workflow: 'inventory' },});
const checkoutWorkflow = createWorkflow('checkout', checkoutDeps, { onEvent: checkoutTelemetry.handleEvent,});
const inventoryWorkflow = createWorkflow('inventory', inventoryDeps, { onEvent: inventoryTelemetry.handleEvent,});Combining with other event handlers
Section titled “Combining with other event handlers”import { createWorkflow } from 'awaitly/workflow';import { createAutotelAdapter } from 'awaitly/otel';import { createVisualizer } from 'awaitly-visualizer';
const autotel = createAutotelAdapter({ serviceName: 'checkout' });const viz = createVisualizer({ workflowName: 'checkout' });
const workflow = createWorkflow('workflow', deps, { onEvent: (event) => { autotel.handleEvent(event); viz.handleEvent(event); },});Custom metrics
Section titled “Custom metrics”Extend the adapter output with your own metrics:
const autotel = createAutotelAdapter({ serviceName: 'checkout' });
const workflow = createWorkflow('workflow', deps, { onEvent: (event) => { autotel.handleEvent(event);
// Custom metric tracking if (event.type === 'step_complete' && !event.result.ok) { customMetrics.increment('checkout.step.failures', { step: event.stepName, error: String(event.result.error), }); } },});Environment variables
Section titled “Environment variables”| Variable | Description |
|---|---|
AUTOTEL_DEBUG | Set to true for console output |
OTEL_SERVICE_NAME | Default service name (overridden by config) |
Integration with OTEL collectors
Section titled “Integration with OTEL collectors”The adapter works with standard OpenTelemetry collectors. Configure your collector endpoint:
import { trace } from 'autotel';
// Configure your OTEL exportertrace.configure({ endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT, headers: { 'api-key': process.env.OTEL_API_KEY, },});
const traced = withAutotelTracing(trace, { serviceName: 'checkout' });