autotel-cloudflare 2.19.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  ## Features
10
10
 
11
+ - ✅ **Cloudflare native tracing (automatic)** - `trace()`/`span()` nest inside Cloudflare's native trace waterfall when enabled; no duplicate spans, no exporter code
11
12
  - ✅ **Native Cloudflare OTel integration** - Works with `wrangler.toml` destinations
12
13
  - ✅ **Complete bindings coverage** - KV, R2, D1, DO, AI, Vectorize, Hyperdrive, and more
13
14
  - ✅ **Multiple API styles** - `instrument()`, `wrapModule()`, `wrapDurableObject()`, functional
@@ -27,6 +28,46 @@ The package direction is to make Cloudflare observability feel the same across W
27
28
 
28
29
  See [docs/CLOUDFLARE-DX.md](../../docs/CLOUDFLARE-DX.md) for the design target and review rules.
29
30
 
31
+ ## Cloudflare native tracing (automatic)
32
+
33
+ Cloudflare Workers ship [native tracing](https://developers.cloudflare.com/workers/observability/traces/): enable it in Wrangler and Cloudflare instruments fetch/KV/R2/D1/handlers and exports OTLP to any backend — no exporter code. autotel integrates **automatically**: the same `trace()`/`span()`/`enterSpan()` code nests inside Cloudflare's native waterfall when native tracing is on, and falls back to autotel's own OTLP pipeline everywhere else (other edge runtimes, native off, local `wrangler dev`).
34
+
35
+ ```toml
36
+ # wrangler.toml — that's the only setup
37
+ [observability.traces]
38
+ enabled = true
39
+ ```
40
+
41
+ When native tracing is active, autotel **defers to the platform**: it does not proxy-instrument bindings (no duplicate spans) and does not run its own exporter. It routes your custom spans to `tracing.enterSpan()` and surfaces the `cf-ray` id as `ctx.correlationId` + a `correlation.id` span attribute. Controlled by `nativeTracing: 'auto' | 'on' | 'off'` (default `'auto'`).
42
+
43
+ ### Same code, both modes — captured evidence
44
+
45
+ This is real output from [`apps/cloudflare-example`](../../apps/cloudflare-example) (`node scripts/capture-evidence.mjs`). Identical business logic, two runtimes:
46
+
47
+ ```text
48
+ # autotel OTLP pipeline (local dev / non-Workers / nativeTracing:'off')
49
+ • GET /orders
50
+ • order.price order.id=ORD-1 order.total=120
51
+ • order.subtotal order.subtotal=100
52
+ • KV MY_KV: get db.system=cloudflare-kv db.operation=get ← autotel-instrumented
53
+ • order.total order.total=120
54
+ • user.create user.duplicate=true
55
+ • db.checkDuplicate
56
+ • payment.charge [ERROR] exception.message="card declined"
57
+
58
+ # Cloudflare native tracing (autotel → ctx.tracing.enterSpan)
59
+ • order.price [OK] correlation.id=8f1c2d3e…-LHR order.id=ORD-1 order.total=120
60
+ • order.subtotal correlation.id=8f1c2d3e…-LHR order.subtotal=100
61
+ • order.total correlation.id=8f1c2d3e…-LHR order.total=120
62
+ • user.create correlation.id=8f1c2d3e…-LHR user.duplicate=true
63
+ • db.checkDuplicate
64
+ • payment.charge [ERROR] error=true exception.message="card declined" correlation.id=8f1c2d3e…-LHR
65
+ ```
66
+
67
+ Note the native tree has **no `KV MY_KV: get` span** — Cloudflare emits that natively (your custom spans nest above it on deploy), so there are no duplicates.
68
+
69
+ Full details, degradation map, and the forward-compatible trace-id story: [docs/CLOUDFLARE-NATIVE-TRACING.md](../../docs/CLOUDFLARE-NATIVE-TRACING.md).
70
+
30
71
  ## Installation
31
72
 
32
73
  ```bash
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { a as instrumentQueueProducer, c as instrumentAI, d as instrumentKV, f as instrumentR2, i as instrumentAnalyticsEngine, l as instrumentBindings, n as instrumentRateLimiter, o as instrumentHyperdrive, p as instrumentServiceBinding, r as instrumentImages, s as instrumentVectorize, t as instrumentBrowserRendering, u as instrumentD1 } from "./bindings-xEZcXo5r.js";
2
2
  import { n as instrumentDO, t as instrumentWorkflow } from "./handlers-C_VojUeA.js";
3
3
  import { a as createWorkersLogger, c as getRequestLogger, i as WorkersLoggerOptions, l as getWorkflowLogger, n as ExecutionLogger, o as getActorLogger, r as ExecutionLoggerOptions, s as getQueueLogger, t as ExecutionLogSnapshot } from "./execution-logger-tgQmJeeU.js";
4
+ import { n as isNativeTracingAvailable, t as getNativeTracerFromCtx } from "./native-tracing-DSo5mody.js";
4
5
  import { ConfigurationOption } from "autotel-edge";
5
6
  export * from "autotel-edge";
6
7
 
@@ -102,5 +103,5 @@ declare function instrumentGlobalFetch(): void;
102
103
  */
103
104
  declare function instrumentGlobalCache(): void;
104
105
  //#endregion
105
- export { type DefineWorkerFetchOptions, type ExecutionLogSnapshot, type ExecutionLogger, type ExecutionLoggerOptions, type WorkerFetchHandler, type WorkersLoggerOptions, createWorkersLogger, defineWorkerFetch, getActorLogger, getQueueLogger, getRequestLogger, getWorkflowLogger, instrument, instrumentAI, instrumentAnalyticsEngine, instrumentBindings, instrumentBrowserRendering, instrumentD1, instrumentDO, instrumentGlobalCache, instrumentGlobalFetch, instrumentHyperdrive, instrumentImages, instrumentKV, instrumentQueueProducer, instrumentR2, instrumentRateLimiter, instrumentServiceBinding, instrumentVectorize, instrumentWorkflow, wrapDurableObject, wrapModule };
106
+ export { type DefineWorkerFetchOptions, type ExecutionLogSnapshot, type ExecutionLogger, type ExecutionLoggerOptions, type WorkerFetchHandler, type WorkersLoggerOptions, createWorkersLogger, defineWorkerFetch, getActorLogger, getNativeTracerFromCtx, getQueueLogger, getRequestLogger, getWorkflowLogger, instrument, instrumentAI, instrumentAnalyticsEngine, instrumentBindings, instrumentBrowserRendering, instrumentD1, instrumentDO, instrumentGlobalCache, instrumentGlobalFetch, instrumentHyperdrive, instrumentImages, instrumentKV, instrumentQueueProducer, instrumentR2, instrumentRateLimiter, instrumentServiceBinding, instrumentVectorize, instrumentWorkflow, isNativeTracingAvailable, wrapDurableObject, wrapModule };
106
107
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wrappers/instrument.ts","../src/wrappers/wrap-module.ts","../src/wrappers/wrap-do.ts","../src/wrappers/define-worker-fetch.ts","../src/global/fetch.ts","../src/global/cache.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0qB0B;;;;AC3oB1B;iBDwoBgB,UAAA,sBACd,OAAA,EAAS,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA,GAC/B,MAAA,EAAQ,mBAAA,GACP,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA;;;;;;;;;;;iBC3oBT,UAAA,sBACd,MAAA,EAAQ,mBAAA,EACR,OAAA,EAAS,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA,IAC9B,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA;;;;;;;;;;;iBCNT,iBAAA,WAA4B,aAAA,EAC1C,MAAA,EAAQ,mBAAA,EACR,OAAA,OAAc,KAAA,EAAO,kBAAA,EAAoB,GAAA,UAAa,CAAA,QAChD,KAAA,EAAO,kBAAA,EAAoB,GAAA,UAAa,CAAA;;;KCDpC,wBAAA,GAA2B,oBAAoB;AAAA,KAE/C,kBAAA,OACV,OAAA,EAAS,OAAA,EACT,GAAA,EAAK,CAAA,EACL,GAAA,EAAK,gBAAA,EACL,GAAA,EAAK,eAAA,KACF,QAAA,GAAW,OAAA,CAAQ,QAAA;;;;;;iBAOR,iBAAA,cACd,MAAA,EAAQ,mBAAA,EACR,OAAA,EAAS,kBAAA,CAAmB,CAAA,GAC5B,aAAA,GAAe,wBAAA;EACZ,KAAA,GAAQ,OAAA,EAAS,OAAA,EAAS,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,gBAAA,KAAqB,OAAA,CAAQ,QAAA;AAAA;;;;;;;;;;;AHunBzE;;;;;;;;;iBI9mBgB,qBAAA;;;;;;;;;;;AJ8mBhB;;;;;;;;AAAA,iBKzhBgB,qBAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wrappers/instrument.ts","../src/wrappers/wrap-module.ts","../src/wrappers/wrap-do.ts","../src/wrappers/define-worker-fetch.ts","../src/global/fetch.ts","../src/global/cache.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwvB0B;;;;iBAHV,UAAA,sBACd,OAAA,EAAS,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA,GAC/B,MAAA,EAAQ,mBAAA,GACP,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA;;;;;;;;;;;iBCztBT,UAAA,sBACd,MAAA,EAAQ,mBAAA,EACR,OAAA,EAAS,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA,IAC9B,eAAA,CAAgB,CAAA,EAAG,CAAA,EAAG,CAAA;;;;;;;;;;;iBCNT,iBAAA,WAA4B,aAAA,EAC1C,MAAA,EAAQ,mBAAA,EACR,OAAA,OAAc,KAAA,EAAO,kBAAA,EAAoB,GAAA,UAAa,CAAA,QAChD,KAAA,EAAO,kBAAA,EAAoB,GAAA,UAAa,CAAA;;;KCDpC,wBAAA,GAA2B,oBAAoB;AAAA,KAE/C,kBAAA,OACV,OAAA,EAAS,OAAA,EACT,GAAA,EAAK,CAAA,EACL,GAAA,EAAK,gBAAA,EACL,GAAA,EAAK,eAAA,KACF,QAAA,GAAW,OAAA,CAAQ,QAAA;;;;;;iBAOR,iBAAA,cACd,MAAA,EAAQ,mBAAA,EACR,OAAA,EAAS,kBAAA,CAAmB,CAAA,GAC5B,aAAA,GAAe,wBAAA;EACZ,KAAA,GAAQ,OAAA,EAAS,OAAA,EAAS,GAAA,EAAK,CAAA,EAAG,GAAA,EAAK,gBAAA,KAAqB,OAAA,CAAQ,QAAA;AAAA;;;;;;;;;;;;AHqsBzE;;;;;;;;AAAA,iBI5rBgB,qBAAA;;;;;;;;;;;;AJ4rBhB;;;;;;;iBKvmBgB,qBAAA"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { a as getWorkflowLogger, i as getRequestLogger, n as getActorLogger, r as getQueueLogger, t as createWorkersLogger } from "./execution-logger-BxmgP8jF.js";
2
+ import { n as isNativeTracingAvailable, t as getNativeTracerFromCtx } from "./native-tracing-D0EyAtsF.js";
2
3
  import { a as wrap, i as unwrap, n as proxyExecutionContext } from "./common-DiWH6nmG.js";
3
4
  import { a as instrumentKV, c as instrumentImages, d as instrumentHyperdrive, f as instrumentVectorize, i as instrumentD1, l as instrumentAnalyticsEngine, n as instrumentRateLimiter, o as instrumentR2, p as instrumentAI, r as instrumentBindings, s as instrumentServiceBinding, t as instrumentBrowserRendering, u as instrumentQueueProducer } from "./bindings-C10RI6Il.js";
4
5
  import { n as instrumentDO, t as instrumentWorkflow } from "./handlers-CMg9l_T-.js";
5
- import { WorkerTracer, WorkerTracerProvider, createInitialiser, getActiveConfig, getServiceForPath, setConfig, shouldInstrumentPath } from "autotel-edge";
6
+ import { WorkerTracer, WorkerTracerProvider, createInitialiser, ensureGlobalContextManager, getActiveConfig, getServiceForPath, setConfig, shouldInstrumentPath, withNativeTracer } from "autotel-edge";
6
7
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
7
8
  import { resourceFromAttributes } from "@opentelemetry/resources";
8
9
 
@@ -516,6 +517,39 @@ function createHandlerFlow(instrumentation) {
516
517
  });
517
518
  };
518
519
  }
520
+ let warnedMissingNative = false;
521
+ /**
522
+ * Resolve whether this invocation should use Cloudflare's native tracer.
523
+ *
524
+ * - `nativeTracing: 'off'` → never use native (always autotel's OTLP pipeline).
525
+ * - `nativeTracing: 'on'` → require native; warn once and fall back if absent.
526
+ * - `nativeTracing: 'auto'` (default) → use native when `ctx.tracing` exists.
527
+ */
528
+ function resolveNativeTracer(config, ctx, trigger) {
529
+ const mode = config.nativeTracing ?? "auto";
530
+ if (mode === "off") return null;
531
+ const nativeTracer = getNativeTracerFromCtx(ctx, (trigger instanceof Request ? trigger.headers.get("cf-ray") : null) ?? crypto.randomUUID());
532
+ if (!nativeTracer && mode === "on" && !warnedMissingNative) {
533
+ warnedMissingNative = true;
534
+ console.warn("[autotel-cloudflare] nativeTracing is 'on' but Cloudflare native tracing was not detected. Enable observability.traces in your Wrangler config (and use a recent compatibility_date). Falling back to the autotel OTLP exporter.");
535
+ }
536
+ return nativeTracer;
537
+ }
538
+ /**
539
+ * Run a handler in native-tracing mode.
540
+ *
541
+ * Cloudflare already creates the root handler span and instruments platform
542
+ * operations (fetch / KV / R2 / D1 / DO), so autotel defers entirely: no
543
+ * binding proxies (avoids duplicate spans), no provider/exporter, no manual
544
+ * flush. We install the native tracer into the active context so that the
545
+ * user's `span()` / `trace()` / `enterSpan()` calls nest inside Cloudflare's
546
+ * native waterfall and are exported by the platform.
547
+ */
548
+ function runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer) {
549
+ ensureGlobalContextManager();
550
+ const nativeContext = withNativeTracer(nativeTracer, setConfig(config));
551
+ return context.with(nativeContext, () => handlerFn(trigger, env, ctx));
552
+ }
519
553
  /**
520
554
  * Create handler proxy
521
555
  */
@@ -523,6 +557,8 @@ function createHandlerProxy(_handler, handlerFn, initialiser, instrumentation) {
523
557
  return (trigger, env, ctx) => {
524
558
  const config = initialiser(env, trigger);
525
559
  if (config.instrumentation.disabled) return handlerFn(trigger, env, ctx);
560
+ const nativeTracer = resolveNativeTracer(config, ctx, trigger);
561
+ if (nativeTracer) return runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer);
526
562
  const instrumentedEnv = instrumentBindings(env);
527
563
  const configContext = setConfig(config);
528
564
  initProvider(config);
@@ -551,6 +587,8 @@ function createHandlerProxyWithConfig(_handler, handlerFn, initialiser, createIn
551
587
  exclude: fetchCfg.exclude
552
588
  })) return handlerFn(trigger, env, ctx);
553
589
  }
590
+ const nativeTracer = resolveNativeTracer(config, ctx, trigger);
591
+ if (nativeTracer) return runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer);
554
592
  const instrumentedEnv = instrumentBindings(env);
555
593
  const configContext = setConfig(config);
556
594
  initProvider(config);
@@ -698,5 +736,5 @@ function defineWorkerFetch(config, handler, loggerOptions = {}) {
698
736
  }
699
737
 
700
738
  //#endregion
701
- export { createWorkersLogger, defineWorkerFetch, getActorLogger, getQueueLogger, getRequestLogger, getWorkflowLogger, instrument, instrumentAI, instrumentAnalyticsEngine, instrumentBindings, instrumentBrowserRendering, instrumentD1, instrumentDO, instrumentGlobalCache, instrumentGlobalFetch, instrumentHyperdrive, instrumentImages, instrumentKV, instrumentQueueProducer, instrumentR2, instrumentRateLimiter, instrumentServiceBinding, instrumentVectorize, instrumentWorkflow, wrapDurableObject, wrapModule };
739
+ export { createWorkersLogger, defineWorkerFetch, getActorLogger, getNativeTracerFromCtx, getQueueLogger, getRequestLogger, getWorkflowLogger, instrument, instrumentAI, instrumentAnalyticsEngine, instrumentBindings, instrumentBrowserRendering, instrumentD1, instrumentDO, instrumentGlobalCache, instrumentGlobalFetch, instrumentHyperdrive, instrumentImages, instrumentKV, instrumentQueueProducer, instrumentR2, instrumentRateLimiter, instrumentServiceBinding, instrumentVectorize, instrumentWorkflow, isNativeTracingAvailable, wrapDurableObject, wrapModule };
702
740
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["api_context","api_context","context"],"sources":["../src/global/fetch.ts","../src/global/cache.ts","../src/wrappers/instrument.ts","../src/wrappers/wrap-module.ts","../src/wrappers/wrap-do.ts","../src/wrappers/define-worker-fetch.ts"],"sourcesContent":["/**\n * Global fetch() instrumentation for autotel-edge\n *\n * Automatically traces all outgoing fetch() calls with:\n * - HTTP method, URL, status code\n * - Request/response headers\n * - Automatic context propagation\n * - Error tracking\n */\n\nimport {\n trace,\n context as api_context,\n propagation,\n SpanStatusCode,\n SpanKind,\n} from '@opentelemetry/api';\nimport { getActiveConfig, WorkerTracer } from 'autotel-edge';\n\n/**\n * Gather HTTP request attributes following OpenTelemetry semantic conventions\n */\nfunction gatherRequestAttributes(request: Request): Record<string, any> {\n const url = new URL(request.url);\n const config = getActiveConfig();\n const redactQuery = config?.dataSafety?.redactQueryParams === true;\n\n return {\n 'http.request.method': request.method.toUpperCase(),\n 'url.full': redactQuery ? `${url.origin}${url.pathname}` : request.url,\n 'url.scheme': url.protocol.replace(':', ''),\n 'server.address': url.host,\n 'url.path': url.pathname,\n 'url.query': redactQuery ? (url.search ? '[REDACTED]' : '') : url.search,\n 'network.protocol.name': 'http',\n 'user_agent.original': request.headers.get('user-agent') || undefined,\n };\n}\n\n/**\n * Gather HTTP response attributes\n */\nfunction gatherResponseAttributes(response: Response): Record<string, any> {\n return {\n 'http.response.status_code': response.status,\n 'http.response.body.size': response.headers.get('content-length') || undefined,\n };\n}\n\n/**\n * Instrument the global fetch function\n *\n * This wraps globalThis.fetch to automatically create spans for all outgoing HTTP requests.\n *\n * **Note:** This is called automatically when the library is initialized with\n * `instrumentation.instrumentGlobalFetch: true` (default).\n */\nexport function instrumentGlobalFetch(): void {\n const originalFetch = globalThis.fetch;\n\n const instrumentedFetch = function fetch(\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> {\n const request = new Request(input, init);\n\n // Skip non-HTTP requests\n if (!request.url.startsWith('http')) {\n return originalFetch(input, init);\n }\n\n // Skip if no active config (not initialized yet)\n const config = getActiveConfig();\n if (!config) {\n return originalFetch(input, init);\n }\n\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n const url = new URL(request.url);\n const spanName = `${request.method} ${url.host}`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.CLIENT,\n attributes: gatherRequestAttributes(request),\n },\n async (span) => {\n try {\n // Inject trace context into request headers for distributed tracing\n const shouldIncludeContext =\n typeof config.fetch?.includeTraceContext === 'function'\n ? config.fetch.includeTraceContext(request)\n : (config.fetch?.includeTraceContext ?? true);\n\n if (shouldIncludeContext) {\n propagation.inject(api_context.active(), request.headers, {\n set: (headers, key, value) => {\n if (typeof value === 'string') {\n headers.set(key, value);\n }\n },\n });\n }\n\n // Make the actual fetch call\n const response = await originalFetch(request);\n\n // Add response attributes\n span.setAttributes(gatherResponseAttributes(response));\n\n // Set span status based on response\n if (response.ok) {\n span.setStatus({ code: SpanStatusCode.OK });\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n\n // Replace global fetch\n globalThis.fetch = instrumentedFetch as typeof fetch;\n}\n","/**\n * Global Cache API instrumentation for Cloudflare Workers\n *\n * Automatically traces cache operations:\n * - cache.match() - Read from cache\n * - cache.put() - Write to cache\n * - cache.delete() - Delete from cache\n */\n\nimport { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';\nimport { wrap } from '../bindings/common';\nimport { WorkerTracer } from 'autotel-edge';\n\ntype CacheOperation = 'match' | 'put' | 'delete';\n\n/**\n * Sanitize URL for span attributes (remove query params that might contain sensitive data)\n */\nfunction sanitizeURL(url: string): string {\n const u = new URL(url);\n return `${u.protocol}//${u.host}${u.pathname}`;\n}\n\n/**\n * Instrument a cache method (match, put, delete)\n */\nfunction instrumentCacheMethod<T extends Function>(\n fn: T,\n cacheName: string,\n operation: CacheOperation,\n): T {\n const handler: ProxyHandler<T> = {\n async apply(target, thisArg, argArray) {\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n // Extract URL from first argument (Request or string)\n const firstArg = argArray[0];\n const url =\n firstArg instanceof Request\n ? firstArg.url\n : typeof firstArg === 'string'\n ? firstArg\n : undefined;\n\n const spanName = `Cache ${cacheName}.${operation}`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n 'cache.name': cacheName,\n 'cache.operation': operation,\n 'cache.key': url ? sanitizeURL(url) : undefined,\n },\n },\n async (span) => {\n try {\n const result = await Reflect.apply(target, thisArg, argArray);\n\n // For match operations, record whether it was a hit or miss\n if (operation === 'match') {\n span.setAttribute('cache.hit', !!result);\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n },\n };\n\n return wrap(fn, handler);\n}\n\n/**\n * Instrument a Cache instance\n */\nfunction instrumentCache(cache: Cache, cacheName: string): Cache {\n const handler: ProxyHandler<Cache> = {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n\n // Instrument the cache operation methods\n if (\n (prop === 'match' || prop === 'put' || prop === 'delete') &&\n typeof value === 'function'\n ) {\n return instrumentCacheMethod(\n value.bind(target),\n cacheName,\n prop as CacheOperation,\n );\n }\n\n // Bind other methods to preserve `this` context\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n };\n\n return wrap(cache, handler);\n}\n\n/**\n * Instrument caches.open()\n */\nfunction instrumentCachesOpen(\n openFn: CacheStorage['open'],\n): CacheStorage['open'] {\n const handler: ProxyHandler<CacheStorage['open']> = {\n async apply(target, thisArg, argArray) {\n const cacheName = argArray[0];\n const cache = await Reflect.apply(target, thisArg, argArray);\n return instrumentCache(cache, cacheName);\n },\n };\n\n return wrap(openFn, handler);\n}\n\n/**\n * Instrument the global caches API\n *\n * This wraps globalThis.caches to automatically create spans for all cache operations.\n *\n * **Note:** This is called automatically when the library is initialized with\n * `instrumentation.instrumentGlobalCache: true` (default).\n */\nexport function instrumentGlobalCache(): void {\n const handler: ProxyHandler<typeof caches> = {\n get(target, prop) {\n if (prop === 'default') {\n // Wrap the default cache\n return instrumentCache(target.default, 'default');\n } else if (prop === 'open') {\n // Wrap the open method\n const openFn = Reflect.get(target, prop);\n if (typeof openFn === 'function') {\n return instrumentCachesOpen(openFn.bind(target));\n }\n }\n\n return Reflect.get(target, prop);\n },\n };\n\n // Replace global caches\n // @ts-ignore - TypeScript doesn't like reassigning globalThis.caches\n globalThis.caches = wrap(caches, handler);\n}\n","/**\n * Handler instrumentation for Cloudflare Workers\n * \n * Note: This file uses Cloudflare Workers types (ExportedHandler, Request, Response, etc.)\n * which are globally available via @cloudflare/workers-types when listed in tsconfig.json.\n * These types are devDependencies only - they're not runtime dependencies.\n * At runtime, Cloudflare Workers runtime provides the actual implementations.\n * \n * Provides automatic OpenTelemetry tracing for:\n * - HTTP handlers (fetch)\n * - Scheduled/cron handlers\n * - Queue handlers (with message tracking)\n * - Email handlers\n * - Auto-instrumentation of Cloudflare bindings (KV, R2, D1, Service Bindings)\n * - Global fetch and cache instrumentation\n * - Post-processor support for span customization\n * - Tail sampling support\n * - Cold start tracking\n */\n\nimport {\n trace,\n context as api_context,\n propagation,\n SpanStatusCode,\n SpanKind,\n} from '@opentelemetry/api';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport type {\n ConfigurationOption,\n ResolvedEdgeConfig,\n Trigger,\n HandlerInstrumentation,\n InitialSpanInfo,\n ReadableSpan,\n RouteServiceConfig,\n} from 'autotel-edge';\nimport {\n createInitialiser,\n setConfig,\n getActiveConfig,\n getServiceForPath,\n shouldInstrumentPath,\n type Initialiser,\n WorkerTracerProvider,\n WorkerTracer,\n} from 'autotel-edge';\nimport { proxyExecutionContext, unwrap, wrap, type PromiseTracker } from '../bindings/common';\nimport { instrumentGlobalFetch } from '../global/fetch';\nimport { instrumentGlobalCache } from '../global/cache';\nimport { instrumentBindings } from '../bindings/bindings';\nimport type { Attributes, Span, TextMapGetter } from '@opentelemetry/api';\n\n// Web API Headers objects require .get() — property access via defaultTextMapGetter won't work\nconst headersGetter: TextMapGetter<Headers> = {\n get: (carrier, key) => carrier.get(key) ?? undefined,\n keys: (carrier) => [...carrier.keys()],\n};\n\ntype FetchHandler = (\n request: Request,\n env: any,\n ctx: ExecutionContext,\n) => Response | Promise<Response>;\n\ntype ScheduledHandler = (\n event: ScheduledController,\n env: any,\n ctx: ExecutionContext,\n) => void | Promise<void>;\n\ntype QueueHandler = (\n batch: MessageBatch,\n env: any,\n ctx: ExecutionContext,\n) => void | Promise<void>;\n\ntype EmailHandler = (\n message: ForwardableEmailMessage,\n env: any,\n ctx: ExecutionContext,\n) => void | Promise<void>;\n\n/**\n * Create fetch handler instrumentation with config support for postProcess\n */\n/**\n * Extract Cloudflare-specific attributes from a request\n */\nfunction extractCfAttributes(request: Request): Record<string, string | number | boolean> {\n const cf = (request as any).cf;\n if (!cf) return {};\n\n const attrs: Record<string, string | number | boolean> = {};\n const set = (key: string, value: unknown) => {\n if (value !== undefined && value !== null) {\n attrs[key] = value as string | number | boolean;\n }\n };\n\n set('cloudflare.colo', cf.colo);\n const ray = request.headers.get('cf-ray');\n if (ray) attrs['cloudflare.ray_id'] = ray;\n set('cloudflare.country', cf.country);\n set('cloudflare.city', cf.city);\n set('cloudflare.region', cf.region);\n set('cloudflare.continent', cf.continent);\n set('cloudflare.timezone', cf.timezone);\n set('cloudflare.latitude', cf.latitude);\n set('cloudflare.longitude', cf.longitude);\n set('cloudflare.asn', cf.asn);\n set('cloudflare.as_organization', cf.asOrganization);\n set('cloudflare.http_protocol', cf.httpProtocol);\n set('cloudflare.tls_version', cf.tlsVersion);\n set('cloudflare.client_tcp_rtt', cf.clientTcpRtt);\n\n return attrs;\n}\n\nfunction createFetchInstrumentation(\n config: ResolvedEdgeConfig,\n): HandlerInstrumentation<Request, Response> {\n return {\n getInitialSpanInfo: (request: Request): InitialSpanInfo => {\n const url = new URL(request.url);\n const routeService = getServiceForPath(\n url.pathname,\n config.handlers.fetch.routes as Record<string, RouteServiceConfig> | undefined,\n );\n\n const cfAttrs = (config as any).extractCfAttributes === false\n ? {}\n : extractCfAttributes(request);\n\n return {\n name: `${request.method} ${url.pathname}`,\n options: {\n kind: SpanKind.SERVER,\n attributes: {\n 'http.request.method': request.method,\n 'url.full': request.url,\n ...(routeService ? { 'service.name': routeService, 'autotel.route.service': routeService } : {}),\n ...cfAttrs,\n },\n },\n context: propagation.extract(api_context.active(), request.headers, headersGetter),\n };\n },\n getAttributesFromResult: (response: Response) => ({\n 'http.response.status_code': response.status,\n }),\n executionSucces: (span: Span, trigger: Request, result: Response) => {\n // Override span status for server errors (5xx)\n if (result.status >= 500) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n\n // Call postProcess callback if configured\n if (config.handlers.fetch.postProcess) {\n const readableSpan = span as unknown as ReadableSpan;\n config.handlers.fetch.postProcess(span, {\n request: trigger,\n response: result,\n readable: readableSpan,\n });\n }\n },\n };\n}\n\n/**\n * Scheduled handler instrumentation\n */\nconst scheduledInstrumentation: HandlerInstrumentation<ScheduledController, void> = {\n getInitialSpanInfo: (event: ScheduledController): InitialSpanInfo => {\n return {\n name: `scheduledHandler ${event.cron || 'unknown'}`,\n options: {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'faas.trigger': 'timer',\n 'faas.cron': event.cron || 'unknown',\n 'faas.scheduled_time': new Date(event.scheduledTime).toISOString(),\n },\n },\n };\n },\n};\n\n/**\n * Tracks message status counts for queue processing\n */\nclass MessageStatusCount {\n succeeded = 0;\n failed = 0;\n implicitly_acked = 0;\n implicitly_retried = 0;\n readonly total: number;\n\n constructor(total: number) {\n this.total = total;\n }\n\n ack() {\n this.succeeded = this.succeeded + 1;\n }\n\n ackRemaining() {\n this.implicitly_acked = this.total - this.succeeded - this.failed;\n this.succeeded = this.total - this.failed;\n }\n\n retry() {\n this.failed = this.failed + 1;\n }\n\n retryRemaining() {\n this.implicitly_retried = this.total - this.succeeded - this.failed;\n this.failed = this.total - this.succeeded;\n }\n\n toAttributes(): Attributes {\n return {\n 'queue.messages_count': this.total,\n 'queue.messages_success': this.succeeded,\n 'queue.messages_failed': this.failed,\n 'queue.batch_success': this.succeeded === this.total,\n 'queue.implicitly_acked': this.implicitly_acked,\n 'queue.implicitly_retried': this.implicitly_retried,\n };\n }\n}\n\n/**\n * Add event to active span\n */\nfunction addQueueEvent(name: string, msg?: Message, delaySeconds?: number) {\n const attrs: Attributes = {};\n if (msg) {\n attrs['queue.message_id'] = msg.id;\n attrs['queue.message_timestamp'] = msg.timestamp.toISOString();\n // Add attempts if available (from Cloudflare Queues API)\n if ('attempts' in msg && typeof msg.attempts === 'number') {\n attrs['queue.message_attempts'] = msg.attempts;\n }\n }\n if (delaySeconds !== undefined) {\n attrs['queue.retry_delay_seconds'] = delaySeconds;\n }\n trace.getActiveSpan()?.addEvent(name, attrs);\n}\n\n/**\n * Proxy a queue message to track ack/retry operations\n */\nfunction proxyQueueMessage<Q>(msg: Message<Q>, count: MessageStatusCount): Message<Q> {\n const msgHandler: ProxyHandler<Message<Q>> = {\n get: (target, prop) => {\n if (prop === 'ack') {\n const ackFn = Reflect.get(target, prop);\n return new Proxy(ackFn, {\n apply: (fnTarget) => {\n addQueueEvent('messageAck', msg);\n count.ack();\n Reflect.apply(fnTarget, msg, []);\n },\n });\n } else if (prop === 'retry') {\n const retryFn = Reflect.get(target, prop);\n return new Proxy(retryFn, {\n apply: (fnTarget, _thisArg, args) => {\n // Extract delay and content type from retry options if provided\n const retryOptions = args[0] as\n | { delaySeconds?: number; contentType?: string }\n | undefined;\n const delaySeconds = retryOptions?.delaySeconds;\n\n addQueueEvent('messageRetry', msg, delaySeconds);\n\n // Add content type attribute if provided\n if (retryOptions?.contentType) {\n const span = trace.getActiveSpan();\n if (span) {\n span.setAttribute('queue.message.content_type', retryOptions.contentType);\n }\n }\n\n count.retry();\n const result = Reflect.apply(fnTarget, msg, args);\n return result;\n },\n });\n } else {\n return Reflect.get(target, prop, msg);\n }\n },\n };\n return wrap(msg, msgHandler);\n}\n\n/**\n * Proxy MessageBatch to track ackAll/retryAll operations\n */\nfunction proxyMessageBatch(batch: MessageBatch, count: MessageStatusCount): MessageBatch {\n const batchHandler: ProxyHandler<MessageBatch> = {\n get: (target, prop) => {\n if (prop === 'messages') {\n const messages = Reflect.get(target, prop);\n const messagesHandler: ProxyHandler<MessageBatch['messages']> = {\n get: (target, prop) => {\n if (typeof prop === 'string' && !isNaN(parseInt(prop))) {\n const message = Reflect.get(target, prop);\n return proxyQueueMessage(message, count);\n } else {\n return Reflect.get(target, prop);\n }\n },\n };\n return wrap(messages, messagesHandler);\n } else if (prop === 'ackAll') {\n const ackFn = Reflect.get(target, prop);\n return new Proxy(ackFn, {\n apply: (fnTarget) => {\n addQueueEvent('ackAll');\n count.ackRemaining();\n Reflect.apply(fnTarget, batch, []);\n },\n });\n } else if (prop === 'retryAll') {\n const retryFn = Reflect.get(target, prop);\n return new Proxy(retryFn, {\n apply: (fnTarget, _thisArg, args) => {\n // Extract delay from retryAll options if provided\n const retryOptions = args[0] as { delaySeconds?: number } | undefined;\n const delaySeconds = retryOptions?.delaySeconds;\n\n addQueueEvent('retryAll', undefined, delaySeconds);\n count.retryRemaining();\n Reflect.apply(fnTarget, batch, args);\n },\n });\n }\n return Reflect.get(target, prop);\n },\n };\n return wrap(batch, batchHandler);\n}\n\n/**\n * Queue handler instrumentation with message tracking\n */\nclass QueueInstrumentation implements HandlerInstrumentation<MessageBatch, void> {\n private count?: MessageStatusCount;\n\n getInitialSpanInfo(batch: MessageBatch): InitialSpanInfo {\n return {\n name: `queueHandler ${batch.queue || 'unknown'}`,\n options: {\n kind: SpanKind.CONSUMER,\n attributes: {\n 'faas.trigger': 'pubsub',\n 'queue.name': batch.queue || 'unknown',\n },\n },\n };\n }\n\n instrumentTrigger(batch: MessageBatch): MessageBatch {\n this.count = new MessageStatusCount(batch.messages.length);\n return proxyMessageBatch(batch, this.count);\n }\n\n executionSucces(span: Span, _trigger: MessageBatch, _result: void) {\n if (this.count) {\n this.count.ackRemaining();\n span.setAttributes(this.count.toAttributes());\n }\n }\n\n executionFailed(span: Span, _trigger: MessageBatch, _error?: any) {\n if (this.count) {\n this.count.retryRemaining();\n span.setAttributes(this.count.toAttributes());\n }\n }\n}\n\n/**\n * Converts email headers into OpenTelemetry attributes.\n * When dataSafety.emailHeaderAllowlist is configured, only allowed headers are captured.\n */\nfunction headerAttributes(message: { headers: Headers }): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (message.headers instanceof Headers) {\n const config = getActiveConfig();\n const allowlist: string[] | undefined = config?.dataSafety?.emailHeaderAllowlist;\n for (const [key, value] of message.headers.entries()) {\n if (allowlist && !allowlist.includes(key.toLowerCase())) {\n continue;\n }\n attrs[`email.header.${key}`] = value;\n }\n }\n return attrs;\n}\n\n/**\n * Email handler instrumentation\n */\nconst emailInstrumentation: HandlerInstrumentation<ForwardableEmailMessage, void> = {\n getInitialSpanInfo: (message: ForwardableEmailMessage): InitialSpanInfo => {\n const attributes: Record<string, string> = {\n 'faas.trigger': 'other',\n 'messaging.destination.name': message.to || 'unknown',\n };\n\n // Add message ID if available\n if ('headers' in message && message.headers instanceof Headers) {\n const messageId = message.headers.get('Message-Id');\n if (messageId) {\n attributes['rpc.message.id'] = messageId;\n }\n // Add all headers as attributes\n Object.assign(attributes, headerAttributes(message));\n }\n\n return {\n name: `emailHandler ${message.to || 'unknown'}`,\n options: {\n kind: SpanKind.CONSUMER,\n attributes,\n },\n };\n },\n};\n\n\n/**\n * Export spans after request completes\n */\nasync function exportSpans(\n traceId: string,\n tracker: PromiseTracker | undefined,\n ctx: ExecutionContext,\n) {\n const tracer = trace.getTracer('autotel-edge');\n if (tracer instanceof WorkerTracer) {\n try {\n // scheduler is available on ExecutionContext at runtime\n const ctxWithScheduler = ctx as ExecutionContext & { scheduler?: { wait(ms: number): Promise<void> } };\n if (ctxWithScheduler.scheduler) {\n await ctxWithScheduler.scheduler.wait(1);\n }\n await tracker?.wait();\n await tracer.forceFlush(traceId);\n } catch (error) {\n // Silently handle exporter errors to prevent worker crashes\n // Exporter failures should not affect the worker's ability to process requests\n // In production, consider logging to a monitoring service\n console.error('[autotel-edge] Failed to export spans:', error);\n }\n }\n}\n\n/**\n * Create handler flow with instrumentation\n */\nfunction createHandlerFlow<T extends Trigger, E, R>(\n instrumentation: HandlerInstrumentation<T, R>,\n) {\n return (\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n [trigger, env, context]: [T, E, ExecutionContext],\n ) => {\n const { ctx: proxiedCtx, tracker } = proxyExecutionContext(context);\n\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n const { name, options, context: spanContext } =\n instrumentation.getInitialSpanInfo(trigger);\n\n // Add cold start tracking\n if (options.attributes) {\n options.attributes['faas.coldstart'] = coldStart;\n } else {\n options.attributes = { 'faas.coldstart': coldStart };\n }\n coldStart = false;\n\n const parentContext = spanContext || api_context.active();\n\n // Instrument trigger if supported (e.g., for queue handler)\n const instrumentedTrigger = instrumentation.instrumentTrigger\n ? instrumentation.instrumentTrigger(trigger)\n : trigger;\n\n return tracer.startActiveSpan(name, options, parentContext, async (span) => {\n try {\n const result = await handlerFn(instrumentedTrigger, env, proxiedCtx);\n\n if (instrumentation.getAttributesFromResult) {\n const attributes = instrumentation.getAttributesFromResult(result);\n span.setAttributes(attributes);\n }\n\n // Set default OK status; executionSucces may override (e.g. HTTP 5xx)\n span.setStatus({ code: SpanStatusCode.OK });\n\n if (instrumentation.executionSucces) {\n instrumentation.executionSucces(span, trigger, result);\n }\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n if (instrumentation.executionFailed) {\n instrumentation.executionFailed(span, trigger, error);\n }\n throw error;\n } finally {\n span.end();\n context.waitUntil(exportSpans(span.spanContext().traceId, tracker, context));\n }\n });\n };\n}\n\n/**\n * Create handler proxy\n */\nfunction createHandlerProxy<T extends Trigger, E, R>(\n _handler: unknown,\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n initialiser: Initialiser,\n instrumentation: HandlerInstrumentation<T, R>,\n): (trigger: T, env: E, ctx: ExecutionContext) => ReturnType<typeof handlerFn> {\n return (trigger: T, env: E, ctx: ExecutionContext) => {\n const config = initialiser(env, trigger);\n \n // Check if instrumentation is disabled (useful for local dev)\n if (config.instrumentation.disabled) {\n // Return handler as-is without instrumentation\n return handlerFn(trigger, env, ctx);\n }\n \n // Auto-instrument Cloudflare bindings in the environment\n const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;\n \n const configContext = setConfig(config);\n\n // Initialize provider on first call\n initProvider(config);\n\n const flowFn = createHandlerFlow<T, E, R>(instrumentation);\n\n // Execute the handler flow within the config context\n return api_context.with(configContext, () => {\n return flowFn(handlerFn, [trigger, instrumentedEnv, ctx]) as ReturnType<typeof handlerFn>;\n });\n };\n}\n\n/**\n * Create handler proxy with dynamic instrumentation (for fetch with postProcess)\n */\nfunction createHandlerProxyWithConfig<T extends Trigger, E, R>(\n _handler: unknown,\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n initialiser: Initialiser,\n createInstrumentation: (config: ResolvedEdgeConfig) => HandlerInstrumentation<T, R>,\n): (trigger: T, env: E, ctx: ExecutionContext) => ReturnType<typeof handlerFn> {\n return (trigger: T, env: E, ctx: ExecutionContext) => {\n const config = initialiser(env, trigger);\n \n // Check if instrumentation is disabled (useful for local dev)\n if (config.instrumentation.disabled) {\n // Return handler as-is without instrumentation\n return handlerFn(trigger, env, ctx);\n }\n\n if (trigger instanceof Request) {\n const pathname = new URL(trigger.url).pathname;\n const fetchCfg = config.handlers.fetch;\n if (\n !shouldInstrumentPath(pathname, {\n include: fetchCfg.include,\n exclude: fetchCfg.exclude,\n })\n ) {\n return handlerFn(trigger, env, ctx) as ReturnType<typeof handlerFn>;\n }\n }\n \n // Auto-instrument Cloudflare bindings in the environment\n const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;\n \n const configContext = setConfig(config);\n\n // Initialize provider on first call\n initProvider(config);\n\n // Create instrumentation with config\n const instrumentation = createInstrumentation(config);\n const flowFn = createHandlerFlow<T, E, R>(instrumentation);\n\n // Execute the handler flow within the config context\n return api_context.with(configContext, () => {\n return flowFn(handlerFn, [trigger, instrumentedEnv, ctx]) as ReturnType<typeof handlerFn>;\n });\n };\n}\n\nlet providerInitialized = false;\nlet coldStart = true;\n\n/**\n * Initialize the tracer provider\n */\nfunction initProvider(config: ResolvedEdgeConfig): void {\n if (providerInitialized) return;\n\n // Install global instrumentations\n if (config.instrumentation.instrumentGlobalFetch) {\n instrumentGlobalFetch();\n }\n if (config.instrumentation.instrumentGlobalCache) {\n instrumentGlobalCache();\n }\n\n // Set up propagator\n propagation.setGlobalPropagator(config.propagator);\n\n // Create resource\n const resource = resourceFromAttributes({\n 'service.name': config.service.name,\n 'service.version': config.service.version,\n 'service.namespace': config.service.namespace,\n 'cloud.provider': 'cloudflare',\n 'cloud.platform': 'cloudflare.workers',\n 'telemetry.sdk.name': 'autotel-edge',\n 'telemetry.sdk.language': 'js',\n });\n\n // Create and register provider\n const provider = new WorkerTracerProvider(config.spanProcessors, resource);\n provider.register();\n\n // Set head sampler on tracer\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n tracer.setHeadSampler(config.sampling.headSampler);\n\n providerInitialized = true;\n}\n\n/**\n * Instrument a Cloudflare Workers handler\n *\n * @example\n * ```typescript\n * import { instrument } from 'autotel-edge'\n *\n * const handler = {\n * async fetch(request, env, ctx) {\n * return new Response('Hello World')\n * }\n * }\n *\n * export default instrument(handler, {\n * exporter: {\n * url: env.OTLP_ENDPOINT,\n * headers: { 'x-api-key': env.API_KEY }\n * },\n * service: { name: 'my-worker' }\n * })\n * ```\n */\nexport function instrument<E, Q = any, C = any>(\n handler: ExportedHandler<E, Q, C>,\n config: ConfigurationOption,\n): ExportedHandler<E, Q, C> {\n const initialiser = createInitialiser(config);\n\n if (handler.fetch) {\n const fetcher = unwrap(handler.fetch) as FetchHandler;\n // Create fetch instrumentation with config support\n handler.fetch = createHandlerProxyWithConfig(\n handler,\n fetcher,\n initialiser,\n createFetchInstrumentation,\n );\n }\n\n if (handler.scheduled) {\n const scheduled = unwrap(handler.scheduled) as ScheduledHandler;\n handler.scheduled = createHandlerProxy(\n handler,\n scheduled,\n initialiser,\n scheduledInstrumentation,\n );\n }\n\n if (handler.queue) {\n const queue = unwrap(handler.queue) as QueueHandler;\n handler.queue = createHandlerProxy(\n handler,\n queue,\n initialiser,\n new QueueInstrumentation(),\n );\n }\n\n if (handler.email) {\n const email = unwrap(handler.email) as EmailHandler;\n handler.email = createHandlerProxy(\n handler,\n email,\n initialiser,\n emailInstrumentation,\n );\n }\n\n return handler;\n}\n","/**\n * workers-honeycomb-logger style wrapper API\n *\n * @example\n * ```typescript\n * import { wrapModule } from 'autotel-cloudflare'\n *\n * const handler = {\n * async fetch(req, env, ctx) {\n * return new Response('Hello')\n * }\n * }\n *\n * export default wrapModule(\n * { service: { name: 'my-worker' } },\n * handler\n * )\n * ```\n */\n\nimport { instrument } from './instrument';\nimport type { ConfigurationOption } from 'autotel-edge';\n\n/**\n * Wrap a Cloudflare Workers module-style handler\n * Alternative API style inspired by workers-honeycomb-logger\n *\n * @param config Configuration (can be static object or function)\n * @param handler The worker handler to wrap\n * @returns Instrumented handler\n */\nexport function wrapModule<E, Q = any, C = any>(\n config: ConfigurationOption,\n handler: ExportedHandler<E, Q, C>,\n): ExportedHandler<E, Q, C> {\n return instrument(handler, config);\n}\n","/**\n * Durable Object wrapper\n *\n * @example\n * ```typescript\n * import { wrapDurableObject } from 'autotel-cloudflare'\n *\n * class Counter implements DurableObject {\n * async fetch(request: Request) {\n * return new Response('count')\n * }\n * }\n *\n * export default wrapDurableObject({ service: { name: 'counter-do' } }, Counter)\n * ```\n */\n\nimport { instrumentDO } from '../handlers/durable-objects';\nimport type { ConfigurationOption } from 'autotel-edge';\n\n/**\n * Wrap a Durable Object class with instrumentation\n * Alternative API style inspired by workers-honeycomb-logger\n *\n * @param config Configuration (can be static object or function)\n * @param doClass The Durable Object class to wrap\n * @returns Instrumented Durable Object class\n */\nexport function wrapDurableObject<T extends DurableObject>(\n config: ConfigurationOption,\n doClass: new (state: DurableObjectState, env: any) => T,\n): new (state: DurableObjectState, env: any) => T {\n return instrumentDO(doClass, config);\n}\n","/**\n * Ergonomic wrapper for Cloudflare Workers `fetch` handlers.\n *\n * `defineWorkerFetch` instruments the handler the same way `instrument()` does\n * (so span exports already flush via `ctx.waitUntil`), and additionally injects\n * a request-scoped `ExecutionLogger` as the fourth argument — pre-populated\n * with method, path, cf-ray and `request.cf` context.\n *\n * Use this instead of `wrapModule` / `instrument` when your handler wants the\n * logger handed in directly rather than reaching for `getRequestLogger()`.\n *\n * @example\n * ```ts\n * import { defineWorkerFetch } from 'autotel-cloudflare'\n *\n * export default defineWorkerFetch(\n * { service: { name: 'my-worker' } },\n * async (request, env, ctx, log) => {\n * log.set({ route: '/health' })\n * log.emitNow({ status: 200 })\n * return new Response('ok')\n * },\n * )\n * ```\n */\nimport type { ConfigurationOption } from 'autotel-edge';\nimport { instrument } from './instrument';\nimport { createWorkersLogger, type WorkersLoggerOptions } from '../execution-logger';\nimport type { ExecutionLogger } from '../execution-logger';\n\nexport type DefineWorkerFetchOptions = WorkersLoggerOptions;\n\nexport type WorkerFetchHandler<E> = (\n request: Request,\n env: E,\n ctx: ExecutionContext,\n log: ExecutionLogger,\n) => Response | Promise<Response>;\n\n/**\n * Wrap a Workers fetch handler so:\n * - the handler is instrumented (spans, propagation, waitUntil export flush)\n * - the handler receives a request-scoped logger as its fourth argument\n */\nexport function defineWorkerFetch<E = unknown>(\n config: ConfigurationOption,\n handler: WorkerFetchHandler<E>,\n loggerOptions: DefineWorkerFetchOptions = {},\n): { fetch: (request: Request, env: E, ctx: ExecutionContext) => Promise<Response> } {\n const wrapped = instrument(\n {\n fetch(request: Request, env: E, ctx: ExecutionContext) {\n const log = createWorkersLogger(request, loggerOptions);\n return handler(request, env, ctx, log);\n },\n },\n config,\n );\n\n return {\n fetch(request, env, ctx) {\n return Promise.resolve(\n (wrapped.fetch as unknown as (\n req: Request,\n env: E,\n ctx: ExecutionContext,\n ) => Response | Promise<Response>)(request, env, ctx),\n );\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["api_context","api_context","context"],"sources":["../src/global/fetch.ts","../src/global/cache.ts","../src/wrappers/instrument.ts","../src/wrappers/wrap-module.ts","../src/wrappers/wrap-do.ts","../src/wrappers/define-worker-fetch.ts"],"sourcesContent":["/**\n * Global fetch() instrumentation for autotel-edge\n *\n * Automatically traces all outgoing fetch() calls with:\n * - HTTP method, URL, status code\n * - Request/response headers\n * - Automatic context propagation\n * - Error tracking\n */\n\nimport {\n trace,\n context as api_context,\n propagation,\n SpanStatusCode,\n SpanKind,\n} from '@opentelemetry/api';\nimport { getActiveConfig, WorkerTracer } from 'autotel-edge';\n\n/**\n * Gather HTTP request attributes following OpenTelemetry semantic conventions\n */\nfunction gatherRequestAttributes(request: Request): Record<string, any> {\n const url = new URL(request.url);\n const config = getActiveConfig();\n const redactQuery = config?.dataSafety?.redactQueryParams === true;\n\n return {\n 'http.request.method': request.method.toUpperCase(),\n 'url.full': redactQuery ? `${url.origin}${url.pathname}` : request.url,\n 'url.scheme': url.protocol.replace(':', ''),\n 'server.address': url.host,\n 'url.path': url.pathname,\n 'url.query': redactQuery ? (url.search ? '[REDACTED]' : '') : url.search,\n 'network.protocol.name': 'http',\n 'user_agent.original': request.headers.get('user-agent') || undefined,\n };\n}\n\n/**\n * Gather HTTP response attributes\n */\nfunction gatherResponseAttributes(response: Response): Record<string, any> {\n return {\n 'http.response.status_code': response.status,\n 'http.response.body.size': response.headers.get('content-length') || undefined,\n };\n}\n\n/**\n * Instrument the global fetch function\n *\n * This wraps globalThis.fetch to automatically create spans for all outgoing HTTP requests.\n *\n * **Note:** This is called automatically when the library is initialized with\n * `instrumentation.instrumentGlobalFetch: true` (default).\n */\nexport function instrumentGlobalFetch(): void {\n const originalFetch = globalThis.fetch;\n\n const instrumentedFetch = function fetch(\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> {\n const request = new Request(input, init);\n\n // Skip non-HTTP requests\n if (!request.url.startsWith('http')) {\n return originalFetch(input, init);\n }\n\n // Skip if no active config (not initialized yet)\n const config = getActiveConfig();\n if (!config) {\n return originalFetch(input, init);\n }\n\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n const url = new URL(request.url);\n const spanName = `${request.method} ${url.host}`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.CLIENT,\n attributes: gatherRequestAttributes(request),\n },\n async (span) => {\n try {\n // Inject trace context into request headers for distributed tracing\n const shouldIncludeContext =\n typeof config.fetch?.includeTraceContext === 'function'\n ? config.fetch.includeTraceContext(request)\n : (config.fetch?.includeTraceContext ?? true);\n\n if (shouldIncludeContext) {\n propagation.inject(api_context.active(), request.headers, {\n set: (headers, key, value) => {\n if (typeof value === 'string') {\n headers.set(key, value);\n }\n },\n });\n }\n\n // Make the actual fetch call\n const response = await originalFetch(request);\n\n // Add response attributes\n span.setAttributes(gatherResponseAttributes(response));\n\n // Set span status based on response\n if (response.ok) {\n span.setStatus({ code: SpanStatusCode.OK });\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n\n // Replace global fetch\n globalThis.fetch = instrumentedFetch as typeof fetch;\n}\n","/**\n * Global Cache API instrumentation for Cloudflare Workers\n *\n * Automatically traces cache operations:\n * - cache.match() - Read from cache\n * - cache.put() - Write to cache\n * - cache.delete() - Delete from cache\n */\n\nimport { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';\nimport { wrap } from '../bindings/common';\nimport { WorkerTracer } from 'autotel-edge';\n\ntype CacheOperation = 'match' | 'put' | 'delete';\n\n/**\n * Sanitize URL for span attributes (remove query params that might contain sensitive data)\n */\nfunction sanitizeURL(url: string): string {\n const u = new URL(url);\n return `${u.protocol}//${u.host}${u.pathname}`;\n}\n\n/**\n * Instrument a cache method (match, put, delete)\n */\nfunction instrumentCacheMethod<T extends Function>(\n fn: T,\n cacheName: string,\n operation: CacheOperation,\n): T {\n const handler: ProxyHandler<T> = {\n async apply(target, thisArg, argArray) {\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n // Extract URL from first argument (Request or string)\n const firstArg = argArray[0];\n const url =\n firstArg instanceof Request\n ? firstArg.url\n : typeof firstArg === 'string'\n ? firstArg\n : undefined;\n\n const spanName = `Cache ${cacheName}.${operation}`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n 'cache.name': cacheName,\n 'cache.operation': operation,\n 'cache.key': url ? sanitizeURL(url) : undefined,\n },\n },\n async (span) => {\n try {\n const result = await Reflect.apply(target, thisArg, argArray);\n\n // For match operations, record whether it was a hit or miss\n if (operation === 'match') {\n span.setAttribute('cache.hit', !!result);\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n },\n };\n\n return wrap(fn, handler);\n}\n\n/**\n * Instrument a Cache instance\n */\nfunction instrumentCache(cache: Cache, cacheName: string): Cache {\n const handler: ProxyHandler<Cache> = {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n\n // Instrument the cache operation methods\n if (\n (prop === 'match' || prop === 'put' || prop === 'delete') &&\n typeof value === 'function'\n ) {\n return instrumentCacheMethod(\n value.bind(target),\n cacheName,\n prop as CacheOperation,\n );\n }\n\n // Bind other methods to preserve `this` context\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n };\n\n return wrap(cache, handler);\n}\n\n/**\n * Instrument caches.open()\n */\nfunction instrumentCachesOpen(\n openFn: CacheStorage['open'],\n): CacheStorage['open'] {\n const handler: ProxyHandler<CacheStorage['open']> = {\n async apply(target, thisArg, argArray) {\n const cacheName = argArray[0];\n const cache = await Reflect.apply(target, thisArg, argArray);\n return instrumentCache(cache, cacheName);\n },\n };\n\n return wrap(openFn, handler);\n}\n\n/**\n * Instrument the global caches API\n *\n * This wraps globalThis.caches to automatically create spans for all cache operations.\n *\n * **Note:** This is called automatically when the library is initialized with\n * `instrumentation.instrumentGlobalCache: true` (default).\n */\nexport function instrumentGlobalCache(): void {\n const handler: ProxyHandler<typeof caches> = {\n get(target, prop) {\n if (prop === 'default') {\n // Wrap the default cache\n return instrumentCache(target.default, 'default');\n } else if (prop === 'open') {\n // Wrap the open method\n const openFn = Reflect.get(target, prop);\n if (typeof openFn === 'function') {\n return instrumentCachesOpen(openFn.bind(target));\n }\n }\n\n return Reflect.get(target, prop);\n },\n };\n\n // Replace global caches\n // @ts-ignore - TypeScript doesn't like reassigning globalThis.caches\n globalThis.caches = wrap(caches, handler);\n}\n","/**\n * Handler instrumentation for Cloudflare Workers\n * \n * Note: This file uses Cloudflare Workers types (ExportedHandler, Request, Response, etc.)\n * which are globally available via @cloudflare/workers-types when listed in tsconfig.json.\n * These types are devDependencies only - they're not runtime dependencies.\n * At runtime, Cloudflare Workers runtime provides the actual implementations.\n * \n * Provides automatic OpenTelemetry tracing for:\n * - HTTP handlers (fetch)\n * - Scheduled/cron handlers\n * - Queue handlers (with message tracking)\n * - Email handlers\n * - Auto-instrumentation of Cloudflare bindings (KV, R2, D1, Service Bindings)\n * - Global fetch and cache instrumentation\n * - Post-processor support for span customization\n * - Tail sampling support\n * - Cold start tracking\n */\n\nimport {\n trace,\n context as api_context,\n propagation,\n SpanStatusCode,\n SpanKind,\n} from '@opentelemetry/api';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport type {\n ConfigurationOption,\n ResolvedEdgeConfig,\n Trigger,\n HandlerInstrumentation,\n InitialSpanInfo,\n ReadableSpan,\n RouteServiceConfig,\n} from 'autotel-edge';\nimport {\n createInitialiser,\n setConfig,\n getActiveConfig,\n getServiceForPath,\n shouldInstrumentPath,\n withNativeTracer,\n ensureGlobalContextManager,\n type Initialiser,\n type NativeTracer,\n WorkerTracerProvider,\n WorkerTracer,\n} from 'autotel-edge';\nimport { getNativeTracerFromCtx } from '../native/native-tracing';\nimport { proxyExecutionContext, unwrap, wrap, type PromiseTracker } from '../bindings/common';\nimport { instrumentGlobalFetch } from '../global/fetch';\nimport { instrumentGlobalCache } from '../global/cache';\nimport { instrumentBindings } from '../bindings/bindings';\nimport type { Attributes, Span, TextMapGetter } from '@opentelemetry/api';\n\n// Web API Headers objects require .get() — property access via defaultTextMapGetter won't work\nconst headersGetter: TextMapGetter<Headers> = {\n get: (carrier, key) => carrier.get(key) ?? undefined,\n keys: (carrier) => [...carrier.keys()],\n};\n\ntype FetchHandler = (\n request: Request,\n env: any,\n ctx: ExecutionContext,\n) => Response | Promise<Response>;\n\ntype ScheduledHandler = (\n event: ScheduledController,\n env: any,\n ctx: ExecutionContext,\n) => void | Promise<void>;\n\ntype QueueHandler = (\n batch: MessageBatch,\n env: any,\n ctx: ExecutionContext,\n) => void | Promise<void>;\n\ntype EmailHandler = (\n message: ForwardableEmailMessage,\n env: any,\n ctx: ExecutionContext,\n) => void | Promise<void>;\n\n/**\n * Create fetch handler instrumentation with config support for postProcess\n */\n/**\n * Extract Cloudflare-specific attributes from a request\n */\nfunction extractCfAttributes(request: Request): Record<string, string | number | boolean> {\n const cf = (request as any).cf;\n if (!cf) return {};\n\n const attrs: Record<string, string | number | boolean> = {};\n const set = (key: string, value: unknown) => {\n if (value !== undefined && value !== null) {\n attrs[key] = value as string | number | boolean;\n }\n };\n\n set('cloudflare.colo', cf.colo);\n const ray = request.headers.get('cf-ray');\n if (ray) attrs['cloudflare.ray_id'] = ray;\n set('cloudflare.country', cf.country);\n set('cloudflare.city', cf.city);\n set('cloudflare.region', cf.region);\n set('cloudflare.continent', cf.continent);\n set('cloudflare.timezone', cf.timezone);\n set('cloudflare.latitude', cf.latitude);\n set('cloudflare.longitude', cf.longitude);\n set('cloudflare.asn', cf.asn);\n set('cloudflare.as_organization', cf.asOrganization);\n set('cloudflare.http_protocol', cf.httpProtocol);\n set('cloudflare.tls_version', cf.tlsVersion);\n set('cloudflare.client_tcp_rtt', cf.clientTcpRtt);\n\n return attrs;\n}\n\nfunction createFetchInstrumentation(\n config: ResolvedEdgeConfig,\n): HandlerInstrumentation<Request, Response> {\n return {\n getInitialSpanInfo: (request: Request): InitialSpanInfo => {\n const url = new URL(request.url);\n const routeService = getServiceForPath(\n url.pathname,\n config.handlers.fetch.routes as Record<string, RouteServiceConfig> | undefined,\n );\n\n const cfAttrs = (config as any).extractCfAttributes === false\n ? {}\n : extractCfAttributes(request);\n\n return {\n name: `${request.method} ${url.pathname}`,\n options: {\n kind: SpanKind.SERVER,\n attributes: {\n 'http.request.method': request.method,\n 'url.full': request.url,\n ...(routeService ? { 'service.name': routeService, 'autotel.route.service': routeService } : {}),\n ...cfAttrs,\n },\n },\n context: propagation.extract(api_context.active(), request.headers, headersGetter),\n };\n },\n getAttributesFromResult: (response: Response) => ({\n 'http.response.status_code': response.status,\n }),\n executionSucces: (span: Span, trigger: Request, result: Response) => {\n // Override span status for server errors (5xx)\n if (result.status >= 500) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n\n // Call postProcess callback if configured\n if (config.handlers.fetch.postProcess) {\n const readableSpan = span as unknown as ReadableSpan;\n config.handlers.fetch.postProcess(span, {\n request: trigger,\n response: result,\n readable: readableSpan,\n });\n }\n },\n };\n}\n\n/**\n * Scheduled handler instrumentation\n */\nconst scheduledInstrumentation: HandlerInstrumentation<ScheduledController, void> = {\n getInitialSpanInfo: (event: ScheduledController): InitialSpanInfo => {\n return {\n name: `scheduledHandler ${event.cron || 'unknown'}`,\n options: {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'faas.trigger': 'timer',\n 'faas.cron': event.cron || 'unknown',\n 'faas.scheduled_time': new Date(event.scheduledTime).toISOString(),\n },\n },\n };\n },\n};\n\n/**\n * Tracks message status counts for queue processing\n */\nclass MessageStatusCount {\n succeeded = 0;\n failed = 0;\n implicitly_acked = 0;\n implicitly_retried = 0;\n readonly total: number;\n\n constructor(total: number) {\n this.total = total;\n }\n\n ack() {\n this.succeeded = this.succeeded + 1;\n }\n\n ackRemaining() {\n this.implicitly_acked = this.total - this.succeeded - this.failed;\n this.succeeded = this.total - this.failed;\n }\n\n retry() {\n this.failed = this.failed + 1;\n }\n\n retryRemaining() {\n this.implicitly_retried = this.total - this.succeeded - this.failed;\n this.failed = this.total - this.succeeded;\n }\n\n toAttributes(): Attributes {\n return {\n 'queue.messages_count': this.total,\n 'queue.messages_success': this.succeeded,\n 'queue.messages_failed': this.failed,\n 'queue.batch_success': this.succeeded === this.total,\n 'queue.implicitly_acked': this.implicitly_acked,\n 'queue.implicitly_retried': this.implicitly_retried,\n };\n }\n}\n\n/**\n * Add event to active span\n */\nfunction addQueueEvent(name: string, msg?: Message, delaySeconds?: number) {\n const attrs: Attributes = {};\n if (msg) {\n attrs['queue.message_id'] = msg.id;\n attrs['queue.message_timestamp'] = msg.timestamp.toISOString();\n // Add attempts if available (from Cloudflare Queues API)\n if ('attempts' in msg && typeof msg.attempts === 'number') {\n attrs['queue.message_attempts'] = msg.attempts;\n }\n }\n if (delaySeconds !== undefined) {\n attrs['queue.retry_delay_seconds'] = delaySeconds;\n }\n trace.getActiveSpan()?.addEvent(name, attrs);\n}\n\n/**\n * Proxy a queue message to track ack/retry operations\n */\nfunction proxyQueueMessage<Q>(msg: Message<Q>, count: MessageStatusCount): Message<Q> {\n const msgHandler: ProxyHandler<Message<Q>> = {\n get: (target, prop) => {\n if (prop === 'ack') {\n const ackFn = Reflect.get(target, prop);\n return new Proxy(ackFn, {\n apply: (fnTarget) => {\n addQueueEvent('messageAck', msg);\n count.ack();\n Reflect.apply(fnTarget, msg, []);\n },\n });\n } else if (prop === 'retry') {\n const retryFn = Reflect.get(target, prop);\n return new Proxy(retryFn, {\n apply: (fnTarget, _thisArg, args) => {\n // Extract delay and content type from retry options if provided\n const retryOptions = args[0] as\n | { delaySeconds?: number; contentType?: string }\n | undefined;\n const delaySeconds = retryOptions?.delaySeconds;\n\n addQueueEvent('messageRetry', msg, delaySeconds);\n\n // Add content type attribute if provided\n if (retryOptions?.contentType) {\n const span = trace.getActiveSpan();\n if (span) {\n span.setAttribute('queue.message.content_type', retryOptions.contentType);\n }\n }\n\n count.retry();\n const result = Reflect.apply(fnTarget, msg, args);\n return result;\n },\n });\n } else {\n return Reflect.get(target, prop, msg);\n }\n },\n };\n return wrap(msg, msgHandler);\n}\n\n/**\n * Proxy MessageBatch to track ackAll/retryAll operations\n */\nfunction proxyMessageBatch(batch: MessageBatch, count: MessageStatusCount): MessageBatch {\n const batchHandler: ProxyHandler<MessageBatch> = {\n get: (target, prop) => {\n if (prop === 'messages') {\n const messages = Reflect.get(target, prop);\n const messagesHandler: ProxyHandler<MessageBatch['messages']> = {\n get: (target, prop) => {\n if (typeof prop === 'string' && !isNaN(parseInt(prop))) {\n const message = Reflect.get(target, prop);\n return proxyQueueMessage(message, count);\n } else {\n return Reflect.get(target, prop);\n }\n },\n };\n return wrap(messages, messagesHandler);\n } else if (prop === 'ackAll') {\n const ackFn = Reflect.get(target, prop);\n return new Proxy(ackFn, {\n apply: (fnTarget) => {\n addQueueEvent('ackAll');\n count.ackRemaining();\n Reflect.apply(fnTarget, batch, []);\n },\n });\n } else if (prop === 'retryAll') {\n const retryFn = Reflect.get(target, prop);\n return new Proxy(retryFn, {\n apply: (fnTarget, _thisArg, args) => {\n // Extract delay from retryAll options if provided\n const retryOptions = args[0] as { delaySeconds?: number } | undefined;\n const delaySeconds = retryOptions?.delaySeconds;\n\n addQueueEvent('retryAll', undefined, delaySeconds);\n count.retryRemaining();\n Reflect.apply(fnTarget, batch, args);\n },\n });\n }\n return Reflect.get(target, prop);\n },\n };\n return wrap(batch, batchHandler);\n}\n\n/**\n * Queue handler instrumentation with message tracking\n */\nclass QueueInstrumentation implements HandlerInstrumentation<MessageBatch, void> {\n private count?: MessageStatusCount;\n\n getInitialSpanInfo(batch: MessageBatch): InitialSpanInfo {\n return {\n name: `queueHandler ${batch.queue || 'unknown'}`,\n options: {\n kind: SpanKind.CONSUMER,\n attributes: {\n 'faas.trigger': 'pubsub',\n 'queue.name': batch.queue || 'unknown',\n },\n },\n };\n }\n\n instrumentTrigger(batch: MessageBatch): MessageBatch {\n this.count = new MessageStatusCount(batch.messages.length);\n return proxyMessageBatch(batch, this.count);\n }\n\n executionSucces(span: Span, _trigger: MessageBatch, _result: void) {\n if (this.count) {\n this.count.ackRemaining();\n span.setAttributes(this.count.toAttributes());\n }\n }\n\n executionFailed(span: Span, _trigger: MessageBatch, _error?: any) {\n if (this.count) {\n this.count.retryRemaining();\n span.setAttributes(this.count.toAttributes());\n }\n }\n}\n\n/**\n * Converts email headers into OpenTelemetry attributes.\n * When dataSafety.emailHeaderAllowlist is configured, only allowed headers are captured.\n */\nfunction headerAttributes(message: { headers: Headers }): Record<string, string> {\n const attrs: Record<string, string> = {};\n if (message.headers instanceof Headers) {\n const config = getActiveConfig();\n const allowlist: string[] | undefined = config?.dataSafety?.emailHeaderAllowlist;\n for (const [key, value] of message.headers.entries()) {\n if (allowlist && !allowlist.includes(key.toLowerCase())) {\n continue;\n }\n attrs[`email.header.${key}`] = value;\n }\n }\n return attrs;\n}\n\n/**\n * Email handler instrumentation\n */\nconst emailInstrumentation: HandlerInstrumentation<ForwardableEmailMessage, void> = {\n getInitialSpanInfo: (message: ForwardableEmailMessage): InitialSpanInfo => {\n const attributes: Record<string, string> = {\n 'faas.trigger': 'other',\n 'messaging.destination.name': message.to || 'unknown',\n };\n\n // Add message ID if available\n if ('headers' in message && message.headers instanceof Headers) {\n const messageId = message.headers.get('Message-Id');\n if (messageId) {\n attributes['rpc.message.id'] = messageId;\n }\n // Add all headers as attributes\n Object.assign(attributes, headerAttributes(message));\n }\n\n return {\n name: `emailHandler ${message.to || 'unknown'}`,\n options: {\n kind: SpanKind.CONSUMER,\n attributes,\n },\n };\n },\n};\n\n\n/**\n * Export spans after request completes\n */\nasync function exportSpans(\n traceId: string,\n tracker: PromiseTracker | undefined,\n ctx: ExecutionContext,\n) {\n const tracer = trace.getTracer('autotel-edge');\n if (tracer instanceof WorkerTracer) {\n try {\n // scheduler is available on ExecutionContext at runtime\n const ctxWithScheduler = ctx as ExecutionContext & { scheduler?: { wait(ms: number): Promise<void> } };\n if (ctxWithScheduler.scheduler) {\n await ctxWithScheduler.scheduler.wait(1);\n }\n await tracker?.wait();\n await tracer.forceFlush(traceId);\n } catch (error) {\n // Silently handle exporter errors to prevent worker crashes\n // Exporter failures should not affect the worker's ability to process requests\n // In production, consider logging to a monitoring service\n console.error('[autotel-edge] Failed to export spans:', error);\n }\n }\n}\n\n/**\n * Create handler flow with instrumentation\n */\nfunction createHandlerFlow<T extends Trigger, E, R>(\n instrumentation: HandlerInstrumentation<T, R>,\n) {\n return (\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n [trigger, env, context]: [T, E, ExecutionContext],\n ) => {\n const { ctx: proxiedCtx, tracker } = proxyExecutionContext(context);\n\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n const { name, options, context: spanContext } =\n instrumentation.getInitialSpanInfo(trigger);\n\n // Add cold start tracking\n if (options.attributes) {\n options.attributes['faas.coldstart'] = coldStart;\n } else {\n options.attributes = { 'faas.coldstart': coldStart };\n }\n coldStart = false;\n\n const parentContext = spanContext || api_context.active();\n\n // Instrument trigger if supported (e.g., for queue handler)\n const instrumentedTrigger = instrumentation.instrumentTrigger\n ? instrumentation.instrumentTrigger(trigger)\n : trigger;\n\n return tracer.startActiveSpan(name, options, parentContext, async (span) => {\n try {\n const result = await handlerFn(instrumentedTrigger, env, proxiedCtx);\n\n if (instrumentation.getAttributesFromResult) {\n const attributes = instrumentation.getAttributesFromResult(result);\n span.setAttributes(attributes);\n }\n\n // Set default OK status; executionSucces may override (e.g. HTTP 5xx)\n span.setStatus({ code: SpanStatusCode.OK });\n\n if (instrumentation.executionSucces) {\n instrumentation.executionSucces(span, trigger, result);\n }\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n if (instrumentation.executionFailed) {\n instrumentation.executionFailed(span, trigger, error);\n }\n throw error;\n } finally {\n span.end();\n context.waitUntil(exportSpans(span.spanContext().traceId, tracker, context));\n }\n });\n };\n}\n\nlet warnedMissingNative = false;\n\n/**\n * Resolve whether this invocation should use Cloudflare's native tracer.\n *\n * - `nativeTracing: 'off'` → never use native (always autotel's OTLP pipeline).\n * - `nativeTracing: 'on'` → require native; warn once and fall back if absent.\n * - `nativeTracing: 'auto'` (default) → use native when `ctx.tracing` exists.\n */\nfunction resolveNativeTracer(\n config: ResolvedEdgeConfig,\n ctx: ExecutionContext,\n trigger: Trigger,\n): NativeTracer | null {\n const mode = config.nativeTracing ?? 'auto';\n if (mode === 'off') {\n return null;\n }\n // Correlation id: prefer Cloudflare's ray id (also stamped on the native root\n // span + carried by the workers logger), so logs/spans/dashboard share a key.\n // Fall back to a per-invocation uuid for non-fetch triggers.\n const rayId = trigger instanceof Request ? trigger.headers.get('cf-ray') : null;\n const correlationId = rayId ?? crypto.randomUUID();\n const nativeTracer = getNativeTracerFromCtx(ctx, correlationId);\n if (!nativeTracer && mode === 'on' && !warnedMissingNative) {\n warnedMissingNative = true;\n console.warn(\n \"[autotel-cloudflare] nativeTracing is 'on' but Cloudflare native tracing was not detected. \" +\n 'Enable observability.traces in your Wrangler config (and use a recent compatibility_date). ' +\n 'Falling back to the autotel OTLP exporter.',\n );\n }\n return nativeTracer;\n}\n\n/**\n * Run a handler in native-tracing mode.\n *\n * Cloudflare already creates the root handler span and instruments platform\n * operations (fetch / KV / R2 / D1 / DO), so autotel defers entirely: no\n * binding proxies (avoids duplicate spans), no provider/exporter, no manual\n * flush. We install the native tracer into the active context so that the\n * user's `span()` / `trace()` / `enterSpan()` calls nest inside Cloudflare's\n * native waterfall and are exported by the platform.\n */\nfunction runWithNativeTracing<T extends Trigger, E, R>(\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n trigger: T,\n env: E,\n ctx: ExecutionContext,\n config: ResolvedEdgeConfig,\n nativeTracer: NativeTracer,\n): R | Promise<R> {\n // Register the context manager (we skip initProvider in native mode) so the\n // native tracer propagates through OTel context into span()/trace() — without\n // it, getActiveNativeTracer() returns null and custom spans never route to\n // enterSpan. Bindings are NOT proxied: Cloudflare instruments them natively.\n ensureGlobalContextManager();\n const nativeContext = withNativeTracer(nativeTracer, setConfig(config));\n return api_context.with(nativeContext, () => handlerFn(trigger, env, ctx));\n}\n\n/**\n * Create handler proxy\n */\nfunction createHandlerProxy<T extends Trigger, E, R>(\n _handler: unknown,\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n initialiser: Initialiser,\n instrumentation: HandlerInstrumentation<T, R>,\n): (trigger: T, env: E, ctx: ExecutionContext) => ReturnType<typeof handlerFn> {\n return (trigger: T, env: E, ctx: ExecutionContext) => {\n const config = initialiser(env, trigger);\n\n // Check if instrumentation is disabled (useful for local dev)\n if (config.instrumentation.disabled) {\n // Return handler as-is without instrumentation\n return handlerFn(trigger, env, ctx);\n }\n\n // Prefer Cloudflare native tracing when available — defer to the platform.\n const nativeTracer = resolveNativeTracer(config, ctx, trigger);\n if (nativeTracer) {\n return runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer);\n }\n\n // Auto-instrument Cloudflare bindings in the environment\n const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;\n\n const configContext = setConfig(config);\n\n // Initialize provider on first call\n initProvider(config);\n\n const flowFn = createHandlerFlow<T, E, R>(instrumentation);\n\n // Execute the handler flow within the config context\n return api_context.with(configContext, () => {\n return flowFn(handlerFn, [trigger, instrumentedEnv, ctx]) as ReturnType<typeof handlerFn>;\n });\n };\n}\n\n/**\n * Create handler proxy with dynamic instrumentation (for fetch with postProcess)\n */\nfunction createHandlerProxyWithConfig<T extends Trigger, E, R>(\n _handler: unknown,\n handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,\n initialiser: Initialiser,\n createInstrumentation: (config: ResolvedEdgeConfig) => HandlerInstrumentation<T, R>,\n): (trigger: T, env: E, ctx: ExecutionContext) => ReturnType<typeof handlerFn> {\n return (trigger: T, env: E, ctx: ExecutionContext) => {\n const config = initialiser(env, trigger);\n \n // Check if instrumentation is disabled (useful for local dev)\n if (config.instrumentation.disabled) {\n // Return handler as-is without instrumentation\n return handlerFn(trigger, env, ctx);\n }\n\n if (trigger instanceof Request) {\n const pathname = new URL(trigger.url).pathname;\n const fetchCfg = config.handlers.fetch;\n if (\n !shouldInstrumentPath(pathname, {\n include: fetchCfg.include,\n exclude: fetchCfg.exclude,\n })\n ) {\n return handlerFn(trigger, env, ctx) as ReturnType<typeof handlerFn>;\n }\n }\n\n // Prefer Cloudflare native tracing when available — defer to the platform.\n const nativeTracer = resolveNativeTracer(config, ctx, trigger);\n if (nativeTracer) {\n return runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer) as ReturnType<typeof handlerFn>;\n }\n\n // Auto-instrument Cloudflare bindings in the environment\n const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;\n\n const configContext = setConfig(config);\n\n // Initialize provider on first call\n initProvider(config);\n\n // Create instrumentation with config\n const instrumentation = createInstrumentation(config);\n const flowFn = createHandlerFlow<T, E, R>(instrumentation);\n\n // Execute the handler flow within the config context\n return api_context.with(configContext, () => {\n return flowFn(handlerFn, [trigger, instrumentedEnv, ctx]) as ReturnType<typeof handlerFn>;\n });\n };\n}\n\nlet providerInitialized = false;\nlet coldStart = true;\n\n/**\n * Initialize the tracer provider\n */\nfunction initProvider(config: ResolvedEdgeConfig): void {\n if (providerInitialized) return;\n\n // Install global instrumentations\n if (config.instrumentation.instrumentGlobalFetch) {\n instrumentGlobalFetch();\n }\n if (config.instrumentation.instrumentGlobalCache) {\n instrumentGlobalCache();\n }\n\n // Set up propagator\n propagation.setGlobalPropagator(config.propagator);\n\n // Create resource\n const resource = resourceFromAttributes({\n 'service.name': config.service.name,\n 'service.version': config.service.version,\n 'service.namespace': config.service.namespace,\n 'cloud.provider': 'cloudflare',\n 'cloud.platform': 'cloudflare.workers',\n 'telemetry.sdk.name': 'autotel-edge',\n 'telemetry.sdk.language': 'js',\n });\n\n // Create and register provider\n const provider = new WorkerTracerProvider(config.spanProcessors, resource);\n provider.register();\n\n // Set head sampler on tracer\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n tracer.setHeadSampler(config.sampling.headSampler);\n\n providerInitialized = true;\n}\n\n/**\n * Instrument a Cloudflare Workers handler\n *\n * @example\n * ```typescript\n * import { instrument } from 'autotel-edge'\n *\n * const handler = {\n * async fetch(request, env, ctx) {\n * return new Response('Hello World')\n * }\n * }\n *\n * export default instrument(handler, {\n * exporter: {\n * url: env.OTLP_ENDPOINT,\n * headers: { 'x-api-key': env.API_KEY }\n * },\n * service: { name: 'my-worker' }\n * })\n * ```\n */\nexport function instrument<E, Q = any, C = any>(\n handler: ExportedHandler<E, Q, C>,\n config: ConfigurationOption,\n): ExportedHandler<E, Q, C> {\n const initialiser = createInitialiser(config);\n\n if (handler.fetch) {\n const fetcher = unwrap(handler.fetch) as FetchHandler;\n // Create fetch instrumentation with config support\n handler.fetch = createHandlerProxyWithConfig(\n handler,\n fetcher,\n initialiser,\n createFetchInstrumentation,\n );\n }\n\n if (handler.scheduled) {\n const scheduled = unwrap(handler.scheduled) as ScheduledHandler;\n handler.scheduled = createHandlerProxy(\n handler,\n scheduled,\n initialiser,\n scheduledInstrumentation,\n );\n }\n\n if (handler.queue) {\n const queue = unwrap(handler.queue) as QueueHandler;\n handler.queue = createHandlerProxy(\n handler,\n queue,\n initialiser,\n new QueueInstrumentation(),\n );\n }\n\n if (handler.email) {\n const email = unwrap(handler.email) as EmailHandler;\n handler.email = createHandlerProxy(\n handler,\n email,\n initialiser,\n emailInstrumentation,\n );\n }\n\n return handler;\n}\n","/**\n * workers-honeycomb-logger style wrapper API\n *\n * @example\n * ```typescript\n * import { wrapModule } from 'autotel-cloudflare'\n *\n * const handler = {\n * async fetch(req, env, ctx) {\n * return new Response('Hello')\n * }\n * }\n *\n * export default wrapModule(\n * { service: { name: 'my-worker' } },\n * handler\n * )\n * ```\n */\n\nimport { instrument } from './instrument';\nimport type { ConfigurationOption } from 'autotel-edge';\n\n/**\n * Wrap a Cloudflare Workers module-style handler\n * Alternative API style inspired by workers-honeycomb-logger\n *\n * @param config Configuration (can be static object or function)\n * @param handler The worker handler to wrap\n * @returns Instrumented handler\n */\nexport function wrapModule<E, Q = any, C = any>(\n config: ConfigurationOption,\n handler: ExportedHandler<E, Q, C>,\n): ExportedHandler<E, Q, C> {\n return instrument(handler, config);\n}\n","/**\n * Durable Object wrapper\n *\n * @example\n * ```typescript\n * import { wrapDurableObject } from 'autotel-cloudflare'\n *\n * class Counter implements DurableObject {\n * async fetch(request: Request) {\n * return new Response('count')\n * }\n * }\n *\n * export default wrapDurableObject({ service: { name: 'counter-do' } }, Counter)\n * ```\n */\n\nimport { instrumentDO } from '../handlers/durable-objects';\nimport type { ConfigurationOption } from 'autotel-edge';\n\n/**\n * Wrap a Durable Object class with instrumentation\n * Alternative API style inspired by workers-honeycomb-logger\n *\n * @param config Configuration (can be static object or function)\n * @param doClass The Durable Object class to wrap\n * @returns Instrumented Durable Object class\n */\nexport function wrapDurableObject<T extends DurableObject>(\n config: ConfigurationOption,\n doClass: new (state: DurableObjectState, env: any) => T,\n): new (state: DurableObjectState, env: any) => T {\n return instrumentDO(doClass, config);\n}\n","/**\n * Ergonomic wrapper for Cloudflare Workers `fetch` handlers.\n *\n * `defineWorkerFetch` instruments the handler the same way `instrument()` does\n * (so span exports already flush via `ctx.waitUntil`), and additionally injects\n * a request-scoped `ExecutionLogger` as the fourth argument — pre-populated\n * with method, path, cf-ray and `request.cf` context.\n *\n * Use this instead of `wrapModule` / `instrument` when your handler wants the\n * logger handed in directly rather than reaching for `getRequestLogger()`.\n *\n * @example\n * ```ts\n * import { defineWorkerFetch } from 'autotel-cloudflare'\n *\n * export default defineWorkerFetch(\n * { service: { name: 'my-worker' } },\n * async (request, env, ctx, log) => {\n * log.set({ route: '/health' })\n * log.emitNow({ status: 200 })\n * return new Response('ok')\n * },\n * )\n * ```\n */\nimport type { ConfigurationOption } from 'autotel-edge';\nimport { instrument } from './instrument';\nimport { createWorkersLogger, type WorkersLoggerOptions } from '../execution-logger';\nimport type { ExecutionLogger } from '../execution-logger';\n\nexport type DefineWorkerFetchOptions = WorkersLoggerOptions;\n\nexport type WorkerFetchHandler<E> = (\n request: Request,\n env: E,\n ctx: ExecutionContext,\n log: ExecutionLogger,\n) => Response | Promise<Response>;\n\n/**\n * Wrap a Workers fetch handler so:\n * - the handler is instrumented (spans, propagation, waitUntil export flush)\n * - the handler receives a request-scoped logger as its fourth argument\n */\nexport function defineWorkerFetch<E = unknown>(\n config: ConfigurationOption,\n handler: WorkerFetchHandler<E>,\n loggerOptions: DefineWorkerFetchOptions = {},\n): { fetch: (request: Request, env: E, ctx: ExecutionContext) => Promise<Response> } {\n const wrapped = instrument(\n {\n fetch(request: Request, env: E, ctx: ExecutionContext) {\n const log = createWorkersLogger(request, loggerOptions);\n return handler(request, env, ctx, log);\n },\n },\n config,\n );\n\n return {\n fetch(request, env, ctx) {\n return Promise.resolve(\n (wrapped.fetch as unknown as (\n req: Request,\n env: E,\n ctx: ExecutionContext,\n ) => Response | Promise<Response>)(request, env, ctx),\n );\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ //#region src/native/native-tracing.ts
2
+ function readTracing(carrier) {
3
+ const tracing = carrier?.tracing;
4
+ return typeof tracing?.enterSpan === "function" ? tracing : void 0;
5
+ }
6
+ /**
7
+ * Returns `true` when Cloudflare native custom-span tracing is available on the
8
+ * given ExecutionContext (i.e. tracing is enabled for this Worker).
9
+ */
10
+ function isNativeTracingAvailable(ctx) {
11
+ return readTracing(ctx) !== void 0;
12
+ }
13
+ /**
14
+ * Wrap Cloudflare's `ctx.tracing` as an autotel {@link NativeTracer}, or return
15
+ * `null` when native tracing is unavailable on this context.
16
+ *
17
+ * @param correlationId Optional per-request id (e.g. the `cf-ray` header)
18
+ * surfaced as `ctx.correlationId` and a `correlation.id` span attribute, so
19
+ * logs, custom spans, and the Cloudflare dashboard share one queryable key
20
+ * today — before Cloudflare exposes in-code trace/span ids.
21
+ */
22
+ function getNativeTracerFromCtx(ctx, correlationId) {
23
+ const tracing = readTracing(ctx);
24
+ if (!tracing) return null;
25
+ return {
26
+ correlationId,
27
+ enterSpan: (name, callback) => tracing.enterSpan(name, callback)
28
+ };
29
+ }
30
+
31
+ //#endregion
32
+ export { isNativeTracingAvailable as n, getNativeTracerFromCtx as t };
33
+ //# sourceMappingURL=native-tracing-D0EyAtsF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-tracing-D0EyAtsF.js","names":[],"sources":["../src/native/native-tracing.ts"],"sourcesContent":["/**\n * Cloudflare native tracing adapter\n *\n * Bridges Cloudflare Workers' built-in custom-span API\n * (`ctx.tracing.enterSpan()` / `import { tracing } from \"cloudflare:workers\"`)\n * to autotel-edge's runtime-agnostic {@link NativeTracer} seam.\n *\n * When a Worker has tracing enabled (`observability.traces.enabled = true` in\n * Wrangler) the runtime exposes `ctx.tracing`. The handler wrappers detect it,\n * wrap it as a {@link NativeTracer}, and install it into the active context with\n * `withNativeTracer()`. From then on every autotel `span()` / `trace()` /\n * `enterSpan()` call — including deep inside utility functions and libraries —\n * automatically routes to Cloudflare's native tracer and nests inside the\n * platform's trace waterfall (fetch / KV / R2 / D1 / handler spans), exported\n * by Cloudflare to whichever destination is configured in Wrangler.\n *\n * No `cloudflare:workers` import is required here: the native tracer travels\n * through autotel's AsyncLocalStorage context, so code without access to `ctx`\n * still picks it up via `getActiveNativeTracer()`.\n */\n\nimport type { NativeTracer, NativeSpanHandle } from 'autotel-edge';\n\n/**\n * Cloudflare's native custom-span surface. Declared locally because it is not\n * yet present in `@cloudflare/workers-types`. Structurally compatible with\n * autotel-edge's {@link NativeSpanHandle}.\n */\ninterface CloudflareSpan {\n readonly isTraced: boolean;\n setAttribute(key: string, value: string | number | boolean | undefined): void;\n}\n\n/**\n * Cloudflare's `tracing` object, available as `ctx.tracing` on the\n * ExecutionContext and as the `tracing` export of `cloudflare:workers`.\n */\ninterface CloudflareTracing {\n enterSpan<T, A extends unknown[]>(\n name: string,\n callback: (span: CloudflareSpan, ...args: A) => T,\n ...args: A\n ): T;\n}\n\ntype MaybeTracingCarrier = { tracing?: CloudflareTracing } | null | undefined;\n\nfunction readTracing(carrier: unknown): CloudflareTracing | undefined {\n const tracing = (carrier as MaybeTracingCarrier)?.tracing;\n return typeof tracing?.enterSpan === 'function' ? tracing : undefined;\n}\n\n/**\n * Returns `true` when Cloudflare native custom-span tracing is available on the\n * given ExecutionContext (i.e. tracing is enabled for this Worker).\n */\nexport function isNativeTracingAvailable(ctx: unknown): boolean {\n return readTracing(ctx) !== undefined;\n}\n\n/**\n * Wrap Cloudflare's `ctx.tracing` as an autotel {@link NativeTracer}, or return\n * `null` when native tracing is unavailable on this context.\n *\n * @param correlationId Optional per-request id (e.g. the `cf-ray` header)\n * surfaced as `ctx.correlationId` and a `correlation.id` span attribute, so\n * logs, custom spans, and the Cloudflare dashboard share one queryable key\n * today — before Cloudflare exposes in-code trace/span ids.\n */\nexport function getNativeTracerFromCtx(\n ctx: unknown,\n correlationId?: string,\n): NativeTracer | null {\n const tracing = readTracing(ctx);\n if (!tracing) {\n return null;\n }\n return {\n correlationId,\n enterSpan: <T>(name: string, callback: (span: NativeSpanHandle) => T): T =>\n tracing.enterSpan(name, callback as (span: CloudflareSpan) => T),\n };\n}\n"],"mappings":";AA+CA,SAAS,YAAY,SAAiD;CACpE,MAAM,UAAW,SAAiC;CAClD,OAAO,OAAO,SAAS,cAAc,aAAa,UAAU;AAC9D;;;;;AAMA,SAAgB,yBAAyB,KAAuB;CAC9D,OAAO,YAAY,GAAG,MAAM;AAC9B;;;;;;;;;;AAWA,SAAgB,uBACd,KACA,eACqB;CACrB,MAAM,UAAU,YAAY,GAAG;CAC/B,IAAI,CAAC,SACH,OAAO;CAET,OAAO;EACL;EACA,YAAe,MAAc,aAC3B,QAAQ,UAAU,MAAM,QAAuC;CACnE;AACF"}
@@ -0,0 +1,21 @@
1
+ import { NativeTracer } from "autotel-edge";
2
+
3
+ //#region src/native/native-tracing.d.ts
4
+ /**
5
+ * Returns `true` when Cloudflare native custom-span tracing is available on the
6
+ * given ExecutionContext (i.e. tracing is enabled for this Worker).
7
+ */
8
+ declare function isNativeTracingAvailable(ctx: unknown): boolean;
9
+ /**
10
+ * Wrap Cloudflare's `ctx.tracing` as an autotel {@link NativeTracer}, or return
11
+ * `null` when native tracing is unavailable on this context.
12
+ *
13
+ * @param correlationId Optional per-request id (e.g. the `cf-ray` header)
14
+ * surfaced as `ctx.correlationId` and a `correlation.id` span attribute, so
15
+ * logs, custom spans, and the Cloudflare dashboard share one queryable key
16
+ * today — before Cloudflare exposes in-code trace/span ids.
17
+ */
18
+ declare function getNativeTracerFromCtx(ctx: unknown, correlationId?: string): NativeTracer | null;
19
+ //#endregion
20
+ export { isNativeTracingAvailable as n, getNativeTracerFromCtx as t };
21
+ //# sourceMappingURL=native-tracing-DSo5mody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-tracing-DSo5mody.d.ts","names":[],"sources":["../src/native/native-tracing.ts"],"mappings":";;;;;;;iBAwDgB,wBAAA,CAAyB,GAAY;;;;;;;;;;iBAarC,sBAAA,CACd,GAAA,WACA,aAAA,YACC,YAAY"}
@@ -0,0 +1,2 @@
1
+ import { n as isNativeTracingAvailable, t as getNativeTracerFromCtx } from "./native-tracing-DSo5mody.js";
2
+ export { getNativeTracerFromCtx, isNativeTracingAvailable };
package/dist/native.js ADDED
@@ -0,0 +1,3 @@
1
+ import { n as isNativeTracingAvailable, t as getNativeTracerFromCtx } from "./native-tracing-D0EyAtsF.js";
2
+
3
+ export { getNativeTracerFromCtx, isNativeTracingAvailable };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-cloudflare",
3
- "version": "2.19.0",
3
+ "version": "3.0.0",
4
4
  "description": "The #1 OpenTelemetry package for Cloudflare Workers - complete bindings coverage, native CF OTel integration, advanced sampling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,6 +43,10 @@
43
43
  "types": "./dist/agents.d.ts",
44
44
  "import": "./dist/agents.js"
45
45
  },
46
+ "./native": {
47
+ "types": "./dist/native.d.ts",
48
+ "import": "./dist/native.js"
49
+ },
46
50
  "./parse-error": {
47
51
  "types": "./dist/parse-error.d.ts",
48
52
  "import": "./dist/parse-error.js"
@@ -77,7 +81,7 @@
77
81
  },
78
82
  "peerDependencies": {
79
83
  "@cloudflare/workers-types": "^4.20260607.1",
80
- "autotel-edge": "3.16.15"
84
+ "autotel-edge": "3.17.0"
81
85
  },
82
86
  "peerDependenciesMeta": {
83
87
  "autotel-edge": {
@@ -93,7 +97,7 @@
93
97
  "typescript": "^6.0.3",
94
98
  "vitest": "^4.1.8",
95
99
  "vitest-mock-extended": "^4.0.0",
96
- "autotel-edge": "3.16.15"
100
+ "autotel-edge": "3.17.0"
97
101
  },
98
102
  "repository": {
99
103
  "type": "git",
package/src/index.ts CHANGED
@@ -81,3 +81,11 @@ export {
81
81
 
82
82
  // Global instrumentations
83
83
  export { instrumentGlobalFetch, instrumentGlobalCache } from './global';
84
+
85
+ // Cloudflare native tracing helpers (auto-wired by the handler wrappers;
86
+ // exported for manual detection). `enterSpan` is re-exported from autotel-edge
87
+ // above via `export * from 'autotel-edge'`.
88
+ export {
89
+ isNativeTracingAvailable,
90
+ getNativeTracerFromCtx,
91
+ } from './native/native-tracing';
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import {
3
+ isNativeTracingAvailable,
4
+ getNativeTracerFromCtx,
5
+ } from './native-tracing';
6
+
7
+ function ctxWithTracing() {
8
+ const enterSpan = vi.fn((_name: string, cb: (s: any) => unknown) =>
9
+ cb({ isTraced: true, setAttribute: vi.fn() }),
10
+ );
11
+ return { tracing: { enterSpan }, waitUntil() {}, passThroughOnException() {} };
12
+ }
13
+
14
+ describe('isNativeTracingAvailable', () => {
15
+ it('is true when ctx.tracing.enterSpan is a function', () => {
16
+ expect(isNativeTracingAvailable(ctxWithTracing())).toBe(true);
17
+ });
18
+
19
+ it('is false for a plain ExecutionContext (tracing disabled / old runtime)', () => {
20
+ expect(isNativeTracingAvailable({ waitUntil() {} })).toBe(false);
21
+ });
22
+
23
+ it('is false for null/undefined or malformed tracing', () => {
24
+ expect(isNativeTracingAvailable(undefined)).toBe(false);
25
+ expect(isNativeTracingAvailable(null)).toBe(false);
26
+ expect(isNativeTracingAvailable({ tracing: {} })).toBe(false);
27
+ expect(isNativeTracingAvailable({ tracing: { enterSpan: 123 } })).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe('getNativeTracerFromCtx', () => {
32
+ it('returns null when native tracing is unavailable', () => {
33
+ expect(getNativeTracerFromCtx({ waitUntil() {} })).toBeNull();
34
+ });
35
+
36
+ it('wraps ctx.tracing.enterSpan as a NativeTracer', () => {
37
+ const ctx = ctxWithTracing();
38
+ const tracer = getNativeTracerFromCtx(ctx);
39
+ expect(tracer).not.toBeNull();
40
+
41
+ const result = tracer!.enterSpan('work', (span) => {
42
+ span.setAttribute('k', 'v');
43
+ return 7;
44
+ });
45
+
46
+ expect(result).toBe(7);
47
+ expect(ctx.tracing.enterSpan).toHaveBeenCalledWith('work', expect.any(Function));
48
+ });
49
+
50
+ it('attaches the supplied correlation id to the tracer', () => {
51
+ const tracer = getNativeTracerFromCtx(ctxWithTracing(), 'ray-xyz');
52
+ expect(tracer?.correlationId).toBe('ray-xyz');
53
+ });
54
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Cloudflare native tracing adapter
3
+ *
4
+ * Bridges Cloudflare Workers' built-in custom-span API
5
+ * (`ctx.tracing.enterSpan()` / `import { tracing } from "cloudflare:workers"`)
6
+ * to autotel-edge's runtime-agnostic {@link NativeTracer} seam.
7
+ *
8
+ * When a Worker has tracing enabled (`observability.traces.enabled = true` in
9
+ * Wrangler) the runtime exposes `ctx.tracing`. The handler wrappers detect it,
10
+ * wrap it as a {@link NativeTracer}, and install it into the active context with
11
+ * `withNativeTracer()`. From then on every autotel `span()` / `trace()` /
12
+ * `enterSpan()` call — including deep inside utility functions and libraries —
13
+ * automatically routes to Cloudflare's native tracer and nests inside the
14
+ * platform's trace waterfall (fetch / KV / R2 / D1 / handler spans), exported
15
+ * by Cloudflare to whichever destination is configured in Wrangler.
16
+ *
17
+ * No `cloudflare:workers` import is required here: the native tracer travels
18
+ * through autotel's AsyncLocalStorage context, so code without access to `ctx`
19
+ * still picks it up via `getActiveNativeTracer()`.
20
+ */
21
+
22
+ import type { NativeTracer, NativeSpanHandle } from 'autotel-edge';
23
+
24
+ /**
25
+ * Cloudflare's native custom-span surface. Declared locally because it is not
26
+ * yet present in `@cloudflare/workers-types`. Structurally compatible with
27
+ * autotel-edge's {@link NativeSpanHandle}.
28
+ */
29
+ interface CloudflareSpan {
30
+ readonly isTraced: boolean;
31
+ setAttribute(key: string, value: string | number | boolean | undefined): void;
32
+ }
33
+
34
+ /**
35
+ * Cloudflare's `tracing` object, available as `ctx.tracing` on the
36
+ * ExecutionContext and as the `tracing` export of `cloudflare:workers`.
37
+ */
38
+ interface CloudflareTracing {
39
+ enterSpan<T, A extends unknown[]>(
40
+ name: string,
41
+ callback: (span: CloudflareSpan, ...args: A) => T,
42
+ ...args: A
43
+ ): T;
44
+ }
45
+
46
+ type MaybeTracingCarrier = { tracing?: CloudflareTracing } | null | undefined;
47
+
48
+ function readTracing(carrier: unknown): CloudflareTracing | undefined {
49
+ const tracing = (carrier as MaybeTracingCarrier)?.tracing;
50
+ return typeof tracing?.enterSpan === 'function' ? tracing : undefined;
51
+ }
52
+
53
+ /**
54
+ * Returns `true` when Cloudflare native custom-span tracing is available on the
55
+ * given ExecutionContext (i.e. tracing is enabled for this Worker).
56
+ */
57
+ export function isNativeTracingAvailable(ctx: unknown): boolean {
58
+ return readTracing(ctx) !== undefined;
59
+ }
60
+
61
+ /**
62
+ * Wrap Cloudflare's `ctx.tracing` as an autotel {@link NativeTracer}, or return
63
+ * `null` when native tracing is unavailable on this context.
64
+ *
65
+ * @param correlationId Optional per-request id (e.g. the `cf-ray` header)
66
+ * surfaced as `ctx.correlationId` and a `correlation.id` span attribute, so
67
+ * logs, custom spans, and the Cloudflare dashboard share one queryable key
68
+ * today — before Cloudflare exposes in-code trace/span ids.
69
+ */
70
+ export function getNativeTracerFromCtx(
71
+ ctx: unknown,
72
+ correlationId?: string,
73
+ ): NativeTracer | null {
74
+ const tracing = readTracing(ctx);
75
+ if (!tracing) {
76
+ return null;
77
+ }
78
+ return {
79
+ correlationId,
80
+ enterSpan: <T>(name: string, callback: (span: NativeSpanHandle) => T): T =>
81
+ tracing.enterSpan(name, callback as (span: CloudflareSpan) => T),
82
+ };
83
+ }
package/src/native.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * autotel-cloudflare/native
3
+ *
4
+ * Cloudflare native tracing helpers. Most users never need this entry point —
5
+ * the handler wrappers (`instrument`, `wrapModule`, `defineWorkerFetch`,
6
+ * `wrapDurableObject`) auto-detect and wire up native tracing for you. Import
7
+ * from here only when you want to detect native tracing yourself.
8
+ */
9
+ export {
10
+ isNativeTracingAvailable,
11
+ getNativeTracerFromCtx,
12
+ } from './native/native-tracing';
@@ -41,10 +41,14 @@ import {
41
41
  getActiveConfig,
42
42
  getServiceForPath,
43
43
  shouldInstrumentPath,
44
+ withNativeTracer,
45
+ ensureGlobalContextManager,
44
46
  type Initialiser,
47
+ type NativeTracer,
45
48
  WorkerTracerProvider,
46
49
  WorkerTracer,
47
50
  } from 'autotel-edge';
51
+ import { getNativeTracerFromCtx } from '../native/native-tracing';
48
52
  import { proxyExecutionContext, unwrap, wrap, type PromiseTracker } from '../bindings/common';
49
53
  import { instrumentGlobalFetch } from '../global/fetch';
50
54
  import { instrumentGlobalCache } from '../global/cache';
@@ -528,6 +532,68 @@ function createHandlerFlow<T extends Trigger, E, R>(
528
532
  };
529
533
  }
530
534
 
535
+ let warnedMissingNative = false;
536
+
537
+ /**
538
+ * Resolve whether this invocation should use Cloudflare's native tracer.
539
+ *
540
+ * - `nativeTracing: 'off'` → never use native (always autotel's OTLP pipeline).
541
+ * - `nativeTracing: 'on'` → require native; warn once and fall back if absent.
542
+ * - `nativeTracing: 'auto'` (default) → use native when `ctx.tracing` exists.
543
+ */
544
+ function resolveNativeTracer(
545
+ config: ResolvedEdgeConfig,
546
+ ctx: ExecutionContext,
547
+ trigger: Trigger,
548
+ ): NativeTracer | null {
549
+ const mode = config.nativeTracing ?? 'auto';
550
+ if (mode === 'off') {
551
+ return null;
552
+ }
553
+ // Correlation id: prefer Cloudflare's ray id (also stamped on the native root
554
+ // span + carried by the workers logger), so logs/spans/dashboard share a key.
555
+ // Fall back to a per-invocation uuid for non-fetch triggers.
556
+ const rayId = trigger instanceof Request ? trigger.headers.get('cf-ray') : null;
557
+ const correlationId = rayId ?? crypto.randomUUID();
558
+ const nativeTracer = getNativeTracerFromCtx(ctx, correlationId);
559
+ if (!nativeTracer && mode === 'on' && !warnedMissingNative) {
560
+ warnedMissingNative = true;
561
+ console.warn(
562
+ "[autotel-cloudflare] nativeTracing is 'on' but Cloudflare native tracing was not detected. " +
563
+ 'Enable observability.traces in your Wrangler config (and use a recent compatibility_date). ' +
564
+ 'Falling back to the autotel OTLP exporter.',
565
+ );
566
+ }
567
+ return nativeTracer;
568
+ }
569
+
570
+ /**
571
+ * Run a handler in native-tracing mode.
572
+ *
573
+ * Cloudflare already creates the root handler span and instruments platform
574
+ * operations (fetch / KV / R2 / D1 / DO), so autotel defers entirely: no
575
+ * binding proxies (avoids duplicate spans), no provider/exporter, no manual
576
+ * flush. We install the native tracer into the active context so that the
577
+ * user's `span()` / `trace()` / `enterSpan()` calls nest inside Cloudflare's
578
+ * native waterfall and are exported by the platform.
579
+ */
580
+ function runWithNativeTracing<T extends Trigger, E, R>(
581
+ handlerFn: (trigger: T, env: E, ctx: ExecutionContext) => R | Promise<R>,
582
+ trigger: T,
583
+ env: E,
584
+ ctx: ExecutionContext,
585
+ config: ResolvedEdgeConfig,
586
+ nativeTracer: NativeTracer,
587
+ ): R | Promise<R> {
588
+ // Register the context manager (we skip initProvider in native mode) so the
589
+ // native tracer propagates through OTel context into span()/trace() — without
590
+ // it, getActiveNativeTracer() returns null and custom spans never route to
591
+ // enterSpan. Bindings are NOT proxied: Cloudflare instruments them natively.
592
+ ensureGlobalContextManager();
593
+ const nativeContext = withNativeTracer(nativeTracer, setConfig(config));
594
+ return api_context.with(nativeContext, () => handlerFn(trigger, env, ctx));
595
+ }
596
+
531
597
  /**
532
598
  * Create handler proxy
533
599
  */
@@ -539,16 +605,22 @@ function createHandlerProxy<T extends Trigger, E, R>(
539
605
  ): (trigger: T, env: E, ctx: ExecutionContext) => ReturnType<typeof handlerFn> {
540
606
  return (trigger: T, env: E, ctx: ExecutionContext) => {
541
607
  const config = initialiser(env, trigger);
542
-
608
+
543
609
  // Check if instrumentation is disabled (useful for local dev)
544
610
  if (config.instrumentation.disabled) {
545
611
  // Return handler as-is without instrumentation
546
612
  return handlerFn(trigger, env, ctx);
547
613
  }
548
-
614
+
615
+ // Prefer Cloudflare native tracing when available — defer to the platform.
616
+ const nativeTracer = resolveNativeTracer(config, ctx, trigger);
617
+ if (nativeTracer) {
618
+ return runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer);
619
+ }
620
+
549
621
  // Auto-instrument Cloudflare bindings in the environment
550
622
  const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;
551
-
623
+
552
624
  const configContext = setConfig(config);
553
625
 
554
626
  // Initialize provider on first call
@@ -593,10 +665,16 @@ function createHandlerProxyWithConfig<T extends Trigger, E, R>(
593
665
  return handlerFn(trigger, env, ctx) as ReturnType<typeof handlerFn>;
594
666
  }
595
667
  }
596
-
668
+
669
+ // Prefer Cloudflare native tracing when available — defer to the platform.
670
+ const nativeTracer = resolveNativeTracer(config, ctx, trigger);
671
+ if (nativeTracer) {
672
+ return runWithNativeTracing(handlerFn, trigger, env, ctx, config, nativeTracer) as ReturnType<typeof handlerFn>;
673
+ }
674
+
597
675
  // Auto-instrument Cloudflare bindings in the environment
598
676
  const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;
599
-
677
+
600
678
  const configContext = setConfig(config);
601
679
 
602
680
  // Initialize provider on first call
@@ -0,0 +1,153 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { instrument } from './instrument';
3
+ import { span } from 'autotel-edge';
4
+ import { isWrapped } from '../bindings/common';
5
+
6
+ /**
7
+ * A KV-shaped binding so instrumentBindings() would proxy it in the OTLP path.
8
+ * In native mode we must NOT proxy it (Cloudflare traces KV natively) — these
9
+ * tests assert that the handler receives the original, unwrapped binding and
10
+ * that user spans route to ctx.tracing.enterSpan instead of the OTel pipeline.
11
+ */
12
+ function fakeKv() {
13
+ return {
14
+ get: vi.fn(async () => 'value'),
15
+ put: vi.fn(async () => {}),
16
+ delete: vi.fn(async () => {}),
17
+ list: vi.fn(async () => ({ keys: [] })),
18
+ };
19
+ }
20
+
21
+ function nativeCtx() {
22
+ const enteredSpans: string[] = [];
23
+ const waitUntil = vi.fn();
24
+ const ctx = {
25
+ waitUntil,
26
+ passThroughOnException() {},
27
+ tracing: {
28
+ enterSpan: vi.fn((name: string, cb: (s: any) => unknown) => {
29
+ enteredSpans.push(name);
30
+ return cb({ isTraced: true, setAttribute: vi.fn() });
31
+ }),
32
+ },
33
+ };
34
+ return { ctx, enteredSpans, waitUntil };
35
+ }
36
+
37
+ const baseConfig = {
38
+ service: { name: 'native-test' },
39
+ exporter: { url: 'http://localhost:4318/v1/traces' },
40
+ } as const;
41
+
42
+ describe('instrument() with Cloudflare native tracing (auto)', () => {
43
+ it('defers binding instrumentation to the platform (no proxy wrapping)', async () => {
44
+ const { ctx } = nativeCtx();
45
+ const kv = fakeKv();
46
+ let seenBinding: unknown;
47
+
48
+ const handler = instrument(
49
+ {
50
+ async fetch(_req, env: { MY_KV: ReturnType<typeof fakeKv> }) {
51
+ seenBinding = env.MY_KV;
52
+ await env.MY_KV.get('k');
53
+ return new Response('ok');
54
+ },
55
+ },
56
+ baseConfig,
57
+ );
58
+
59
+ await handler.fetch!(new Request('https://x/'), { MY_KV: kv }, ctx as never);
60
+
61
+ // Handler received the ORIGINAL binding, not an autotel proxy.
62
+ expect(seenBinding).toBe(kv);
63
+ expect(isWrapped(seenBinding)).toBe(false);
64
+ // No "KV ...: get" span was created via the native tracer.
65
+ expect(
66
+ ctx.tracing.enterSpan.mock.calls.some(([name]) =>
67
+ String(name).startsWith('KV '),
68
+ ),
69
+ ).toBe(false);
70
+ });
71
+
72
+ it('surfaces the cf-ray header as a correlation.id span attribute', async () => {
73
+ const attrs: Record<string, unknown> = {};
74
+ const ctx = {
75
+ waitUntil() {},
76
+ passThroughOnException() {},
77
+ tracing: {
78
+ enterSpan: vi.fn((_name: string, cb: (s: any) => unknown) =>
79
+ cb({
80
+ isTraced: true,
81
+ setAttribute(k: string, v: unknown) {
82
+ attrs[k] = v;
83
+ },
84
+ }),
85
+ ),
86
+ },
87
+ };
88
+
89
+ const handler = instrument(
90
+ { async fetch() { return span('work', () => new Response('ok')); } },
91
+ baseConfig,
92
+ );
93
+
94
+ const req = new Request('https://x/', { headers: { 'cf-ray': 'ray-7f' } });
95
+ await handler.fetch!(req, {}, ctx as never);
96
+ expect(attrs['correlation.id']).toBe('ray-7f');
97
+ });
98
+
99
+ it('routes user span() calls to ctx.tracing.enterSpan', async () => {
100
+ const { ctx, enteredSpans } = nativeCtx();
101
+
102
+ const handler = instrument(
103
+ {
104
+ async fetch() {
105
+ return span('user.work', (s) => {
106
+ s.setAttribute('ok', true);
107
+ return new Response('done');
108
+ });
109
+ },
110
+ },
111
+ baseConfig,
112
+ );
113
+
114
+ const res = await handler.fetch!(new Request('https://x/'), {}, ctx as never);
115
+ expect(await res.text()).toBe('done');
116
+ expect(enteredSpans).toContain('user.work');
117
+ });
118
+
119
+ it('does not run the OTLP export flow (no waitUntil) in native mode', async () => {
120
+ const { ctx, waitUntil } = nativeCtx();
121
+ const handler = instrument(
122
+ { async fetch() { return new Response('ok'); } },
123
+ baseConfig,
124
+ );
125
+ await handler.fetch!(new Request('https://x/'), {}, ctx as never);
126
+ expect(waitUntil).not.toHaveBeenCalled();
127
+ });
128
+ });
129
+
130
+ describe('instrument() with nativeTracing: "off"', () => {
131
+ it('ignores ctx.tracing and uses the autotel pipeline (proxies bindings)', async () => {
132
+ const { ctx } = nativeCtx();
133
+ const kv = fakeKv();
134
+ let seenBinding: unknown;
135
+
136
+ const handler = instrument(
137
+ {
138
+ async fetch(_req, env: { MY_KV: ReturnType<typeof fakeKv> }) {
139
+ seenBinding = env.MY_KV;
140
+ return new Response('ok');
141
+ },
142
+ },
143
+ { ...baseConfig, nativeTracing: 'off' },
144
+ );
145
+
146
+ await handler.fetch!(new Request('https://x/'), { MY_KV: kv }, ctx as never);
147
+
148
+ // OTLP path proxies the binding, so the handler sees a wrapped object.
149
+ expect(isWrapped(seenBinding)).toBe(true);
150
+ // Native tracer was never used.
151
+ expect(ctx.tracing.enterSpan).not.toHaveBeenCalled();
152
+ });
153
+ });