autotel-cloudflare 3.1.0 → 4.0.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/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +7 -0
- package/dist/agents.js.map +1 -1
- package/package.json +8 -4
- package/src/actors/alarms.ts +0 -225
- package/src/actors/index.ts +0 -36
- package/src/actors/instrument-actor.test.ts +0 -179
- package/src/actors/instrument-actor.ts +0 -574
- package/src/actors/sockets.ts +0 -217
- package/src/actors/storage.ts +0 -263
- package/src/actors/traced-handler.ts +0 -300
- package/src/actors/types.ts +0 -98
- package/src/actors.ts +0 -50
- package/src/agents/agent.ts +0 -327
- package/src/agents/base.ts +0 -26
- package/src/agents/index.ts +0 -46
- package/src/agents/mcp.ts +0 -38
- package/src/agents/observability.ts +0 -145
- package/src/agents/otel-observability.test.ts +0 -269
- package/src/agents/otel-observability.ts +0 -557
- package/src/agents/types.ts +0 -61
- package/src/agents.ts +0 -87
- package/src/bindings/ai.test.ts +0 -170
- package/src/bindings/ai.ts +0 -73
- package/src/bindings/analytics-engine.test.ts +0 -175
- package/src/bindings/analytics-engine.ts +0 -78
- package/src/bindings/bindings-cache.test.ts +0 -80
- package/src/bindings/bindings-detection.test.ts +0 -235
- package/src/bindings/bindings-this-binding.test.ts +0 -294
- package/src/bindings/bindings.ts +0 -720
- package/src/bindings/browser-rendering.test.ts +0 -160
- package/src/bindings/browser-rendering.ts +0 -70
- package/src/bindings/common.ts +0 -84
- package/src/bindings/hyperdrive.test.ts +0 -176
- package/src/bindings/hyperdrive.ts +0 -74
- package/src/bindings/images.test.ts +0 -262
- package/src/bindings/images.ts +0 -182
- package/src/bindings/index.ts +0 -20
- package/src/bindings/queue-producer.test.ts +0 -224
- package/src/bindings/queue-producer.ts +0 -105
- package/src/bindings/rate-limiter.test.ts +0 -140
- package/src/bindings/rate-limiter.ts +0 -69
- package/src/bindings/vectorize.test.ts +0 -362
- package/src/bindings/vectorize.ts +0 -86
- package/src/bindings.ts +0 -6
- package/src/events.ts +0 -6
- package/src/execution-logger.test.ts +0 -127
- package/src/execution-logger.ts +0 -112
- package/src/global/cache.test.ts +0 -292
- package/src/global/cache.ts +0 -164
- package/src/global/fetch.test.ts +0 -399
- package/src/global/fetch.ts +0 -136
- package/src/global/index.ts +0 -7
- package/src/handlers/durable-objects.test.ts +0 -524
- package/src/handlers/durable-objects.ts +0 -250
- package/src/handlers/index.ts +0 -6
- package/src/handlers/workflows.test.ts +0 -411
- package/src/handlers/workflows.ts +0 -345
- package/src/handlers.ts +0 -6
- package/src/index.ts +0 -91
- package/src/logger.ts +0 -15
- package/src/native/native-tracing.test.ts +0 -54
- package/src/native/native-tracing.ts +0 -83
- package/src/native.ts +0 -12
- package/src/parse-error.ts +0 -1
- package/src/sampling.ts +0 -6
- package/src/testing.ts +0 -6
- package/src/wrappers/cf-attributes.test.ts +0 -334
- package/src/wrappers/define-worker-fetch.test.ts +0 -102
- package/src/wrappers/define-worker-fetch.ts +0 -71
- package/src/wrappers/index.ts +0 -13
- package/src/wrappers/instrument.integration.test.ts +0 -578
- package/src/wrappers/instrument.ts +0 -806
- package/src/wrappers/native-instrument.test.ts +0 -153
- package/src/wrappers/wrap-do.ts +0 -34
- package/src/wrappers/wrap-module.ts +0 -37
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
|
|
3
|
-
import { instrumentImages } from './images';
|
|
4
|
-
|
|
5
|
-
describe('Images Binding Instrumentation', () => {
|
|
6
|
-
let mockTracer: any;
|
|
7
|
-
let mockSpan: any;
|
|
8
|
-
let getTracerSpy: any;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockSpan = {
|
|
12
|
-
spanContext: () => ({
|
|
13
|
-
traceId: 'test-trace-id',
|
|
14
|
-
spanId: 'test-span-id',
|
|
15
|
-
traceFlags: 1,
|
|
16
|
-
}),
|
|
17
|
-
setAttribute: vi.fn(),
|
|
18
|
-
setAttributes: vi.fn(),
|
|
19
|
-
setStatus: vi.fn(),
|
|
20
|
-
recordException: vi.fn(),
|
|
21
|
-
end: vi.fn(),
|
|
22
|
-
isRecording: () => true,
|
|
23
|
-
updateName: vi.fn(),
|
|
24
|
-
addEvent: vi.fn(),
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
mockTracer = {
|
|
28
|
-
startActiveSpan: vi.fn((name, options, fn) => {
|
|
29
|
-
return fn(mockSpan);
|
|
30
|
-
}),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
getTracerSpy.mockRestore();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
function createMockImages() {
|
|
41
|
-
const mockTransformer: any = {
|
|
42
|
-
transform: vi.fn(function (this: any) {
|
|
43
|
-
return mockTransformer;
|
|
44
|
-
}),
|
|
45
|
-
draw: vi.fn(function (this: any) {
|
|
46
|
-
return mockTransformer;
|
|
47
|
-
}),
|
|
48
|
-
output: vi.fn(async () => ({
|
|
49
|
-
response: () => new Response('image-data'),
|
|
50
|
-
blob: async () => new Blob(['image-data']),
|
|
51
|
-
arrayBuffer: async () => new ArrayBuffer(8),
|
|
52
|
-
})),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
images: {
|
|
57
|
-
info: vi.fn(async () => ({ width: 800, height: 600, format: 'png' })),
|
|
58
|
-
input: vi.fn(() => mockTransformer),
|
|
59
|
-
someOtherMethod: vi.fn(() => 'passthrough'),
|
|
60
|
-
someProperty: 'test-value',
|
|
61
|
-
},
|
|
62
|
-
mockTransformer,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe('info()', () => {
|
|
67
|
-
it('should create its own span with correct attributes', async () => {
|
|
68
|
-
const { images } = createMockImages();
|
|
69
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
70
|
-
|
|
71
|
-
await instrumented.info(new ArrayBuffer(8));
|
|
72
|
-
|
|
73
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
|
|
74
|
-
|
|
75
|
-
const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
76
|
-
expect(spanName).toBe('Images my-images: info');
|
|
77
|
-
expect(options.kind).toBe(SpanKind.CLIENT);
|
|
78
|
-
expect(options.attributes['images.system']).toBe('cloudflare-images');
|
|
79
|
-
expect(options.attributes['images.operation']).toBe('info');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should record width, height, format from result', async () => {
|
|
83
|
-
const { images } = createMockImages();
|
|
84
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
85
|
-
|
|
86
|
-
await instrumented.info(new ArrayBuffer(8));
|
|
87
|
-
|
|
88
|
-
expect(mockSpan.setAttribute).toHaveBeenCalledWith('images.width', 800);
|
|
89
|
-
expect(mockSpan.setAttribute).toHaveBeenCalledWith('images.height', 600);
|
|
90
|
-
expect(mockSpan.setAttribute).toHaveBeenCalledWith('images.format', 'png');
|
|
91
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
|
|
92
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should handle errors', async () => {
|
|
96
|
-
const { images } = createMockImages();
|
|
97
|
-
const testError = new Error('Image info failed');
|
|
98
|
-
images.info = vi.fn(async () => {
|
|
99
|
-
throw testError;
|
|
100
|
-
});
|
|
101
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
102
|
-
|
|
103
|
-
await expect(instrumented.info(new ArrayBuffer(8))).rejects.toThrow('Image info failed');
|
|
104
|
-
|
|
105
|
-
expect(mockSpan.recordException).toHaveBeenCalledWith(testError);
|
|
106
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({
|
|
107
|
-
code: SpanStatusCode.ERROR,
|
|
108
|
-
message: 'Image info failed',
|
|
109
|
-
});
|
|
110
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('pipeline: input() -> output()', () => {
|
|
115
|
-
it('should create a single span at output() with operation_count = 0', async () => {
|
|
116
|
-
const { images } = createMockImages();
|
|
117
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
118
|
-
|
|
119
|
-
const transformer = instrumented.input(new ArrayBuffer(8));
|
|
120
|
-
await transformer.output();
|
|
121
|
-
|
|
122
|
-
// Only one span created at output(), not at input()
|
|
123
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
|
|
124
|
-
|
|
125
|
-
const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
126
|
-
expect(spanName).toBe('Images my-images: output');
|
|
127
|
-
expect(options.attributes['images.system']).toBe('cloudflare-images');
|
|
128
|
-
expect(options.attributes['images.pipeline.operation_count']).toBe(0);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('pipeline: input() -> transform() -> output()', () => {
|
|
133
|
-
it('should create span with operation_count = 1', async () => {
|
|
134
|
-
const { images } = createMockImages();
|
|
135
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
136
|
-
|
|
137
|
-
const transformer = instrumented.input(new ArrayBuffer(8));
|
|
138
|
-
const transformed = transformer.transform({ width: 400 });
|
|
139
|
-
await transformed.output();
|
|
140
|
-
|
|
141
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
|
|
142
|
-
|
|
143
|
-
const [, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
144
|
-
expect(options.attributes['images.pipeline.operation_count']).toBe(1);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe('pipeline: input() -> transform() -> draw() -> output()', () => {
|
|
149
|
-
it('should create span with operation_count = 2', async () => {
|
|
150
|
-
const { images } = createMockImages();
|
|
151
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
152
|
-
|
|
153
|
-
const transformer = instrumented.input(new ArrayBuffer(8));
|
|
154
|
-
const transformed = transformer.transform({ width: 400 });
|
|
155
|
-
const drawn = transformed.draw({});
|
|
156
|
-
await drawn.output();
|
|
157
|
-
|
|
158
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
|
|
159
|
-
|
|
160
|
-
const [, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
161
|
-
expect(options.attributes['images.pipeline.operation_count']).toBe(2);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('output() format capture', () => {
|
|
166
|
-
it('should capture format from string arg', async () => {
|
|
167
|
-
const { images } = createMockImages();
|
|
168
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
169
|
-
|
|
170
|
-
const transformer = instrumented.input(new ArrayBuffer(8));
|
|
171
|
-
await transformer.output('webp');
|
|
172
|
-
|
|
173
|
-
const [, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
174
|
-
expect(options.attributes['images.output.format']).toBe('webp');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should capture format from options object', async () => {
|
|
178
|
-
const { images } = createMockImages();
|
|
179
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
180
|
-
|
|
181
|
-
const transformer = instrumented.input(new ArrayBuffer(8));
|
|
182
|
-
await transformer.output({ format: 'avif' });
|
|
183
|
-
|
|
184
|
-
const [, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
185
|
-
expect(options.attributes['images.output.format']).toBe('avif');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it('should handle errors in output()', async () => {
|
|
189
|
-
const { images, mockTransformer } = createMockImages();
|
|
190
|
-
const testError = new Error('Output failed');
|
|
191
|
-
mockTransformer.output = vi.fn(async () => {
|
|
192
|
-
throw testError;
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
196
|
-
const transformer = instrumented.input(new ArrayBuffer(8));
|
|
197
|
-
|
|
198
|
-
await expect(transformer.output()).rejects.toThrow('Output failed');
|
|
199
|
-
|
|
200
|
-
expect(mockSpan.recordException).toHaveBeenCalledWith(testError);
|
|
201
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({
|
|
202
|
-
code: SpanStatusCode.ERROR,
|
|
203
|
-
message: 'Output failed',
|
|
204
|
-
});
|
|
205
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
describe('this-binding', () => {
|
|
210
|
-
it('should invoke info() with original object as this, not the proxy', async () => {
|
|
211
|
-
let receivedThis: any;
|
|
212
|
-
const mockImagesObj = {
|
|
213
|
-
info: vi.fn(async function(this: any) {
|
|
214
|
-
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
215
|
-
receivedThis = this;
|
|
216
|
-
return { width: 800, height: 600, format: 'png' };
|
|
217
|
-
}),
|
|
218
|
-
input: vi.fn(() => ({ transform: vi.fn(), draw: vi.fn(), output: vi.fn() })),
|
|
219
|
-
};
|
|
220
|
-
const instrumented = instrumentImages(mockImagesObj as any, 'test');
|
|
221
|
-
await instrumented.info(new ArrayBuffer(8));
|
|
222
|
-
expect(receivedThis).toBe(mockImagesObj);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('should invoke input() with original object as this, not the proxy', () => {
|
|
226
|
-
let receivedThis: any;
|
|
227
|
-
const mockTransformer = { transform: vi.fn(), draw: vi.fn(), output: vi.fn(async () => ({})) };
|
|
228
|
-
const mockImagesObj = {
|
|
229
|
-
info: vi.fn(async () => ({ width: 800, height: 600, format: 'png' })),
|
|
230
|
-
input: vi.fn(function(this: any) {
|
|
231
|
-
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
232
|
-
receivedThis = this;
|
|
233
|
-
return mockTransformer;
|
|
234
|
-
}),
|
|
235
|
-
};
|
|
236
|
-
const instrumented = instrumentImages(mockImagesObj as any, 'test');
|
|
237
|
-
instrumented.input(new ArrayBuffer(8));
|
|
238
|
-
expect(receivedThis).toBe(mockImagesObj);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('non-instrumented methods', () => {
|
|
243
|
-
it('should pass through non-instrumented methods unchanged', () => {
|
|
244
|
-
const { images } = createMockImages();
|
|
245
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
246
|
-
|
|
247
|
-
const result = (instrumented as any).someOtherMethod();
|
|
248
|
-
|
|
249
|
-
expect(result).toBe('passthrough');
|
|
250
|
-
expect(images.someOtherMethod).toHaveBeenCalled();
|
|
251
|
-
expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('should pass through non-instrumented properties unchanged', () => {
|
|
255
|
-
const { images } = createMockImages();
|
|
256
|
-
const instrumented = instrumentImages(images as any, 'my-images');
|
|
257
|
-
|
|
258
|
-
expect((instrumented as any).someProperty).toBe('test-value');
|
|
259
|
-
expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
});
|
package/src/bindings/images.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Images binding instrumentation
|
|
3
|
-
*
|
|
4
|
-
* The Images binding uses a fluent chain: input() -> transform() -> draw() -> output()
|
|
5
|
-
* We only create a span at the terminal output() call to avoid intermediate noise.
|
|
6
|
-
* info() is a standalone operation and gets its own span.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
trace,
|
|
11
|
-
SpanKind,
|
|
12
|
-
SpanStatusCode,
|
|
13
|
-
} from '@opentelemetry/api';
|
|
14
|
-
import type { WorkerTracer } from 'autotel-edge';
|
|
15
|
-
import { wrap, setAttr } from './common';
|
|
16
|
-
|
|
17
|
-
const pipelineMetaSymbol = Symbol('images-pipeline-meta');
|
|
18
|
-
|
|
19
|
-
interface PipelineMeta {
|
|
20
|
-
operationCount: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface ImagesLike {
|
|
24
|
-
info(blob: ReadableStream | ArrayBuffer | Blob): Promise<{ width: number; height: number; format: string }>;
|
|
25
|
-
input(blob: ReadableStream | ArrayBuffer | Blob): ImageTransformerLike;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface ImageTransformerLike {
|
|
29
|
-
transform(options: unknown): ImageTransformerLike;
|
|
30
|
-
draw(image: unknown, options?: unknown): ImageTransformerLike;
|
|
31
|
-
output(options?: unknown): Promise<ImageOutputLike>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface ImageOutputLike {
|
|
35
|
-
response(): Response;
|
|
36
|
-
blob(): Promise<Blob>;
|
|
37
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function proxyTransformer(transformer: ImageTransformerLike, meta: PipelineMeta, bindingName: string): ImageTransformerLike {
|
|
41
|
-
const handler: ProxyHandler<ImageTransformerLike> = {
|
|
42
|
-
get(target, prop) {
|
|
43
|
-
const value = Reflect.get(target, prop);
|
|
44
|
-
|
|
45
|
-
if ((prop === 'transform' || prop === 'draw') && typeof value === 'function') {
|
|
46
|
-
return new Proxy(value, {
|
|
47
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
48
|
-
meta.operationCount++;
|
|
49
|
-
const result = Reflect.apply(fnTarget, target, args);
|
|
50
|
-
// If the result is the transformer itself (fluent chain), return our proxy
|
|
51
|
-
if (result === target || (result && typeof result === 'object' && 'output' in result)) {
|
|
52
|
-
return proxyTransformer(result as ImageTransformerLike, meta, bindingName);
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (prop === 'output' && typeof value === 'function') {
|
|
60
|
-
return new Proxy(value, {
|
|
61
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
62
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
63
|
-
const [formatOrOptions] = args;
|
|
64
|
-
|
|
65
|
-
const attributes: Record<string, string | number> = {
|
|
66
|
-
'images.system': 'cloudflare-images',
|
|
67
|
-
'images.pipeline.operation_count': meta.operationCount,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Capture output format
|
|
71
|
-
if (typeof formatOrOptions === 'string') {
|
|
72
|
-
attributes['images.output.format'] = formatOrOptions;
|
|
73
|
-
} else if (formatOrOptions && typeof formatOrOptions === 'object') {
|
|
74
|
-
const fmt = (formatOrOptions as any).format;
|
|
75
|
-
if (fmt) attributes['images.output.format'] = fmt;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return tracer.startActiveSpan(
|
|
79
|
-
`Images ${bindingName}: output`,
|
|
80
|
-
{
|
|
81
|
-
kind: SpanKind.CLIENT,
|
|
82
|
-
attributes,
|
|
83
|
-
},
|
|
84
|
-
async (span) => {
|
|
85
|
-
try {
|
|
86
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
87
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
88
|
-
return result;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
span.recordException(error as Error);
|
|
91
|
-
span.setStatus({
|
|
92
|
-
code: SpanStatusCode.ERROR,
|
|
93
|
-
message: error instanceof Error ? error.message : String(error),
|
|
94
|
-
});
|
|
95
|
-
throw error;
|
|
96
|
-
} finally {
|
|
97
|
-
span.end();
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
);
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return value;
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const proxy = new Proxy(transformer, handler);
|
|
110
|
-
Object.defineProperty(proxy, pipelineMetaSymbol, {
|
|
111
|
-
value: meta,
|
|
112
|
-
writable: false,
|
|
113
|
-
enumerable: false,
|
|
114
|
-
configurable: false,
|
|
115
|
-
});
|
|
116
|
-
return proxy;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Instrument Images binding
|
|
121
|
-
*/
|
|
122
|
-
export function instrumentImages<T extends ImagesLike>(images: T, bindingName?: string): T {
|
|
123
|
-
const name = bindingName || 'images';
|
|
124
|
-
|
|
125
|
-
const handler: ProxyHandler<T> = {
|
|
126
|
-
get(target, prop) {
|
|
127
|
-
const value = Reflect.get(target, prop);
|
|
128
|
-
|
|
129
|
-
if (prop === 'info' && typeof value === 'function') {
|
|
130
|
-
return new Proxy(value, {
|
|
131
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
132
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
133
|
-
|
|
134
|
-
return tracer.startActiveSpan(
|
|
135
|
-
`Images ${name}: info`,
|
|
136
|
-
{
|
|
137
|
-
kind: SpanKind.CLIENT,
|
|
138
|
-
attributes: {
|
|
139
|
-
'images.system': 'cloudflare-images',
|
|
140
|
-
'images.operation': 'info',
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
async (span) => {
|
|
144
|
-
try {
|
|
145
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
146
|
-
setAttr(span, 'images.width', result?.width);
|
|
147
|
-
setAttr(span, 'images.height', result?.height);
|
|
148
|
-
setAttr(span, 'images.format', result?.format);
|
|
149
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
150
|
-
return result;
|
|
151
|
-
} catch (error) {
|
|
152
|
-
span.recordException(error as Error);
|
|
153
|
-
span.setStatus({
|
|
154
|
-
code: SpanStatusCode.ERROR,
|
|
155
|
-
message: error instanceof Error ? error.message : String(error),
|
|
156
|
-
});
|
|
157
|
-
throw error;
|
|
158
|
-
} finally {
|
|
159
|
-
span.end();
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (prop === 'input' && typeof value === 'function') {
|
|
168
|
-
return new Proxy(value, {
|
|
169
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
170
|
-
const transformer = Reflect.apply(fnTarget, target, args) as ImageTransformerLike;
|
|
171
|
-
const meta: PipelineMeta = { operationCount: 0 };
|
|
172
|
-
return proxyTransformer(transformer, meta, name);
|
|
173
|
-
},
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return value;
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
return wrap(images, handler);
|
|
182
|
-
}
|
package/src/bindings/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bindings instrumentation for Cloudflare Workers
|
|
3
|
-
* Auto-instrument KV, R2, D1, Service Bindings, and more
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
instrumentKV,
|
|
8
|
-
instrumentR2,
|
|
9
|
-
instrumentD1,
|
|
10
|
-
instrumentServiceBinding,
|
|
11
|
-
instrumentBindings,
|
|
12
|
-
} from './bindings';
|
|
13
|
-
export { instrumentAI } from './ai';
|
|
14
|
-
export { instrumentVectorize } from './vectorize';
|
|
15
|
-
export { instrumentHyperdrive } from './hyperdrive';
|
|
16
|
-
export { instrumentQueueProducer } from './queue-producer';
|
|
17
|
-
export { instrumentAnalyticsEngine } from './analytics-engine';
|
|
18
|
-
export { instrumentImages } from './images';
|
|
19
|
-
export { instrumentRateLimiter } from './rate-limiter';
|
|
20
|
-
export { instrumentBrowserRendering } from './browser-rendering';
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { instrumentQueueProducer } from './queue-producer';
|
|
3
|
-
import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
|
|
4
|
-
|
|
5
|
-
describe('Queue Producer Binding Instrumentation', () => {
|
|
6
|
-
let mockTracer: any;
|
|
7
|
-
let mockSpan: any;
|
|
8
|
-
let getTracerSpy: any;
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mockSpan = {
|
|
12
|
-
spanContext: () => ({
|
|
13
|
-
traceId: 'test-trace-id',
|
|
14
|
-
spanId: 'test-span-id',
|
|
15
|
-
traceFlags: 1,
|
|
16
|
-
}),
|
|
17
|
-
setAttribute: vi.fn(),
|
|
18
|
-
setAttributes: vi.fn(),
|
|
19
|
-
setStatus: vi.fn(),
|
|
20
|
-
recordException: vi.fn(),
|
|
21
|
-
end: vi.fn(),
|
|
22
|
-
isRecording: () => true,
|
|
23
|
-
updateName: vi.fn(),
|
|
24
|
-
addEvent: vi.fn(),
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
mockTracer = {
|
|
28
|
-
startActiveSpan: vi.fn((name, options, fn) => {
|
|
29
|
-
return fn(mockSpan);
|
|
30
|
-
}),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
getTracerSpy.mockRestore();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
function createMockQueue(overrides: Partial<Queue> = {}): Queue {
|
|
41
|
-
return {
|
|
42
|
-
send: vi.fn(async () => ({ messageId: 'msg-123', outcome: 'ok' })),
|
|
43
|
-
sendBatch: vi.fn(async () => ({})),
|
|
44
|
-
...overrides,
|
|
45
|
-
} as unknown as Queue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
describe('send()', () => {
|
|
49
|
-
it('should create span with PRODUCER kind and correct attributes', async () => {
|
|
50
|
-
const mockQueue = createMockQueue();
|
|
51
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
52
|
-
|
|
53
|
-
await instrumented.send({ data: 'test-payload' });
|
|
54
|
-
|
|
55
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
|
|
56
|
-
|
|
57
|
-
const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
58
|
-
expect(spanName).toBe('Queue my-queue: send');
|
|
59
|
-
expect(options.kind).toBe(SpanKind.PRODUCER);
|
|
60
|
-
expect(options.attributes['messaging.system']).toBe('cloudflare-queues');
|
|
61
|
-
expect(options.attributes['messaging.operation.type']).toBe('publish');
|
|
62
|
-
expect(options.attributes['messaging.operation']).toBe('send');
|
|
63
|
-
expect(options.attributes['messaging.destination.name']).toBe('my-queue');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should record messageId from result when available', async () => {
|
|
67
|
-
const mockQueue = createMockQueue({
|
|
68
|
-
send: vi.fn(async () => ({ messageId: 'msg-456', outcome: 'ok' })) as any,
|
|
69
|
-
});
|
|
70
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
71
|
-
|
|
72
|
-
await instrumented.send({ data: 'test-payload' });
|
|
73
|
-
|
|
74
|
-
expect(mockSpan.setAttribute).toHaveBeenCalledWith('messaging.message.id', 'msg-456');
|
|
75
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
|
|
76
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should not set messageId attribute when result has no messageId', async () => {
|
|
80
|
-
const mockQueue = createMockQueue({
|
|
81
|
-
send: vi.fn(async () => ({})) as any,
|
|
82
|
-
});
|
|
83
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
84
|
-
|
|
85
|
-
await instrumented.send({ data: 'test-payload' });
|
|
86
|
-
|
|
87
|
-
// setAttr uses the common helper which skips undefined/null values
|
|
88
|
-
const messageIdCalls = mockSpan.setAttribute.mock.calls.filter(
|
|
89
|
-
(call: any) => call[0] === 'messaging.message.id'
|
|
90
|
-
);
|
|
91
|
-
expect(messageIdCalls.length).toBe(0);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should handle errors in send()', async () => {
|
|
95
|
-
const sendError = new Error('Queue full');
|
|
96
|
-
const mockQueue = createMockQueue({
|
|
97
|
-
send: vi.fn(async () => {
|
|
98
|
-
throw sendError;
|
|
99
|
-
}) as any,
|
|
100
|
-
});
|
|
101
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
102
|
-
|
|
103
|
-
await expect(instrumented.send({ data: 'test-payload' })).rejects.toThrow('Queue full');
|
|
104
|
-
|
|
105
|
-
expect(mockSpan.recordException).toHaveBeenCalledWith(sendError);
|
|
106
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({
|
|
107
|
-
code: SpanStatusCode.ERROR,
|
|
108
|
-
message: 'Queue full',
|
|
109
|
-
});
|
|
110
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should use default queue name when none provided', async () => {
|
|
114
|
-
const mockQueue = createMockQueue();
|
|
115
|
-
const instrumented = instrumentQueueProducer(mockQueue);
|
|
116
|
-
|
|
117
|
-
await instrumented.send({ data: 'test-payload' });
|
|
118
|
-
|
|
119
|
-
const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
120
|
-
expect(spanName).toBe('Queue queue: send');
|
|
121
|
-
expect(options.attributes['messaging.destination.name']).toBe('queue');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('sendBatch()', () => {
|
|
126
|
-
it('should create span with batch_message_count', async () => {
|
|
127
|
-
const mockQueue = createMockQueue();
|
|
128
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
129
|
-
|
|
130
|
-
const messages = [
|
|
131
|
-
{ body: { data: 'msg-1' } },
|
|
132
|
-
{ body: { data: 'msg-2' } },
|
|
133
|
-
{ body: { data: 'msg-3' } },
|
|
134
|
-
];
|
|
135
|
-
|
|
136
|
-
await instrumented.sendBatch(messages);
|
|
137
|
-
|
|
138
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1);
|
|
139
|
-
|
|
140
|
-
const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0];
|
|
141
|
-
expect(spanName).toBe('Queue my-queue: sendBatch');
|
|
142
|
-
expect(options.kind).toBe(SpanKind.PRODUCER);
|
|
143
|
-
expect(options.attributes['messaging.system']).toBe('cloudflare-queues');
|
|
144
|
-
expect(options.attributes['messaging.operation.type']).toBe('publish');
|
|
145
|
-
expect(options.attributes['messaging.operation']).toBe('sendBatch');
|
|
146
|
-
expect(options.attributes['messaging.destination.name']).toBe('my-queue');
|
|
147
|
-
expect(options.attributes['messaging.batch.message_count']).toBe(3);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should set OK status and end span on success', async () => {
|
|
151
|
-
const mockQueue = createMockQueue();
|
|
152
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
153
|
-
|
|
154
|
-
await instrumented.sendBatch([{ body: { data: 'msg-1' } }]);
|
|
155
|
-
|
|
156
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
|
|
157
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should handle errors in sendBatch()', async () => {
|
|
161
|
-
const batchError = new Error('Batch limit exceeded');
|
|
162
|
-
const mockQueue = createMockQueue({
|
|
163
|
-
sendBatch: vi.fn(async () => {
|
|
164
|
-
throw batchError;
|
|
165
|
-
}) as any,
|
|
166
|
-
});
|
|
167
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
168
|
-
|
|
169
|
-
const messages = [{ body: { data: 'msg-1' } }, { body: { data: 'msg-2' } }];
|
|
170
|
-
|
|
171
|
-
await expect(instrumented.sendBatch(messages)).rejects.toThrow('Batch limit exceeded');
|
|
172
|
-
|
|
173
|
-
expect(mockSpan.recordException).toHaveBeenCalledWith(batchError);
|
|
174
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({
|
|
175
|
-
code: SpanStatusCode.ERROR,
|
|
176
|
-
message: 'Batch limit exceeded',
|
|
177
|
-
});
|
|
178
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe('this-binding', () => {
|
|
183
|
-
it('should invoke send() with original object as this, not the proxy', async () => {
|
|
184
|
-
let receivedThis: any;
|
|
185
|
-
const mockQueue = {
|
|
186
|
-
send: vi.fn(async function(this: any) {
|
|
187
|
-
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
188
|
-
receivedThis = this;
|
|
189
|
-
return { messageId: 'msg-123' };
|
|
190
|
-
}),
|
|
191
|
-
sendBatch: vi.fn(async () => ({})),
|
|
192
|
-
} as unknown as Queue;
|
|
193
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'test');
|
|
194
|
-
await instrumented.send({ data: 'test' });
|
|
195
|
-
expect(receivedThis).toBe(mockQueue);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should invoke sendBatch() with original object as this, not the proxy', async () => {
|
|
199
|
-
let receivedThis: any;
|
|
200
|
-
const mockQueue = {
|
|
201
|
-
send: vi.fn(async () => ({ messageId: 'msg-123' })),
|
|
202
|
-
sendBatch: vi.fn(async function(this: any) {
|
|
203
|
-
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
204
|
-
receivedThis = this;
|
|
205
|
-
return {};
|
|
206
|
-
}),
|
|
207
|
-
} as unknown as Queue;
|
|
208
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'test');
|
|
209
|
-
await instrumented.sendBatch([{ body: { data: 'test' } }]);
|
|
210
|
-
expect(receivedThis).toBe(mockQueue);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('non-instrumented methods', () => {
|
|
215
|
-
it('should pass through non-instrumented properties', () => {
|
|
216
|
-
const mockQueue = createMockQueue();
|
|
217
|
-
(mockQueue as any).customProp = 'test-value';
|
|
218
|
-
|
|
219
|
-
const instrumented = instrumentQueueProducer(mockQueue, 'my-queue');
|
|
220
|
-
|
|
221
|
-
expect((instrumented as any).customProp).toBe('test-value');
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
});
|