autotel-cloudflare 2.1.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/LICENSE +21 -0
- package/README.md +432 -0
- package/dist/actors.d.ts +248 -0
- package/dist/actors.js +1030 -0
- package/dist/actors.js.map +1 -0
- package/dist/agents.d.ts +219 -0
- package/dist/agents.js +276 -0
- package/dist/agents.js.map +1 -0
- package/dist/bindings.d.ts +40 -0
- package/dist/bindings.js +4 -0
- package/dist/bindings.js.map +1 -0
- package/dist/chunk-JDPN3HND.js +520 -0
- package/dist/chunk-JDPN3HND.js.map +1 -0
- package/dist/chunk-QXFYTHQF.js +298 -0
- package/dist/chunk-QXFYTHQF.js.map +1 -0
- package/dist/chunk-SKKRPS5K.js +50 -0
- package/dist/chunk-SKKRPS5K.js.map +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.js +3 -0
- package/dist/events.js.map +1 -0
- package/dist/handlers.d.ts +121 -0
- package/dist/handlers.js +4 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +576 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/sampling.d.ts +4 -0
- package/dist/sampling.js +3 -0
- package/dist/sampling.js.map +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +3 -0
- package/dist/testing.js.map +1 -0
- package/package.json +107 -0
- package/src/actors/alarms.ts +225 -0
- package/src/actors/index.ts +36 -0
- package/src/actors/instrument-actor.test.ts +179 -0
- package/src/actors/instrument-actor.ts +574 -0
- package/src/actors/sockets.ts +217 -0
- package/src/actors/storage.ts +263 -0
- package/src/actors/traced-handler.ts +300 -0
- package/src/actors/types.ts +98 -0
- package/src/actors.ts +50 -0
- package/src/agents/index.ts +42 -0
- package/src/agents/otel-observability.test.ts +329 -0
- package/src/agents/otel-observability.ts +465 -0
- package/src/agents/types.ts +167 -0
- package/src/agents.ts +76 -0
- package/src/bindings/bindings.ts +621 -0
- package/src/bindings/common.ts +75 -0
- package/src/bindings/index.ts +12 -0
- package/src/bindings.ts +6 -0
- package/src/events.ts +6 -0
- package/src/global/cache.test.ts +292 -0
- package/src/global/cache.ts +164 -0
- package/src/global/fetch.test.ts +344 -0
- package/src/global/fetch.ts +134 -0
- package/src/global/index.ts +7 -0
- package/src/handlers/durable-objects.test.ts +524 -0
- package/src/handlers/durable-objects.ts +250 -0
- package/src/handlers/index.ts +6 -0
- package/src/handlers/workflows.ts +318 -0
- package/src/handlers.ts +6 -0
- package/src/index.ts +57 -0
- package/src/logger.ts +6 -0
- package/src/sampling.ts +6 -0
- package/src/testing.ts +6 -0
- package/src/wrappers/index.ts +8 -0
- package/src/wrappers/instrument.integration.test.ts +468 -0
- package/src/wrappers/instrument.ts +643 -0
- package/src/wrappers/wrap-do.ts +34 -0
- package/src/wrappers/wrap-module.ts +37 -0
|
@@ -0,0 +1,344 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
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
|
+
|
|
26
|
+
return {
|
|
27
|
+
'http.request.method': request.method.toUpperCase(),
|
|
28
|
+
'url.full': request.url,
|
|
29
|
+
'url.scheme': url.protocol.replace(':', ''),
|
|
30
|
+
'server.address': url.host,
|
|
31
|
+
'url.path': url.pathname,
|
|
32
|
+
'url.query': url.search,
|
|
33
|
+
'network.protocol.name': 'http',
|
|
34
|
+
'user_agent.original': request.headers.get('user-agent') || undefined,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Gather HTTP response attributes
|
|
40
|
+
*/
|
|
41
|
+
function gatherResponseAttributes(response: Response): Record<string, any> {
|
|
42
|
+
return {
|
|
43
|
+
'http.response.status_code': response.status,
|
|
44
|
+
'http.response.body.size': response.headers.get('content-length') || undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Instrument the global fetch function
|
|
50
|
+
*
|
|
51
|
+
* This wraps globalThis.fetch to automatically create spans for all outgoing HTTP requests.
|
|
52
|
+
*
|
|
53
|
+
* **Note:** This is called automatically when the library is initialized with
|
|
54
|
+
* `instrumentation.instrumentGlobalFetch: true` (default).
|
|
55
|
+
*/
|
|
56
|
+
export function instrumentGlobalFetch(): void {
|
|
57
|
+
const originalFetch = globalThis.fetch;
|
|
58
|
+
|
|
59
|
+
const instrumentedFetch = function fetch(
|
|
60
|
+
input: RequestInfo | URL,
|
|
61
|
+
init?: RequestInit,
|
|
62
|
+
): Promise<Response> {
|
|
63
|
+
const request = new Request(input, init);
|
|
64
|
+
|
|
65
|
+
// Skip non-HTTP requests
|
|
66
|
+
if (!request.url.startsWith('http')) {
|
|
67
|
+
return originalFetch(input, init);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Skip if no active config (not initialized yet)
|
|
71
|
+
const config = getActiveConfig();
|
|
72
|
+
if (!config) {
|
|
73
|
+
return originalFetch(input, init);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
77
|
+
const url = new URL(request.url);
|
|
78
|
+
const spanName = `${request.method} ${url.host}`;
|
|
79
|
+
|
|
80
|
+
return tracer.startActiveSpan(
|
|
81
|
+
spanName,
|
|
82
|
+
{
|
|
83
|
+
kind: SpanKind.CLIENT,
|
|
84
|
+
attributes: gatherRequestAttributes(request),
|
|
85
|
+
},
|
|
86
|
+
async (span) => {
|
|
87
|
+
try {
|
|
88
|
+
// Inject trace context into request headers for distributed tracing
|
|
89
|
+
const shouldIncludeContext =
|
|
90
|
+
typeof config.fetch?.includeTraceContext === 'function'
|
|
91
|
+
? config.fetch.includeTraceContext(request)
|
|
92
|
+
: (config.fetch?.includeTraceContext ?? true);
|
|
93
|
+
|
|
94
|
+
if (shouldIncludeContext) {
|
|
95
|
+
propagation.inject(api_context.active(), request.headers, {
|
|
96
|
+
set: (headers, key, value) => {
|
|
97
|
+
if (typeof value === 'string') {
|
|
98
|
+
headers.set(key, value);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Make the actual fetch call
|
|
105
|
+
const response = await originalFetch(request);
|
|
106
|
+
|
|
107
|
+
// Add response attributes
|
|
108
|
+
span.setAttributes(gatherResponseAttributes(response));
|
|
109
|
+
|
|
110
|
+
// Set span status based on response
|
|
111
|
+
if (response.ok) {
|
|
112
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
113
|
+
} else {
|
|
114
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return response;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
span.recordException(error as Error);
|
|
120
|
+
span.setStatus({
|
|
121
|
+
code: SpanStatusCode.ERROR,
|
|
122
|
+
message: error instanceof Error ? error.message : String(error),
|
|
123
|
+
});
|
|
124
|
+
throw error;
|
|
125
|
+
} finally {
|
|
126
|
+
span.end();
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Replace global fetch
|
|
133
|
+
globalThis.fetch = instrumentedFetch as typeof fetch;
|
|
134
|
+
}
|