autotel-cloudflare 2.18.3 → 2.18.5
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/README.md +64 -0
- package/dist/chunk-C3QLNZRK.js +63 -0
- package/dist/chunk-C3QLNZRK.js.map +1 -0
- package/dist/{chunk-WDNZVVRW.js → chunk-MIDMNKDC.js} +38 -2
- package/dist/chunk-MIDMNKDC.js.map +1 -0
- package/dist/handlers.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +37 -2
- package/dist/index.js.map +1 -1
- package/dist/logger-DGS3kP-A.d.ts +20 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +1 -0
- package/dist/sampling.d.ts +0 -1
- package/package.json +12 -12
- package/src/execution-logger.test.ts +127 -0
- package/src/execution-logger.ts +112 -0
- package/src/handlers/workflows.test.ts +86 -0
- package/src/handlers/workflows.ts +47 -30
- package/src/index.ts +11 -0
- package/src/logger.ts +10 -1
- package/src/wrappers/cf-attributes.test.ts +59 -0
- package/src/wrappers/instrument.ts +55 -0
- package/dist/chunk-WDNZVVRW.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { instrumentWorkflow } from './workflows';
|
|
3
|
+
import { getWorkflowLogger } from '../execution-logger';
|
|
3
4
|
import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
|
|
4
5
|
|
|
5
6
|
describe('Workflow Instrumentation', () => {
|
|
@@ -22,6 +23,8 @@ describe('Workflow Instrumentation', () => {
|
|
|
22
23
|
isRecording: () => true,
|
|
23
24
|
updateName: vi.fn(),
|
|
24
25
|
addEvent: vi.fn(),
|
|
26
|
+
addLink: vi.fn(),
|
|
27
|
+
addLinks: vi.fn(),
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
mockTracer = {
|
|
@@ -203,6 +206,50 @@ describe('Workflow Instrumentation', () => {
|
|
|
203
206
|
});
|
|
204
207
|
expect(mockSpan.end).toHaveBeenCalled();
|
|
205
208
|
});
|
|
209
|
+
|
|
210
|
+
it('should make getWorkflowLogger() available inside run()', async () => {
|
|
211
|
+
const getActiveSpanSpy = vi
|
|
212
|
+
.spyOn(trace, 'getActiveSpan')
|
|
213
|
+
.mockReturnValue(mockSpan as any);
|
|
214
|
+
|
|
215
|
+
class TestWorkflow {
|
|
216
|
+
constructor(public ctx: any, public env: any) {}
|
|
217
|
+
async run() {
|
|
218
|
+
const log = getWorkflowLogger();
|
|
219
|
+
log.set({ workflow: { id: 'wf-logger' } });
|
|
220
|
+
log.info('workflow started', { provider: { name: 'curvepay' } });
|
|
221
|
+
return log.emitNow({ outcome: 'running' });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const Instrumented = instrumentWorkflow(TestWorkflow, 'logger-test', {
|
|
226
|
+
service: { name: 'test' },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const instance = new Instrumented({}, {});
|
|
230
|
+
const event = { payload: {}, timestamp: new Date(), instanceId: 'wf-logger' };
|
|
231
|
+
const step = { do: vi.fn(), sleep: vi.fn(), sleepUntil: vi.fn() };
|
|
232
|
+
|
|
233
|
+
const snapshot = await instance.run(event, step);
|
|
234
|
+
|
|
235
|
+
expect(snapshot).toMatchObject({
|
|
236
|
+
traceId: 'test-trace-id',
|
|
237
|
+
spanId: 'test-span-id',
|
|
238
|
+
context: {
|
|
239
|
+
workflow: { id: 'wf-logger' },
|
|
240
|
+
outcome: 'running',
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
expect(mockSpan.setAttributes).toHaveBeenCalledWith({
|
|
244
|
+
'workflow.id': 'wf-logger',
|
|
245
|
+
});
|
|
246
|
+
expect(mockSpan.addEvent).toHaveBeenCalledWith('log.info', {
|
|
247
|
+
message: 'workflow started',
|
|
248
|
+
'provider.name': 'curvepay',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
getActiveSpanSpy.mockRestore();
|
|
252
|
+
});
|
|
206
253
|
});
|
|
207
254
|
|
|
208
255
|
describe('step.do() instrumentation', () => {
|
|
@@ -298,6 +345,45 @@ describe('Workflow Instrumentation', () => {
|
|
|
298
345
|
expect(sleepSpanCall[1].attributes['workflow.sleep.duration']).toBe('2 hours');
|
|
299
346
|
expect(sleepSpanCall[1].attributes['workflow.name']).toBe('sleep-test');
|
|
300
347
|
});
|
|
348
|
+
|
|
349
|
+
it('should create span for step.sleepUntil() calls', async () => {
|
|
350
|
+
const wakeAt = new Date('2026-04-09T10:00:00.000Z');
|
|
351
|
+
|
|
352
|
+
class TestWorkflow {
|
|
353
|
+
constructor(public ctx: any, public env: any) {}
|
|
354
|
+
async run(event: any, step: any) {
|
|
355
|
+
await step.sleepUntil('wait until tomorrow', wakeAt);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const Instrumented = instrumentWorkflow(TestWorkflow, 'sleep-until-test', {
|
|
360
|
+
service: { name: 'test' },
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const instance = new Instrumented({}, {});
|
|
364
|
+
const event = { payload: {}, timestamp: new Date(), instanceId: 'wf-sleep-until' };
|
|
365
|
+
const step = {
|
|
366
|
+
do: vi.fn(),
|
|
367
|
+
sleep: vi.fn(),
|
|
368
|
+
sleepUntil: vi.fn().mockResolvedValue(undefined),
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
await instance.run(event, step);
|
|
372
|
+
|
|
373
|
+
const sleepUntilSpanCall = mockTracer.startActiveSpan.mock.calls[1];
|
|
374
|
+
expect(sleepUntilSpanCall[0]).toBe(
|
|
375
|
+
'Workflow sleep-until-test: sleepUntil wait until tomorrow',
|
|
376
|
+
);
|
|
377
|
+
expect(sleepUntilSpanCall[1].attributes['workflow.sleep.name']).toBe(
|
|
378
|
+
'wait until tomorrow',
|
|
379
|
+
);
|
|
380
|
+
expect(sleepUntilSpanCall[1].attributes['workflow.sleep.until']).toBe(
|
|
381
|
+
'2026-04-09T10:00:00.000Z',
|
|
382
|
+
);
|
|
383
|
+
expect(sleepUntilSpanCall[1].attributes['workflow.name']).toBe(
|
|
384
|
+
'sleep-until-test',
|
|
385
|
+
);
|
|
386
|
+
});
|
|
301
387
|
});
|
|
302
388
|
|
|
303
389
|
describe('this binding', () => {
|
|
@@ -14,39 +14,13 @@ import {
|
|
|
14
14
|
SpanStatusCode,
|
|
15
15
|
SpanKind,
|
|
16
16
|
} from '@opentelemetry/api';
|
|
17
|
+
import type { WorkflowEvent, WorkflowStep } from 'cloudflare:workers';
|
|
17
18
|
import type { ConfigurationOption, WorkflowTrigger } from 'autotel-edge';
|
|
18
19
|
import { createInitialiser, setConfig, WorkerTracer } from 'autotel-edge';
|
|
19
20
|
import { wrap } from '../bindings/common';
|
|
20
21
|
|
|
21
|
-
/**
|
|
22
|
-
* Workflow types matching the Cloudflare Workers Workflows API.
|
|
23
|
-
* @see https://developers.cloudflare.com/workflows/
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
interface WorkflowEvent<T = unknown> {
|
|
27
|
-
payload: Readonly<T>;
|
|
28
|
-
timestamp: Date;
|
|
29
|
-
instanceId: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface WorkflowStepConfig {
|
|
33
|
-
retries?: {
|
|
34
|
-
limit: number;
|
|
35
|
-
delay?: string | number;
|
|
36
|
-
backoff?: 'constant' | 'linear' | 'exponential';
|
|
37
|
-
};
|
|
38
|
-
timeout?: string | number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface WorkflowStep {
|
|
42
|
-
do<T>(name: string, callback: () => Promise<T>): Promise<T>;
|
|
43
|
-
do<T>(name: string, config: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>;
|
|
44
|
-
sleep(name: string, duration: string | number): Promise<void>;
|
|
45
|
-
sleepUntil(name: string, timestamp: Date | number): Promise<void>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
22
|
type WorkflowRunFn = (
|
|
49
|
-
event: Readonly<WorkflowEvent
|
|
23
|
+
event: Readonly<WorkflowEvent<unknown>>,
|
|
50
24
|
step: WorkflowStep,
|
|
51
25
|
) => Promise<unknown> | void;
|
|
52
26
|
|
|
@@ -64,7 +38,7 @@ function isColdStart(workflowClass: object): boolean {
|
|
|
64
38
|
}
|
|
65
39
|
|
|
66
40
|
/**
|
|
67
|
-
* Proxy the step object to instrument step.do() and step.
|
|
41
|
+
* Proxy the step object to instrument step.do(), step.sleep(), and step.sleepUntil() calls
|
|
68
42
|
*/
|
|
69
43
|
function instrumentWorkflowStep(
|
|
70
44
|
step: WorkflowStep,
|
|
@@ -153,6 +127,49 @@ function instrumentWorkflowStep(
|
|
|
153
127
|
});
|
|
154
128
|
}
|
|
155
129
|
|
|
130
|
+
if (prop === 'sleepUntil' && typeof value === 'function') {
|
|
131
|
+
return new Proxy(value, {
|
|
132
|
+
apply: (fnTarget, thisArg, args) => {
|
|
133
|
+
const [sleepName, timestamp] = args as [string, Date | number];
|
|
134
|
+
|
|
135
|
+
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
136
|
+
const wakeAt =
|
|
137
|
+
timestamp instanceof Date
|
|
138
|
+
? timestamp.toISOString()
|
|
139
|
+
: new Date(timestamp).toISOString();
|
|
140
|
+
|
|
141
|
+
return tracer.startActiveSpan(
|
|
142
|
+
`Workflow ${workflowName}: sleepUntil ${sleepName}`,
|
|
143
|
+
{
|
|
144
|
+
kind: SpanKind.INTERNAL,
|
|
145
|
+
attributes: {
|
|
146
|
+
'workflow.sleep.name': sleepName,
|
|
147
|
+
'workflow.sleep.until': wakeAt,
|
|
148
|
+
'workflow.name': workflowName,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
async (span) => {
|
|
152
|
+
try {
|
|
153
|
+
const result = await Reflect.apply(fnTarget, thisArg, args);
|
|
154
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
155
|
+
return result;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
span.recordException(error as Error);
|
|
158
|
+
span.setStatus({
|
|
159
|
+
code: SpanStatusCode.ERROR,
|
|
160
|
+
message:
|
|
161
|
+
error instanceof Error ? error.message : String(error),
|
|
162
|
+
});
|
|
163
|
+
throw error;
|
|
164
|
+
} finally {
|
|
165
|
+
span.end();
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
156
173
|
// Pass through other step methods
|
|
157
174
|
if (typeof value === 'function') {
|
|
158
175
|
return value.bind(target);
|
|
@@ -175,7 +192,7 @@ function instrumentWorkflowRun(
|
|
|
175
192
|
): WorkflowRunFn {
|
|
176
193
|
return async function instrumentedRun(
|
|
177
194
|
this: unknown,
|
|
178
|
-
event: Readonly<WorkflowEvent
|
|
195
|
+
event: Readonly<WorkflowEvent<unknown>>,
|
|
179
196
|
step: WorkflowStep,
|
|
180
197
|
): Promise<unknown> {
|
|
181
198
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,17 @@
|
|
|
37
37
|
|
|
38
38
|
// Re-export EVERYTHING from autotel-edge (vendor-agnostic foundation)
|
|
39
39
|
export * from 'autotel-edge';
|
|
40
|
+
export {
|
|
41
|
+
getRequestLogger,
|
|
42
|
+
getQueueLogger,
|
|
43
|
+
getWorkflowLogger,
|
|
44
|
+
getActorLogger,
|
|
45
|
+
createWorkersLogger,
|
|
46
|
+
type ExecutionLogger,
|
|
47
|
+
type ExecutionLoggerOptions,
|
|
48
|
+
type ExecutionLogSnapshot,
|
|
49
|
+
type WorkersLoggerOptions,
|
|
50
|
+
} from './execution-logger';
|
|
40
51
|
|
|
41
52
|
// Cloudflare-specific wrappers
|
|
42
53
|
export { instrument, wrapModule, wrapDurableObject } from './wrappers';
|
package/src/logger.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Logger entry point
|
|
2
|
+
* Logger entry point
|
|
3
3
|
* Entry point: autotel-cloudflare/logger
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export * from 'autotel-edge/logger';
|
|
7
|
+
export {
|
|
8
|
+
getRequestLogger,
|
|
9
|
+
getQueueLogger,
|
|
10
|
+
getWorkflowLogger,
|
|
11
|
+
getActorLogger,
|
|
12
|
+
type ExecutionLogger,
|
|
13
|
+
type ExecutionLoggerOptions,
|
|
14
|
+
type ExecutionLogSnapshot,
|
|
15
|
+
} from './execution-logger';
|
|
@@ -272,4 +272,63 @@ describe('CF Attributes extraction via instrument()', () => {
|
|
|
272
272
|
expect(attrs['cloudflare.asn']).toBe(0);
|
|
273
273
|
expect(attrs['cloudflare.client_tcp_rtt']).toBe(0);
|
|
274
274
|
});
|
|
275
|
+
|
|
276
|
+
it('should skip fetch instrumentation when route does not match include patterns', async () => {
|
|
277
|
+
const handler = {
|
|
278
|
+
async fetch(_request: Request) {
|
|
279
|
+
return new Response('OK', { status: 200 });
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const instrumented = instrument(handler, {
|
|
284
|
+
service: { name: 'test-worker' },
|
|
285
|
+
handlers: {
|
|
286
|
+
fetch: {
|
|
287
|
+
include: ['/api/**'],
|
|
288
|
+
exclude: ['/api/internal/**'],
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const request = new Request('http://example.com/health');
|
|
294
|
+
const env = {} as any;
|
|
295
|
+
const ctx = createMockCtx();
|
|
296
|
+
|
|
297
|
+
const response = await instrumented.fetch!(request, env, ctx);
|
|
298
|
+
|
|
299
|
+
expect(response.status).toBe(200);
|
|
300
|
+
expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
|
|
301
|
+
expect(capturedSpanOptions).toBeNull();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should apply per-route service mapping to fetch span attributes', async () => {
|
|
305
|
+
const handler = {
|
|
306
|
+
async fetch(_request: Request) {
|
|
307
|
+
return new Response('OK', { status: 200 });
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const instrumented = instrument(handler, {
|
|
312
|
+
service: { name: 'default-worker' },
|
|
313
|
+
handlers: {
|
|
314
|
+
fetch: {
|
|
315
|
+
routes: {
|
|
316
|
+
'/api/auth/**': { service: 'auth-service' },
|
|
317
|
+
'/api/**': { service: 'api-service' },
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const request = new Request('http://example.com/api/auth/login');
|
|
324
|
+
const env = {} as any;
|
|
325
|
+
const ctx = createMockCtx();
|
|
326
|
+
|
|
327
|
+
await instrumented.fetch!(request, env, ctx);
|
|
328
|
+
|
|
329
|
+
expect(capturedSpanOptions).toBeDefined();
|
|
330
|
+
const attrs = capturedSpanOptions.attributes;
|
|
331
|
+
expect(attrs['service.name']).toBe('auth-service');
|
|
332
|
+
expect(attrs['autotel.route.service']).toBe('auth-service');
|
|
333
|
+
});
|
|
275
334
|
});
|
|
@@ -72,6 +72,46 @@ type EmailHandler = (
|
|
|
72
72
|
ctx: ExecutionContext,
|
|
73
73
|
) => void | Promise<void>;
|
|
74
74
|
|
|
75
|
+
type RouteServiceConfig = { service: string };
|
|
76
|
+
|
|
77
|
+
function matchesPattern(path: string, pattern: string): boolean {
|
|
78
|
+
const regexPattern = pattern
|
|
79
|
+
.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`)
|
|
80
|
+
.replaceAll('**', '{{GLOBSTAR}}')
|
|
81
|
+
.replaceAll('*', '[^/]*')
|
|
82
|
+
.replaceAll('{{GLOBSTAR}}', '.*')
|
|
83
|
+
.replaceAll('?', '[^/]');
|
|
84
|
+
return new RegExp(`^${regexPattern}$`).test(path);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function shouldInstrumentPath(
|
|
88
|
+
path: string,
|
|
89
|
+
include?: string[],
|
|
90
|
+
exclude?: string[],
|
|
91
|
+
): boolean {
|
|
92
|
+
if (exclude && exclude.some((pattern) => matchesPattern(path, pattern))) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!include || include.length === 0) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return include.some((pattern) => matchesPattern(path, pattern));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getServiceForPath(
|
|
104
|
+
path: string,
|
|
105
|
+
routes?: Record<string, RouteServiceConfig>,
|
|
106
|
+
): string | undefined {
|
|
107
|
+
if (!routes) return undefined;
|
|
108
|
+
for (const [pattern, config] of Object.entries(routes)) {
|
|
109
|
+
if (matchesPattern(path, pattern)) {
|
|
110
|
+
return config.service;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
75
115
|
|
|
76
116
|
/**
|
|
77
117
|
* Create fetch handler instrumentation with config support for postProcess
|
|
@@ -115,6 +155,10 @@ function createFetchInstrumentation(
|
|
|
115
155
|
return {
|
|
116
156
|
getInitialSpanInfo: (request: Request): InitialSpanInfo => {
|
|
117
157
|
const url = new URL(request.url);
|
|
158
|
+
const routeService = getServiceForPath(
|
|
159
|
+
url.pathname,
|
|
160
|
+
config.handlers.fetch.routes as Record<string, RouteServiceConfig> | undefined,
|
|
161
|
+
);
|
|
118
162
|
|
|
119
163
|
const cfAttrs = (config as any).extractCfAttributes === false
|
|
120
164
|
? {}
|
|
@@ -127,6 +171,7 @@ function createFetchInstrumentation(
|
|
|
127
171
|
attributes: {
|
|
128
172
|
'http.request.method': request.method,
|
|
129
173
|
'url.full': request.url,
|
|
174
|
+
...(routeService ? { 'service.name': routeService, 'autotel.route.service': routeService } : {}),
|
|
130
175
|
...cfAttrs,
|
|
131
176
|
},
|
|
132
177
|
},
|
|
@@ -567,6 +612,16 @@ function createHandlerProxyWithConfig<T extends Trigger, E, R>(
|
|
|
567
612
|
// Return handler as-is without instrumentation
|
|
568
613
|
return handlerFn(trigger, env, ctx);
|
|
569
614
|
}
|
|
615
|
+
|
|
616
|
+
if (trigger instanceof Request) {
|
|
617
|
+
const pathname = new URL(trigger.url).pathname;
|
|
618
|
+
const fetchCfg = config.handlers.fetch;
|
|
619
|
+
if (
|
|
620
|
+
!shouldInstrumentPath(pathname, fetchCfg.include, fetchCfg.exclude)
|
|
621
|
+
) {
|
|
622
|
+
return handlerFn(trigger, env, ctx) as ReturnType<typeof handlerFn>;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
570
625
|
|
|
571
626
|
// Auto-instrument Cloudflare bindings in the environment
|
|
572
627
|
const instrumentedEnv = instrumentBindings(env as Record<string, any>) as E;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/handlers/durable-objects.ts","../src/handlers/workflows.ts"],"names":["api_context","context","coldStarts","isColdStart","trace","SpanKind","SpanStatusCode","createInitialiser","setConfig"],"mappings":"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","file":"chunk-WDNZVVRW.js","sourcesContent":["/**\n * Durable Objects instrumentation for Cloudflare Workers\n * \n * Note: This file uses Cloudflare Workers types (DurableObjectId, DurableObjectState, etc.)\n * which are globally available via @cloudflare/workers-types when listed in tsconfig.json.\n * These types are devDependencies only - they're not runtime dependencies.\n * At runtime, Cloudflare Workers runtime provides the actual implementations.\n */\n\nimport {\n trace,\n context as api_context,\n propagation,\n SpanStatusCode,\n SpanKind,\n} from '@opentelemetry/api';\nimport type { ConfigurationOption } from 'autotel-edge';\nimport { createInitialiser, setConfig, WorkerTracer } from 'autotel-edge';\nimport { wrap } from '../bindings/common';\n\n// Durable Object types\ntype DOFetchFn = (request: Request) => Response | Promise<Response>;\ntype DOAlarmFn = () => void | Promise<void>;\n\n/**\n * Track cold starts per DO class\n */\nconst coldStarts = new WeakMap<any, boolean>();\n\nfunction isColdStart(doClass: any): boolean {\n if (!coldStarts.has(doClass)) {\n coldStarts.set(doClass, true);\n return true;\n }\n return false;\n}\n\n/**\n * Instrument a Durable Object fetch method\n */\nfunction instrumentDOFetch(\n fetchFn: DOFetchFn,\n id: DurableObjectId,\n doClass: any,\n): DOFetchFn {\n return async function instrumentedFetch(\n this: any,\n request: Request,\n ): Promise<Response> {\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n // Extract parent context from request headers\n const parentContext = propagation.extract(\n api_context.active(),\n request.headers,\n );\n\n const url = new URL(request.url);\n const spanName = `DO ${id.name || id.toString()}: ${request.method} ${url.pathname}`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.SERVER,\n attributes: {\n 'http.request.method': request.method,\n 'url.full': request.url,\n 'do.id': id.toString(),\n 'do.id.name': id.name || '',\n 'faas.trigger': 'http',\n 'faas.coldstart': isColdStart(doClass),\n },\n },\n parentContext,\n async (span) => {\n try {\n const response = await fetchFn.call(this, request);\n\n span.setAttributes({\n 'http.response.status_code': response.status,\n });\n\n if (response.ok) {\n span.setStatus({ code: SpanStatusCode.OK });\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n\n return response;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\n/**\n * Instrument a Durable Object alarm method\n */\nfunction instrumentDOAlarm(\n alarmFn: DOAlarmFn,\n id: DurableObjectId,\n doClass: any,\n): DOAlarmFn {\n return async function instrumentedAlarm(this: any): Promise<void> {\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n const spanName = `DO ${id.name || id.toString()}: alarm`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'do.id': id.toString(),\n 'do.id.name': id.name || '',\n 'faas.trigger': 'timer',\n 'faas.coldstart': isColdStart(doClass),\n },\n },\n async (span) => {\n try {\n await alarmFn.call(this);\n span.setStatus({ code: SpanStatusCode.OK });\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\n/**\n * Instrument a Durable Object instance\n */\nfunction instrumentDOInstance(\n doInstance: any,\n state: DurableObjectState,\n _env: any,\n doClass: any,\n): any {\n const instanceHandler: ProxyHandler<any> = {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n\n if (prop === 'fetch' && typeof value === 'function') {\n return instrumentDOFetch(value.bind(target), state.id, doClass);\n }\n\n if (prop === 'alarm' && typeof value === 'function') {\n return instrumentDOAlarm(value.bind(target), state.id, doClass);\n }\n\n // Bind other methods to the target\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n };\n\n return wrap(doInstance, instanceHandler);\n}\n\n/**\n * Instrument a Durable Object class\n *\n * This wraps the DO class to automatically trace all fetch and alarm calls,\n * as well as initialize the telemetry configuration.\n *\n * **Usage:**\n * ```typescript\n * import { DurableObject } from 'cloudflare:workers'\n * import { instrumentDO } from 'autotel-edge'\n *\n * export class Counter extends DurableObject<Env> {\n * async fetch(request: Request) {\n * // Your DO logic here\n * return new Response('OK')\n * }\n * }\n *\n * // Wrap the class before exporting\n * export const CounterDO = instrumentDO(Counter, (env: Env) => ({\n * exporter: {\n * url: env.OTLP_ENDPOINT,\n * headers: { 'x-api-key': env.API_KEY }\n * },\n * service: {\n * name: 'my-durable-object',\n * version: '1.0.0'\n * }\n * }))\n * ```\n *\n * **What you get:**\n * - 🎯 Automatic spans for fetch() calls with HTTP attributes\n * - ⏰ Automatic spans for alarm() calls\n * - 🥶 Cold start tracking\n * - 🔗 Context propagation from incoming requests\n * - ⚡ Automatic span lifecycle management\n *\n * @param doClass - The Durable Object class to instrument\n * @param config - Configuration or configuration function\n * @returns Instrumented Durable Object class\n */\nexport function instrumentDO<C extends new (state: DurableObjectState, env: any) => any>(\n doClass: C,\n config: ConfigurationOption,\n): C {\n const initialiser = createInitialiser(config);\n\n const classHandler: ProxyHandler<C> = {\n construct(target, [state, env]: [DurableObjectState, any]) {\n // Initialize config for this DO instance\n const trigger = {\n id: state.id.toString(),\n name: state.id.name,\n };\n const doConfig = initialiser(env, trigger);\n const context = setConfig(doConfig);\n\n // Create the DO instance within the config context\n const doInstance = api_context.with(context, () => {\n return new target(state, env);\n });\n\n // Instrument the instance\n return instrumentDOInstance(doInstance, state, env, doClass);\n },\n };\n\n return wrap(doClass, classHandler);\n}\n","/**\n * Cloudflare Workflows instrumentation for autotel-edge\n *\n * Instruments WorkflowEntrypoint classes to automatically trace workflow execution,\n * step operations, retries, and sleeps.\n *\n * Based on Cloudflare Workflows API:\n * https://developers.cloudflare.com/workflows/\n */\n\nimport {\n trace,\n context as api_context,\n SpanStatusCode,\n SpanKind,\n} from '@opentelemetry/api';\nimport type { ConfigurationOption, WorkflowTrigger } from 'autotel-edge';\nimport { createInitialiser, setConfig, WorkerTracer } from 'autotel-edge';\nimport { wrap } from '../bindings/common';\n\n/**\n * Workflow types matching the Cloudflare Workers Workflows API.\n * @see https://developers.cloudflare.com/workflows/\n */\n\ninterface WorkflowEvent<T = unknown> {\n payload: Readonly<T>;\n timestamp: Date;\n instanceId: string;\n}\n\ninterface WorkflowStepConfig {\n retries?: {\n limit: number;\n delay?: string | number;\n backoff?: 'constant' | 'linear' | 'exponential';\n };\n timeout?: string | number;\n}\n\ninterface WorkflowStep {\n do<T>(name: string, callback: () => Promise<T>): Promise<T>;\n do<T>(name: string, config: WorkflowStepConfig, callback: () => Promise<T>): Promise<T>;\n sleep(name: string, duration: string | number): Promise<void>;\n sleepUntil(name: string, timestamp: Date | number): Promise<void>;\n}\n\ntype WorkflowRunFn = (\n event: Readonly<WorkflowEvent>,\n step: WorkflowStep,\n) => Promise<unknown> | void;\n\n/**\n * Track cold starts per Workflow class\n */\nconst coldStarts = new WeakMap<object, boolean>();\n\nfunction isColdStart(workflowClass: object): boolean {\n if (!coldStarts.has(workflowClass)) {\n coldStarts.set(workflowClass, true);\n return true;\n }\n return false;\n}\n\n/**\n * Proxy the step object to instrument step.do() and step.sleep() calls\n */\nfunction instrumentWorkflowStep(\n step: WorkflowStep,\n workflowName: string,\n): WorkflowStep {\n const stepHandler: ProxyHandler<WorkflowStep> = {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n\n // Instrument step.do() to create spans for each workflow step\n if (prop === 'do' && typeof value === 'function') {\n return new Proxy(value, {\n apply: (fnTarget, thisArg, args) => {\n const [stepName] = args as [string, ...unknown[]];\n\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n return tracer.startActiveSpan(\n `Workflow ${workflowName}: ${stepName}`,\n {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'workflow.step.name': stepName,\n 'workflow.name': workflowName,\n },\n },\n async (span) => {\n try {\n const result = await Reflect.apply(fnTarget, thisArg, args);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n },\n });\n }\n\n // Instrument step.sleep() to track workflow delays\n if (prop === 'sleep' && typeof value === 'function') {\n return new Proxy(value, {\n apply: (fnTarget, thisArg, args) => {\n const [sleepName, duration] = args as [string, string | number];\n\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n return tracer.startActiveSpan(\n `Workflow ${workflowName}: sleep ${sleepName}`,\n {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'workflow.sleep.name': sleepName,\n 'workflow.sleep.duration': String(duration),\n 'workflow.name': workflowName,\n },\n },\n async (span) => {\n try {\n const result = await Reflect.apply(fnTarget, thisArg, args);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n },\n });\n }\n\n // Pass through other step methods\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n };\n\n return wrap(step, stepHandler);\n}\n\n/**\n * Instrument a Workflow run method\n */\nfunction instrumentWorkflowRun(\n runFn: WorkflowRunFn,\n workflowName: string,\n workflowClass: object,\n): WorkflowRunFn {\n return async function instrumentedRun(\n this: unknown,\n event: Readonly<WorkflowEvent>,\n step: WorkflowStep,\n ): Promise<unknown> {\n const tracer = trace.getTracer('autotel-edge') as WorkerTracer;\n\n // Instrument the step object to track individual operations\n const instrumentedStep = instrumentWorkflowStep(step, workflowName);\n\n const spanName = `Workflow ${workflowName}: run`;\n\n return tracer.startActiveSpan(\n spanName,\n {\n kind: SpanKind.INTERNAL,\n attributes: {\n 'workflow.name': workflowName,\n 'workflow.instance_id': event.instanceId,\n 'faas.trigger': 'workflow',\n 'faas.coldstart': isColdStart(workflowClass),\n },\n },\n async (span) => {\n try {\n const result = await runFn.call(this, event, instrumentedStep);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\n/**\n * Instrument a Workflow instance\n */\nfunction instrumentWorkflowInstance(\n workflowInstance: Record<string, unknown>,\n workflowName: string,\n workflowClass: object,\n): Record<string, unknown> {\n const instanceHandler: ProxyHandler<Record<string, unknown>> = {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n\n if (prop === 'run' && typeof value === 'function') {\n return instrumentWorkflowRun(\n value.bind(target) as WorkflowRunFn,\n workflowName,\n workflowClass,\n );\n }\n\n // Bind other methods to the target\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n };\n\n return wrap(workflowInstance, instanceHandler);\n}\n\n/**\n * Instrument a Cloudflare Workflow class\n *\n * This wraps the WorkflowEntrypoint class to automatically trace workflow execution,\n * step operations, retries, and sleeps.\n *\n * **Usage:**\n * ```typescript\n * import { WorkflowEntrypoint } from 'cloudflare:workers'\n * import { instrumentWorkflow } from 'autotel-cloudflare/handlers'\n *\n * class MyWorkflow extends WorkflowEntrypoint {\n * async run(event, step) {\n * await step.do('submit payment', async () => {\n * return await submitToPaymentProcessor(event.payload.payment)\n * })\n *\n * await step.sleep('wait for feedback', '2 days')\n *\n * await step.do('send feedback email', sendFeedbackEmail)\n * }\n * }\n *\n * export const CheckoutWorkflow = instrumentWorkflow(\n * MyWorkflow,\n * 'checkout-workflow',\n * (env: Env) => ({\n * exporter: {\n * url: env.OTLP_ENDPOINT,\n * headers: { 'x-api-key': env.API_KEY }\n * },\n * service: {\n * name: 'checkout-workflow',\n * version: '1.0.0'\n * }\n * })\n * )\n * ```\n *\n * @param workflowClass - The WorkflowEntrypoint class to instrument\n * @param workflowName - The name of the workflow (used in span names)\n * @param config - Configuration or configuration function\n * @returns Instrumented Workflow class\n */\nexport function instrumentWorkflow<\n C extends new (...args: any[]) => any,\n>(\n workflowClass: C,\n workflowName: string,\n config: ConfigurationOption,\n): C {\n const initialiser = createInitialiser(config);\n\n const classHandler: ProxyHandler<C> = {\n construct(target, args: any[]) {\n // Extract env from constructor args (typically last arg)\n const env = args[args.length - 1] || {};\n\n const trigger: WorkflowTrigger = { type: 'workflow', name: workflowName };\n const workflowConfig = initialiser(env, trigger);\n const context = setConfig(workflowConfig);\n\n // Create the workflow instance within the config context\n const workflowInstance = api_context.with(context, () => {\n return new target(...args);\n });\n\n // Instrument the instance\n return instrumentWorkflowInstance(\n workflowInstance,\n workflowName,\n workflowClass,\n );\n },\n };\n\n return wrap(workflowClass, classHandler);\n}\n"]}
|