autotel-cloudflare 3.1.0 → 4.0.1

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.
Files changed (76) hide show
  1. package/dist/agents.d.ts.map +1 -1
  2. package/dist/agents.js +7 -0
  3. package/dist/agents.js.map +1 -1
  4. package/package.json +8 -4
  5. package/src/actors/alarms.ts +0 -225
  6. package/src/actors/index.ts +0 -36
  7. package/src/actors/instrument-actor.test.ts +0 -179
  8. package/src/actors/instrument-actor.ts +0 -574
  9. package/src/actors/sockets.ts +0 -217
  10. package/src/actors/storage.ts +0 -263
  11. package/src/actors/traced-handler.ts +0 -300
  12. package/src/actors/types.ts +0 -98
  13. package/src/actors.ts +0 -50
  14. package/src/agents/agent.ts +0 -327
  15. package/src/agents/base.ts +0 -26
  16. package/src/agents/index.ts +0 -46
  17. package/src/agents/mcp.ts +0 -38
  18. package/src/agents/observability.ts +0 -145
  19. package/src/agents/otel-observability.test.ts +0 -269
  20. package/src/agents/otel-observability.ts +0 -557
  21. package/src/agents/types.ts +0 -61
  22. package/src/agents.ts +0 -87
  23. package/src/bindings/ai.test.ts +0 -170
  24. package/src/bindings/ai.ts +0 -73
  25. package/src/bindings/analytics-engine.test.ts +0 -175
  26. package/src/bindings/analytics-engine.ts +0 -78
  27. package/src/bindings/bindings-cache.test.ts +0 -80
  28. package/src/bindings/bindings-detection.test.ts +0 -235
  29. package/src/bindings/bindings-this-binding.test.ts +0 -294
  30. package/src/bindings/bindings.ts +0 -720
  31. package/src/bindings/browser-rendering.test.ts +0 -160
  32. package/src/bindings/browser-rendering.ts +0 -70
  33. package/src/bindings/common.ts +0 -84
  34. package/src/bindings/hyperdrive.test.ts +0 -176
  35. package/src/bindings/hyperdrive.ts +0 -74
  36. package/src/bindings/images.test.ts +0 -262
  37. package/src/bindings/images.ts +0 -182
  38. package/src/bindings/index.ts +0 -20
  39. package/src/bindings/queue-producer.test.ts +0 -224
  40. package/src/bindings/queue-producer.ts +0 -105
  41. package/src/bindings/rate-limiter.test.ts +0 -140
  42. package/src/bindings/rate-limiter.ts +0 -69
  43. package/src/bindings/vectorize.test.ts +0 -362
  44. package/src/bindings/vectorize.ts +0 -86
  45. package/src/bindings.ts +0 -6
  46. package/src/events.ts +0 -6
  47. package/src/execution-logger.test.ts +0 -127
  48. package/src/execution-logger.ts +0 -112
  49. package/src/global/cache.test.ts +0 -292
  50. package/src/global/cache.ts +0 -164
  51. package/src/global/fetch.test.ts +0 -399
  52. package/src/global/fetch.ts +0 -136
  53. package/src/global/index.ts +0 -7
  54. package/src/handlers/durable-objects.test.ts +0 -524
  55. package/src/handlers/durable-objects.ts +0 -250
  56. package/src/handlers/index.ts +0 -6
  57. package/src/handlers/workflows.test.ts +0 -411
  58. package/src/handlers/workflows.ts +0 -345
  59. package/src/handlers.ts +0 -6
  60. package/src/index.ts +0 -91
  61. package/src/logger.ts +0 -15
  62. package/src/native/native-tracing.test.ts +0 -54
  63. package/src/native/native-tracing.ts +0 -83
  64. package/src/native.ts +0 -12
  65. package/src/parse-error.ts +0 -1
  66. package/src/sampling.ts +0 -6
  67. package/src/testing.ts +0 -6
  68. package/src/wrappers/cf-attributes.test.ts +0 -334
  69. package/src/wrappers/define-worker-fetch.test.ts +0 -102
  70. package/src/wrappers/define-worker-fetch.ts +0 -71
  71. package/src/wrappers/index.ts +0 -13
  72. package/src/wrappers/instrument.integration.test.ts +0 -578
  73. package/src/wrappers/instrument.ts +0 -806
  74. package/src/wrappers/native-instrument.test.ts +0 -153
  75. package/src/wrappers/wrap-do.ts +0 -34
  76. package/src/wrappers/wrap-module.ts +0 -37
@@ -1,164 +0,0 @@
1
- /**
2
- * Global Cache API instrumentation for Cloudflare Workers
3
- *
4
- * Automatically traces cache operations:
5
- * - cache.match() - Read from cache
6
- * - cache.put() - Write to cache
7
- * - cache.delete() - Delete from cache
8
- */
9
-
10
- import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
11
- import { wrap } from '../bindings/common';
12
- import { WorkerTracer } from 'autotel-edge';
13
-
14
- type CacheOperation = 'match' | 'put' | 'delete';
15
-
16
- /**
17
- * Sanitize URL for span attributes (remove query params that might contain sensitive data)
18
- */
19
- function sanitizeURL(url: string): string {
20
- const u = new URL(url);
21
- return `${u.protocol}//${u.host}${u.pathname}`;
22
- }
23
-
24
- /**
25
- * Instrument a cache method (match, put, delete)
26
- */
27
- function instrumentCacheMethod<T extends Function>(
28
- fn: T,
29
- cacheName: string,
30
- operation: CacheOperation,
31
- ): T {
32
- const handler: ProxyHandler<T> = {
33
- async apply(target, thisArg, argArray) {
34
- const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
35
-
36
- // Extract URL from first argument (Request or string)
37
- const firstArg = argArray[0];
38
- const url =
39
- firstArg instanceof Request
40
- ? firstArg.url
41
- : typeof firstArg === 'string'
42
- ? firstArg
43
- : undefined;
44
-
45
- const spanName = `Cache ${cacheName}.${operation}`;
46
-
47
- return tracer.startActiveSpan(
48
- spanName,
49
- {
50
- kind: SpanKind.CLIENT,
51
- attributes: {
52
- 'cache.name': cacheName,
53
- 'cache.operation': operation,
54
- 'cache.key': url ? sanitizeURL(url) : undefined,
55
- },
56
- },
57
- async (span) => {
58
- try {
59
- const result = await Reflect.apply(target, thisArg, argArray);
60
-
61
- // For match operations, record whether it was a hit or miss
62
- if (operation === 'match') {
63
- span.setAttribute('cache.hit', !!result);
64
- }
65
-
66
- span.setStatus({ code: SpanStatusCode.OK });
67
- return result;
68
- } catch (error) {
69
- span.recordException(error as Error);
70
- span.setStatus({
71
- code: SpanStatusCode.ERROR,
72
- message: error instanceof Error ? error.message : String(error),
73
- });
74
- throw error;
75
- } finally {
76
- span.end();
77
- }
78
- },
79
- );
80
- },
81
- };
82
-
83
- return wrap(fn, handler);
84
- }
85
-
86
- /**
87
- * Instrument a Cache instance
88
- */
89
- function instrumentCache(cache: Cache, cacheName: string): Cache {
90
- const handler: ProxyHandler<Cache> = {
91
- get(target, prop) {
92
- const value = Reflect.get(target, prop);
93
-
94
- // Instrument the cache operation methods
95
- if (
96
- (prop === 'match' || prop === 'put' || prop === 'delete') &&
97
- typeof value === 'function'
98
- ) {
99
- return instrumentCacheMethod(
100
- value.bind(target),
101
- cacheName,
102
- prop as CacheOperation,
103
- );
104
- }
105
-
106
- // Bind other methods to preserve `this` context
107
- if (typeof value === 'function') {
108
- return value.bind(target);
109
- }
110
-
111
- return value;
112
- },
113
- };
114
-
115
- return wrap(cache, handler);
116
- }
117
-
118
- /**
119
- * Instrument caches.open()
120
- */
121
- function instrumentCachesOpen(
122
- openFn: CacheStorage['open'],
123
- ): CacheStorage['open'] {
124
- const handler: ProxyHandler<CacheStorage['open']> = {
125
- async apply(target, thisArg, argArray) {
126
- const cacheName = argArray[0];
127
- const cache = await Reflect.apply(target, thisArg, argArray);
128
- return instrumentCache(cache, cacheName);
129
- },
130
- };
131
-
132
- return wrap(openFn, handler);
133
- }
134
-
135
- /**
136
- * Instrument the global caches API
137
- *
138
- * This wraps globalThis.caches to automatically create spans for all cache operations.
139
- *
140
- * **Note:** This is called automatically when the library is initialized with
141
- * `instrumentation.instrumentGlobalCache: true` (default).
142
- */
143
- export function instrumentGlobalCache(): void {
144
- const handler: ProxyHandler<typeof caches> = {
145
- get(target, prop) {
146
- if (prop === 'default') {
147
- // Wrap the default cache
148
- return instrumentCache(target.default, 'default');
149
- } else if (prop === 'open') {
150
- // Wrap the open method
151
- const openFn = Reflect.get(target, prop);
152
- if (typeof openFn === 'function') {
153
- return instrumentCachesOpen(openFn.bind(target));
154
- }
155
- }
156
-
157
- return Reflect.get(target, prop);
158
- },
159
- };
160
-
161
- // Replace global caches
162
- // @ts-ignore - TypeScript doesn't like reassigning globalThis.caches
163
- globalThis.caches = wrap(caches, handler);
164
- }
@@ -1,399 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { instrumentGlobalFetch } from './fetch';
3
- import { trace, SpanStatusCode, SpanKind, context as api_context } from '@opentelemetry/api';
4
- import { setConfig, parseConfig } from 'autotel-edge';
5
-
6
- describe('Global Fetch Instrumentation', () => {
7
- let mockTracer: any;
8
- let mockSpan: any;
9
- let getTracerSpy: any;
10
- let originalFetch: typeof globalThis.fetch;
11
-
12
- beforeEach(() => {
13
- // Save original fetch
14
- originalFetch = globalThis.fetch;
15
-
16
- mockSpan = {
17
- spanContext: () => ({
18
- traceId: 'test-trace-id',
19
- spanId: 'test-span-id',
20
- traceFlags: 1,
21
- }),
22
- setAttribute: vi.fn(),
23
- setAttributes: vi.fn(),
24
- setStatus: vi.fn(),
25
- recordException: vi.fn(),
26
- end: vi.fn(),
27
- isRecording: () => true,
28
- updateName: vi.fn(),
29
- addEvent: vi.fn(),
30
- };
31
-
32
- mockTracer = {
33
- startActiveSpan: vi.fn((name, options, fn) => {
34
- return fn(mockSpan);
35
- }),
36
- };
37
-
38
- getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
39
-
40
- // Mock underlying fetch to return test responses
41
- globalThis.fetch = vi.fn(async (_input) => {
42
- return new Response('{"data": "test"}', {
43
- status: 200,
44
- headers: { 'content-type': 'application/json' },
45
- });
46
- }) as any;
47
- });
48
-
49
- afterEach(() => {
50
- // Restore original fetch
51
- globalThis.fetch = originalFetch;
52
- getTracerSpy.mockRestore();
53
- });
54
-
55
- describe('instrumentGlobalFetch()', () => {
56
- it('should wrap globalThis.fetch', () => {
57
- const originalFetch = globalThis.fetch;
58
- instrumentGlobalFetch();
59
-
60
- expect(globalThis.fetch).not.toBe(originalFetch);
61
- expect(typeof globalThis.fetch).toBe('function');
62
- });
63
-
64
- it('should create span for HTTP requests', async () => {
65
- // Set up config
66
- const config = parseConfig({
67
- service: { name: 'test-service' },
68
- });
69
- const ctx = setConfig(config);
70
-
71
- instrumentGlobalFetch();
72
-
73
- await api_context.with(ctx, async () => {
74
- await fetch('https://api.example.com/users');
75
-
76
- expect(mockTracer.startActiveSpan).toHaveBeenCalled();
77
-
78
- const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
79
- expect(spanName).toContain('api.example.com');
80
- expect(spanName).toContain('GET');
81
- });
82
- });
83
-
84
- it('should add HTTP attributes (method, URL, status, headers)', async () => {
85
- const config = parseConfig({
86
- service: { name: 'test-service' },
87
- });
88
- const ctx = setConfig(config);
89
-
90
- instrumentGlobalFetch();
91
-
92
- await api_context.with(ctx, async () => {
93
- await fetch('https://api.example.com/users', {
94
- method: 'POST',
95
- headers: { 'user-agent': 'test-client/1.0' },
96
- });
97
-
98
- const options = mockTracer.startActiveSpan.mock.calls[0][1];
99
- expect(options.kind).toBe(SpanKind.CLIENT);
100
- expect(options.attributes['http.request.method']).toBe('POST');
101
- expect(options.attributes['url.full']).toBe('https://api.example.com/users');
102
- expect(options.attributes['server.address']).toBe('api.example.com');
103
- expect(options.attributes['url.scheme']).toBe('https');
104
- });
105
- });
106
-
107
- it('should add response attributes', async () => {
108
- const config = parseConfig({
109
- service: { name: 'test-service' },
110
- });
111
- const ctx = setConfig(config);
112
-
113
- instrumentGlobalFetch();
114
-
115
- await api_context.with(ctx, async () => {
116
- await fetch('https://api.example.com/users');
117
-
118
- expect(mockSpan.setAttributes).toHaveBeenCalled();
119
-
120
- // Find the call with response attributes
121
- const responseAttributesCall = mockSpan.setAttributes.mock.calls.find(
122
- (call: any) => call[0]['http.response.status_code'] !== undefined
123
- );
124
-
125
- expect(responseAttributesCall).toBeDefined();
126
- expect(responseAttributesCall[0]['http.response.status_code']).toBe(200);
127
- });
128
- });
129
-
130
- it('should inject traceparent header for context propagation by default', async () => {
131
- const config = parseConfig({
132
- service: { name: 'test-service' },
133
- });
134
- const ctx = setConfig(config);
135
-
136
- instrumentGlobalFetch();
137
-
138
- await api_context.with(ctx, async () => {
139
- await fetch('https://api.example.com/users');
140
-
141
- // Span should have been created
142
- expect(mockTracer.startActiveSpan).toHaveBeenCalled();
143
-
144
- // In a real scenario, traceparent would be injected via propagation.inject()
145
- // For this test, we just verify the span was created
146
- expect(mockSpan.end).toHaveBeenCalled();
147
- });
148
- });
149
-
150
- it('should NOT inject traceparent when includeTraceContext = false', async () => {
151
- const config = parseConfig({
152
- service: { name: 'test-service' },
153
- fetch: { includeTraceContext: false },
154
- });
155
- setConfig(config);
156
-
157
- instrumentGlobalFetch();
158
-
159
- // Create a spy to check if headers.set was called
160
- const headersSpy = vi.spyOn(Headers.prototype, 'set');
161
-
162
- await fetch('https://api.example.com/users');
163
-
164
- // Should not have tried to inject traceparent
165
- const traceparentCalls = headersSpy.mock.calls.filter(
166
- (call) => call[0] === 'traceparent'
167
- );
168
- expect(traceparentCalls.length).toBe(0);
169
-
170
- headersSpy.mockRestore();
171
- });
172
-
173
- it('should skip non-HTTP requests (file://, data://)', async () => {
174
- const config = parseConfig({
175
- service: { name: 'test-service' },
176
- });
177
- setConfig(config);
178
-
179
- instrumentGlobalFetch();
180
-
181
- // Try to fetch a file:// URL (should skip instrumentation)
182
- try {
183
- await fetch('file:///path/to/file.txt');
184
- } catch (_e) {
185
- // file:// will fail, that's expected
186
- }
187
-
188
- // Should not have created a span
189
- expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
190
- });
191
-
192
- it('should skip when no active config (not initialized)', async () => {
193
- // Don't set config
194
- setConfig(null as any);
195
-
196
- instrumentGlobalFetch();
197
-
198
- await fetch('https://api.example.com/users');
199
-
200
- // Should not have created a span
201
- expect(mockTracer.startActiveSpan).not.toHaveBeenCalled();
202
-
203
- // Original fetch should have been called (returning mocked response)
204
- // We can't easily verify this without more complex mocking,
205
- // but the key assertion is that no span was created
206
- });
207
-
208
- it('should handle successful responses (200-299)', async () => {
209
- const config = parseConfig({
210
- service: { name: 'test-service' },
211
- });
212
- const ctx = setConfig(config);
213
-
214
- // Mock successful response
215
- globalThis.fetch = vi.fn(async () => {
216
- return new Response('OK', { status: 200 });
217
- }) as any;
218
-
219
- instrumentGlobalFetch();
220
-
221
- await api_context.with(ctx, async () => {
222
- await fetch('https://api.example.com/users');
223
-
224
- expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
225
- expect(mockSpan.end).toHaveBeenCalled();
226
- });
227
- });
228
-
229
- it('should handle error responses (400-599)', async () => {
230
- const config = parseConfig({
231
- service: { name: 'test-service' },
232
- });
233
- const ctx = setConfig(config);
234
-
235
- // Mock error response
236
- globalThis.fetch = vi.fn(async () => {
237
- return new Response('Not Found', { status: 404 });
238
- }) as any;
239
-
240
- instrumentGlobalFetch();
241
-
242
- await api_context.with(ctx, async () => {
243
- await fetch('https://api.example.com/users');
244
-
245
- expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.ERROR });
246
- expect(mockSpan.end).toHaveBeenCalled();
247
- });
248
- });
249
-
250
- it('should handle network errors', async () => {
251
- const config = parseConfig({
252
- service: { name: 'test-service' },
253
- });
254
- const ctx = setConfig(config);
255
-
256
- // Mock network error
257
- globalThis.fetch = vi.fn(async () => {
258
- throw new Error('Network error');
259
- }) as any;
260
-
261
- instrumentGlobalFetch();
262
-
263
- await api_context.with(ctx, async () => {
264
- await expect(fetch('https://api.example.com/users')).rejects.toThrow('Network error');
265
-
266
- expect(mockSpan.recordException).toHaveBeenCalled();
267
- expect(mockSpan.setStatus).toHaveBeenCalledWith({
268
- code: SpanStatusCode.ERROR,
269
- message: 'Network error',
270
- });
271
- expect(mockSpan.end).toHaveBeenCalled();
272
- });
273
- });
274
-
275
- it('should allow custom includeTraceContext function', async () => {
276
- const includeTraceContextFn = vi.fn((request: Request) => {
277
- // Only include for specific domains
278
- return request.url.includes('internal.example.com');
279
- });
280
-
281
- const config = parseConfig({
282
- service: { name: 'test-service' },
283
- fetch: { includeTraceContext: includeTraceContextFn },
284
- });
285
- const ctx = setConfig(config);
286
-
287
- instrumentGlobalFetch();
288
-
289
- await api_context.with(ctx, async () => {
290
- // Fetch internal domain - should include context
291
- await fetch('https://internal.example.com/api');
292
- expect(includeTraceContextFn).toHaveBeenCalledTimes(1);
293
-
294
- // Fetch external domain - should not include context
295
- await fetch('https://external.com/api');
296
- expect(includeTraceContextFn).toHaveBeenCalledTimes(2);
297
- });
298
- });
299
-
300
- it('should handle Request objects as input', async () => {
301
- const config = parseConfig({
302
- service: { name: 'test-service' },
303
- });
304
- const ctx = setConfig(config);
305
-
306
- instrumentGlobalFetch();
307
-
308
- await api_context.with(ctx, async () => {
309
- const request = new Request('https://api.example.com/users', {
310
- method: 'POST',
311
- headers: { 'content-type': 'application/json' },
312
- body: JSON.stringify({ name: 'test' }),
313
- });
314
-
315
- await fetch(request);
316
-
317
- expect(mockTracer.startActiveSpan).toHaveBeenCalled();
318
-
319
- const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
320
- expect(spanName).toContain('POST');
321
- expect(spanName).toContain('api.example.com');
322
- });
323
- });
324
-
325
- it('should handle URL objects as input', async () => {
326
- const config = parseConfig({
327
- service: { name: 'test-service' },
328
- });
329
- const ctx = setConfig(config);
330
-
331
- instrumentGlobalFetch();
332
-
333
- await api_context.with(ctx, async () => {
334
- const url = new URL('https://api.example.com/users');
335
- await fetch(url);
336
-
337
- expect(mockTracer.startActiveSpan).toHaveBeenCalled();
338
-
339
- const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
340
- expect(spanName).toContain('api.example.com');
341
- });
342
- });
343
- });
344
-
345
- describe('dataSafety.redactQueryParams', () => {
346
- it('should capture full URL and query string by default', async () => {
347
- const config = parseConfig({
348
- service: { name: 'test-service' },
349
- });
350
- const ctx = setConfig(config);
351
-
352
- instrumentGlobalFetch();
353
-
354
- await api_context.with(ctx, async () => {
355
- await fetch('https://api.example.com/users?token=secret&page=1');
356
-
357
- const options = mockTracer.startActiveSpan.mock.calls[0][1];
358
- expect(options.attributes['url.full']).toBe('https://api.example.com/users?token=secret&page=1');
359
- expect(options.attributes['url.query']).toBe('?token=secret&page=1');
360
- });
361
- });
362
-
363
- it('should redact query params when redactQueryParams is true', async () => {
364
- const config = parseConfig({
365
- service: { name: 'test-service' },
366
- dataSafety: { redactQueryParams: true },
367
- });
368
- const ctx = setConfig(config);
369
-
370
- instrumentGlobalFetch();
371
-
372
- await api_context.with(ctx, async () => {
373
- await fetch('https://api.example.com/users?token=secret&page=1');
374
-
375
- const options = mockTracer.startActiveSpan.mock.calls[0][1];
376
- expect(options.attributes['url.full']).toBe('https://api.example.com/users');
377
- expect(options.attributes['url.query']).toBe('[REDACTED]');
378
- });
379
- });
380
-
381
- it('should handle URLs without query string when redactQueryParams is true', async () => {
382
- const config = parseConfig({
383
- service: { name: 'test-service' },
384
- dataSafety: { redactQueryParams: true },
385
- });
386
- const ctx = setConfig(config);
387
-
388
- instrumentGlobalFetch();
389
-
390
- await api_context.with(ctx, async () => {
391
- await fetch('https://api.example.com/users');
392
-
393
- const options = mockTracer.startActiveSpan.mock.calls[0][1];
394
- expect(options.attributes['url.full']).toBe('https://api.example.com/users');
395
- expect(options.attributes['url.query']).toBe('');
396
- });
397
- });
398
- });
399
- });
@@ -1,136 +0,0 @@
1
- /**
2
- * Global fetch() instrumentation for autotel-edge
3
- *
4
- * Automatically traces all outgoing fetch() calls with:
5
- * - HTTP method, URL, status code
6
- * - Request/response headers
7
- * - Automatic context propagation
8
- * - Error tracking
9
- */
10
-
11
- import {
12
- trace,
13
- context as api_context,
14
- propagation,
15
- SpanStatusCode,
16
- SpanKind,
17
- } from '@opentelemetry/api';
18
- import { getActiveConfig, WorkerTracer } from 'autotel-edge';
19
-
20
- /**
21
- * Gather HTTP request attributes following OpenTelemetry semantic conventions
22
- */
23
- function gatherRequestAttributes(request: Request): Record<string, any> {
24
- const url = new URL(request.url);
25
- const config = getActiveConfig();
26
- const redactQuery = config?.dataSafety?.redactQueryParams === true;
27
-
28
- return {
29
- 'http.request.method': request.method.toUpperCase(),
30
- 'url.full': redactQuery ? `${url.origin}${url.pathname}` : request.url,
31
- 'url.scheme': url.protocol.replace(':', ''),
32
- 'server.address': url.host,
33
- 'url.path': url.pathname,
34
- 'url.query': redactQuery ? (url.search ? '[REDACTED]' : '') : url.search,
35
- 'network.protocol.name': 'http',
36
- 'user_agent.original': request.headers.get('user-agent') || undefined,
37
- };
38
- }
39
-
40
- /**
41
- * Gather HTTP response attributes
42
- */
43
- function gatherResponseAttributes(response: Response): Record<string, any> {
44
- return {
45
- 'http.response.status_code': response.status,
46
- 'http.response.body.size': response.headers.get('content-length') || undefined,
47
- };
48
- }
49
-
50
- /**
51
- * Instrument the global fetch function
52
- *
53
- * This wraps globalThis.fetch to automatically create spans for all outgoing HTTP requests.
54
- *
55
- * **Note:** This is called automatically when the library is initialized with
56
- * `instrumentation.instrumentGlobalFetch: true` (default).
57
- */
58
- export function instrumentGlobalFetch(): void {
59
- const originalFetch = globalThis.fetch;
60
-
61
- const instrumentedFetch = function fetch(
62
- input: RequestInfo | URL,
63
- init?: RequestInit,
64
- ): Promise<Response> {
65
- const request = new Request(input, init);
66
-
67
- // Skip non-HTTP requests
68
- if (!request.url.startsWith('http')) {
69
- return originalFetch(input, init);
70
- }
71
-
72
- // Skip if no active config (not initialized yet)
73
- const config = getActiveConfig();
74
- if (!config) {
75
- return originalFetch(input, init);
76
- }
77
-
78
- const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
79
- const url = new URL(request.url);
80
- const spanName = `${request.method} ${url.host}`;
81
-
82
- return tracer.startActiveSpan(
83
- spanName,
84
- {
85
- kind: SpanKind.CLIENT,
86
- attributes: gatherRequestAttributes(request),
87
- },
88
- async (span) => {
89
- try {
90
- // Inject trace context into request headers for distributed tracing
91
- const shouldIncludeContext =
92
- typeof config.fetch?.includeTraceContext === 'function'
93
- ? config.fetch.includeTraceContext(request)
94
- : (config.fetch?.includeTraceContext ?? true);
95
-
96
- if (shouldIncludeContext) {
97
- propagation.inject(api_context.active(), request.headers, {
98
- set: (headers, key, value) => {
99
- if (typeof value === 'string') {
100
- headers.set(key, value);
101
- }
102
- },
103
- });
104
- }
105
-
106
- // Make the actual fetch call
107
- const response = await originalFetch(request);
108
-
109
- // Add response attributes
110
- span.setAttributes(gatherResponseAttributes(response));
111
-
112
- // Set span status based on response
113
- if (response.ok) {
114
- span.setStatus({ code: SpanStatusCode.OK });
115
- } else {
116
- span.setStatus({ code: SpanStatusCode.ERROR });
117
- }
118
-
119
- return response;
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
- // Replace global fetch
135
- globalThis.fetch = instrumentedFetch as typeof fetch;
136
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Global instrumentation for Cloudflare Workers
3
- * Automatically instrument fetch() and cache APIs
4
- */
5
-
6
- export { instrumentGlobalFetch } from './fetch';
7
- export { instrumentGlobalCache } from './cache';