autotel-cloudflare 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +432 -0
  3. package/dist/actors.d.ts +248 -0
  4. package/dist/actors.js +1030 -0
  5. package/dist/actors.js.map +1 -0
  6. package/dist/agents.d.ts +219 -0
  7. package/dist/agents.js +276 -0
  8. package/dist/agents.js.map +1 -0
  9. package/dist/bindings.d.ts +40 -0
  10. package/dist/bindings.js +4 -0
  11. package/dist/bindings.js.map +1 -0
  12. package/dist/chunk-JDPN3HND.js +520 -0
  13. package/dist/chunk-JDPN3HND.js.map +1 -0
  14. package/dist/chunk-QXFYTHQF.js +298 -0
  15. package/dist/chunk-QXFYTHQF.js.map +1 -0
  16. package/dist/chunk-SKKRPS5K.js +50 -0
  17. package/dist/chunk-SKKRPS5K.js.map +1 -0
  18. package/dist/events.d.ts +1 -0
  19. package/dist/events.js +3 -0
  20. package/dist/events.js.map +1 -0
  21. package/dist/handlers.d.ts +121 -0
  22. package/dist/handlers.js +4 -0
  23. package/dist/handlers.js.map +1 -0
  24. package/dist/index.d.ts +144 -0
  25. package/dist/index.js +576 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/logger.d.ts +1 -0
  28. package/dist/logger.js +3 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/sampling.d.ts +4 -0
  31. package/dist/sampling.js +3 -0
  32. package/dist/sampling.js.map +1 -0
  33. package/dist/testing.d.ts +1 -0
  34. package/dist/testing.js +3 -0
  35. package/dist/testing.js.map +1 -0
  36. package/package.json +107 -0
  37. package/src/actors/alarms.ts +225 -0
  38. package/src/actors/index.ts +36 -0
  39. package/src/actors/instrument-actor.test.ts +179 -0
  40. package/src/actors/instrument-actor.ts +574 -0
  41. package/src/actors/sockets.ts +217 -0
  42. package/src/actors/storage.ts +263 -0
  43. package/src/actors/traced-handler.ts +300 -0
  44. package/src/actors/types.ts +98 -0
  45. package/src/actors.ts +50 -0
  46. package/src/agents/index.ts +42 -0
  47. package/src/agents/otel-observability.test.ts +329 -0
  48. package/src/agents/otel-observability.ts +465 -0
  49. package/src/agents/types.ts +167 -0
  50. package/src/agents.ts +76 -0
  51. package/src/bindings/bindings.ts +621 -0
  52. package/src/bindings/common.ts +75 -0
  53. package/src/bindings/index.ts +12 -0
  54. package/src/bindings.ts +6 -0
  55. package/src/events.ts +6 -0
  56. package/src/global/cache.test.ts +292 -0
  57. package/src/global/cache.ts +164 -0
  58. package/src/global/fetch.test.ts +344 -0
  59. package/src/global/fetch.ts +134 -0
  60. package/src/global/index.ts +7 -0
  61. package/src/handlers/durable-objects.test.ts +524 -0
  62. package/src/handlers/durable-objects.ts +250 -0
  63. package/src/handlers/index.ts +6 -0
  64. package/src/handlers/workflows.ts +318 -0
  65. package/src/handlers.ts +6 -0
  66. package/src/index.ts +57 -0
  67. package/src/logger.ts +6 -0
  68. package/src/sampling.ts +6 -0
  69. package/src/testing.ts +6 -0
  70. package/src/wrappers/index.ts +8 -0
  71. package/src/wrappers/instrument.integration.test.ts +468 -0
  72. package/src/wrappers/instrument.ts +643 -0
  73. package/src/wrappers/wrap-do.ts +34 -0
  74. package/src/wrappers/wrap-module.ts +37 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Traced handler wrapper for @cloudflare/actors
3
+ *
4
+ * Wraps the Actors handler() to provide:
5
+ * - Root span for the entire request lifecycle
6
+ * - Actor name extraction and correlation
7
+ * - Request routing tracing
8
+ */
9
+
10
+ import {
11
+ trace,
12
+ context as api_context,
13
+ propagation,
14
+ SpanStatusCode,
15
+ SpanKind,
16
+ } from '@opentelemetry/api';
17
+ import type { ConfigurationOption } from 'autotel-edge';
18
+ import { createInitialiser, setConfig, WorkerTracer } from 'autotel-edge';
19
+ import type { ActorConfig, ActorConstructor, ActorLike } from './types';
20
+
21
+ /**
22
+ * Get the tracer instance
23
+ */
24
+ function getTracer(): WorkerTracer {
25
+ return trace.getTracer('autotel-cloudflare-actors') as WorkerTracer;
26
+ }
27
+
28
+ /**
29
+ * Worker handler type matching @cloudflare/actors output
30
+ */
31
+ interface WorkerHandler<E = unknown> {
32
+ fetch(request: Request, env: E, ctx: ExecutionContext): Promise<Response>;
33
+ }
34
+
35
+ /**
36
+ * Create a traced handler that combines Actor instrumentation with request tracing
37
+ *
38
+ * This is an all-in-one wrapper that:
39
+ * 1. Initializes telemetry for the Worker
40
+ * 2. Creates a root span for each incoming request
41
+ * 3. Extracts the Actor name using `nameFromRequest`
42
+ * 4. Instruments the Actor class with lifecycle tracing
43
+ * 5. Routes the request to the instrumented Actor
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * import { Actor } from '@cloudflare/actors'
48
+ * import { tracedHandler } from 'autotel-cloudflare/actors'
49
+ *
50
+ * class MyActor extends Actor<Env> {
51
+ * protected onRequest(request: Request) {
52
+ * return new Response('Hello!')
53
+ * }
54
+ * }
55
+ *
56
+ * // Export the Actor class and use tracedHandler
57
+ * export { MyActor }
58
+ * export default tracedHandler(MyActor, (env) => ({
59
+ * service: { name: 'my-actor-service' },
60
+ * exporter: { url: env.OTLP_ENDPOINT }
61
+ * }))
62
+ * ```
63
+ *
64
+ * @param actorClass - The Actor class to handle requests
65
+ * @param config - Configuration (static object or function)
66
+ * @returns A Worker handler with full tracing
67
+ */
68
+ export function tracedHandler<E, A extends ActorLike>(
69
+ actorClass: ActorConstructor<A> & {
70
+ nameFromRequest?(request: Request): Promise<string | undefined>;
71
+ configuration?(request: Request): { locationHint?: DurableObjectLocationHint };
72
+ },
73
+ config: ActorConfig | ((env: E, trigger?: unknown) => ActorConfig),
74
+ ): WorkerHandler<E> {
75
+ const initialiser = createInitialiser(config as ConfigurationOption);
76
+
77
+ // Note: The Actor class instrumentation happens at the DO level, not here.
78
+ // This handler wraps the Worker entrypoint that routes to the DO.
79
+
80
+ return {
81
+ async fetch(request: Request, env: E, _ctx: ExecutionContext): Promise<Response> {
82
+ // Initialize telemetry for this request
83
+ const telemetryConfig = initialiser(env, { type: 'http' });
84
+ const configContext = setConfig(telemetryConfig);
85
+
86
+ // Extract parent context from request headers
87
+ const parentContext = propagation.extract(configContext, request.headers);
88
+
89
+ const tracer = getTracer();
90
+ const url = new URL(request.url);
91
+ const actorClassName = actorClass.name || 'Actor';
92
+
93
+ // Get actor name from request (using the Actor's static method if available)
94
+ let actorName: string | undefined;
95
+ try {
96
+ if (actorClass.nameFromRequest) {
97
+ actorName = await actorClass.nameFromRequest(request);
98
+ }
99
+ } catch {
100
+ actorName = undefined;
101
+ }
102
+
103
+ const spanName = `${actorClassName} handler: ${request.method} ${url.pathname}`;
104
+
105
+ return tracer.startActiveSpan(
106
+ spanName,
107
+ {
108
+ kind: SpanKind.SERVER,
109
+ attributes: {
110
+ 'http.request.method': request.method,
111
+ 'url.full': request.url,
112
+ 'url.path': url.pathname,
113
+ 'url.query': url.search,
114
+ 'actor.class': actorClassName,
115
+ ...(actorName && { 'actor.name': actorName }),
116
+ 'faas.trigger': 'http',
117
+ },
118
+ },
119
+ parentContext,
120
+ async (span) => {
121
+ try {
122
+ // Get Actor stub using the same pattern as @cloudflare/actors handler()
123
+ const envObj = env as Record<string, DurableObjectNamespace>;
124
+
125
+ // Find the binding name for this Actor class
126
+ const bindingName = Object.keys(envObj).find((key) => {
127
+ const binding = (env as Record<string, unknown>).__DURABLE_OBJECT_BINDINGS as
128
+ | Record<string, { class_name?: string }>
129
+ | undefined;
130
+ return key === actorClassName || binding?.[key]?.class_name === actorClassName;
131
+ });
132
+
133
+ if (!bindingName) {
134
+ span.setStatus({
135
+ code: SpanStatusCode.ERROR,
136
+ message: `No Durable Object binding found for ${actorClassName}`,
137
+ });
138
+ return Response.json(
139
+ {
140
+ error: 'Configuration Error',
141
+ message: `No Durable Object binding found for actor class ${actorClassName}`,
142
+ },
143
+ { status: 500, headers: { 'Content-Type': 'application/json' } },
144
+ );
145
+ }
146
+
147
+ const namespace = envObj[bindingName];
148
+ const idString = actorName || 'default';
149
+
150
+ // Get location hint if available
151
+ const locationHint = actorClass.configuration?.(request)?.locationHint;
152
+
153
+ // Get the Durable Object stub
154
+ const stub = namespace.getByName(idString, { locationHint });
155
+
156
+ // Set the name on the stub (as @cloudflare/actors does)
157
+ if ('setName' in stub && typeof stub.setName === 'function') {
158
+ (stub as unknown as { setName(id: string): void }).setName(idString);
159
+ }
160
+
161
+ // Inject trace context into the request for propagation to the DO
162
+ const headers = new Headers(request.headers);
163
+ propagation.inject(api_context.active(), headers);
164
+
165
+ // Create a new request with the injected headers
166
+ const tracedRequest = new Request(request.url, {
167
+ method: request.method,
168
+ headers,
169
+ body: request.body,
170
+ redirect: request.redirect,
171
+ });
172
+
173
+ // Forward the request to the Durable Object
174
+ const response = await stub.fetch(tracedRequest);
175
+
176
+ span.setAttributes({
177
+ 'http.response.status_code': response.status,
178
+ 'actor.name': idString,
179
+ });
180
+
181
+ if (response.ok) {
182
+ span.setStatus({ code: SpanStatusCode.OK });
183
+ } else {
184
+ span.setStatus({ code: SpanStatusCode.ERROR });
185
+ }
186
+
187
+ return response;
188
+ } catch (error) {
189
+ span.recordException(error as Error);
190
+ span.setStatus({
191
+ code: SpanStatusCode.ERROR,
192
+ message: error instanceof Error ? error.message : String(error),
193
+ });
194
+
195
+ return Response.json(
196
+ {
197
+ error: 'Internal Server Error',
198
+ message: error instanceof Error ? error.message : 'Unknown error',
199
+ },
200
+ { status: 500, headers: { 'Content-Type': 'application/json' } },
201
+ );
202
+ } finally {
203
+ span.end();
204
+ }
205
+ },
206
+ );
207
+ },
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Alternative: Create a handler wrapper that uses the existing @cloudflare/actors handler
213
+ *
214
+ * This is useful if you want to use the original handler() but add tracing around it.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * import { Actor, handler } from '@cloudflare/actors'
219
+ * import { wrapHandler } from 'autotel-cloudflare/actors'
220
+ *
221
+ * class MyActor extends Actor<Env> {}
222
+ *
223
+ * export { MyActor }
224
+ * export default wrapHandler(handler(MyActor), (env) => ({
225
+ * service: { name: 'my-service' }
226
+ * }))
227
+ * ```
228
+ */
229
+ export function wrapHandler<E>(
230
+ originalHandler: WorkerHandler<E>,
231
+ config: ActorConfig | ((env: E, trigger?: unknown) => ActorConfig),
232
+ ): WorkerHandler<E> {
233
+ const initialiser = createInitialiser(config as ConfigurationOption);
234
+
235
+ return {
236
+ async fetch(request: Request, env: E, ctx: ExecutionContext): Promise<Response> {
237
+ // Initialize telemetry for this request
238
+ const telemetryConfig = initialiser(env, { type: 'http' });
239
+ const configContext = setConfig(telemetryConfig);
240
+
241
+ // Extract parent context from request headers
242
+ const parentContext = propagation.extract(configContext, request.headers);
243
+
244
+ const tracer = getTracer();
245
+ const url = new URL(request.url);
246
+
247
+ return tracer.startActiveSpan(
248
+ `Worker: ${request.method} ${url.pathname}`,
249
+ {
250
+ kind: SpanKind.SERVER,
251
+ attributes: {
252
+ 'http.request.method': request.method,
253
+ 'url.full': request.url,
254
+ 'url.path': url.pathname,
255
+ 'url.query': url.search,
256
+ 'faas.trigger': 'http',
257
+ },
258
+ },
259
+ parentContext,
260
+ async (span) => {
261
+ try {
262
+ // Inject trace context into request
263
+ const headers = new Headers(request.headers);
264
+ propagation.inject(api_context.active(), headers);
265
+
266
+ const tracedRequest = new Request(request.url, {
267
+ method: request.method,
268
+ headers,
269
+ body: request.body,
270
+ redirect: request.redirect,
271
+ });
272
+
273
+ const response = await originalHandler.fetch(tracedRequest, env, ctx);
274
+
275
+ span.setAttributes({
276
+ 'http.response.status_code': response.status,
277
+ });
278
+
279
+ if (response.ok) {
280
+ span.setStatus({ code: SpanStatusCode.OK });
281
+ } else {
282
+ span.setStatus({ code: SpanStatusCode.ERROR });
283
+ }
284
+
285
+ return response;
286
+ } catch (error) {
287
+ span.recordException(error as Error);
288
+ span.setStatus({
289
+ code: SpanStatusCode.ERROR,
290
+ message: error instanceof Error ? error.message : String(error),
291
+ });
292
+ throw error;
293
+ } finally {
294
+ span.end();
295
+ }
296
+ },
297
+ );
298
+ },
299
+ };
300
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Type definitions for @cloudflare/actors integration
3
+ */
4
+
5
+ import type { ConfigurationOption } from 'autotel-edge';
6
+
7
+ /**
8
+ * Actor-specific instrumentation options
9
+ */
10
+ export interface ActorInstrumentationOptions {
11
+ /**
12
+ * Whether to instrument storage operations (sql queries, etc.)
13
+ * @default true
14
+ */
15
+ instrumentStorage?: boolean;
16
+
17
+ /**
18
+ * Whether to instrument alarm operations
19
+ * @default true
20
+ */
21
+ instrumentAlarms?: boolean;
22
+
23
+ /**
24
+ * Whether to instrument socket operations
25
+ * @default true
26
+ */
27
+ instrumentSockets?: boolean;
28
+
29
+ /**
30
+ * Whether to capture persist events as spans
31
+ * @default true
32
+ */
33
+ capturePersistEvents?: boolean;
34
+
35
+ /**
36
+ * Custom span name formatter for lifecycle methods
37
+ */
38
+ spanNameFormatter?: (actorName: string, lifecycle: string) => string;
39
+ }
40
+
41
+ /**
42
+ * Actor-specific configuration
43
+ * Can be a static config object with actors options, or a function that returns config
44
+ */
45
+ export type ActorConfig = ConfigurationOption & {
46
+ /**
47
+ * Actor-specific instrumentation options
48
+ */
49
+ actors?: ActorInstrumentationOptions;
50
+ };
51
+
52
+ /**
53
+ * Actor lifecycle events that can be traced
54
+ */
55
+ export type ActorLifecycle =
56
+ | 'init'
57
+ | 'request'
58
+ | 'alarm'
59
+ | 'persist'
60
+ | 'websocket.connect'
61
+ | 'websocket.message'
62
+ | 'websocket.disconnect'
63
+ | 'websocket.upgrade'
64
+ | 'destroy';
65
+
66
+ /**
67
+ * Semantic attributes for Actor spans
68
+ */
69
+ export interface ActorSpanAttributes {
70
+ 'actor.name': string;
71
+ 'actor.class': string;
72
+ 'actor.lifecycle': ActorLifecycle;
73
+ 'actor.coldstart'?: boolean;
74
+ 'actor.identifier'?: string;
75
+ 'actor.tracking.enabled'?: boolean;
76
+ }
77
+
78
+ /**
79
+ * Minimal interface matching @cloudflare/actors Actor class
80
+ * We don't import the actual type to avoid coupling
81
+ */
82
+ export interface ActorLike {
83
+ name?: string;
84
+ identifier?: string;
85
+ storage?: unknown;
86
+ alarms?: unknown;
87
+ sockets?: unknown;
88
+ fetch?(request: Request): Promise<Response>;
89
+ alarm?(alarmInfo?: unknown): Promise<void>;
90
+ }
91
+
92
+ /**
93
+ * Constructor type for Actor classes
94
+ */
95
+ export type ActorConstructor<T extends ActorLike = ActorLike> = new (
96
+ state: DurableObjectState,
97
+ env: unknown,
98
+ ) => T;
package/src/actors.ts ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @cloudflare/actors integration entry point
3
+ *
4
+ * Provides deep OpenTelemetry instrumentation for the Cloudflare Actors framework.
5
+ *
6
+ * @example Basic Usage
7
+ * ```typescript
8
+ * import { Actor } from '@cloudflare/actors'
9
+ * import { instrumentActor, tracedHandler } from 'autotel-cloudflare/actors'
10
+ *
11
+ * class MyActor extends Actor<Env> {
12
+ * protected onInit() {
13
+ * // Automatically traced
14
+ * }
15
+ *
16
+ * protected onRequest(request: Request) {
17
+ * // Automatically traced with HTTP semantics
18
+ * return new Response('Hello!')
19
+ * }
20
+ * }
21
+ *
22
+ * export { MyActor }
23
+ * export default tracedHandler(MyActor, (env) => ({
24
+ * service: { name: 'my-actor-service' },
25
+ * exporter: { url: env.OTLP_ENDPOINT }
26
+ * }))
27
+ * ```
28
+ *
29
+ * @example With Existing handler()
30
+ * ```typescript
31
+ * import { Actor, handler } from '@cloudflare/actors'
32
+ * import { wrapHandler } from 'autotel-cloudflare/actors'
33
+ *
34
+ * class MyActor extends Actor<Env> {}
35
+ *
36
+ * export { MyActor }
37
+ * export default wrapHandler(handler(MyActor), (env) => ({
38
+ * service: { name: 'my-service' }
39
+ * }))
40
+ * ```
41
+ *
42
+ * @packageDocumentation
43
+ */
44
+
45
+ export { instrumentActor } from './actors/instrument-actor';
46
+ export { tracedHandler, wrapHandler } from './actors/traced-handler';
47
+ export { instrumentActorStorage } from './actors/storage';
48
+ export { instrumentActorAlarms } from './actors/alarms';
49
+ export { instrumentActorSockets } from './actors/sockets';
50
+ export type { ActorConfig, ActorInstrumentationOptions, ActorLifecycle } from './actors/types';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Cloudflare Agents SDK observability integration
3
+ *
4
+ * Provides an OpenTelemetry-based Observability implementation for the
5
+ * Cloudflare Agents SDK (https://github.com/cloudflare/agents).
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { Agent } from 'agents'
10
+ * import { createOtelObservability } from 'autotel-cloudflare/agents'
11
+ *
12
+ * class MyAgent extends Agent<Env> {
13
+ * // Override the observability property with OpenTelemetry
14
+ * observability = createOtelObservability({
15
+ * service: { name: 'my-agent' }
16
+ * })
17
+ *
18
+ * @callable()
19
+ * async doSomething() {
20
+ * // This RPC call will be automatically traced
21
+ * return 'done'
22
+ * }
23
+ * }
24
+ * ```
25
+ *
26
+ * @packageDocumentation
27
+ */
28
+
29
+ export {
30
+ createOtelObservability,
31
+ createOtelObservabilityFromEnv,
32
+ OtelObservability,
33
+ } from './otel-observability';
34
+ export type {
35
+ OtelObservabilityConfig,
36
+ AgentObservabilityEvent,
37
+ MCPObservabilityEvent,
38
+ ObservabilityEvent,
39
+ Observability,
40
+ AgentInstrumentationOptions,
41
+ AgentSpanAttributes,
42
+ } from './types';