Edge Runtimes
autotel-edge is a lightweight, vendor-agnostic OpenTelemetry implementation
for edge runtimes. It’s the foundation under
autotel-cloudflare and works directly on
Vercel Edge, Netlify Edge, Deno Deploy, and any runtime with fetch() and
AsyncLocalStorage. There are no Node.js APIs and no auto-instrumentation
libraries. The core SDK ships with sampling, events, logger, and testing
helpers.
| Bundle | Size |
|---|---|
Core (autotel-edge) | ~20KB minified, ~8KB gz |
| All entry points combined | ~25KB minified, ~10KB gz |
Cloudflare Workers users: use
autotel-cloudflareinstead. It builds on this same foundation and adds complete bindings instrumentation.
Installation
Section titled “Installation”npm install autotel-edgeWhen to use this directly
Section titled “When to use this directly”- Vercel Edge Functions / Edge Middleware
- Netlify Edge Functions
- Deno Deploy
- AWS Lambda@Edge (limited
AsyncLocalStoragesupport) - Custom edge runtimes (e.g. building your own
autotel-vercel)
Quick start
Section titled “Quick start”import { trace, init } from 'autotel-edge';
init({ service: { name: 'my-edge-function' }, exporter: { url: process.env.OTEL_ENDPOINT ?? 'http://localhost:4318/v1/traces', },});
export const handler = trace(async (request: Request) => { return new Response('Hello World');});Core API
Section titled “Core API”trace(fn) / trace(options, fn)
Section titled “trace(fn) / trace(options, fn)”Wrap a function so its execution becomes a span. Three call shapes:
// Bare functionconst handler = trace(async (request: Request) => new Response('OK'));
// With optionsconst handler = trace( { name: 'fetch-handler', attributesFromArgs: ([request]) => ({ 'http.method': request.method }), }, async (request: Request) => new Response('OK'),);
// Factory pattern — receives ctx, returns the handlerconst handler = trace((ctx) => async (request: Request) => { ctx.setAttribute('custom.attr', 'value'); return new Response('OK');});span(options, fn)
Section titled “span(options, fn)”Create a named child span around a code block:
import { span } from 'autotel-edge';
const rows = await span( { name: 'database.query', attributes: { table: 'users' } }, async (s) => { const data = await db.query('SELECT * FROM users'); s.setAttribute('rows', data.length); return data; },);init(config)
Section titled “init(config)”Configure the SDK once, at module load:
init({ service: { name: 'my-edge-function', version: '1.0.0', namespace: 'production', }, exporter: { url: 'https://api.honeycomb.io/v1/traces', headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY }, }, sampling: { tailSampler: SamplingPresets.production() },});init() accepts a function that closes over the runtime environment too:
init((env) => ({ service: { name: env.SERVICE_NAME }, exporter: { url: env.OTEL_ENDPOINT },}));Fetch route controls
Section titled “Fetch route controls”Filter which paths are traced and map URL patterns to per-route service names:
init({ service: { name: 'edge-app' }, exporter: { url: 'https://otlp.example.com/v1/traces' }, handlers: { fetch: { include: ['/api/**'], exclude: ['/api/internal/**', '/health'], routes: { '/api/auth/**': { service: 'auth-service' }, '/api/**': { service: 'api-service' }, }, }, },});Sampling
Section titled “Sampling”Tail sampling decides after the trace finishes, so you can keep 100% of errors and slow requests while sampling everything else at any rate.
Presets
Section titled “Presets”import { SamplingPresets } from 'autotel-edge/sampling';
init({ service: { name: 'my-app' }, sampling: { tailSampler: SamplingPresets.production() },});| Preset | Behaviour |
|---|---|
SamplingPresets.development() | 100% sampling |
SamplingPresets.production() | 10% baseline + 100% errors + slow >1s |
SamplingPresets.highTraffic() | 1% baseline + 100% errors + slow >1s |
SamplingPresets.debugging() | Errors only |
Custom sampler
Section titled “Custom sampler”import { createCustomTailSampler } from 'autotel-edge/sampling';import { SpanStatusCode } from '@opentelemetry/api';
const sampler = createCustomTailSampler((trace) => { const span = trace.localRootSpan; if (span.attributes['http.route']?.toString().startsWith('/api/')) return true; if (span.status.code === SpanStatusCode.ERROR) return true; return false;});Logger
Section titled “Logger”Simple logger
Section titled “Simple logger”import { createEdgeLogger } from 'autotel-edge/logger';
const log = createEdgeLogger('my-service');log.info('Processing request', { userId: '123' });log.error('Request failed', { error });// trace_id and span_id are attached automaticallyExecution logger
Section titled “Execution logger”The execution logger is a request-scoped accumulator: build context across the
request, then emit once. Supports fork() for background work that should keep
its own snapshot:
import { getExecutionLogger, trace } from 'autotel-edge';
export const handler = trace(async (request) => { const log = getExecutionLogger();
log.set({ feature: 'checkout' }); log.info('Processing order', { orderId: '123' });
log.fork('send-email', async () => { await sendEmail('123'); });
log.emitNow(); return Response.json({ ok: true });});Events
Section titled “Events”Track product events with the active trace ID attached automatically:
import { publishEvent } from 'autotel-edge/events';
await publishEvent({ name: 'order.completed', userId: '123', properties: { orderId: 'abc', amount: 99.99 },});Runtime notes
Section titled “Runtime notes”Vercel Edge Functions
Section titled “Vercel Edge Functions”// app/api/route.ts (or middleware.ts)import { trace, init } from 'autotel-edge';
init({ service: { name: 'vercel-edge' }, exporter: { url: process.env.OTEL_ENDPOINT! },});
export const runtime = 'edge';
export const GET = trace(async (request: Request) => { return Response.json({ ok: true });});Netlify Edge Functions
Section titled “Netlify Edge Functions”import { trace, init } from 'autotel-edge';
init({ service: { name: 'netlify-edge' }, exporter: { url: Deno.env.get('OTEL_ENDPOINT')! },});
export default trace(async (request: Request) => { return new Response('Hello from the edge');});Deno Deploy
Section titled “Deno Deploy”import { trace, init } from 'https://esm.sh/autotel-edge';
init({ service: { name: 'deno-deploy' }, exporter: { url: Deno.env.get('OTEL_ENDPOINT')! },});
Deno.serve(trace(async (request) => new Response('OK')));Testing
Section titled “Testing”import { createTraceCollector, assertTraceCreated } from 'autotel-edge/testing';
const collector = createTraceCollector();await myFunction();assertTraceCreated(collector, 'myFunction');Constraints
Section titled “Constraints”- No Node.js APIs. No
fs,net,process(beyondprocess.envpolyfills); use Web APIs (fetch,crypto.subtle) instead. - No auto-instrumentations. Edge runtimes don’t support
@opentelemetry/auto-instrumentations-node. Instrument explicitly withtrace()andspan(). AsyncLocalStoragerequired. Some runtimes need it enabled (e.g. Cloudflare’scompatibility_flags = ["nodejs_compat"]).
Route matching helpers
Section titled “Route matching helpers”The toolkit exports three functions for building your own middleware. They drive the route controls above and are useful when you wire instrumentation by hand.
import { shouldInstrumentPath, getServiceForPath, runMiddlewareFinishPipeline,} from 'autotel-edge';
// Skip noisy paths before opening a span.const trace = shouldInstrumentPath('/api/users', { exclude: ['/health', '/metrics'] });
// Name the span's service from the route.const service = getServiceForPath('/users/123', { '/users/**': { service: 'users-api' }, '/admin/**': { service: 'admin-api' },});
// After the handler runs, enrich the span and drain async work.await runMiddlewareFinishPipeline( { event: request, status: 200 }, { enrichers: [(ctx) => span.setAttributes({ 'http.status_code': ctx.status })], drains: [(ctx) => analytics.record(ctx)], },);Each enricher and drain runs inside its own try/catch, so one failure does not cascade into the others.
Entry points
Section titled “Entry points”| Import | Contents |
|---|---|
autotel-edge | Core + toolkit: trace, span, init, definePlugin, createPluginRunner, shouldInstrumentPath, getServiceForPath, runMiddlewareFinishPipeline |
autotel-edge/sampling | Presets and custom samplers |
autotel-edge/events | publishEvent, subscribers |
autotel-edge/logger | createEdgeLogger, execution logger |
autotel-edge/testing | Trace collectors and assertions |
Vendor packages
Section titled “Vendor packages”autotel-cloudflare— Cloudflare Workers with full bindings coverageautotel-vercel— coming soonautotel-netlify— coming soon
Examples
Section titled “Examples”cloudflare-example— Cloudflare Workers built on autotel-edge.