UNPKG

autotel-cloudflare

Version:

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

1,020 lines (1,010 loc) 33.8 kB
import { _ as trapArgs, a as asRecord, c as asString, d as hasMethods, f as member, g as readProperty, h as readPath, i as asNumber, m as numberAt, n as asBoolean, r as asFunction, u as hasMethod } from "./values-BC6rdpGG.js"; import { i as setAttr, n as isWrapped, o as wrap, t as toException } from "./exception-D3xOCdBW.js"; import { t as workerTracer } from "./tracer-z8zwRBVS.js"; import { getActiveConfig } from "autotel-edge"; import { SpanKind, SpanStatusCode } from "@opentelemetry/api"; //#region src/bindings/ai.ts /** * Workers AI binding instrumentation */ /** * Instrument Workers AI binding */ function instrumentAI(ai, bindingName) { const name = bindingName || "ai"; return wrap(ai, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "run" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [model] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`AI ${name}: run ${model}`, { kind: SpanKind.CLIENT, attributes: { "gen_ai.provider.name": "cloudflare-workers-ai", "gen_ai.operation.name": "run", "gen_ai.request.model": model } }, async (span) => { try { const result = await fnTarget.apply(target, args); const inputTokens = numberAt(result, "usage", "prompt_tokens"); if (inputTokens !== void 0) setAttr(span, "gen_ai.usage.input_tokens", inputTokens); const outputTokens = numberAt(result, "usage", "completion_tokens"); if (outputTokens !== void 0) setAttr(span, "gen_ai.usage.output_tokens", outputTokens); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion //#region src/bindings/vectorize.ts /** * Vectorize binding instrumentation */ const TRACED_METHODS = [ "query", "insert", "upsert", "deleteByIds", "getByIds", "describe" ]; /** * Instrument Vectorize index binding */ function instrumentVectorize(vectorize, indexName) { const name = indexName || "vectorize"; return wrap(vectorize, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (typeof prop === "string" && TRACED_METHODS.includes(prop) && method !== void 0) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const operation = prop; const tracer = workerTracer("autotel-edge"); const attributes = { "db.system.name": "cloudflare-vectorize", "db.operation.name": operation, "db.collection.name": name }; if (operation === "query") { const queryInput = args[0]; if (queryInput?.topK !== void 0) attributes["db.vectorize.top_k"] = queryInput.topK; } if ((operation === "insert" || operation === "upsert") && Array.isArray(args[0])) attributes["db.vectorize.vectors_count"] = args[0].length; return tracer.startActiveSpan(`Vectorize ${name}: ${operation}`, { kind: SpanKind.CLIENT, attributes }, async (span) => { try { const result = await fnTarget.apply(target, args); const matches = readProperty(result, "matches"); if (operation === "query" && Array.isArray(matches)) setAttr(span, "db.vectorize.matches_count", matches.length); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion //#region src/bindings/hyperdrive.ts /** * Hyperdrive binding instrumentation */ /** * Instrument Hyperdrive binding */ function instrumentHyperdrive(hyperdrive, bindingName) { const name = bindingName || "hyperdrive"; return wrap(hyperdrive, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "connect" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const tracer = workerTracer("autotel-edge"); const attributes = { "db.system.name": "cloudflare-hyperdrive", "db.operation.name": "connect" }; try { setAttr({ setAttribute: (k, v) => { if (v !== void 0 && v !== null) attributes[k] = v; } }, "server.address", target.host); setAttr({ setAttribute: (k, v) => { if (v !== void 0 && v !== null) attributes[k] = v; } }, "server.port", target.port); setAttr({ setAttribute: (k, v) => { if (v !== void 0 && v !== null) attributes[k] = v; } }, "db.user", target.user); } catch {} return tracer.startActiveSpan(`Hyperdrive ${name}: connect`, { kind: SpanKind.CLIENT, attributes }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion //#region src/bindings/queue-producer.ts /** * Queue producer binding instrumentation */ /** * Instrument Queue producer binding */ function instrumentQueueProducer(queue, queueName) { const name = queueName || "queue"; return wrap(queue, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "send" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { return workerTracer("autotel-edge").startActiveSpan(`Queue ${name}: send`, { kind: SpanKind.PRODUCER, attributes: { "messaging.system": "cloudflare-queues", "messaging.operation.type": "publish", "messaging.operation.name": "send", "messaging.destination.name": name } }, async (span) => { try { const result = await fnTarget.apply(target, args); setAttr(span, "messaging.message.id", result?.messageId); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "sendBatch" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [messages] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`Queue ${name}: sendBatch`, { kind: SpanKind.PRODUCER, attributes: { "messaging.system": "cloudflare-queues", "messaging.operation.type": "publish", "messaging.operation.name": "sendBatch", "messaging.destination.name": name, "messaging.batch.message_count": Array.isArray(messages) ? messages.length : 0 } }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion //#region src/bindings/analytics-engine.ts /** * Analytics Engine binding instrumentation */ /** * Instrument Analytics Engine binding */ function instrumentAnalyticsEngine(ae, datasetName) { const name = datasetName || "analytics-engine"; return wrap(ae, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "writeDataPoint" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [dataPoint] = trapArgs(args); const tracer = workerTracer("autotel-edge"); const attributes = { "analytics.system": "cloudflare-analytics-engine", "analytics.operation": "writeDataPoint" }; if (dataPoint) { if (dataPoint.indexes) attributes["analytics.indexes_count"] = Array.isArray(dataPoint.indexes) ? dataPoint.indexes.length : 1; if (dataPoint.doubles) attributes["analytics.doubles_count"] = dataPoint.doubles.length; if (dataPoint.blobs) attributes["analytics.blobs_count"] = dataPoint.blobs.length; } return tracer.startActiveSpan(`AnalyticsEngine ${name}: writeDataPoint`, { kind: SpanKind.CLIENT, attributes }, (span) => { try { fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion //#region src/bindings/images.ts /** * Images binding instrumentation * * The Images binding uses a fluent chain: input() -> transform() -> draw() -> output() * We only create a span at the terminal output() call to avoid intermediate noise. * info() is a standalone operation and gets its own span. */ const pipelineMetaSymbol = Symbol("images-pipeline-meta"); function proxyTransformer(transformer, meta, bindingName) { const proxy = new Proxy(transformer, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if ((prop === "transform" || prop === "draw") && method !== void 0) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { meta.operationCount++; const result = fnTarget.apply(target, args); if (result === target || result && typeof result === "object" && "output" in result) return proxyTransformer(result, meta, bindingName); return result; } }); if (prop === "output" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const tracer = workerTracer("autotel-edge"); const [formatOrOptions] = args; const attributes = { "images.system": "cloudflare-images", "images.pipeline.operation_count": meta.operationCount }; if (typeof formatOrOptions === "string") attributes["images.output.format"] = formatOrOptions; else if (formatOrOptions && typeof formatOrOptions === "object") { const fmt = formatOrOptions.format; if (fmt) attributes["images.output.format"] = fmt; } return tracer.startActiveSpan(`Images ${bindingName}: output`, { kind: SpanKind.CLIENT, attributes }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); Object.defineProperty(proxy, pipelineMetaSymbol, { value: meta, writable: false, enumerable: false, configurable: false }); return proxy; } /** * Instrument Images binding */ function instrumentImages(images, bindingName) { const name = bindingName || "images"; return wrap(images, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "info" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { return workerTracer("autotel-edge").startActiveSpan(`Images ${name}: info`, { kind: SpanKind.CLIENT, attributes: { "images.system": "cloudflare-images", "images.operation": "info" } }, async (span) => { try { const result = await fnTarget.apply(target, args); setAttr(span, "images.width", numberAt(result, "width")); setAttr(span, "images.height", numberAt(result, "height")); setAttr(span, "images.format", asString(readProperty(result, "format"))); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "input" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { return proxyTransformer(fnTarget.apply(target, args), { operationCount: 0 }, name); } }); return value; } }); } //#endregion //#region src/bindings/bindings.ts /** * Auto-instrumentation for Cloudflare Workers bindings * * Note: This file uses Cloudflare Workers types (KVNamespace, R2Bucket, D1Database, Fetcher, etc.) * which are globally available via @cloudflare/workers-types when listed in tsconfig.json. * These types are devDependencies only - they're not runtime dependencies. * At runtime, Cloudflare Workers runtime provides the actual implementations. * * This module provides automatic tracing for Cloudflare bindings: * - KV (key-value operations) * - R2 (object storage operations) * - D1 (database operations) * - Service Bindings * - Events Engine * - Workers AI * - Vectorize * - Hyperdrive */ /** * Sanitize a SQL statement based on the capture mode. * - 'full': returns the statement as-is * - 'obfuscated': replaces string literals and numbers with '?' * - 'off': returns undefined (attribute not set) */ function sanitizeStatement(query, mode) { if (mode === "off") return void 0; if (mode === "obfuscated") return query.replaceAll(/'[^']*'/g, "'?'").replaceAll(/\b\d+\b/g, "?"); return query; } /** * Instrument KV namespace */ function instrumentKV(kv, namespaceName) { const name = namespaceName || "kv"; return wrap(kv, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "get" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [key, options] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`KV ${name}: get`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-kv", "db.operation.name": "get", "db.namespace": name, "db.key": key, "db.cache_hit": options?.cacheTtl !== void 0 } }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setAttribute("db.result.type", result === null ? "null" : typeof result); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "put" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [key] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`KV ${name}: put`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-kv", "db.operation.name": "put", "db.namespace": name, "db.key": key } }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "delete" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [key] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`KV ${name}: delete`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-kv", "db.operation.name": "delete", "db.namespace": name, "db.key": key } }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "list" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [options] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`KV ${name}: list`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-kv", "db.operation.name": "list", "db.namespace": name, "db.prefix": options?.prefix || void 0, "db.limit": options?.limit || void 0 } }, async (span) => { try { const result = await fnTarget.apply(target, args); const keys = readProperty(result, "keys"); if (Array.isArray(keys)) span.setAttribute("db.result.keys_count", keys.length); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } /** * Instrument R2 bucket */ function instrumentR2(r2, bucketName) { const name = bucketName || "r2"; return wrap(r2, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "get" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [key] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`R2 ${name}: get`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-r2", "db.operation.name": "get", "db.bucket": name, "db.key": key } }, async (span) => { try { const result = await fnTarget.apply(target, args); if (result) { setResultAttr(span, "db.result.size", result, "size"); setResultAttr(span, "db.result.etag", result, "etag"); const contentType = asString(readPath(result, "httpMetadata", "contentType")); if (contentType !== void 0) span.setAttribute("db.result.content_type", contentType); } else span.setAttribute("db.result.exists", false); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "put" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [key] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`R2 ${name}: put`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-r2", "db.operation.name": "put", "db.bucket": name, "db.key": key } }, async (span) => { try { const result = await fnTarget.apply(target, args); setResultAttr(span, "db.result.etag", result, "etag"); setResultAttr(span, "db.result.uploaded", result, "uploaded"); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "delete" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const keys = args; return workerTracer("autotel-edge").startActiveSpan(`R2 ${name}: delete`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-r2", "db.operation.name": "delete", "db.bucket": name, "db.keys_count": keys.length } }, async (span) => { try { const result = await fnTarget.apply(target, args); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); if (prop === "list" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [options] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`R2 ${name}: list`, { kind: SpanKind.CLIENT, attributes: { "db.system.name": "cloudflare-r2", "db.operation.name": "list", "db.bucket": name, "db.prefix": options?.prefix || void 0, "db.limit": options?.limit || void 0 } }, async (span) => { try { const result = await fnTarget.apply(target, args); const objects = readProperty(result, "objects"); if (Array.isArray(objects)) span.setAttribute("db.result.objects_count", objects.length); setResultAttr(span, "db.result.truncated", result, "truncated"); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } /** * Instrument D1 database */ function instrumentD1(d1, databaseName) { const name = databaseName || "d1"; return wrap(d1, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "prepare" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [query] = trapArgs(args); const tracer = workerTracer("autotel-edge"); return wrap(fnTarget.apply(target, args), { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (method && (prop === "first" || prop === "run" || prop === "all" || prop === "raw")) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const captureMode = getActiveConfig()?.dataSafety?.captureDbStatement ?? "full"; const statement = sanitizeStatement(query, captureMode); const attributes = { "db.system.name": "cloudflare-d1", "db.operation.name": prop, "db.namespace": name }; if (statement !== void 0) attributes["db.query.text"] = statement; return tracer.startActiveSpan(`D1 ${name}: ${prop}`, { kind: SpanKind.CLIENT, attributes }, async (span) => { try { const result = await fnTarget.apply(target, args); if (prop === "all" && Array.isArray(result)) span.setAttribute("db.result.rows_count", result.length); else if (prop === "first" && result) span.setAttribute("db.result.exists", true); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } }); if (prop === "exec" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [query] = trapArgs(args); const tracer = workerTracer("autotel-edge"); const statement = sanitizeStatement(query, getActiveConfig()?.dataSafety?.captureDbStatement ?? "full"); const attributes = { "db.system.name": "cloudflare-d1", "db.operation.name": "exec", "db.namespace": name }; if (statement !== void 0) attributes["db.query.text"] = statement; return tracer.startActiveSpan(`D1 ${name}: exec`, { kind: SpanKind.CLIENT, attributes }, async (span) => { try { const result = await fnTarget.apply(target, args); const count = numberAt(result, "count"); if (count !== void 0) span.setAttribute("db.result.count", count); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } /** * Instrument service binding (Fetcher) * * Unlike other bindings, Fetcher objects are native Cloudflare C++ bindings * whose methods throw "Illegal invocation" when called through a Proxy with * a different `this` reference. We work around this by calling `target.fetch()` * directly on the original binding instead of using `fn.apply` on a * detached function reference. */ function instrumentServiceBinding(fetcher, serviceName) { const name = serviceName || "service"; return wrap(fetcher, { get(target, prop) { if (prop === "fetch" && typeof target.fetch === "function") { const tracedFetch = (...args) => { const [input, init] = trapArgs(args); const request = new Request(input, init); return workerTracer("autotel-edge").startActiveSpan(`Service ${name}: ${request.method}`, { kind: SpanKind.CLIENT, attributes: { "rpc.system.name": "cloudflare-service-binding", "rpc.service": name, "http.request.method": request.method, "url.full": request.url } }, async (span) => { try { const response = await target.fetch(input, init); span.setAttribute("http.response.status_code", response.status); span.setStatus({ code: SpanStatusCode.OK }); return response; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); }; return tracedFetch; } const value = member(target, prop); const method = asFunction(value); if (method !== void 0) return method.bind(target); return value; } }); } /** * Detection helpers - a binding is recognised by the methods it carries. */ const hasExactMethods = (obj, methods) => hasMethods(obj, methods); /** * Auto-instrument all Cloudflare bindings in the environment * * Detection order (most specific first): * 1. R2 — get, put, delete, list, head * 2. KV — get, put, delete, list (not head) * 3. D1 — prepare, exec * 4. Vectorize — query, insert, upsert, describe * 5. AI — run + (gateway or models discriminator) * 6. Hyperdrive — connect + connectionString + host * 7. Queue Producer — send, sendBatch (not get) * 8. Analytics Engine — writeDataPoint * 9. Images — info, input * 10. Service Binding — fetch (broadest, must be last) * * Not auto-detected (manual only): * - Rate Limiter — limit() alone too generic * - Browser Rendering — indistinguishable from Service Binding */ const envCache = /* @__PURE__ */ new WeakMap(); /** * One Worker binding, read as the type its method set identifies it as. * * SAFETY: each call below has just checked the exact set of methods that * distinguishes one binding kind from every other, in the documented order * (R2 before KV because R2 also has `head`, service bindings last because * `fetch` is the broadest). The wrapper that receives it calls only the * methods that check just found. */ /** Set an attribute from a result field, when the result carries one there. */ function setResultAttr(span, attribute, result, key) { const value = readProperty(result, key); const attributeValue = asString(value) ?? asNumber(value) ?? asBoolean(value); if (attributeValue !== void 0) span.setAttribute(attribute, attributeValue); } function asBinding(value) { return value; } /** A binding is an object; anything else in env is a plain config value. */ function asObjectBinding(value) { return asRecord(value); } function instrumentBindings(env) { const cached = envCache.get(env); if (cached) return cached; const instrumented = {}; for (const [key, entry] of Object.entries(env)) { const value = asObjectBinding(entry); if (!value) { instrumented[key] = entry; continue; } if (isWrapped(value)) { instrumented[key] = value; continue; } if (hasExactMethods(value, [ "get", "put", "delete", "list", "head" ])) { instrumented[key] = instrumentR2(asBinding(value), key); continue; } if (hasExactMethods(value, [ "get", "put", "delete", "list" ]) && !("head" in value)) { instrumented[key] = instrumentKV(asBinding(value), key); continue; } if (hasExactMethods(value, ["prepare", "exec"])) { instrumented[key] = instrumentD1(asBinding(value), key); continue; } if (hasExactMethods(value, [ "query", "insert", "upsert", "describe" ])) { instrumented[key] = instrumentVectorize(asBinding(value), key); continue; } if (hasMethod(value, "run") && ("gateway" in value || "models" in value)) { instrumented[key] = instrumentAI(asBinding(value), key); continue; } if (hasMethod(value, "connect") && "connectionString" in value && "host" in value) { instrumented[key] = instrumentHyperdrive(asBinding(value), key); continue; } if (hasExactMethods(value, ["send", "sendBatch"]) && !("get" in value)) { instrumented[key] = instrumentQueueProducer(asBinding(value), key); continue; } if (hasMethod(value, "writeDataPoint")) { instrumented[key] = instrumentAnalyticsEngine(asBinding(value), key); continue; } if (hasExactMethods(value, ["info", "input"])) { instrumented[key] = instrumentImages(asBinding(value), key); continue; } if (hasMethod(value, "fetch")) { instrumented[key] = instrumentServiceBinding(asBinding(value), key); continue; } instrumented[key] = value; } envCache.set(env, instrumented); return instrumented; } //#endregion //#region src/bindings/rate-limiter.ts /** * Rate Limiter binding instrumentation */ /** * Instrument Rate Limiter binding (manual only — not auto-detected) */ function instrumentRateLimiter(limiter, bindingName) { const name = bindingName || "rate-limiter"; return wrap(limiter, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "limit" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [options] = trapArgs(args); return workerTracer("autotel-edge").startActiveSpan(`RateLimiter ${name}: limit`, { kind: SpanKind.CLIENT, attributes: { "rate_limiter.system": "cloudflare-rate-limiter", "rate_limiter.key": options?.key } }, async (span) => { try { const result = await fnTarget.apply(target, args); setAttr(span, "rate_limiter.success", asBoolean(readProperty(result, "success"))); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion //#region src/bindings/browser-rendering.ts /** * Browser Rendering binding instrumentation */ /** * Instrument Browser Rendering binding (manual only — not auto-detected) */ function instrumentBrowserRendering(browser, bindingName) { const name = bindingName || "browser"; return wrap(browser, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "fetch" && method) return new Proxy(method, { apply: (fnTarget, _thisArg, args) => { const [input] = trapArgs(args); const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; return workerTracer("autotel-edge").startActiveSpan(`BrowserRendering ${name}: fetch`, { kind: SpanKind.CLIENT, attributes: { "browser.system": "cloudflare-browser-rendering", "url.full": url } }, async (span) => { try { const result = await fnTarget.apply(target, args); setAttr(span, "http.response.status_code", numberAt(result, "status")); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); throw error; } finally { span.end(); } }); } }); return value; } }); } //#endregion export { instrumentKV as a, instrumentImages as c, instrumentHyperdrive as d, instrumentVectorize as f, instrumentD1 as i, instrumentAnalyticsEngine as l, instrumentRateLimiter as n, instrumentR2 as o, instrumentAI as p, instrumentBindings as r, instrumentServiceBinding as s, instrumentBrowserRendering as t, instrumentQueueProducer as u };