npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

autotel-cloudflare

v2.18.14

Published

The #1 OpenTelemetry package for Cloudflare Workers - complete bindings coverage, native CF OTel integration, advanced sampling

Downloads

1,487

Readme

autotel-cloudflare

The #1 OpenTelemetry package for Cloudflare Workers - complete bindings coverage, native CF OTel integration, advanced sampling, zero vendor lock-in.

npm version Bundle Size License: MIT

Features

  • Native Cloudflare OTel integration - Works with wrangler.toml destinations
  • Complete bindings coverage - KV, R2, D1, DO, AI, Vectorize, Hyperdrive, and more
  • Multiple API styles - instrument(), wrapModule(), wrapDurableObject(), functional
  • Advanced sampling - Adaptive tail sampling (10% baseline, 100% errors/slow)
  • Events integration - Product analytics with trace correlation
  • Zero vendor lock-in - OTLP compatible, works with any backend
  • Tree-shakeable - Import only what you need
  • TypeScript native - Full type safety

DX Direction

The package direction is to make Cloudflare observability feel the same across Workers, Queues, Durable Objects, alarms, and Workflows:

  • use Cloudflare-native wrappers to create the root span
  • use the trace(..., (ctx) => ...) factory form for business logic
  • prefer span attributes and one execution snapshot over scattered info logs

See docs/CLOUDFLARE-DX.md for the design target and review rules.

Installation

npm install autotel-cloudflare
# or
pnpm add autotel-cloudflare
# or
yarn add autotel-cloudflare

Quickstart: Structured logging in a Cloudflare Worker

For just structured logs (no tracing setup required), import from autotel-cloudflare/logger:

import { createEdgeLogger } from 'autotel-cloudflare/logger'

const log = createEdgeLogger('my-worker', { level: 'info' })

export default {
  async fetch() {
    log.info({ user_id: 'u1' }, 'request handled')
    return new Response('ok')
  },
}

autotel-cloudflare/logger is the edge-clean entry: it re-exports createEdgeLogger from autotel-edge/logger plus the Cloudflare execution-logger helpers (createWorkersLogger, getRequestLogger, getQueueLogger). The root autotel-cloudflare import pulls in tracing, AsyncLocalStorage, and the wrappers — only use it when you want spans.

Quick Start

1. Configure Cloudflare Native OTel (wrangler.toml)

[observability.traces]
enabled = true
destinations = ["honeycomb"]  # Configure in CF dashboard
head_sampling_rate = 1.0      # Let autotel handle sampling

2. Instrument Your Worker

import { wrapModule, trace } from 'autotel-cloudflare'

// Zero-boilerplate function tracing
const processOrder = trace(async (orderId: string, kv: KVNamespace) => {
  const order = await kv.get(orderId)  // Auto-instrumented!
  return order
})

export default wrapModule(
  {
    service: { name: 'my-worker' },
    instrumentBindings: true,    // Auto-instrument KV, R2, D1, etc.
    sampling: 'adaptive'          // 10% baseline, 100% errors/slow
  },
  {
    async fetch(req, env, ctx) {
      return Response.json(await processOrder('123', env.ORDERS_KV))
    }
  }
)

API Styles

Style 1: wrapModule (Recommended)

Inspired by workers-honeycomb-logger:

import { wrapModule } from 'autotel-cloudflare'

const handler = {
  async fetch(req, env, ctx) {
    return new Response('Hello')
  }
}

export default wrapModule(
  { service: { name: 'my-worker' } },
  handler
)

Style 2: instrument

import { instrument } from 'autotel-cloudflare'

export default instrument(
  {
    async fetch(req, env, ctx) {
      return new Response('Hello')
    }
  },
  { service: { name: 'my-worker' } }
)

Fetch Route Controls

Filter which fetch routes are instrumented and map route patterns to service names:

import { wrapModule } from 'autotel-cloudflare'

export default wrapModule(
  {
    service: { name: 'edge-gateway' },
    handlers: {
      fetch: {
        include: ['/api/**'],
        exclude: ['/api/internal/**', '/health'],
        routes: {
          '/api/auth/**': { service: 'auth-service' },
          '/api/**': { service: 'api-service' },
        },
      },
    },
  },
  {
    async fetch(req) {
      return new Response('ok')
    },
  },
)

Style 3: Functional API (Unique)

Zero-boilerplate function tracing:

import { trace, span } from 'autotel-cloudflare'

// Automatic trace name inference
export const createUser = trace(async (data: UserData) => {
  return await db.insert(data)
})

// Factory pattern for context access
export const processPayment = trace(ctx => async (amount: number) => {
  ctx.setAttribute('amount', amount)

  await span('validate.card', () => validateCard())
  await span('charge.card', () => chargeCard(amount))

  return { success: true }
})

Request Logger Bootstrap

Use createWorkersLogger() for request-scoped snapshots with Cloudflare context pre-filled.

import { wrapModule, createWorkersLogger } from 'autotel-cloudflare'

export default wrapModule(
  { service: { name: 'checkout-worker' } },
  {
    async fetch(request) {
      const log = createWorkersLogger(request, {
        headers: ['x-request-id'],
      })

      log.info('checkout.started')
      log.set({ checkout: { stage: 'validated' } })
      log.emitNow({ status: 200 })

      return new Response('ok')
    },
  },
)

Complete Bindings Coverage

Auto-Instrumented Bindings

All bindings are automatically instrumented when instrumentBindings: true:

// KV
await env.MY_KV.get('key')           // → Span: "KV MY_KV: get"
await env.MY_KV.put('key', 'value')  // → Span: "KV MY_KV: put"

// R2
await env.MY_R2.get('file.txt')      // → Span: "R2 MY_R2: get"
await env.MY_R2.put('file.txt', data) // → Span: "R2 MY_R2: put"

// D1
await env.MY_D1.prepare('SELECT * FROM users').all()  // → Span: "D1 MY_D1: all"

// Durable Objects
await env.MY_DO.get(id).fetch(req)   // → Span: "DO MY_DO: fetch"

// Workers AI
await env.AI.run('@cf/meta/llama', { prompt: '...' })  // → Span: "AI: run"

// Vectorize
await env.VECTOR.query(vector)       // → Span: "Vectorize VECTOR: query"

// Service Bindings
await env.MY_SERVICE.fetch(req)      // → Span: "Service MY_SERVICE: fetch"

// Queue
await env.MY_QUEUE.send({ data })    // → Span: "Queue MY_QUEUE: send"

// Analytics Engine
await env.ANALYTICS.writeDataPoint({ ... })  // → Span: "Analytics: writeDataPoint"

Supported Bindings:

  • ✅ KV (get, put, delete, list, getWithMetadata)
  • ✅ R2 (head, get, put, delete, list, createMultipartUpload)
  • ✅ D1 (prepare, batch, exec, dump)
  • ✅ Durable Objects (fetch, alarm)
  • ✅ Workflows (get, create, getInstance)
  • ✅ Workers AI (run)
  • ✅ Vectorize (insert, query, getByIds, deleteByIds, upsert)
  • ✅ Hyperdrive (all queries)
  • ✅ Service Bindings (fetch)
  • ✅ Queue (send, sendBatch)
  • ✅ Analytics Engine (writeDataPoint)
  • ✅ Email (send, forward)

Sampling Strategies

Adaptive Sampling (Recommended)

import { SamplingPresets } from 'autotel-cloudflare/sampling'

wrapModule(
  {
    service: { name: 'my-worker' },
    sampling: {
      tailSampler: SamplingPresets.production()
      // 10% baseline, 100% errors, 100% slow requests (>1s)
    }
  },
  handler
)

Available Presets

// Development - 100% sampling
sampling: { tailSampler: SamplingPresets.development() }

// Production - 10% baseline, all errors, slow >1s
sampling: { tailSampler: SamplingPresets.production() }

// High traffic - 1% baseline, all errors, slow >1s
sampling: { tailSampler: SamplingPresets.highTraffic() }

// Debugging - errors only
sampling: { tailSampler: SamplingPresets.debugging() }

// Or use shorthand
sampling: 'adaptive'      // Same as SamplingPresets.production()
sampling: 'error-only'    // Same as SamplingPresets.debugging()

Custom Sampling

import { createCustomTailSampler } from 'autotel-cloudflare/sampling'

const customSampler = createCustomTailSampler((trace) => {
  const span = trace.localRootSpan

  // Always sample /api/* endpoints
  if (span.attributes['http.route']?.toString().startsWith('/api/')) {
    return true
  }

  // Sample all errors
  if (span.status.code === SpanStatusCode.ERROR) {
    return true
  }

  // Sample slow requests
  const duration = (span.endTime[0] - span.startTime[0]) / 1_000_000
  if (duration > 1000) {
    return true
  }

  return Math.random() < 0.1  // 10% of everything else
})

Durable Objects

Instrument Durable Object Class

import { wrapDurableObject } from 'autotel-cloudflare'

class Counter implements DurableObject {
  async fetch(request: Request) {
    // Auto-traced with span "Counter: fetch"
    const count = await this.state.storage.get('count') || 0
    await this.state.storage.put('count', count + 1)
    return new Response(String(count + 1))
  }

  async alarm() {
    // Auto-traced with span "Counter: alarm"
    console.log('Alarm triggered')
  }
}

export default wrapDurableObject(
  { service: { name: 'counter-do' } },
  Counter
)

Events Integration

Track product events with automatic trace correlation:

import { publishEvent } from 'autotel-cloudflare/events'

wrapModule(
  {
    service: { name: 'my-worker' },
    // Configure event subscribers
    subscribers: [
      async (event) => {
        // Send to your analytics platform
        await fetch('https://analytics.example.com/events', {
          method: 'POST',
          body: JSON.stringify(event)
        })
      }
    ]
  },
  {
    async fetch(req, env, ctx) {
      // Track user events
      await publishEvent({
        name: 'order.completed',
        userId: '123',
        properties: {
          orderId: 'abc',
          amount: 99.99
        }
        // Automatically includes current trace ID
      })

      return new Response('OK')
    }
  }
)

Configuration

Complete Example

import { wrapModule, SamplingPresets } from 'autotel-cloudflare'

export default wrapModule(
  {
    // Service identification
    service: {
      name: 'my-worker',
      version: '1.0.0',
      namespace: 'production'
    },

    // Auto-instrument bindings
    instrumentBindings: true,

    // Global instrumentations
    instrumentation: {
      instrumentGlobalFetch: true,   // Trace all fetch() calls
      instrumentGlobalCache: true,   // Trace cache API
      disabled: false                // Set true to disable all tracing
    },

    // Sampling strategy
    sampling: {
      tailSampler: SamplingPresets.production()
    },

    // Handler-specific config
    handlers: {
      fetch: {
        postProcess: (span, { request, response }) => {
          // Add custom attributes
          const url = new URL(request.url)
          if (url.pathname.startsWith('/api/')) {
            span.setAttribute('api.endpoint', url.pathname)
          }
        }
      }
    }
  },
  handler
)

Dynamic Configuration

// Configuration can be a function
export default wrapModule(
  (env, trigger) => ({
    service: { name: env.SERVICE_NAME || 'my-worker' },
    exporter: {
      url: env.OTEL_ENDPOINT,
      headers: { 'x-api-key': env.API_KEY }
    },
    sampling: {
      tailSampler: env.ENVIRONMENT === 'production'
        ? SamplingPresets.production()
        : SamplingPresets.development()
    }
  }),
  handler
)

Entry Points (Tree-Shaking)

// Main export (everything — pulls AsyncLocalStorage, needs nodejs_compat)
import { wrapModule, trace, instrument } from 'autotel-cloudflare'

// Tree-shakeable entry points
import { instrumentKV, instrumentR2 } from 'autotel-cloudflare/bindings'
import { instrumentDO } from 'autotel-cloudflare/handlers'
import { SamplingPresets } from 'autotel-cloudflare/sampling'
import { publishEvent } from 'autotel-cloudflare/events'
import { createEdgeLogger } from 'autotel-cloudflare/logger'
import { createTraceCollector } from 'autotel-cloudflare/testing'

Cloudflare compatibility

Workers run on V8 isolates, not Node. Anything that transitively imports node:async_hooks (used by AsyncLocalStorageContextManager for span context) or node:buffer requires compatibility_flags = ["nodejs_compat"] in your wrangler.toml. The /logger subpath stays clear of those.

| Entry | Edge-safe without nodejs_compat? | Notes | | --- | --- | --- | | autotel-cloudflare | No — needs nodejs_compat | Pulls AsyncLocalStorage (span context) and Buffer transitively via autotel-edge root | | autotel-cloudflare/logger | Yes | Edge-clean. createEdgeLogger, createWorkersLogger, getRequestLogger, getQueueLogger, getWorkflowLogger, getActorLogger | | autotel-cloudflare/bindings | No — needs nodejs_compat | Wrappers create spans, require context manager | | autotel-cloudflare/handlers | No — needs nodejs_compat | DO/Workflow wrappers create spans | | autotel-cloudflare/sampling | Yes | Pure samplers, no runtime imports | | autotel-cloudflare/events | No — needs nodejs_compat | Pulls trace context for correlation | | autotel-cloudflare/testing | Yes (test-only) | Collectors live in test runtime; do not ship to Workers | | autotel-cloudflare/actors | No — needs nodejs_compat | Builds on DO wrappers | | autotel-cloudflare/agents | No — needs nodejs_compat | Builds on DO wrappers | | autotel-cloudflare/parse-error | Yes | Pure error normaliser |

Rule of thumb: if you only need logs, import autotel-cloudflare/logger and skip nodejs_compat. If you want spans, add nodejs_compat and import from the root.

See also

  • autotel-edge — vendor-agnostic foundation re-exported by this package
  • autotel — Node.js entry, full SDK with auto-instrumentation
  • autotel-drizzle — Drizzle ORM spans (Node only)

Testing

import { createTraceCollector, assertTraceCreated } from 'autotel-cloudflare/testing'

describe('my worker', () => {
  it('creates traces', async () => {
    const collector = createTraceCollector()

    await myFunction()

    assertTraceCreated(collector, 'myFunction')
  })
})

Examples

See apps/cloudflare-example for a complete working example with:

  • ✅ All bindings instrumented (KV, R2, D1, etc.)
  • ✅ Multiple handler types (fetch, scheduled, queue, email)
  • ✅ Durable Objects
  • ✅ Custom spans and attributes
  • ✅ Error handling
  • ✅ Sampling strategies
  • ✅ Events tracking

License

MIT © Jag Reehal

Links