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.
- package/LICENSE +21 -0
- package/README.md +432 -0
- package/dist/actors.d.ts +248 -0
- package/dist/actors.js +1030 -0
- package/dist/actors.js.map +1 -0
- package/dist/agents.d.ts +219 -0
- package/dist/agents.js +276 -0
- package/dist/agents.js.map +1 -0
- package/dist/bindings.d.ts +40 -0
- package/dist/bindings.js +4 -0
- package/dist/bindings.js.map +1 -0
- package/dist/chunk-JDPN3HND.js +520 -0
- package/dist/chunk-JDPN3HND.js.map +1 -0
- package/dist/chunk-QXFYTHQF.js +298 -0
- package/dist/chunk-QXFYTHQF.js.map +1 -0
- package/dist/chunk-SKKRPS5K.js +50 -0
- package/dist/chunk-SKKRPS5K.js.map +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.js +3 -0
- package/dist/events.js.map +1 -0
- package/dist/handlers.d.ts +121 -0
- package/dist/handlers.js +4 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +576 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +4 -0
- package/dist/sampling.js +3 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/package.json +107 -0
- package/src/actors/alarms.ts +225 -0
- package/src/actors/index.ts +36 -0
- package/src/actors/instrument-actor.test.ts +179 -0
- package/src/actors/instrument-actor.ts +574 -0
- package/src/actors/sockets.ts +217 -0
- package/src/actors/storage.ts +263 -0
- package/src/actors/traced-handler.ts +300 -0
- package/src/actors/types.ts +98 -0
- package/src/actors.ts +50 -0
- package/src/agents/index.ts +42 -0
- package/src/agents/otel-observability.test.ts +329 -0
- package/src/agents/otel-observability.ts +465 -0
- package/src/agents/types.ts +167 -0
- package/src/agents.ts +76 -0
- package/src/bindings/bindings.ts +621 -0
- package/src/bindings/common.ts +75 -0
- package/src/bindings/index.ts +12 -0
- package/src/bindings.ts +6 -0
- package/src/events.ts +6 -0
- package/src/global/cache.test.ts +292 -0
- package/src/global/cache.ts +164 -0
- package/src/global/fetch.test.ts +344 -0
- package/src/global/fetch.ts +134 -0
- package/src/global/index.ts +7 -0
- package/src/handlers/durable-objects.test.ts +524 -0
- package/src/handlers/durable-objects.ts +250 -0
- package/src/handlers/index.ts +6 -0
- package/src/handlers/workflows.ts +318 -0
- package/src/handlers.ts +6 -0
- package/src/index.ts +57 -0
- package/src/logger.ts +6 -0
- package/src/sampling.ts +6 -0
- package/src/testing.ts +6 -0
- package/src/wrappers/index.ts +8 -0
- package/src/wrappers/instrument.integration.test.ts +468 -0
- package/src/wrappers/instrument.ts +643 -0
- package/src/wrappers/wrap-do.ts +34 -0
- package/src/wrappers/wrap-module.ts +37 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actor class instrumentation for @cloudflare/actors
|
|
3
|
+
*
|
|
4
|
+
* Wraps Actor lifecycle methods with OpenTelemetry tracing:
|
|
5
|
+
* - onInit: Traced as 'actor.lifecycle': 'init'
|
|
6
|
+
* - onRequest: Traced with full HTTP semantics
|
|
7
|
+
* - onAlarm: Traced as 'actor.lifecycle': 'alarm'
|
|
8
|
+
* - onPersist: Traced as 'actor.lifecycle': 'persist'
|
|
9
|
+
* - WebSocket methods: Traced with socket semantics
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
trace,
|
|
14
|
+
context as api_context,
|
|
15
|
+
propagation,
|
|
16
|
+
SpanStatusCode,
|
|
17
|
+
SpanKind,
|
|
18
|
+
} from '@opentelemetry/api';
|
|
19
|
+
import type { ConfigurationOption } from 'autotel-edge';
|
|
20
|
+
import { createInitialiser, setConfig, WorkerTracer } from 'autotel-edge';
|
|
21
|
+
import { wrap } from '../bindings/common';
|
|
22
|
+
import type {
|
|
23
|
+
ActorConfig,
|
|
24
|
+
ActorConstructor,
|
|
25
|
+
ActorLike,
|
|
26
|
+
ActorLifecycle,
|
|
27
|
+
ActorInstrumentationOptions,
|
|
28
|
+
} from './types';
|
|
29
|
+
import { instrumentActorStorage } from './storage';
|
|
30
|
+
import { instrumentActorAlarms } from './alarms';
|
|
31
|
+
import { instrumentActorSockets } from './sockets';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Track cold starts per Actor class
|
|
35
|
+
*/
|
|
36
|
+
const coldStarts = new WeakMap<object, boolean>();
|
|
37
|
+
|
|
38
|
+
function isColdStart(actorClass: object): boolean {
|
|
39
|
+
if (!coldStarts.has(actorClass)) {
|
|
40
|
+
coldStarts.set(actorClass, true);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the tracer instance
|
|
48
|
+
*/
|
|
49
|
+
function getTracer(): WorkerTracer {
|
|
50
|
+
return trace.getTracer('autotel-cloudflare-actors') as WorkerTracer;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Default span name formatter
|
|
55
|
+
*/
|
|
56
|
+
function defaultSpanNameFormatter(
|
|
57
|
+
actorName: string,
|
|
58
|
+
actorClass: string,
|
|
59
|
+
lifecycle: ActorLifecycle,
|
|
60
|
+
): string {
|
|
61
|
+
const displayName = actorName || actorClass;
|
|
62
|
+
return `Actor ${displayName}: ${lifecycle}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create base Actor span attributes
|
|
67
|
+
*/
|
|
68
|
+
function createActorAttributes(
|
|
69
|
+
actorInstance: ActorLike,
|
|
70
|
+
actorClass: object,
|
|
71
|
+
lifecycle: ActorLifecycle,
|
|
72
|
+
): Record<string, string | boolean | number> {
|
|
73
|
+
return {
|
|
74
|
+
'actor.name': actorInstance.name || 'unknown',
|
|
75
|
+
'actor.class': (actorClass as { name?: string }).name || 'Actor',
|
|
76
|
+
'actor.lifecycle': lifecycle,
|
|
77
|
+
'actor.coldstart': isColdStart(actorClass),
|
|
78
|
+
...(actorInstance.identifier && { 'actor.identifier': actorInstance.identifier }),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Instrument the onInit lifecycle method
|
|
84
|
+
*/
|
|
85
|
+
function instrumentOnInit(
|
|
86
|
+
originalMethod: () => Promise<void>,
|
|
87
|
+
actorInstance: ActorLike,
|
|
88
|
+
actorClass: object,
|
|
89
|
+
options: ActorInstrumentationOptions,
|
|
90
|
+
): () => Promise<void> {
|
|
91
|
+
return async function instrumentedOnInit(): Promise<void> {
|
|
92
|
+
const tracer = getTracer();
|
|
93
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
94
|
+
const spanName = options.spanNameFormatter
|
|
95
|
+
? options.spanNameFormatter(actorInstance.name || '', 'init')
|
|
96
|
+
: defaultSpanNameFormatter(actorInstance.name || '', actorClassName, 'init');
|
|
97
|
+
|
|
98
|
+
return tracer.startActiveSpan(
|
|
99
|
+
spanName,
|
|
100
|
+
{
|
|
101
|
+
kind: SpanKind.INTERNAL,
|
|
102
|
+
attributes: createActorAttributes(actorInstance, actorClass, 'init'),
|
|
103
|
+
},
|
|
104
|
+
async (span) => {
|
|
105
|
+
try {
|
|
106
|
+
await originalMethod.call(actorInstance);
|
|
107
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
span.recordException(error as Error);
|
|
110
|
+
span.setStatus({
|
|
111
|
+
code: SpanStatusCode.ERROR,
|
|
112
|
+
message: error instanceof Error ? error.message : String(error),
|
|
113
|
+
});
|
|
114
|
+
throw error;
|
|
115
|
+
} finally {
|
|
116
|
+
span.end();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Instrument the onRequest lifecycle method
|
|
125
|
+
*/
|
|
126
|
+
function instrumentOnRequest(
|
|
127
|
+
originalMethod: (request: Request) => Promise<Response>,
|
|
128
|
+
actorInstance: ActorLike,
|
|
129
|
+
actorClass: object,
|
|
130
|
+
options: ActorInstrumentationOptions,
|
|
131
|
+
): (request: Request) => Promise<Response> {
|
|
132
|
+
return async function instrumentedOnRequest(request: Request): Promise<Response> {
|
|
133
|
+
const tracer = getTracer();
|
|
134
|
+
|
|
135
|
+
// Extract parent context from request headers
|
|
136
|
+
const parentContext = propagation.extract(api_context.active(), request.headers);
|
|
137
|
+
|
|
138
|
+
const url = new URL(request.url);
|
|
139
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
140
|
+
const spanName = options.spanNameFormatter
|
|
141
|
+
? options.spanNameFormatter(actorInstance.name || '', 'request')
|
|
142
|
+
: `Actor ${actorInstance.name || actorClassName}: ${request.method} ${url.pathname}`;
|
|
143
|
+
|
|
144
|
+
return tracer.startActiveSpan(
|
|
145
|
+
spanName,
|
|
146
|
+
{
|
|
147
|
+
kind: SpanKind.SERVER,
|
|
148
|
+
attributes: {
|
|
149
|
+
...createActorAttributes(actorInstance, actorClass, 'request'),
|
|
150
|
+
'http.request.method': request.method,
|
|
151
|
+
'url.full': request.url,
|
|
152
|
+
'url.path': url.pathname,
|
|
153
|
+
'url.query': url.search,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
parentContext,
|
|
157
|
+
async (span) => {
|
|
158
|
+
try {
|
|
159
|
+
const response = await originalMethod.call(actorInstance, request);
|
|
160
|
+
|
|
161
|
+
span.setAttributes({
|
|
162
|
+
'http.response.status_code': response.status,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (response.ok) {
|
|
166
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
167
|
+
} else {
|
|
168
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return response;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
span.recordException(error as Error);
|
|
174
|
+
span.setStatus({
|
|
175
|
+
code: SpanStatusCode.ERROR,
|
|
176
|
+
message: error instanceof Error ? error.message : String(error),
|
|
177
|
+
});
|
|
178
|
+
throw error;
|
|
179
|
+
} finally {
|
|
180
|
+
span.end();
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Instrument the onAlarm lifecycle method
|
|
189
|
+
*/
|
|
190
|
+
function instrumentOnAlarm(
|
|
191
|
+
originalMethod: (alarmInfo?: unknown) => Promise<void>,
|
|
192
|
+
actorInstance: ActorLike,
|
|
193
|
+
actorClass: object,
|
|
194
|
+
options: ActorInstrumentationOptions,
|
|
195
|
+
): (alarmInfo?: unknown) => Promise<void> {
|
|
196
|
+
return async function instrumentedOnAlarm(alarmInfo?: unknown): Promise<void> {
|
|
197
|
+
const tracer = getTracer();
|
|
198
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
199
|
+
const spanName = options.spanNameFormatter
|
|
200
|
+
? options.spanNameFormatter(actorInstance.name || '', 'alarm')
|
|
201
|
+
: defaultSpanNameFormatter(actorInstance.name || '', actorClassName, 'alarm');
|
|
202
|
+
|
|
203
|
+
return tracer.startActiveSpan(
|
|
204
|
+
spanName,
|
|
205
|
+
{
|
|
206
|
+
kind: SpanKind.INTERNAL,
|
|
207
|
+
attributes: {
|
|
208
|
+
...createActorAttributes(actorInstance, actorClass, 'alarm'),
|
|
209
|
+
'faas.trigger': 'timer',
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
async (span) => {
|
|
213
|
+
try {
|
|
214
|
+
await originalMethod.call(actorInstance, alarmInfo);
|
|
215
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
216
|
+
} catch (error) {
|
|
217
|
+
span.recordException(error as Error);
|
|
218
|
+
span.setStatus({
|
|
219
|
+
code: SpanStatusCode.ERROR,
|
|
220
|
+
message: error instanceof Error ? error.message : String(error),
|
|
221
|
+
});
|
|
222
|
+
throw error;
|
|
223
|
+
} finally {
|
|
224
|
+
span.end();
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Instrument the onPersist lifecycle method
|
|
233
|
+
*/
|
|
234
|
+
function instrumentOnPersist(
|
|
235
|
+
originalMethod: (key: string, value: unknown) => void,
|
|
236
|
+
actorInstance: ActorLike,
|
|
237
|
+
actorClass: object,
|
|
238
|
+
options: ActorInstrumentationOptions,
|
|
239
|
+
): (key: string, value: unknown) => void {
|
|
240
|
+
if (!options.capturePersistEvents) {
|
|
241
|
+
return originalMethod;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return function instrumentedOnPersist(key: string, value: unknown): void {
|
|
245
|
+
const tracer = getTracer();
|
|
246
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
247
|
+
const spanName = options.spanNameFormatter
|
|
248
|
+
? options.spanNameFormatter(actorInstance.name || '', 'persist')
|
|
249
|
+
: `Actor ${actorInstance.name || actorClassName}: persist ${key}`;
|
|
250
|
+
|
|
251
|
+
tracer.startActiveSpan(
|
|
252
|
+
spanName,
|
|
253
|
+
{
|
|
254
|
+
kind: SpanKind.INTERNAL,
|
|
255
|
+
attributes: {
|
|
256
|
+
...createActorAttributes(actorInstance, actorClass, 'persist'),
|
|
257
|
+
'actor.persist.key': key,
|
|
258
|
+
'actor.persist.value_type': typeof value,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
(span) => {
|
|
262
|
+
try {
|
|
263
|
+
originalMethod.call(actorInstance, key, value);
|
|
264
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
265
|
+
} catch (error) {
|
|
266
|
+
span.recordException(error as Error);
|
|
267
|
+
span.setStatus({
|
|
268
|
+
code: SpanStatusCode.ERROR,
|
|
269
|
+
message: error instanceof Error ? error.message : String(error),
|
|
270
|
+
});
|
|
271
|
+
throw error;
|
|
272
|
+
} finally {
|
|
273
|
+
span.end();
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Instrument WebSocket lifecycle methods
|
|
282
|
+
*/
|
|
283
|
+
function instrumentWebSocketConnect(
|
|
284
|
+
originalMethod: (ws: WebSocket, request: Request) => void,
|
|
285
|
+
actorInstance: ActorLike,
|
|
286
|
+
actorClass: object,
|
|
287
|
+
options: ActorInstrumentationOptions,
|
|
288
|
+
): (ws: WebSocket, request: Request) => void {
|
|
289
|
+
return function instrumentedWebSocketConnect(ws: WebSocket, request: Request): void {
|
|
290
|
+
const tracer = getTracer();
|
|
291
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
292
|
+
const spanName = options.spanNameFormatter
|
|
293
|
+
? options.spanNameFormatter(actorInstance.name || '', 'websocket.connect')
|
|
294
|
+
: defaultSpanNameFormatter(actorInstance.name || '', actorClassName, 'websocket.connect');
|
|
295
|
+
|
|
296
|
+
tracer.startActiveSpan(
|
|
297
|
+
spanName,
|
|
298
|
+
{
|
|
299
|
+
kind: SpanKind.SERVER,
|
|
300
|
+
attributes: {
|
|
301
|
+
...createActorAttributes(actorInstance, actorClass, 'websocket.connect'),
|
|
302
|
+
'url.full': request.url,
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
(span) => {
|
|
306
|
+
try {
|
|
307
|
+
originalMethod.call(actorInstance, ws, request);
|
|
308
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
309
|
+
} catch (error) {
|
|
310
|
+
span.recordException(error as Error);
|
|
311
|
+
span.setStatus({
|
|
312
|
+
code: SpanStatusCode.ERROR,
|
|
313
|
+
message: error instanceof Error ? error.message : String(error),
|
|
314
|
+
});
|
|
315
|
+
throw error;
|
|
316
|
+
} finally {
|
|
317
|
+
span.end();
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function instrumentWebSocketMessage(
|
|
325
|
+
originalMethod: (ws: WebSocket, message: unknown) => void,
|
|
326
|
+
actorInstance: ActorLike,
|
|
327
|
+
actorClass: object,
|
|
328
|
+
options: ActorInstrumentationOptions,
|
|
329
|
+
): (ws: WebSocket, message: unknown) => void {
|
|
330
|
+
return function instrumentedWebSocketMessage(ws: WebSocket, message: unknown): void {
|
|
331
|
+
const tracer = getTracer();
|
|
332
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
333
|
+
const spanName = options.spanNameFormatter
|
|
334
|
+
? options.spanNameFormatter(actorInstance.name || '', 'websocket.message')
|
|
335
|
+
: defaultSpanNameFormatter(actorInstance.name || '', actorClassName, 'websocket.message');
|
|
336
|
+
|
|
337
|
+
tracer.startActiveSpan(
|
|
338
|
+
spanName,
|
|
339
|
+
{
|
|
340
|
+
kind: SpanKind.SERVER,
|
|
341
|
+
attributes: {
|
|
342
|
+
...createActorAttributes(actorInstance, actorClass, 'websocket.message'),
|
|
343
|
+
'websocket.message.type': typeof message,
|
|
344
|
+
'websocket.message.size':
|
|
345
|
+
typeof message === 'string'
|
|
346
|
+
? message.length
|
|
347
|
+
: message instanceof ArrayBuffer
|
|
348
|
+
? message.byteLength
|
|
349
|
+
: 0,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
(span) => {
|
|
353
|
+
try {
|
|
354
|
+
originalMethod.call(actorInstance, ws, message);
|
|
355
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
356
|
+
} catch (error) {
|
|
357
|
+
span.recordException(error as Error);
|
|
358
|
+
span.setStatus({
|
|
359
|
+
code: SpanStatusCode.ERROR,
|
|
360
|
+
message: error instanceof Error ? error.message : String(error),
|
|
361
|
+
});
|
|
362
|
+
throw error;
|
|
363
|
+
} finally {
|
|
364
|
+
span.end();
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function instrumentWebSocketDisconnect(
|
|
372
|
+
originalMethod: (ws: WebSocket) => void,
|
|
373
|
+
actorInstance: ActorLike,
|
|
374
|
+
actorClass: object,
|
|
375
|
+
options: ActorInstrumentationOptions,
|
|
376
|
+
): (ws: WebSocket) => void {
|
|
377
|
+
return function instrumentedWebSocketDisconnect(ws: WebSocket): void {
|
|
378
|
+
const tracer = getTracer();
|
|
379
|
+
const actorClassName = (actorClass as { name?: string }).name || 'Actor';
|
|
380
|
+
const spanName = options.spanNameFormatter
|
|
381
|
+
? options.spanNameFormatter(actorInstance.name || '', 'websocket.disconnect')
|
|
382
|
+
: defaultSpanNameFormatter(actorInstance.name || '', actorClassName, 'websocket.disconnect');
|
|
383
|
+
|
|
384
|
+
tracer.startActiveSpan(
|
|
385
|
+
spanName,
|
|
386
|
+
{
|
|
387
|
+
kind: SpanKind.SERVER,
|
|
388
|
+
attributes: createActorAttributes(actorInstance, actorClass, 'websocket.disconnect'),
|
|
389
|
+
},
|
|
390
|
+
(span) => {
|
|
391
|
+
try {
|
|
392
|
+
originalMethod.call(actorInstance, ws);
|
|
393
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
394
|
+
} catch (error) {
|
|
395
|
+
span.recordException(error as Error);
|
|
396
|
+
span.setStatus({
|
|
397
|
+
code: SpanStatusCode.ERROR,
|
|
398
|
+
message: error instanceof Error ? error.message : String(error),
|
|
399
|
+
});
|
|
400
|
+
throw error;
|
|
401
|
+
} finally {
|
|
402
|
+
span.end();
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Instrument an Actor instance by wrapping all lifecycle methods
|
|
411
|
+
*/
|
|
412
|
+
function instrumentActorInstance(
|
|
413
|
+
actorInstance: ActorLike,
|
|
414
|
+
_state: DurableObjectState,
|
|
415
|
+
_env: unknown,
|
|
416
|
+
actorClass: object,
|
|
417
|
+
options: ActorInstrumentationOptions,
|
|
418
|
+
): ActorLike {
|
|
419
|
+
const instanceHandler: ProxyHandler<ActorLike> = {
|
|
420
|
+
get(target, prop) {
|
|
421
|
+
const value = Reflect.get(target, prop);
|
|
422
|
+
|
|
423
|
+
// Lifecycle methods that need instrumentation
|
|
424
|
+
if (prop === 'onInit' && typeof value === 'function') {
|
|
425
|
+
return instrumentOnInit(value.bind(target), target, actorClass, options);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (prop === 'onRequest' && typeof value === 'function') {
|
|
429
|
+
return instrumentOnRequest(value.bind(target), target, actorClass, options);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (prop === 'onAlarm' && typeof value === 'function') {
|
|
433
|
+
return instrumentOnAlarm(value.bind(target), target, actorClass, options);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (prop === 'onPersist' && typeof value === 'function') {
|
|
437
|
+
return instrumentOnPersist(value.bind(target), target, actorClass, options);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (prop === 'onWebSocketConnect' && typeof value === 'function') {
|
|
441
|
+
return instrumentWebSocketConnect(value.bind(target), target, actorClass, options);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (prop === 'onWebSocketMessage' && typeof value === 'function') {
|
|
445
|
+
return instrumentWebSocketMessage(value.bind(target), target, actorClass, options);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (prop === 'onWebSocketDisconnect' && typeof value === 'function') {
|
|
449
|
+
return instrumentWebSocketDisconnect(value.bind(target), target, actorClass, options);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Instrument sub-components if enabled
|
|
453
|
+
if (prop === 'storage' && value && options.instrumentStorage !== false) {
|
|
454
|
+
return instrumentActorStorage(value, target, actorClass);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (prop === 'alarms' && value && options.instrumentAlarms !== false) {
|
|
458
|
+
return instrumentActorAlarms(value, target, actorClass);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (prop === 'sockets' && value && options.instrumentSockets !== false) {
|
|
462
|
+
return instrumentActorSockets(value, target, actorClass);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Bind other methods to the target
|
|
466
|
+
if (typeof value === 'function') {
|
|
467
|
+
return value.bind(target);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return value;
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return wrap(actorInstance, instanceHandler);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Instrument an Actor class for comprehensive OpenTelemetry tracing
|
|
479
|
+
*
|
|
480
|
+
* This wraps the Actor class to automatically trace all lifecycle methods:
|
|
481
|
+
* - onInit: Actor initialization
|
|
482
|
+
* - onRequest: HTTP request handling
|
|
483
|
+
* - onAlarm: Alarm triggers
|
|
484
|
+
* - onPersist: Property persistence events
|
|
485
|
+
* - WebSocket methods: Connection, message, disconnect
|
|
486
|
+
*
|
|
487
|
+
* It also optionally instruments:
|
|
488
|
+
* - actor.storage: SQL queries and storage operations
|
|
489
|
+
* - actor.alarms: Alarm scheduling operations
|
|
490
|
+
* - actor.sockets: WebSocket operations
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```typescript
|
|
494
|
+
* import { Actor } from '@cloudflare/actors'
|
|
495
|
+
* import { instrumentActor } from 'autotel-cloudflare/actors'
|
|
496
|
+
*
|
|
497
|
+
* class Counter extends Actor<Env> {
|
|
498
|
+
* protected onInit() {
|
|
499
|
+
* console.log('Counter initialized')
|
|
500
|
+
* }
|
|
501
|
+
*
|
|
502
|
+
* protected onRequest(request: Request) {
|
|
503
|
+
* return new Response('count: 42')
|
|
504
|
+
* }
|
|
505
|
+
* }
|
|
506
|
+
*
|
|
507
|
+
* // Wrap the class
|
|
508
|
+
* export const InstrumentedCounter = instrumentActor(Counter, (env: Env) => ({
|
|
509
|
+
* service: { name: 'counter-actor' },
|
|
510
|
+
* exporter: { url: env.OTLP_ENDPOINT },
|
|
511
|
+
* actors: {
|
|
512
|
+
* instrumentStorage: true,
|
|
513
|
+
* capturePersistEvents: true
|
|
514
|
+
* }
|
|
515
|
+
* }))
|
|
516
|
+
* ```
|
|
517
|
+
*
|
|
518
|
+
* @param actorClass - The Actor class to instrument
|
|
519
|
+
* @param config - Configuration (static object or function)
|
|
520
|
+
* @returns Instrumented Actor class
|
|
521
|
+
*/
|
|
522
|
+
export function instrumentActor<C extends ActorConstructor>(
|
|
523
|
+
actorClass: C,
|
|
524
|
+
config: ActorConfig | ((env: unknown, trigger?: unknown) => ActorConfig),
|
|
525
|
+
): C {
|
|
526
|
+
const initialiser = createInitialiser(config as ConfigurationOption);
|
|
527
|
+
|
|
528
|
+
// Default options
|
|
529
|
+
const defaultOptions: ActorInstrumentationOptions = {
|
|
530
|
+
instrumentStorage: true,
|
|
531
|
+
instrumentAlarms: true,
|
|
532
|
+
instrumentSockets: true,
|
|
533
|
+
capturePersistEvents: true,
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const classHandler: ProxyHandler<C> = {
|
|
537
|
+
construct(target, [state, env]: [DurableObjectState, unknown]) {
|
|
538
|
+
// Get config (either static or from function)
|
|
539
|
+
const resolvedConfig =
|
|
540
|
+
typeof config === 'function'
|
|
541
|
+
? config(env, { id: state.id.toString(), name: state.id.name })
|
|
542
|
+
: config;
|
|
543
|
+
|
|
544
|
+
// Merge options with defaults
|
|
545
|
+
// Handle the case where config might not have actors property
|
|
546
|
+
const actorOptions =
|
|
547
|
+
resolvedConfig && typeof resolvedConfig === 'object' && 'actors' in resolvedConfig
|
|
548
|
+
? (resolvedConfig as { actors?: ActorInstrumentationOptions }).actors
|
|
549
|
+
: undefined;
|
|
550
|
+
const options: ActorInstrumentationOptions = {
|
|
551
|
+
...defaultOptions,
|
|
552
|
+
...actorOptions,
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// Initialize telemetry config
|
|
556
|
+
const trigger = {
|
|
557
|
+
id: state.id.toString(),
|
|
558
|
+
name: state.id.name,
|
|
559
|
+
};
|
|
560
|
+
const telemetryConfig = initialiser(env, trigger);
|
|
561
|
+
const context = setConfig(telemetryConfig);
|
|
562
|
+
|
|
563
|
+
// Create the Actor instance within the config context
|
|
564
|
+
const actorInstance = api_context.with(context, () => {
|
|
565
|
+
return new target(state, env);
|
|
566
|
+
}) as ActorLike;
|
|
567
|
+
|
|
568
|
+
// Instrument the instance
|
|
569
|
+
return instrumentActorInstance(actorInstance, state, env, actorClass, options);
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
return wrap(actorClass, classHandler);
|
|
574
|
+
}
|