UNPKG

autotel-cloudflare

Version:

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

1,036 lines (1,030 loc) 34.4 kB
import { a as asRecord, c as asString, f as member, g as readProperty, l as describeValue, n as asBoolean, r as asFunction } from "./values-BC6rdpGG.js"; import { o as wrap, t as toException } from "./exception-D3xOCdBW.js"; import { t as workerTracer } from "./tracer-z8zwRBVS.js"; import { createInitialiser, setConfig } from "autotel-edge"; import { SpanKind, SpanStatusCode, context, propagation } from "@opentelemetry/api"; //#region src/actors/storage.ts /** * Actor storage instrumentation * * Traces operations on actor.storage including SQL queries */ /** * Get the tracer instance */ function getTracer$4() { return workerTracer("autotel-cloudflare-actors"); } /** * Instrument Actor storage for tracing * * Captures: * - SQL query operations * - Key-value operations (if available) */ function instrumentActorStorage(storage, actorInstance, actorClass) { const storageRecord = asRecord(storage); if (!storageRecord) return storage; const actorClassName = asString(member(actorClass, "name")) || "Actor"; const actorName = actorInstance.name || actorClassName; return wrap(storageRecord, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "exec" && method) return function instrumentedExec(query, ...params) { const tracer = getTracer$4(); const spanName = `Actor ${actorName}: storage.exec`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "db.system.name": "sqlite", "db.operation.name": "exec", "db.query.text": query, "db.statement.params_count": params.length } }, (span) => { try { const result = method.call(target, query, ...params); 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 === "query" && method) return function instrumentedQuery(query, ...params) { const tracer = getTracer$4(); const spanName = `Actor ${actorName}: storage.query`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "db.system.name": "sqlite", "db.operation.name": "query", "db.query.text": query, "db.statement.params_count": params.length } }, (span) => { try { const result = method.call(target, query, ...params); 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 === "get" && method) return async function instrumentedGet(key) { const tracer = getTracer$4(); const spanName = `Actor ${actorName}: storage.get`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "db.system.name": "durable_object_storage", "db.operation.name": "get", "db.key": key } }, async (span) => { try { const result = await method.call(target, key); span.setAttributes({ "db.result.found": result !== null && result !== void 0 }); 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 async function instrumentedPut(key, val) { const tracer = getTracer$4(); const spanName = `Actor ${actorName}: storage.put`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "db.system.name": "durable_object_storage", "db.operation.name": "put", "db.key": key, "db.value_type": typeof val } }, async (span) => { try { await method.call(target, key, val); 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(); } }); }; if (prop === "delete" && method) return async function instrumentedDelete(key) { const tracer = getTracer$4(); const spanName = `Actor ${actorName}: storage.delete`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "db.system.name": "durable_object_storage", "db.operation.name": "delete", "db.key": key } }, async (span) => { try { const result = await method.call(target, key); const deleted = asBoolean(result); if (deleted !== void 0) span.setAttributes({ "db.result.deleted": deleted }); 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 method ? method.bind(target) : value; } }); } //#endregion //#region src/actors/alarms.ts /** * Actor alarms instrumentation * * Traces operations on actor.alarms */ /** * Get the tracer instance */ function getTracer$3() { return workerTracer("autotel-cloudflare-actors"); } /** * Instrument Actor alarms for tracing * * Captures: * - set: Schedule a single alarm * - setMultiple: Schedule multiple alarms * - cancel: Cancel an alarm * - cancelAll: Cancel all alarms */ function instrumentActorAlarms(alarms, actorInstance, actorClass) { const alarmsRecord = asRecord(alarms); if (!alarmsRecord) return alarms; const actorClassName = asString(member(actorClass, "name")) || "Actor"; const actorName = actorInstance.name || actorClassName; return wrap(alarmsRecord, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "set" && method) return async function instrumentedSet(...args) { const tracer = getTracer$3(); const spanName = `Actor ${actorName}: alarms.set`; const alarmAttributes = { "actor.name": actorName, "actor.class": actorClassName, "alarm.operation": "set" }; if (args.length > 0) { const firstArg = args[0]; if (typeof firstArg === "number") alarmAttributes["alarm.delay_ms"] = firstArg; else if (firstArg instanceof Date) alarmAttributes["alarm.scheduled_time"] = firstArg.toISOString(); else if (typeof firstArg === "string") alarmAttributes["alarm.cron"] = firstArg; } return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: alarmAttributes }, async (span) => { try { const result = await method.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 === "setMultiple" && method) return async function instrumentedSetMultiple(alarmDefs) { const tracer = getTracer$3(); const spanName = `Actor ${actorName}: alarms.setMultiple`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "alarm.operation": "setMultiple", "alarm.count": Array.isArray(alarmDefs) ? alarmDefs.length : 0 } }, async (span) => { try { const result = await method.call(target, alarmDefs); 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 === "cancel" && method) return async function instrumentedCancel(alarmId) { const tracer = getTracer$3(); const spanName = `Actor ${actorName}: alarms.cancel`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "alarm.operation": "cancel", "alarm.id": alarmId } }, async (span) => { try { const result = await method.call(target, alarmId); 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 === "cancelAll" && method) return async function instrumentedCancelAll() { const tracer = getTracer$3(); const spanName = `Actor ${actorName}: alarms.cancelAll`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "alarm.operation": "cancelAll" } }, async (span) => { try { const result = await method.call(target); 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 method ? method.bind(target) : value; } }); } //#endregion //#region src/actors/sockets.ts /** * Actor sockets instrumentation * * Traces operations on actor.sockets */ /** * Get the tracer instance */ function getTracer$2() { return workerTracer("autotel-cloudflare-actors"); } /** * Instrument Actor sockets for tracing * * Captures: * - acceptWebSocket: Accept an incoming WebSocket connection * - broadcast: Send message to all connected sockets * - send: Send message to a specific socket */ function instrumentActorSockets(sockets, actorInstance, actorClass) { const socketsRecord = asRecord(sockets); if (!socketsRecord) return sockets; const actorClassName = asString(member(actorClass, "name")) || "Actor"; const actorName = actorInstance.name || actorClassName; return wrap(socketsRecord, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "acceptWebSocket" && method) return function instrumentedAcceptWebSocket(request) { const tracer = getTracer$2(); const spanName = `Actor ${actorName}: sockets.acceptWebSocket`; return tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes: { "actor.name": actorName, "actor.class": actorClassName, "websocket.operation": "accept", "url.full": request.url } }, (span) => { try { const result = method.call(target, request); 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 === "broadcast" && method) return function instrumentedBroadcast(message) { const tracer = getTracer$2(); const spanName = `Actor ${actorName}: sockets.broadcast`; tracer.startActiveSpan(spanName, { kind: SpanKind.PRODUCER, attributes: { "actor.name": actorName, "actor.class": actorClassName, "websocket.operation": "broadcast", "websocket.message.type": typeof message, "websocket.message.size": typeof message === "string" ? message.length : message instanceof ArrayBuffer ? message.byteLength : 0 } }, (span) => { try { method.call(target, message); 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(); } }); }; if (prop === "send" && method) return function instrumentedSend(ws, message) { const tracer = getTracer$2(); const spanName = `Actor ${actorName}: sockets.send`; tracer.startActiveSpan(spanName, { kind: SpanKind.PRODUCER, attributes: { "actor.name": actorName, "actor.class": actorClassName, "websocket.operation": "send", "websocket.message.type": typeof message, "websocket.message.size": typeof message === "string" ? message.length : message instanceof ArrayBuffer ? message.byteLength : 0 } }, (span) => { try { method.call(target, ws, message); 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(); } }); }; if (prop === "getConnections" && method) return function instrumentedGetConnections() { const tracer = getTracer$2(); const spanName = `Actor ${actorName}: sockets.getConnections`; return tracer.startActiveSpan(spanName, { kind: SpanKind.CLIENT, attributes: { "actor.name": actorName, "actor.class": actorClassName, "websocket.operation": "getConnections" } }, (span) => { try { const result = method.call(target); if (Array.isArray(result)) span.setAttribute("websocket.connections.count", result.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 method ? method.bind(target) : value; } }); } //#endregion //#region src/actors/instrument-actor.ts /** * Actor class instrumentation for @cloudflare/actors * * Wraps Actor lifecycle methods with OpenTelemetry tracing: * - onInit: Traced as 'actor.lifecycle': 'init' * - onRequest: Traced with full HTTP semantics * - onAlarm: Traced as 'actor.lifecycle': 'alarm' * - onPersist: Traced as 'actor.lifecycle': 'persist' * - WebSocket methods: Traced with socket semantics */ /** * Track cold starts per Actor class */ const coldStarts = /* @__PURE__ */ new WeakMap(); function isColdStart(actorClass) { if (!coldStarts.has(actorClass)) { coldStarts.set(actorClass, true); return true; } return false; } /** * Get the tracer instance */ function getTracer$1() { return workerTracer("autotel-cloudflare-actors"); } /** * Default span name formatter */ function defaultSpanNameFormatter(actorName, actorClass, lifecycle) { return `Actor ${actorName || actorClass}: ${lifecycle}`; } function createActorAttributes(actorInstance, actorClass, lifecycle) { return { "actor.name": actorInstance.name || "unknown", "actor.class": actorClass.name || "Actor", "actor.lifecycle": lifecycle, "actor.coldstart": isColdStart(actorClass), ...actorInstance.identifier && { "actor.identifier": actorInstance.identifier } }; } /** * Instrument the onInit lifecycle method */ function instrumentOnInit(originalMethod, actorInstance, actorClass, options) { return async function instrumentedOnInit() { const tracer = getTracer$1(); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "init") : defaultSpanNameFormatter(actorInstance.name || "", actorClassName, "init"); return tracer.startActiveSpan(spanName, { kind: SpanKind.INTERNAL, attributes: createActorAttributes(actorInstance, actorClass, "init") }, async (span) => { try { await originalMethod.call(actorInstance); 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(); } }); }; } /** * Instrument the onRequest lifecycle method */ function instrumentOnRequest(originalMethod, actorInstance, actorClass, options) { return async function instrumentedOnRequest(request) { const tracer = getTracer$1(); const parentContext = propagation.extract(context.active(), request.headers); const url = new URL(request.url); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "request") : `Actor ${actorInstance.name || actorClassName}: ${request.method} ${url.pathname}`; return tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes: { ...createActorAttributes(actorInstance, actorClass, "request"), "http.request.method": request.method, "url.full": request.url, "url.path": url.pathname, "url.query": url.search } }, parentContext, async (span) => { try { const response = await originalMethod.call(actorInstance, request); span.setAttributes({ "http.response.status_code": response.status }); if (response.ok) span.setStatus({ code: SpanStatusCode.OK }); else span.setStatus({ code: SpanStatusCode.ERROR }); 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(); } }); }; } /** * Instrument the onAlarm lifecycle method */ function instrumentOnAlarm(originalMethod, actorInstance, actorClass, options) { return async function instrumentedOnAlarm(alarmInfo) { const tracer = getTracer$1(); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "alarm") : defaultSpanNameFormatter(actorInstance.name || "", actorClassName, "alarm"); return tracer.startActiveSpan(spanName, { kind: SpanKind.INTERNAL, attributes: { ...createActorAttributes(actorInstance, actorClass, "alarm"), "faas.trigger": "timer" } }, async (span) => { try { await originalMethod.call(actorInstance, alarmInfo); 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(); } }); }; } /** * Instrument the onPersist lifecycle method */ function instrumentOnPersist(originalMethod, actorInstance, actorClass, options) { if (!options.capturePersistEvents) return originalMethod; return function instrumentedOnPersist(key, value) { const tracer = getTracer$1(); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "persist") : `Actor ${actorInstance.name || actorClassName}: persist ${key}`; tracer.startActiveSpan(spanName, { kind: SpanKind.INTERNAL, attributes: { ...createActorAttributes(actorInstance, actorClass, "persist"), "actor.persist.key": key, "actor.persist.value_type": describeValue(value) } }, (span) => { try { originalMethod.call(actorInstance, key, value); 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(); } }); }; } /** * Instrument WebSocket lifecycle methods */ function instrumentWebSocketConnect(originalMethod, actorInstance, actorClass, options) { return function instrumentedWebSocketConnect(ws, request) { const tracer = getTracer$1(); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "websocket.connect") : defaultSpanNameFormatter(actorInstance.name || "", actorClassName, "websocket.connect"); tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes: { ...createActorAttributes(actorInstance, actorClass, "websocket.connect"), "url.full": request.url } }, (span) => { try { originalMethod.call(actorInstance, ws, request); 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(); } }); }; } function instrumentWebSocketMessage(originalMethod, actorInstance, actorClass, options) { return function instrumentedWebSocketMessage(ws, message) { const tracer = getTracer$1(); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "websocket.message") : defaultSpanNameFormatter(actorInstance.name || "", actorClassName, "websocket.message"); tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes: { ...createActorAttributes(actorInstance, actorClass, "websocket.message"), "websocket.message.type": describeValue(message), "websocket.message.size": asString(message)?.length ?? (message instanceof ArrayBuffer ? message.byteLength : 0) } }, (span) => { try { originalMethod.call(actorInstance, ws, message); 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(); } }); }; } function instrumentWebSocketDisconnect(originalMethod, actorInstance, actorClass, options) { return function instrumentedWebSocketDisconnect(ws) { const tracer = getTracer$1(); const actorClassName = actorClass.name || "Actor"; const spanName = options.spanNameFormatter ? options.spanNameFormatter(actorInstance.name || "", "websocket.disconnect") : defaultSpanNameFormatter(actorInstance.name || "", actorClassName, "websocket.disconnect"); tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes: createActorAttributes(actorInstance, actorClass, "websocket.disconnect") }, (span) => { try { originalMethod.call(actorInstance, ws); 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(); } }); }; } /** * Instrument an Actor instance by wrapping all lifecycle methods */ function instrumentActorInstance(actorInstance, _state, _env, actorClass, options) { return wrap(actorInstance, { get(target, prop) { const value = member(target, prop); const method = asFunction(value); if (prop === "onInit" && method) return instrumentOnInit(method.bind(target), target, actorClass, options); if (prop === "onRequest" && method) return instrumentOnRequest(method.bind(target), target, actorClass, options); if (prop === "onAlarm" && method) return instrumentOnAlarm(method.bind(target), target, actorClass, options); if (prop === "onPersist" && method) return instrumentOnPersist(method.bind(target), target, actorClass, options); if (prop === "onWebSocketConnect" && method) return instrumentWebSocketConnect(method.bind(target), target, actorClass, options); if (prop === "onWebSocketMessage" && method) return instrumentWebSocketMessage(method.bind(target), target, actorClass, options); if (prop === "onWebSocketDisconnect" && method) return instrumentWebSocketDisconnect(method.bind(target), target, actorClass, options); if (prop === "storage" && value && options.instrumentStorage !== false) return instrumentActorStorage(value, target, actorClass); if (prop === "alarms" && value && options.instrumentAlarms !== false) return instrumentActorAlarms(value, target, actorClass); if (prop === "sockets" && value && options.instrumentSockets !== false) return instrumentActorSockets(value, target, actorClass); return method ? method.bind(target) : value; } }); } /** * Instrument an Actor class for comprehensive OpenTelemetry tracing * * This wraps the Actor class to automatically trace all lifecycle methods: * - onInit: Actor initialization * - onRequest: HTTP request handling * - onAlarm: Alarm triggers * - onPersist: Property persistence events * - WebSocket methods: Connection, message, disconnect * * It also optionally instruments: * - actor.storage: SQL queries and storage operations * - actor.alarms: Alarm scheduling operations * - actor.sockets: WebSocket operations * * @example * ```typescript * import { Actor } from '@cloudflare/actors' * import { instrumentActor } from 'autotel-cloudflare/actors' * * class Counter extends Actor<Env> { * protected onInit() { * console.log('Counter initialized') * } * * protected onRequest(request: Request) { * return new Response('count: 42') * } * } * * // Wrap the class * export const InstrumentedCounter = instrumentActor(Counter, (env: Env) => ({ * service: { name: 'counter-actor' }, * exporter: { url: env.OTLP_ENDPOINT }, * actors: { * instrumentStorage: true, * capturePersistEvents: true * } * })) * ``` * * @param actorClass - The Actor class to instrument * @param config - Configuration (static object or function) * @returns Instrumented Actor class */ function instrumentActor(actorClass, config) { const initialiser = createInitialiser(config); const defaultOptions = { instrumentStorage: true, instrumentAlarms: true, instrumentSockets: true, capturePersistEvents: true }; return wrap(actorClass, { construct(target, [state, env]) { const actorOptions = readProperty(typeof config === "function" ? config(env, { id: state.id.toString(), name: state.id.name }) : config, "actors"); const options = { ...defaultOptions, ...actorOptions }; const trigger = { id: state.id.toString(), name: state.id.name }; const context$1 = setConfig(initialiser(env, trigger)); return instrumentActorInstance(context.with(context$1, () => { return new target(state, env); }), state, env, actorClass, options); } }); } //#endregion //#region src/actors/traced-handler.ts /** * Traced handler wrapper for @cloudflare/actors * * Wraps the Actors handler() to provide: * - Root span for the entire request lifecycle * - Actor name extraction and correlation * - Request routing tracing */ /** * Get the tracer instance */ function getTracer() { return workerTracer("autotel-cloudflare-actors"); } /** * Create a traced handler that combines Actor instrumentation with request tracing * * This is an all-in-one wrapper that: * 1. Initializes telemetry for the Worker * 2. Creates a root span for each incoming request * 3. Extracts the Actor name using `nameFromRequest` * 4. Instruments the Actor class with lifecycle tracing * 5. Routes the request to the instrumented Actor * * @example * ```typescript * import { Actor } from '@cloudflare/actors' * import { tracedHandler } from 'autotel-cloudflare/actors' * * class MyActor extends Actor<Env> { * protected onRequest(request: Request) { * return new Response('Hello!') * } * } * * // Export the Actor class and use tracedHandler * export { MyActor } * export default tracedHandler(MyActor, (env) => ({ * service: { name: 'my-actor-service' }, * exporter: { url: env.OTLP_ENDPOINT } * })) * ``` * * @param actorClass - The Actor class to handle requests * @param config - Configuration (static object or function) * @returns A Worker handler with full tracing */ function tracedHandler(actorClass, config) { const initialiser = createInitialiser(config); return { async fetch(request, env, _ctx) { const configContext = setConfig(initialiser(env, { type: "http" })); const parentContext = propagation.extract(configContext, request.headers); const tracer = getTracer(); const url = new URL(request.url); const actorClassName = actorClass.name || "Actor"; let actorName; try { if (actorClass.nameFromRequest) actorName = await actorClass.nameFromRequest(request); } catch { actorName = void 0; } const spanName = `${actorClassName} handler: ${request.method} ${url.pathname}`; return tracer.startActiveSpan(spanName, { kind: SpanKind.SERVER, attributes: { "http.request.method": request.method, "url.full": request.url, "url.path": url.pathname, "url.query": url.search, "actor.class": actorClassName, ...actorName && { "actor.name": actorName }, "faas.trigger": "http" } }, parentContext, async (span) => { try { const envObj = env; const bindingName = Object.keys(envObj).find((key) => { const binding = env.__DURABLE_OBJECT_BINDINGS; return key === actorClassName || binding?.[key]?.class_name === actorClassName; }); if (!bindingName) { span.setStatus({ code: SpanStatusCode.ERROR, message: `No Durable Object binding found for ${actorClassName}` }); return Response.json({ error: "Configuration Error", message: `No Durable Object binding found for actor class ${actorClassName}` }, { status: 500, headers: { "Content-Type": "application/json" } }); } const namespace = envObj[bindingName]; const idString = actorName || "default"; const locationHint = actorClass.configuration?.(request)?.locationHint; const stub = namespace.getByName(idString, { locationHint }); if ("setName" in stub && typeof stub.setName === "function") stub.setName(idString); const headers = new Headers(request.headers); propagation.inject(context.active(), headers); const tracedRequest = new Request(request.url, { method: request.method, headers, body: request.body, redirect: request.redirect }); const response = await stub.fetch(tracedRequest); span.setAttributes({ "http.response.status_code": response.status, "actor.name": idString }); if (response.ok) span.setStatus({ code: SpanStatusCode.OK }); else span.setStatus({ code: SpanStatusCode.ERROR }); return response; } catch (error) { span.recordException(toException(error)); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) }); return Response.json({ error: "Internal Server Error", message: error instanceof Error ? error.message : "Unknown error" }, { status: 500, headers: { "Content-Type": "application/json" } }); } finally { span.end(); } }); } }; } /** * Alternative: Create a handler wrapper that uses the existing @cloudflare/actors handler * * This is useful if you want to use the original handler() but add tracing around it. * * @example * ```typescript * import { Actor, handler } from '@cloudflare/actors' * import { wrapHandler } from 'autotel-cloudflare/actors' * * class MyActor extends Actor<Env> {} * * export { MyActor } * export default wrapHandler(handler(MyActor), (env) => ({ * service: { name: 'my-service' } * })) * ``` */ function wrapHandler(originalHandler, config) { const initialiser = createInitialiser(config); return { async fetch(request, env, ctx) { const configContext = setConfig(initialiser(env, { type: "http" })); const parentContext = propagation.extract(configContext, request.headers); const tracer = getTracer(); const url = new URL(request.url); return tracer.startActiveSpan(`Worker: ${request.method} ${url.pathname}`, { kind: SpanKind.SERVER, attributes: { "http.request.method": request.method, "url.full": request.url, "url.path": url.pathname, "url.query": url.search, "faas.trigger": "http" } }, parentContext, async (span) => { try { const headers = new Headers(request.headers); propagation.inject(context.active(), headers); const tracedRequest = new Request(request.url, { method: request.method, headers, body: request.body, redirect: request.redirect }); const response = await originalHandler.fetch(tracedRequest, env, ctx); span.setAttributes({ "http.response.status_code": response.status }); if (response.ok) span.setStatus({ code: SpanStatusCode.OK }); else span.setStatus({ code: SpanStatusCode.ERROR }); 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(); } }); } }; } //#endregion export { instrumentActor, instrumentActorAlarms, instrumentActorSockets, instrumentActorStorage, tracedHandler, wrapHandler };