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 +41 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/native-tracing-D0EyAtsF.js +33 -0
- package/dist/native-tracing-D0EyAtsF.js.map +1 -0
- package/dist/native-tracing-DSo5mody.d.ts +21 -0
- package/dist/native-tracing-DSo5mody.d.ts.map +1 -0
- package/dist/native.d.ts +2 -0
- package/dist/native.js +3 -0
- package/package.json +7 -3
- package/src/index.ts +8 -0
- package/src/native/native-tracing.test.ts +54 -0
- package/src/native/native-tracing.ts +83 -0
- package/src/native.ts +12 -0
- package/src/wrappers/instrument.ts +83 -5
- package/src/wrappers/native-instrument.test.ts +153 -0
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/native.d.ts
ADDED
package/dist/native.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-cloudflare",
|
|
3
|
-
"version": "
|
|
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.
|
|
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.
|
|
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
|
+
});
|