autotel-cloudflare 2.15.0 → 2.16.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/README.md +3 -3
- package/dist/bindings.d.ts +0 -19
- package/dist/bindings.js +1 -1
- package/dist/{chunk-4UG2QCPQ.js → chunk-NUNTBJWB.js} +80 -58
- package/dist/chunk-NUNTBJWB.js.map +1 -0
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bindings/ai.test.ts +14 -0
- package/src/bindings/ai.ts +2 -2
- package/src/bindings/analytics-engine.test.ts +15 -0
- package/src/bindings/analytics-engine.ts +2 -2
- package/src/bindings/bindings-cache.test.ts +80 -0
- package/src/bindings/bindings-this-binding.test.ts +258 -0
- package/src/bindings/bindings.ts +80 -48
- package/src/bindings/browser-rendering.test.ts +16 -0
- package/src/bindings/browser-rendering.ts +2 -2
- package/src/bindings/hyperdrive.test.ts +22 -0
- package/src/bindings/hyperdrive.ts +2 -2
- package/src/bindings/images.test.ts +33 -0
- package/src/bindings/images.ts +4 -4
- package/src/bindings/queue-producer.test.ts +32 -0
- package/src/bindings/queue-producer.ts +4 -4
- package/src/bindings/rate-limiter.test.ts +16 -0
- package/src/bindings/rate-limiter.ts +2 -2
- package/src/bindings/vectorize.test.ts +22 -0
- package/src/bindings/vectorize.ts +2 -2
- package/src/global/fetch.test.ts +55 -0
- package/src/global/fetch.ts +4 -2
- package/src/wrappers/instrument.integration.test.ts +103 -0
- package/src/wrappers/instrument.ts +16 -3
- package/dist/chunk-4UG2QCPQ.js.map +0 -1
package/src/bindings/bindings.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
SpanKind,
|
|
23
23
|
SpanStatusCode,
|
|
24
24
|
} from '@opentelemetry/api';
|
|
25
|
-
import { WorkerTracer } from 'autotel-edge';
|
|
25
|
+
import { WorkerTracer, getActiveConfig } from 'autotel-edge';
|
|
26
26
|
import { wrap, isWrapped } from './common';
|
|
27
27
|
import { instrumentAI } from './ai';
|
|
28
28
|
import { instrumentVectorize } from './vectorize';
|
|
@@ -31,6 +31,20 @@ import { instrumentQueueProducer } from './queue-producer';
|
|
|
31
31
|
import { instrumentAnalyticsEngine } from './analytics-engine';
|
|
32
32
|
import { instrumentImages } from './images';
|
|
33
33
|
|
|
34
|
+
type DbStatementCapture = 'off' | 'obfuscated' | 'full';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Sanitize a SQL statement based on the capture mode.
|
|
38
|
+
* - 'full': returns the statement as-is
|
|
39
|
+
* - 'obfuscated': replaces string literals and numbers with '?'
|
|
40
|
+
* - 'off': returns undefined (attribute not set)
|
|
41
|
+
*/
|
|
42
|
+
function sanitizeStatement(query: string, mode: DbStatementCapture): string | undefined {
|
|
43
|
+
if (mode === 'off') return undefined;
|
|
44
|
+
if (mode === 'obfuscated') return query.replaceAll(/'[^']*'/g, "'?'").replaceAll(/\b\d+\b/g, '?');
|
|
45
|
+
return query;
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
/**
|
|
35
49
|
* Instrument KV namespace
|
|
36
50
|
*/
|
|
@@ -43,10 +57,10 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
43
57
|
|
|
44
58
|
if (prop === 'get' && typeof value === 'function') {
|
|
45
59
|
return new Proxy(value, {
|
|
46
|
-
apply: (fnTarget,
|
|
60
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
47
61
|
const [key, options] = args as [string, KVNamespaceGetOptions<unknown> | undefined];
|
|
48
62
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
49
|
-
|
|
63
|
+
|
|
50
64
|
return tracer.startActiveSpan(
|
|
51
65
|
`KV ${name}: get`,
|
|
52
66
|
{
|
|
@@ -61,7 +75,7 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
61
75
|
},
|
|
62
76
|
async (span) => {
|
|
63
77
|
try {
|
|
64
|
-
const result = await Reflect.apply(fnTarget,
|
|
78
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
65
79
|
span.setAttribute('db.result.type', result === null ? 'null' : typeof result);
|
|
66
80
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
67
81
|
return result;
|
|
@@ -83,10 +97,10 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
83
97
|
|
|
84
98
|
if (prop === 'put' && typeof value === 'function') {
|
|
85
99
|
return new Proxy(value, {
|
|
86
|
-
apply: (fnTarget,
|
|
100
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
87
101
|
const [key] = args as [string, unknown, KVNamespacePutOptions | undefined];
|
|
88
102
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
89
|
-
|
|
103
|
+
|
|
90
104
|
return tracer.startActiveSpan(
|
|
91
105
|
`KV ${name}: put`,
|
|
92
106
|
{
|
|
@@ -100,7 +114,7 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
100
114
|
},
|
|
101
115
|
async (span) => {
|
|
102
116
|
try {
|
|
103
|
-
const result = await Reflect.apply(fnTarget,
|
|
117
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
104
118
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
105
119
|
return result;
|
|
106
120
|
} catch (error) {
|
|
@@ -121,10 +135,10 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
121
135
|
|
|
122
136
|
if (prop === 'delete' && typeof value === 'function') {
|
|
123
137
|
return new Proxy(value, {
|
|
124
|
-
apply: (fnTarget,
|
|
138
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
125
139
|
const [key] = args as [string];
|
|
126
140
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
127
|
-
|
|
141
|
+
|
|
128
142
|
return tracer.startActiveSpan(
|
|
129
143
|
`KV ${name}: delete`,
|
|
130
144
|
{
|
|
@@ -138,7 +152,7 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
138
152
|
},
|
|
139
153
|
async (span) => {
|
|
140
154
|
try {
|
|
141
|
-
const result = await Reflect.apply(fnTarget,
|
|
155
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
142
156
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
143
157
|
return result;
|
|
144
158
|
} catch (error) {
|
|
@@ -159,10 +173,10 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
159
173
|
|
|
160
174
|
if (prop === 'list' && typeof value === 'function') {
|
|
161
175
|
return new Proxy(value, {
|
|
162
|
-
apply: (fnTarget,
|
|
176
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
163
177
|
const [options] = args as [KVNamespaceListOptions | undefined];
|
|
164
178
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
165
|
-
|
|
179
|
+
|
|
166
180
|
return tracer.startActiveSpan(
|
|
167
181
|
`KV ${name}: list`,
|
|
168
182
|
{
|
|
@@ -177,7 +191,7 @@ export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: strin
|
|
|
177
191
|
},
|
|
178
192
|
async (span) => {
|
|
179
193
|
try {
|
|
180
|
-
const result = await Reflect.apply(fnTarget,
|
|
194
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
181
195
|
span.setAttribute('db.result.keys_count', result.keys.length);
|
|
182
196
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
183
197
|
return result;
|
|
@@ -216,10 +230,10 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
216
230
|
|
|
217
231
|
if (prop === 'get' && typeof value === 'function') {
|
|
218
232
|
return new Proxy(value, {
|
|
219
|
-
apply: (fnTarget,
|
|
233
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
220
234
|
const [key] = args as [string, R2GetOptions | undefined];
|
|
221
235
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
222
|
-
|
|
236
|
+
|
|
223
237
|
return tracer.startActiveSpan(
|
|
224
238
|
`R2 ${name}: get`,
|
|
225
239
|
{
|
|
@@ -233,7 +247,7 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
233
247
|
},
|
|
234
248
|
async (span) => {
|
|
235
249
|
try {
|
|
236
|
-
const result = await Reflect.apply(fnTarget,
|
|
250
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
237
251
|
if (result) {
|
|
238
252
|
span.setAttribute('db.result.size', result.size);
|
|
239
253
|
span.setAttribute('db.result.etag', result.etag);
|
|
@@ -261,10 +275,10 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
261
275
|
|
|
262
276
|
if (prop === 'put' && typeof value === 'function') {
|
|
263
277
|
return new Proxy(value, {
|
|
264
|
-
apply: (fnTarget,
|
|
278
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
265
279
|
const [key] = args as [string, ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, R2PutOptions | undefined];
|
|
266
280
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
267
|
-
|
|
281
|
+
|
|
268
282
|
return tracer.startActiveSpan(
|
|
269
283
|
`R2 ${name}: put`,
|
|
270
284
|
{
|
|
@@ -278,7 +292,7 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
278
292
|
},
|
|
279
293
|
async (span) => {
|
|
280
294
|
try {
|
|
281
|
-
const result = await Reflect.apply(fnTarget,
|
|
295
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
282
296
|
span.setAttribute('db.result.etag', result.etag);
|
|
283
297
|
span.setAttribute('db.result.uploaded', result.uploaded);
|
|
284
298
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
@@ -301,10 +315,10 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
301
315
|
|
|
302
316
|
if (prop === 'delete' && typeof value === 'function') {
|
|
303
317
|
return new Proxy(value, {
|
|
304
|
-
apply: (fnTarget,
|
|
318
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
305
319
|
const keys = args as string[];
|
|
306
320
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
307
|
-
|
|
321
|
+
|
|
308
322
|
return tracer.startActiveSpan(
|
|
309
323
|
`R2 ${name}: delete`,
|
|
310
324
|
{
|
|
@@ -318,7 +332,7 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
318
332
|
},
|
|
319
333
|
async (span) => {
|
|
320
334
|
try {
|
|
321
|
-
const result = await Reflect.apply(fnTarget,
|
|
335
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
322
336
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
323
337
|
return result;
|
|
324
338
|
} catch (error) {
|
|
@@ -339,10 +353,10 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
339
353
|
|
|
340
354
|
if (prop === 'list' && typeof value === 'function') {
|
|
341
355
|
return new Proxy(value, {
|
|
342
|
-
apply: (fnTarget,
|
|
356
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
343
357
|
const [options] = args as [R2ListOptions | undefined];
|
|
344
358
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
345
|
-
|
|
359
|
+
|
|
346
360
|
return tracer.startActiveSpan(
|
|
347
361
|
`R2 ${name}: list`,
|
|
348
362
|
{
|
|
@@ -357,7 +371,7 @@ export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R
|
|
|
357
371
|
},
|
|
358
372
|
async (span) => {
|
|
359
373
|
try {
|
|
360
|
-
const result = await Reflect.apply(fnTarget,
|
|
374
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
361
375
|
span.setAttribute('db.result.objects_count', result.objects.length);
|
|
362
376
|
span.setAttribute('db.result.truncated', result.truncated);
|
|
363
377
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
@@ -397,11 +411,11 @@ export function instrumentD1<D extends D1Database>(d1: D, databaseName?: string)
|
|
|
397
411
|
|
|
398
412
|
if (prop === 'prepare' && typeof value === 'function') {
|
|
399
413
|
return new Proxy(value, {
|
|
400
|
-
apply: (fnTarget,
|
|
414
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
401
415
|
const [query] = args as [string];
|
|
402
416
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
403
|
-
|
|
404
|
-
const prepared = Reflect.apply(fnTarget,
|
|
417
|
+
|
|
418
|
+
const prepared = Reflect.apply(fnTarget, target, args);
|
|
405
419
|
|
|
406
420
|
// Instrument the prepared statement
|
|
407
421
|
const preparedHandler: ProxyHandler<typeof prepared> = {
|
|
@@ -410,21 +424,27 @@ export function instrumentD1<D extends D1Database>(d1: D, databaseName?: string)
|
|
|
410
424
|
|
|
411
425
|
if (prop === 'first' || prop === 'run' || prop === 'all' || prop === 'raw') {
|
|
412
426
|
return new Proxy(value, {
|
|
413
|
-
apply: (fnTarget,
|
|
427
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
428
|
+
const activeConfig = getActiveConfig();
|
|
429
|
+
const captureMode: DbStatementCapture = activeConfig?.dataSafety?.captureDbStatement ?? 'full';
|
|
430
|
+
const statement = sanitizeStatement(query, captureMode);
|
|
431
|
+
const attributes: Record<string, any> = {
|
|
432
|
+
'db.system': 'cloudflare-d1',
|
|
433
|
+
'db.operation': prop,
|
|
434
|
+
'db.name': name,
|
|
435
|
+
};
|
|
436
|
+
if (statement !== undefined) {
|
|
437
|
+
attributes['db.statement'] = statement;
|
|
438
|
+
}
|
|
414
439
|
return tracer.startActiveSpan(
|
|
415
440
|
`D1 ${name}: ${prop}`,
|
|
416
441
|
{
|
|
417
442
|
kind: SpanKind.CLIENT,
|
|
418
|
-
attributes
|
|
419
|
-
'db.system': 'cloudflare-d1',
|
|
420
|
-
'db.operation': prop,
|
|
421
|
-
'db.name': name,
|
|
422
|
-
'db.statement': query,
|
|
423
|
-
},
|
|
443
|
+
attributes,
|
|
424
444
|
},
|
|
425
445
|
async (span) => {
|
|
426
446
|
try {
|
|
427
|
-
const result = await Reflect.apply(fnTarget,
|
|
447
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
428
448
|
if (prop === 'all' && Array.isArray(result)) {
|
|
429
449
|
span.setAttribute('db.result.rows_count', result.length);
|
|
430
450
|
} else if (prop === 'first' && result) {
|
|
@@ -459,24 +479,30 @@ export function instrumentD1<D extends D1Database>(d1: D, databaseName?: string)
|
|
|
459
479
|
|
|
460
480
|
if (prop === 'exec' && typeof value === 'function') {
|
|
461
481
|
return new Proxy(value, {
|
|
462
|
-
apply: (fnTarget,
|
|
482
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
463
483
|
const [query] = args as [string];
|
|
464
484
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
465
|
-
|
|
485
|
+
const activeConfig = getActiveConfig();
|
|
486
|
+
const captureMode: DbStatementCapture = activeConfig?.dataSafety?.captureDbStatement ?? 'full';
|
|
487
|
+
const statement = sanitizeStatement(query, captureMode);
|
|
488
|
+
const attributes: Record<string, any> = {
|
|
489
|
+
'db.system': 'cloudflare-d1',
|
|
490
|
+
'db.operation': 'exec',
|
|
491
|
+
'db.name': name,
|
|
492
|
+
};
|
|
493
|
+
if (statement !== undefined) {
|
|
494
|
+
attributes['db.statement'] = statement;
|
|
495
|
+
}
|
|
496
|
+
|
|
466
497
|
return tracer.startActiveSpan(
|
|
467
498
|
`D1 ${name}: exec`,
|
|
468
499
|
{
|
|
469
500
|
kind: SpanKind.CLIENT,
|
|
470
|
-
attributes
|
|
471
|
-
'db.system': 'cloudflare-d1',
|
|
472
|
-
'db.operation': 'exec',
|
|
473
|
-
'db.name': name,
|
|
474
|
-
'db.statement': query,
|
|
475
|
-
},
|
|
501
|
+
attributes,
|
|
476
502
|
},
|
|
477
503
|
async (span) => {
|
|
478
504
|
try {
|
|
479
|
-
const result = await Reflect.apply(fnTarget,
|
|
505
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
480
506
|
span.setAttribute('db.result.count', result.count);
|
|
481
507
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
482
508
|
return result;
|
|
@@ -515,11 +541,11 @@ export function instrumentServiceBinding<F extends Fetcher>(fetcher: F, serviceN
|
|
|
515
541
|
|
|
516
542
|
if (prop === 'fetch' && typeof value === 'function') {
|
|
517
543
|
return new Proxy(value, {
|
|
518
|
-
apply: (fnTarget,
|
|
544
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
519
545
|
const [input, init] = args as [RequestInfo | URL, RequestInit | undefined];
|
|
520
546
|
const request = new Request(input, init);
|
|
521
547
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
522
|
-
|
|
548
|
+
|
|
523
549
|
return tracer.startActiveSpan(
|
|
524
550
|
`Service ${name}: ${request.method}`,
|
|
525
551
|
{
|
|
@@ -533,7 +559,7 @@ export function instrumentServiceBinding<F extends Fetcher>(fetcher: F, serviceN
|
|
|
533
559
|
},
|
|
534
560
|
async (span) => {
|
|
535
561
|
try {
|
|
536
|
-
const response = await Reflect.apply(fnTarget,
|
|
562
|
+
const response = await Reflect.apply(fnTarget, target, args);
|
|
537
563
|
span.setAttribute('http.response.status_code', response.status);
|
|
538
564
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
539
565
|
return response;
|
|
@@ -588,7 +614,12 @@ const hasExactMethods = (obj: any, methods: string[]): boolean =>
|
|
|
588
614
|
* - Rate Limiter — limit() alone too generic
|
|
589
615
|
* - Browser Rendering — indistinguishable from Service Binding
|
|
590
616
|
*/
|
|
617
|
+
const envCache = new WeakMap<object, Record<string, any>>();
|
|
618
|
+
|
|
591
619
|
export function instrumentBindings(env: Record<string, any>): Record<string, any> {
|
|
620
|
+
const cached = envCache.get(env);
|
|
621
|
+
if (cached) return cached;
|
|
622
|
+
|
|
592
623
|
const instrumented: Record<string, any> = {};
|
|
593
624
|
|
|
594
625
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -667,6 +698,7 @@ export function instrumentBindings(env: Record<string, any>): Record<string, any
|
|
|
667
698
|
instrumented[key] = value;
|
|
668
699
|
}
|
|
669
700
|
|
|
701
|
+
envCache.set(env, instrumented);
|
|
670
702
|
return instrumented;
|
|
671
703
|
}
|
|
672
704
|
|
|
@@ -129,6 +129,22 @@ describe('Browser Rendering Instrumentation', () => {
|
|
|
129
129
|
});
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
describe('this-binding', () => {
|
|
133
|
+
it('should invoke fetch() with original object as this, not the proxy', async () => {
|
|
134
|
+
let receivedThis: any;
|
|
135
|
+
const mockBrowserObj = {
|
|
136
|
+
fetch: vi.fn(async function(this: any) {
|
|
137
|
+
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
138
|
+
receivedThis = this;
|
|
139
|
+
return new Response('ok', { status: 200 });
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
142
|
+
const instrumented = instrumentBrowserRendering(mockBrowserObj, 'test');
|
|
143
|
+
await instrumented.fetch('https://example.com');
|
|
144
|
+
expect(receivedThis).toBe(mockBrowserObj);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
132
148
|
describe('Non-instrumented methods', () => {
|
|
133
149
|
it('should pass through non-instrumented methods unchanged', () => {
|
|
134
150
|
const instrumented = instrumentBrowserRendering(mockBrowser, 'my-browser');
|
|
@@ -26,7 +26,7 @@ export function instrumentBrowserRendering<T extends BrowserRenderingLike>(brows
|
|
|
26
26
|
|
|
27
27
|
if (prop === 'fetch' && typeof value === 'function') {
|
|
28
28
|
return new Proxy(value, {
|
|
29
|
-
apply: (fnTarget,
|
|
29
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
30
30
|
const [input] = args as [RequestInfo | URL, RequestInit | undefined];
|
|
31
31
|
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
|
32
32
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
@@ -42,7 +42,7 @@ export function instrumentBrowserRendering<T extends BrowserRenderingLike>(brows
|
|
|
42
42
|
},
|
|
43
43
|
async (span) => {
|
|
44
44
|
try {
|
|
45
|
-
const result = await Reflect.apply(fnTarget,
|
|
45
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
46
46
|
setAttr(span, 'http.response.status_code', result?.status);
|
|
47
47
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
48
48
|
return result;
|
|
@@ -113,6 +113,28 @@ describe('Hyperdrive Binding Instrumentation', () => {
|
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
describe('this-binding', () => {
|
|
117
|
+
it('should invoke connect() with original object as this, not the proxy', async () => {
|
|
118
|
+
let receivedThis: any;
|
|
119
|
+
const mockHyperdrive = {
|
|
120
|
+
connect: vi.fn(async function(this: any) {
|
|
121
|
+
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
122
|
+
receivedThis = this;
|
|
123
|
+
return {} as Socket;
|
|
124
|
+
}),
|
|
125
|
+
connectionString: 'postgresql://user:pass@host:5432/db',
|
|
126
|
+
host: 'host',
|
|
127
|
+
port: 5432,
|
|
128
|
+
user: 'user',
|
|
129
|
+
password: 'pass',
|
|
130
|
+
database: 'db',
|
|
131
|
+
} as unknown as Hyperdrive;
|
|
132
|
+
const instrumented = instrumentHyperdrive(mockHyperdrive, 'test');
|
|
133
|
+
await instrumented.connect();
|
|
134
|
+
expect(receivedThis).toBe(mockHyperdrive);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
116
138
|
describe('non-instrumented properties', () => {
|
|
117
139
|
it('should pass through non-instrumented properties', () => {
|
|
118
140
|
const mockHyperdrive = createMockHyperdrive();
|
|
@@ -22,7 +22,7 @@ export function instrumentHyperdrive<T extends Hyperdrive>(hyperdrive: T, bindin
|
|
|
22
22
|
|
|
23
23
|
if (prop === 'connect' && typeof value === 'function') {
|
|
24
24
|
return new Proxy(value, {
|
|
25
|
-
apply: (fnTarget,
|
|
25
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
26
26
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
27
27
|
|
|
28
28
|
const attributes: Record<string, string | number> = {
|
|
@@ -47,7 +47,7 @@ export function instrumentHyperdrive<T extends Hyperdrive>(hyperdrive: T, bindin
|
|
|
47
47
|
},
|
|
48
48
|
async (span) => {
|
|
49
49
|
try {
|
|
50
|
-
const result = await Reflect.apply(fnTarget,
|
|
50
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
51
51
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
52
52
|
return result;
|
|
53
53
|
} catch (error) {
|
|
@@ -206,6 +206,39 @@ describe('Images Binding Instrumentation', () => {
|
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
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
|
+
|
|
209
242
|
describe('non-instrumented methods', () => {
|
|
210
243
|
it('should pass through non-instrumented methods unchanged', () => {
|
|
211
244
|
const { images } = createMockImages();
|
package/src/bindings/images.ts
CHANGED
|
@@ -128,7 +128,7 @@ export function instrumentImages<T extends ImagesLike>(images: T, bindingName?:
|
|
|
128
128
|
|
|
129
129
|
if (prop === 'info' && typeof value === 'function') {
|
|
130
130
|
return new Proxy(value, {
|
|
131
|
-
apply: (fnTarget,
|
|
131
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
132
132
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
133
133
|
|
|
134
134
|
return tracer.startActiveSpan(
|
|
@@ -142,7 +142,7 @@ export function instrumentImages<T extends ImagesLike>(images: T, bindingName?:
|
|
|
142
142
|
},
|
|
143
143
|
async (span) => {
|
|
144
144
|
try {
|
|
145
|
-
const result = await Reflect.apply(fnTarget,
|
|
145
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
146
146
|
setAttr(span, 'images.width', result?.width);
|
|
147
147
|
setAttr(span, 'images.height', result?.height);
|
|
148
148
|
setAttr(span, 'images.format', result?.format);
|
|
@@ -166,8 +166,8 @@ export function instrumentImages<T extends ImagesLike>(images: T, bindingName?:
|
|
|
166
166
|
|
|
167
167
|
if (prop === 'input' && typeof value === 'function') {
|
|
168
168
|
return new Proxy(value, {
|
|
169
|
-
apply: (fnTarget,
|
|
170
|
-
const transformer = Reflect.apply(fnTarget,
|
|
169
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
170
|
+
const transformer = Reflect.apply(fnTarget, target, args) as ImageTransformerLike;
|
|
171
171
|
const meta: PipelineMeta = { operationCount: 0 };
|
|
172
172
|
return proxyTransformer(transformer, meta, name);
|
|
173
173
|
},
|
|
@@ -179,6 +179,38 @@ describe('Queue Producer Binding Instrumentation', () => {
|
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
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
|
+
|
|
182
214
|
describe('non-instrumented methods', () => {
|
|
183
215
|
it('should pass through non-instrumented properties', () => {
|
|
184
216
|
const mockQueue = createMockQueue();
|
|
@@ -22,7 +22,7 @@ export function instrumentQueueProducer<T extends Queue>(queue: T, queueName?: s
|
|
|
22
22
|
|
|
23
23
|
if (prop === 'send' && typeof value === 'function') {
|
|
24
24
|
return new Proxy(value, {
|
|
25
|
-
apply: (fnTarget,
|
|
25
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
26
26
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
27
27
|
|
|
28
28
|
return tracer.startActiveSpan(
|
|
@@ -38,7 +38,7 @@ export function instrumentQueueProducer<T extends Queue>(queue: T, queueName?: s
|
|
|
38
38
|
},
|
|
39
39
|
async (span) => {
|
|
40
40
|
try {
|
|
41
|
-
const result = await Reflect.apply(fnTarget,
|
|
41
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
42
42
|
setAttr(span, 'messaging.message.id', (result as any)?.messageId);
|
|
43
43
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
44
44
|
return result;
|
|
@@ -60,7 +60,7 @@ export function instrumentQueueProducer<T extends Queue>(queue: T, queueName?: s
|
|
|
60
60
|
|
|
61
61
|
if (prop === 'sendBatch' && typeof value === 'function') {
|
|
62
62
|
return new Proxy(value, {
|
|
63
|
-
apply: (fnTarget,
|
|
63
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
64
64
|
const [messages] = args as [{ body: unknown }[]];
|
|
65
65
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
66
66
|
|
|
@@ -78,7 +78,7 @@ export function instrumentQueueProducer<T extends Queue>(queue: T, queueName?: s
|
|
|
78
78
|
},
|
|
79
79
|
async (span) => {
|
|
80
80
|
try {
|
|
81
|
-
const result = await Reflect.apply(fnTarget,
|
|
81
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
82
82
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
83
83
|
return result;
|
|
84
84
|
} catch (error) {
|
|
@@ -109,6 +109,22 @@ describe('Rate Limiter Instrumentation', () => {
|
|
|
109
109
|
});
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
describe('this-binding', () => {
|
|
113
|
+
it('should invoke limit() with original object as this, not the proxy', async () => {
|
|
114
|
+
let receivedThis: any;
|
|
115
|
+
const mockLim = {
|
|
116
|
+
limit: vi.fn(async function(this: any) {
|
|
117
|
+
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
118
|
+
receivedThis = this;
|
|
119
|
+
return { success: true };
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
const instrumented = instrumentRateLimiter(mockLim, 'test');
|
|
123
|
+
await instrumented.limit({ key: 'user-123' });
|
|
124
|
+
expect(receivedThis).toBe(mockLim);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
112
128
|
describe('Non-instrumented methods', () => {
|
|
113
129
|
it('should pass through non-instrumented methods unchanged', () => {
|
|
114
130
|
const instrumented = instrumentRateLimiter(mockLimiter, 'my-limiter');
|
|
@@ -26,7 +26,7 @@ export function instrumentRateLimiter<T extends RateLimiterLike>(limiter: T, bin
|
|
|
26
26
|
|
|
27
27
|
if (prop === 'limit' && typeof value === 'function') {
|
|
28
28
|
return new Proxy(value, {
|
|
29
|
-
apply: (fnTarget,
|
|
29
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
30
30
|
const [options] = args as [{ key: string }];
|
|
31
31
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
32
32
|
|
|
@@ -41,7 +41,7 @@ export function instrumentRateLimiter<T extends RateLimiterLike>(limiter: T, bin
|
|
|
41
41
|
},
|
|
42
42
|
async (span) => {
|
|
43
43
|
try {
|
|
44
|
-
const result = await Reflect.apply(fnTarget,
|
|
44
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
45
45
|
setAttr(span, 'rate_limiter.success', result?.success);
|
|
46
46
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
47
47
|
return result;
|
|
@@ -205,6 +205,28 @@ describe('Vectorize Binding Instrumentation', () => {
|
|
|
205
205
|
});
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
+
describe('this-binding', () => {
|
|
209
|
+
it('should invoke methods with original object as this, not the proxy', async () => {
|
|
210
|
+
let receivedThis: any;
|
|
211
|
+
const mockVec = {
|
|
212
|
+
query: vi.fn(async function(this: any) {
|
|
213
|
+
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
|
|
214
|
+
receivedThis = this;
|
|
215
|
+
return { matches: [], count: 0 };
|
|
216
|
+
}),
|
|
217
|
+
insert: vi.fn(async () => ({ mutationId: 'mut-1', count: 0 })),
|
|
218
|
+
upsert: vi.fn(async () => ({ mutationId: 'mut-2', count: 0 })),
|
|
219
|
+
deleteByIds: vi.fn(async () => ({ mutationId: 'mut-3', count: 0 })),
|
|
220
|
+
getByIds: vi.fn(async () => []),
|
|
221
|
+
describe: vi.fn(async () => ({ dimensions: 128, vectorCount: 0, processedUpTo: '' })),
|
|
222
|
+
} as unknown as VectorizeIndex;
|
|
223
|
+
|
|
224
|
+
const instrumented = instrumentVectorize(mockVec, 'test');
|
|
225
|
+
await instrumented.query([0.1, 0.2] as any, {} as any);
|
|
226
|
+
expect(receivedThis).toBe(mockVec);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
208
230
|
describe('Error handling', () => {
|
|
209
231
|
it('should record exception and set error status on query() failure', async () => {
|
|
210
232
|
mockVectorize.query = vi.fn(async () => {
|
|
@@ -24,7 +24,7 @@ export function instrumentVectorize<T extends VectorizeIndex>(vectorize: T, inde
|
|
|
24
24
|
|
|
25
25
|
if (typeof prop === 'string' && TRACED_METHODS.includes(prop as any) && typeof value === 'function') {
|
|
26
26
|
return new Proxy(value, {
|
|
27
|
-
apply: (fnTarget,
|
|
27
|
+
apply: (fnTarget, _thisArg, args) => {
|
|
28
28
|
const operation = prop as string;
|
|
29
29
|
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
30
30
|
|
|
@@ -54,7 +54,7 @@ export function instrumentVectorize<T extends VectorizeIndex>(vectorize: T, inde
|
|
|
54
54
|
},
|
|
55
55
|
async (span) => {
|
|
56
56
|
try {
|
|
57
|
-
const result = await Reflect.apply(fnTarget,
|
|
57
|
+
const result = await Reflect.apply(fnTarget, target, args);
|
|
58
58
|
|
|
59
59
|
if (operation === 'query' && result?.matches) {
|
|
60
60
|
setAttr(span, 'db.vectorize.matches_count', result.matches.length);
|