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"]).
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.