autotel-cloudflare 3.0.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 +626 -123
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +260 -171
- 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/index.ts +0 -42
- package/src/agents/otel-observability.test.ts +0 -336
- package/src/agents/otel-observability.ts +0 -474
- package/src/agents/types.ts +0 -167
- package/src/agents.ts +0 -76
- 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
package/src/bindings/bindings.ts
DELETED
|
@@ -1,720 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auto-instrumentation for Cloudflare Workers bindings
|
|
3
|
-
*
|
|
4
|
-
* Note: This file uses Cloudflare Workers types (KVNamespace, R2Bucket, D1Database, Fetcher, etc.)
|
|
5
|
-
* which are globally available via @cloudflare/workers-types when listed in tsconfig.json.
|
|
6
|
-
* These types are devDependencies only - they're not runtime dependencies.
|
|
7
|
-
* At runtime, Cloudflare Workers runtime provides the actual implementations.
|
|
8
|
-
*
|
|
9
|
-
* This module provides automatic tracing for Cloudflare bindings:
|
|
10
|
-
* - KV (key-value operations)
|
|
11
|
-
* - R2 (object storage operations)
|
|
12
|
-
* - D1 (database operations)
|
|
13
|
-
* - Service Bindings
|
|
14
|
-
* - Events Engine
|
|
15
|
-
* - Workers AI
|
|
16
|
-
* - Vectorize
|
|
17
|
-
* - Hyperdrive
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
trace,
|
|
22
|
-
SpanKind,
|
|
23
|
-
SpanStatusCode,
|
|
24
|
-
} from '@opentelemetry/api';
|
|
25
|
-
import { WorkerTracer, getActiveConfig } from 'autotel-edge';
|
|
26
|
-
import { wrap, isWrapped } from './common';
|
|
27
|
-
import { instrumentAI } from './ai';
|
|
28
|
-
import { instrumentVectorize } from './vectorize';
|
|
29
|
-
import { instrumentHyperdrive } from './hyperdrive';
|
|
30
|
-
import { instrumentQueueProducer } from './queue-producer';
|
|
31
|
-
import { instrumentAnalyticsEngine } from './analytics-engine';
|
|
32
|
-
import { instrumentImages } from './images';
|
|
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
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Instrument KV namespace
|
|
50
|
-
*/
|
|
51
|
-
export function instrumentKV<K extends KVNamespace>(kv: K, namespaceName?: string): K {
|
|
52
|
-
const name = namespaceName || 'kv';
|
|
53
|
-
|
|
54
|
-
const kvHandler: ProxyHandler<K> = {
|
|
55
|
-
get(target, prop) {
|
|
56
|
-
const value = Reflect.get(target, prop);
|
|
57
|
-
|
|
58
|
-
if (prop === 'get' && typeof value === 'function') {
|
|
59
|
-
return new Proxy(value, {
|
|
60
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
61
|
-
const [key, options] = args as [string, KVNamespaceGetOptions<unknown> | undefined];
|
|
62
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
63
|
-
|
|
64
|
-
return tracer.startActiveSpan(
|
|
65
|
-
`KV ${name}: get`,
|
|
66
|
-
{
|
|
67
|
-
kind: SpanKind.CLIENT,
|
|
68
|
-
attributes: {
|
|
69
|
-
'db.system': 'cloudflare-kv',
|
|
70
|
-
'db.operation': 'get',
|
|
71
|
-
'db.namespace': name,
|
|
72
|
-
'db.key': key,
|
|
73
|
-
'db.cache_hit': options?.cacheTtl !== undefined,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
async (span) => {
|
|
77
|
-
try {
|
|
78
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
79
|
-
span.setAttribute('db.result.type', result === null ? 'null' : typeof result);
|
|
80
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
81
|
-
return result;
|
|
82
|
-
} catch (error) {
|
|
83
|
-
span.recordException(error as Error);
|
|
84
|
-
span.setStatus({
|
|
85
|
-
code: SpanStatusCode.ERROR,
|
|
86
|
-
message: error instanceof Error ? error.message : String(error),
|
|
87
|
-
});
|
|
88
|
-
throw error;
|
|
89
|
-
} finally {
|
|
90
|
-
span.end();
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
);
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (prop === 'put' && typeof value === 'function') {
|
|
99
|
-
return new Proxy(value, {
|
|
100
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
101
|
-
const [key] = args as [string, unknown, KVNamespacePutOptions | undefined];
|
|
102
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
103
|
-
|
|
104
|
-
return tracer.startActiveSpan(
|
|
105
|
-
`KV ${name}: put`,
|
|
106
|
-
{
|
|
107
|
-
kind: SpanKind.CLIENT,
|
|
108
|
-
attributes: {
|
|
109
|
-
'db.system': 'cloudflare-kv',
|
|
110
|
-
'db.operation': 'put',
|
|
111
|
-
'db.namespace': name,
|
|
112
|
-
'db.key': key,
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
async (span) => {
|
|
116
|
-
try {
|
|
117
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
118
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
119
|
-
return result;
|
|
120
|
-
} catch (error) {
|
|
121
|
-
span.recordException(error as Error);
|
|
122
|
-
span.setStatus({
|
|
123
|
-
code: SpanStatusCode.ERROR,
|
|
124
|
-
message: error instanceof Error ? error.message : String(error),
|
|
125
|
-
});
|
|
126
|
-
throw error;
|
|
127
|
-
} finally {
|
|
128
|
-
span.end();
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (prop === 'delete' && typeof value === 'function') {
|
|
137
|
-
return new Proxy(value, {
|
|
138
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
139
|
-
const [key] = args as [string];
|
|
140
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
141
|
-
|
|
142
|
-
return tracer.startActiveSpan(
|
|
143
|
-
`KV ${name}: delete`,
|
|
144
|
-
{
|
|
145
|
-
kind: SpanKind.CLIENT,
|
|
146
|
-
attributes: {
|
|
147
|
-
'db.system': 'cloudflare-kv',
|
|
148
|
-
'db.operation': 'delete',
|
|
149
|
-
'db.namespace': name,
|
|
150
|
-
'db.key': key,
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
async (span) => {
|
|
154
|
-
try {
|
|
155
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
156
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
157
|
-
return result;
|
|
158
|
-
} catch (error) {
|
|
159
|
-
span.recordException(error as Error);
|
|
160
|
-
span.setStatus({
|
|
161
|
-
code: SpanStatusCode.ERROR,
|
|
162
|
-
message: error instanceof Error ? error.message : String(error),
|
|
163
|
-
});
|
|
164
|
-
throw error;
|
|
165
|
-
} finally {
|
|
166
|
-
span.end();
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (prop === 'list' && typeof value === 'function') {
|
|
175
|
-
return new Proxy(value, {
|
|
176
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
177
|
-
const [options] = args as [KVNamespaceListOptions | undefined];
|
|
178
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
179
|
-
|
|
180
|
-
return tracer.startActiveSpan(
|
|
181
|
-
`KV ${name}: list`,
|
|
182
|
-
{
|
|
183
|
-
kind: SpanKind.CLIENT,
|
|
184
|
-
attributes: {
|
|
185
|
-
'db.system': 'cloudflare-kv',
|
|
186
|
-
'db.operation': 'list',
|
|
187
|
-
'db.namespace': name,
|
|
188
|
-
'db.prefix': options?.prefix || undefined,
|
|
189
|
-
'db.limit': options?.limit || undefined,
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
async (span) => {
|
|
193
|
-
try {
|
|
194
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
195
|
-
span.setAttribute('db.result.keys_count', result.keys.length);
|
|
196
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
197
|
-
return result;
|
|
198
|
-
} catch (error) {
|
|
199
|
-
span.recordException(error as Error);
|
|
200
|
-
span.setStatus({
|
|
201
|
-
code: SpanStatusCode.ERROR,
|
|
202
|
-
message: error instanceof Error ? error.message : String(error),
|
|
203
|
-
});
|
|
204
|
-
throw error;
|
|
205
|
-
} finally {
|
|
206
|
-
span.end();
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
);
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return value;
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
return wrap(kv, kvHandler);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Instrument R2 bucket
|
|
223
|
-
*/
|
|
224
|
-
export function instrumentR2<R extends R2Bucket>(r2: R, bucketName?: string): R {
|
|
225
|
-
const name = bucketName || 'r2';
|
|
226
|
-
|
|
227
|
-
const r2Handler: ProxyHandler<R> = {
|
|
228
|
-
get(target, prop) {
|
|
229
|
-
const value = Reflect.get(target, prop);
|
|
230
|
-
|
|
231
|
-
if (prop === 'get' && typeof value === 'function') {
|
|
232
|
-
return new Proxy(value, {
|
|
233
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
234
|
-
const [key] = args as [string, R2GetOptions | undefined];
|
|
235
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
236
|
-
|
|
237
|
-
return tracer.startActiveSpan(
|
|
238
|
-
`R2 ${name}: get`,
|
|
239
|
-
{
|
|
240
|
-
kind: SpanKind.CLIENT,
|
|
241
|
-
attributes: {
|
|
242
|
-
'db.system': 'cloudflare-r2',
|
|
243
|
-
'db.operation': 'get',
|
|
244
|
-
'db.bucket': name,
|
|
245
|
-
'db.key': key,
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
async (span) => {
|
|
249
|
-
try {
|
|
250
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
251
|
-
if (result) {
|
|
252
|
-
span.setAttribute('db.result.size', result.size);
|
|
253
|
-
span.setAttribute('db.result.etag', result.etag);
|
|
254
|
-
span.setAttribute('db.result.content_type', result.httpMetadata?.contentType);
|
|
255
|
-
} else {
|
|
256
|
-
span.setAttribute('db.result.exists', false);
|
|
257
|
-
}
|
|
258
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
259
|
-
return result;
|
|
260
|
-
} catch (error) {
|
|
261
|
-
span.recordException(error as Error);
|
|
262
|
-
span.setStatus({
|
|
263
|
-
code: SpanStatusCode.ERROR,
|
|
264
|
-
message: error instanceof Error ? error.message : String(error),
|
|
265
|
-
});
|
|
266
|
-
throw error;
|
|
267
|
-
} finally {
|
|
268
|
-
span.end();
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
);
|
|
272
|
-
},
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (prop === 'put' && typeof value === 'function') {
|
|
277
|
-
return new Proxy(value, {
|
|
278
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
279
|
-
const [key] = args as [string, ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, R2PutOptions | undefined];
|
|
280
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
281
|
-
|
|
282
|
-
return tracer.startActiveSpan(
|
|
283
|
-
`R2 ${name}: put`,
|
|
284
|
-
{
|
|
285
|
-
kind: SpanKind.CLIENT,
|
|
286
|
-
attributes: {
|
|
287
|
-
'db.system': 'cloudflare-r2',
|
|
288
|
-
'db.operation': 'put',
|
|
289
|
-
'db.bucket': name,
|
|
290
|
-
'db.key': key,
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
async (span) => {
|
|
294
|
-
try {
|
|
295
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
296
|
-
span.setAttribute('db.result.etag', result.etag);
|
|
297
|
-
span.setAttribute('db.result.uploaded', result.uploaded);
|
|
298
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
299
|
-
return result;
|
|
300
|
-
} catch (error) {
|
|
301
|
-
span.recordException(error as Error);
|
|
302
|
-
span.setStatus({
|
|
303
|
-
code: SpanStatusCode.ERROR,
|
|
304
|
-
message: error instanceof Error ? error.message : String(error),
|
|
305
|
-
});
|
|
306
|
-
throw error;
|
|
307
|
-
} finally {
|
|
308
|
-
span.end();
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
);
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (prop === 'delete' && typeof value === 'function') {
|
|
317
|
-
return new Proxy(value, {
|
|
318
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
319
|
-
const keys = args as string[];
|
|
320
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
321
|
-
|
|
322
|
-
return tracer.startActiveSpan(
|
|
323
|
-
`R2 ${name}: delete`,
|
|
324
|
-
{
|
|
325
|
-
kind: SpanKind.CLIENT,
|
|
326
|
-
attributes: {
|
|
327
|
-
'db.system': 'cloudflare-r2',
|
|
328
|
-
'db.operation': 'delete',
|
|
329
|
-
'db.bucket': name,
|
|
330
|
-
'db.keys_count': keys.length,
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
async (span) => {
|
|
334
|
-
try {
|
|
335
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
336
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
337
|
-
return result;
|
|
338
|
-
} catch (error) {
|
|
339
|
-
span.recordException(error as Error);
|
|
340
|
-
span.setStatus({
|
|
341
|
-
code: SpanStatusCode.ERROR,
|
|
342
|
-
message: error instanceof Error ? error.message : String(error),
|
|
343
|
-
});
|
|
344
|
-
throw error;
|
|
345
|
-
} finally {
|
|
346
|
-
span.end();
|
|
347
|
-
}
|
|
348
|
-
},
|
|
349
|
-
);
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (prop === 'list' && typeof value === 'function') {
|
|
355
|
-
return new Proxy(value, {
|
|
356
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
357
|
-
const [options] = args as [R2ListOptions | undefined];
|
|
358
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
359
|
-
|
|
360
|
-
return tracer.startActiveSpan(
|
|
361
|
-
`R2 ${name}: list`,
|
|
362
|
-
{
|
|
363
|
-
kind: SpanKind.CLIENT,
|
|
364
|
-
attributes: {
|
|
365
|
-
'db.system': 'cloudflare-r2',
|
|
366
|
-
'db.operation': 'list',
|
|
367
|
-
'db.bucket': name,
|
|
368
|
-
'db.prefix': options?.prefix || undefined,
|
|
369
|
-
'db.limit': options?.limit || undefined,
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
async (span) => {
|
|
373
|
-
try {
|
|
374
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
375
|
-
span.setAttribute('db.result.objects_count', result.objects.length);
|
|
376
|
-
span.setAttribute('db.result.truncated', result.truncated);
|
|
377
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
378
|
-
return result;
|
|
379
|
-
} catch (error) {
|
|
380
|
-
span.recordException(error as Error);
|
|
381
|
-
span.setStatus({
|
|
382
|
-
code: SpanStatusCode.ERROR,
|
|
383
|
-
message: error instanceof Error ? error.message : String(error),
|
|
384
|
-
});
|
|
385
|
-
throw error;
|
|
386
|
-
} finally {
|
|
387
|
-
span.end();
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
);
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return value;
|
|
396
|
-
},
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
return wrap(r2, r2Handler);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Instrument D1 database
|
|
404
|
-
*/
|
|
405
|
-
export function instrumentD1<D extends D1Database>(d1: D, databaseName?: string): D {
|
|
406
|
-
const name = databaseName || 'd1';
|
|
407
|
-
|
|
408
|
-
const d1Handler: ProxyHandler<D> = {
|
|
409
|
-
get(target, prop) {
|
|
410
|
-
const value = Reflect.get(target, prop);
|
|
411
|
-
|
|
412
|
-
if (prop === 'prepare' && typeof value === 'function') {
|
|
413
|
-
return new Proxy(value, {
|
|
414
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
415
|
-
const [query] = args as [string];
|
|
416
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
417
|
-
|
|
418
|
-
const prepared = Reflect.apply(fnTarget, target, args);
|
|
419
|
-
|
|
420
|
-
// Instrument the prepared statement
|
|
421
|
-
const preparedHandler: ProxyHandler<typeof prepared> = {
|
|
422
|
-
get(target, prop) {
|
|
423
|
-
const value = Reflect.get(target, prop);
|
|
424
|
-
|
|
425
|
-
if (prop === 'first' || prop === 'run' || prop === 'all' || prop === 'raw') {
|
|
426
|
-
return new Proxy(value, {
|
|
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
|
-
}
|
|
439
|
-
return tracer.startActiveSpan(
|
|
440
|
-
`D1 ${name}: ${prop}`,
|
|
441
|
-
{
|
|
442
|
-
kind: SpanKind.CLIENT,
|
|
443
|
-
attributes,
|
|
444
|
-
},
|
|
445
|
-
async (span) => {
|
|
446
|
-
try {
|
|
447
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
448
|
-
if (prop === 'all' && Array.isArray(result)) {
|
|
449
|
-
span.setAttribute('db.result.rows_count', result.length);
|
|
450
|
-
} else if (prop === 'first' && result) {
|
|
451
|
-
span.setAttribute('db.result.exists', true);
|
|
452
|
-
}
|
|
453
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
454
|
-
return result;
|
|
455
|
-
} catch (error) {
|
|
456
|
-
span.recordException(error as Error);
|
|
457
|
-
span.setStatus({
|
|
458
|
-
code: SpanStatusCode.ERROR,
|
|
459
|
-
message: error instanceof Error ? error.message : String(error),
|
|
460
|
-
});
|
|
461
|
-
throw error;
|
|
462
|
-
} finally {
|
|
463
|
-
span.end();
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
);
|
|
467
|
-
},
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return value;
|
|
472
|
-
},
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
return wrap(prepared, preparedHandler);
|
|
476
|
-
},
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (prop === 'exec' && typeof value === 'function') {
|
|
481
|
-
return new Proxy(value, {
|
|
482
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
483
|
-
const [query] = args as [string];
|
|
484
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
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
|
-
|
|
497
|
-
return tracer.startActiveSpan(
|
|
498
|
-
`D1 ${name}: exec`,
|
|
499
|
-
{
|
|
500
|
-
kind: SpanKind.CLIENT,
|
|
501
|
-
attributes,
|
|
502
|
-
},
|
|
503
|
-
async (span) => {
|
|
504
|
-
try {
|
|
505
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
506
|
-
span.setAttribute('db.result.count', result.count);
|
|
507
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
508
|
-
return result;
|
|
509
|
-
} catch (error) {
|
|
510
|
-
span.recordException(error as Error);
|
|
511
|
-
span.setStatus({
|
|
512
|
-
code: SpanStatusCode.ERROR,
|
|
513
|
-
message: error instanceof Error ? error.message : String(error),
|
|
514
|
-
});
|
|
515
|
-
throw error;
|
|
516
|
-
} finally {
|
|
517
|
-
span.end();
|
|
518
|
-
}
|
|
519
|
-
},
|
|
520
|
-
);
|
|
521
|
-
},
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return value;
|
|
526
|
-
},
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
return wrap(d1, d1Handler);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* Instrument service binding (Fetcher)
|
|
534
|
-
*
|
|
535
|
-
* Unlike other bindings, Fetcher objects are native Cloudflare C++ bindings
|
|
536
|
-
* whose methods throw "Illegal invocation" when called through a Proxy with
|
|
537
|
-
* a different `this` reference. We work around this by calling `target.fetch()`
|
|
538
|
-
* directly on the original binding instead of using `Reflect.apply` on a
|
|
539
|
-
* detached function reference.
|
|
540
|
-
*/
|
|
541
|
-
export function instrumentServiceBinding<F extends Fetcher>(fetcher: F, serviceName?: string): F {
|
|
542
|
-
const name = serviceName || 'service';
|
|
543
|
-
|
|
544
|
-
const fetcherHandler: ProxyHandler<F> = {
|
|
545
|
-
get(target, prop) {
|
|
546
|
-
if (prop === 'fetch' && typeof target.fetch === 'function') {
|
|
547
|
-
// Return a plain function wrapper instead of proxying the native method.
|
|
548
|
-
// This avoids detaching the native method from its binding, which would
|
|
549
|
-
// cause "Illegal invocation" on Cloudflare's native Fetcher objects.
|
|
550
|
-
const tracedFetch = (...args: any[]) => {
|
|
551
|
-
const [input, init] = args as [RequestInfo | URL, RequestInit | undefined];
|
|
552
|
-
const request = new Request(input, init);
|
|
553
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
554
|
-
|
|
555
|
-
return tracer.startActiveSpan(
|
|
556
|
-
`Service ${name}: ${request.method}`,
|
|
557
|
-
{
|
|
558
|
-
kind: SpanKind.CLIENT,
|
|
559
|
-
attributes: {
|
|
560
|
-
'rpc.system': 'cloudflare-service-binding',
|
|
561
|
-
'rpc.service': name,
|
|
562
|
-
'http.request.method': request.method,
|
|
563
|
-
'url.full': request.url,
|
|
564
|
-
},
|
|
565
|
-
},
|
|
566
|
-
async (span) => {
|
|
567
|
-
try {
|
|
568
|
-
// Call fetch directly on the original target to preserve
|
|
569
|
-
// the native `this` binding that Cloudflare requires
|
|
570
|
-
const response = await target.fetch(input, init as RequestInit);
|
|
571
|
-
span.setAttribute('http.response.status_code', response.status);
|
|
572
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
573
|
-
return response;
|
|
574
|
-
} catch (error) {
|
|
575
|
-
span.recordException(error as Error);
|
|
576
|
-
span.setStatus({
|
|
577
|
-
code: SpanStatusCode.ERROR,
|
|
578
|
-
message: error instanceof Error ? error.message : String(error),
|
|
579
|
-
});
|
|
580
|
-
throw error;
|
|
581
|
-
} finally {
|
|
582
|
-
span.end();
|
|
583
|
-
}
|
|
584
|
-
},
|
|
585
|
-
);
|
|
586
|
-
};
|
|
587
|
-
return tracedFetch;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// For non-fetch properties, access the original target directly
|
|
591
|
-
// to avoid Proxy-related issues with native bindings
|
|
592
|
-
const value = Reflect.get(target, prop);
|
|
593
|
-
if (typeof value === 'function') {
|
|
594
|
-
// Bind native methods to the original target to prevent
|
|
595
|
-
// "Illegal invocation" errors
|
|
596
|
-
return value.bind(target);
|
|
597
|
-
}
|
|
598
|
-
return value;
|
|
599
|
-
},
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
return wrap(fetcher, fetcherHandler);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Detection helpers
|
|
607
|
-
*/
|
|
608
|
-
const hasMethod = (obj: any, m: string): boolean =>
|
|
609
|
-
typeof obj?.[m] === 'function';
|
|
610
|
-
|
|
611
|
-
const hasExactMethods = (obj: any, methods: string[]): boolean =>
|
|
612
|
-
methods.every(m => hasMethod(obj, m));
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Auto-instrument all Cloudflare bindings in the environment
|
|
616
|
-
*
|
|
617
|
-
* Detection order (most specific first):
|
|
618
|
-
* 1. R2 — get, put, delete, list, head
|
|
619
|
-
* 2. KV — get, put, delete, list (not head)
|
|
620
|
-
* 3. D1 — prepare, exec
|
|
621
|
-
* 4. Vectorize — query, insert, upsert, describe
|
|
622
|
-
* 5. AI — run + (gateway or models discriminator)
|
|
623
|
-
* 6. Hyperdrive — connect + connectionString + host
|
|
624
|
-
* 7. Queue Producer — send, sendBatch (not get)
|
|
625
|
-
* 8. Analytics Engine — writeDataPoint
|
|
626
|
-
* 9. Images — info, input
|
|
627
|
-
* 10. Service Binding — fetch (broadest, must be last)
|
|
628
|
-
*
|
|
629
|
-
* Not auto-detected (manual only):
|
|
630
|
-
* - Rate Limiter — limit() alone too generic
|
|
631
|
-
* - Browser Rendering — indistinguishable from Service Binding
|
|
632
|
-
*/
|
|
633
|
-
const envCache = new WeakMap<object, Record<string, any>>();
|
|
634
|
-
|
|
635
|
-
export function instrumentBindings(env: Record<string, any>): Record<string, any> {
|
|
636
|
-
const cached = envCache.get(env);
|
|
637
|
-
if (cached) return cached;
|
|
638
|
-
|
|
639
|
-
const instrumented: Record<string, any> = {};
|
|
640
|
-
|
|
641
|
-
for (const [key, value] of Object.entries(env)) {
|
|
642
|
-
if (!value || typeof value !== 'object') {
|
|
643
|
-
instrumented[key] = value;
|
|
644
|
-
continue;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// Skip already-instrumented bindings
|
|
648
|
-
if (isWrapped(value)) {
|
|
649
|
-
instrumented[key] = value;
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// 1. R2 — most specific (has head)
|
|
654
|
-
if (hasExactMethods(value, ['get', 'put', 'delete', 'list', 'head'])) {
|
|
655
|
-
instrumented[key] = instrumentR2(value as R2Bucket, key);
|
|
656
|
-
continue;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// 2. KV — like R2 but without head
|
|
660
|
-
if (hasExactMethods(value, ['get', 'put', 'delete', 'list']) && !('head' in value)) {
|
|
661
|
-
instrumented[key] = instrumentKV(value as KVNamespace, key);
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// 3. D1
|
|
666
|
-
if (hasExactMethods(value, ['prepare', 'exec'])) {
|
|
667
|
-
instrumented[key] = instrumentD1(value as D1Database, key);
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// 4. Vectorize
|
|
672
|
-
if (hasExactMethods(value, ['query', 'insert', 'upsert', 'describe'])) {
|
|
673
|
-
instrumented[key] = instrumentVectorize(value as VectorizeIndex, key);
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// 5. AI — has run() + discriminator properties
|
|
678
|
-
if (hasMethod(value, 'run') && ('gateway' in value || 'models' in value)) {
|
|
679
|
-
instrumented[key] = instrumentAI(value as Ai, key);
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// 6. Hyperdrive — connect + connection properties
|
|
684
|
-
if (hasMethod(value, 'connect') && 'connectionString' in value && 'host' in value) {
|
|
685
|
-
instrumented[key] = instrumentHyperdrive(value as Hyperdrive, key);
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
// 7. Queue Producer — send + sendBatch (not get, to avoid KV collision)
|
|
690
|
-
if (hasExactMethods(value, ['send', 'sendBatch']) && !('get' in value)) {
|
|
691
|
-
instrumented[key] = instrumentQueueProducer(value as Queue, key);
|
|
692
|
-
continue;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// 8. Analytics Engine
|
|
696
|
-
if (hasMethod(value, 'writeDataPoint')) {
|
|
697
|
-
instrumented[key] = instrumentAnalyticsEngine(value as AnalyticsEngineDataset, key);
|
|
698
|
-
continue;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// 9. Images
|
|
702
|
-
if (hasExactMethods(value, ['info', 'input'])) {
|
|
703
|
-
instrumented[key] = instrumentImages(value as any, key);
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// 10. Service Binding (broadest — must be last)
|
|
708
|
-
if (hasMethod(value, 'fetch')) {
|
|
709
|
-
instrumented[key] = instrumentServiceBinding(value as Fetcher, key);
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Unknown binding type — pass through
|
|
714
|
-
instrumented[key] = value;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
envCache.set(env, instrumented);
|
|
718
|
-
return instrumented;
|
|
719
|
-
}
|
|
720
|
-
|