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
JavaScript
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 };