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
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vectorize binding instrumentation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
trace,
|
|
7
|
-
SpanKind,
|
|
8
|
-
SpanStatusCode,
|
|
9
|
-
} from '@opentelemetry/api';
|
|
10
|
-
import type { WorkerTracer } from 'autotel-edge';
|
|
11
|
-
import { wrap, setAttr } from './common';
|
|
12
|
-
|
|
13
|
-
const TRACED_METHODS = ['query', 'insert', 'upsert', 'deleteByIds', 'getByIds', 'describe'] as const;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Instrument Vectorize index binding
|
|
17
|
-
*/
|
|
18
|
-
export function instrumentVectorize<T extends VectorizeIndex>(vectorize: T, indexName?: string): T {
|
|
19
|
-
const name = indexName || 'vectorize';
|
|
20
|
-
|
|
21
|
-
const handler: ProxyHandler<T> = {
|
|
22
|
-
get(target, prop) {
|
|
23
|
-
const value = Reflect.get(target, prop);
|
|
24
|
-
|
|
25
|
-
if (typeof prop === 'string' && TRACED_METHODS.includes(prop as any) && typeof value === 'function') {
|
|
26
|
-
return new Proxy(value, {
|
|
27
|
-
apply: (fnTarget, _thisArg, args) => {
|
|
28
|
-
const operation = prop as string;
|
|
29
|
-
const tracer = trace.getTracer('autotel-edge') as WorkerTracer;
|
|
30
|
-
|
|
31
|
-
const attributes: Record<string, string | number> = {
|
|
32
|
-
'db.system': 'cloudflare-vectorize',
|
|
33
|
-
'db.operation': operation,
|
|
34
|
-
'db.collection.name': name,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Per-operation attributes
|
|
38
|
-
if (operation === 'query') {
|
|
39
|
-
const queryInput = args[0] as { topK?: number } | undefined;
|
|
40
|
-
if (queryInput?.topK !== undefined) {
|
|
41
|
-
attributes['db.vectorize.top_k'] = queryInput.topK;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if ((operation === 'insert' || operation === 'upsert') && Array.isArray(args[0])) {
|
|
46
|
-
attributes['db.vectorize.vectors_count'] = args[0].length;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return tracer.startActiveSpan(
|
|
50
|
-
`Vectorize ${name}: ${operation}`,
|
|
51
|
-
{
|
|
52
|
-
kind: SpanKind.CLIENT,
|
|
53
|
-
attributes,
|
|
54
|
-
},
|
|
55
|
-
async (span) => {
|
|
56
|
-
try {
|
|
57
|
-
const result = await Reflect.apply(fnTarget, target, args);
|
|
58
|
-
|
|
59
|
-
if (operation === 'query' && result?.matches) {
|
|
60
|
-
setAttr(span, 'db.vectorize.matches_count', result.matches.length);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
64
|
-
return result;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
span.recordException(error as Error);
|
|
67
|
-
span.setStatus({
|
|
68
|
-
code: SpanStatusCode.ERROR,
|
|
69
|
-
message: error instanceof Error ? error.message : String(error),
|
|
70
|
-
});
|
|
71
|
-
throw error;
|
|
72
|
-
} finally {
|
|
73
|
-
span.end();
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
);
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return value;
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
return wrap(vectorize, handler);
|
|
86
|
-
}
|
package/src/bindings.ts
DELETED
package/src/events.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
getRequestLogger,
|
|
4
|
-
getQueueLogger,
|
|
5
|
-
getWorkflowLogger,
|
|
6
|
-
getActorLogger,
|
|
7
|
-
createWorkersLogger,
|
|
8
|
-
} from './execution-logger';
|
|
9
|
-
|
|
10
|
-
const createMockContext = () => ({
|
|
11
|
-
traceId: 'trace-id',
|
|
12
|
-
spanId: 'span-id',
|
|
13
|
-
correlationId: 'corr-id',
|
|
14
|
-
setAttribute: vi.fn(),
|
|
15
|
-
setAttributes: vi.fn(),
|
|
16
|
-
setStatus: vi.fn(),
|
|
17
|
-
recordException: vi.fn(),
|
|
18
|
-
addEvent: vi.fn(),
|
|
19
|
-
addLink: vi.fn(),
|
|
20
|
-
addLinks: vi.fn(),
|
|
21
|
-
updateName: vi.fn(),
|
|
22
|
-
isRecording: vi.fn(() => true),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('cloudflare execution logger aliases', () => {
|
|
26
|
-
it('getRequestLogger delegates to the shared execution logger', () => {
|
|
27
|
-
const ctx = createMockContext();
|
|
28
|
-
const log = getRequestLogger(ctx);
|
|
29
|
-
|
|
30
|
-
log.set({ request: { id: 'req-123' } });
|
|
31
|
-
|
|
32
|
-
expect(ctx.setAttributes).toHaveBeenCalledWith({
|
|
33
|
-
'request.id': 'req-123',
|
|
34
|
-
});
|
|
35
|
-
expect(typeof log.fork).toBe('function');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('getQueueLogger delegates to the shared execution logger', () => {
|
|
39
|
-
const ctx = createMockContext();
|
|
40
|
-
const log = getQueueLogger(ctx);
|
|
41
|
-
|
|
42
|
-
log.info('processing batch', { queue: { name: 'payments' } });
|
|
43
|
-
|
|
44
|
-
expect(ctx.addEvent).toHaveBeenCalledWith('log.info', {
|
|
45
|
-
message: 'processing batch',
|
|
46
|
-
'queue.name': 'payments',
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('getWorkflowLogger delegates to the shared execution logger', () => {
|
|
51
|
-
const ctx = createMockContext();
|
|
52
|
-
const log = getWorkflowLogger(ctx);
|
|
53
|
-
const first = log.emitNow({ workflow: { id: 'wf-123' } });
|
|
54
|
-
const second = log.emitNow({ workflow: { id: 'wf-123' } });
|
|
55
|
-
|
|
56
|
-
expect(first.traceId).toBe('trace-id');
|
|
57
|
-
expect(second).toBe(first);
|
|
58
|
-
expect(ctx.addEvent).toHaveBeenCalledWith('log.emit.manual', {
|
|
59
|
-
'workflow.id': 'wf-123',
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('getActorLogger delegates to the shared execution logger', () => {
|
|
64
|
-
const ctx = createMockContext();
|
|
65
|
-
const log = getActorLogger(ctx);
|
|
66
|
-
|
|
67
|
-
log.warn('alarm delayed', { actor: { class: 'Counter' } });
|
|
68
|
-
|
|
69
|
-
expect(ctx.setAttribute).toHaveBeenCalledWith('autotel.log.level', 'warn');
|
|
70
|
-
expect(ctx.addEvent).toHaveBeenCalledWith('log.warn', {
|
|
71
|
-
message: 'alarm delayed',
|
|
72
|
-
'actor.class': 'Counter',
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('createWorkersLogger pre-populates request and cf context fields', () => {
|
|
77
|
-
const ctx = createMockContext();
|
|
78
|
-
const request = new Request('https://example.com/api/orders/123?expand=1', {
|
|
79
|
-
method: 'POST',
|
|
80
|
-
headers: {
|
|
81
|
-
'cf-ray': 'ray-123',
|
|
82
|
-
traceparent: '00-abc-def-01',
|
|
83
|
-
authorization: 'Bearer should-not-include-by-default',
|
|
84
|
-
'x-request-id': 'req-789',
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
Object.defineProperty(request, 'cf', {
|
|
89
|
-
value: {
|
|
90
|
-
colo: 'LHR',
|
|
91
|
-
country: 'GB',
|
|
92
|
-
asn: 13335,
|
|
93
|
-
city: 'London',
|
|
94
|
-
region: 'England',
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const log = createWorkersLogger(request, { headers: ['x-request-id'] }, ctx);
|
|
99
|
-
|
|
100
|
-
const fields = log.getContext();
|
|
101
|
-
expect(fields.request).toMatchObject({
|
|
102
|
-
method: 'POST',
|
|
103
|
-
path: '/api/orders/123',
|
|
104
|
-
url: 'https://example.com/api/orders/123?expand=1',
|
|
105
|
-
requestId: 'ray-123',
|
|
106
|
-
headers: { 'x-request-id': 'req-789' },
|
|
107
|
-
});
|
|
108
|
-
expect(fields.cfRay).toBe('ray-123');
|
|
109
|
-
expect(fields.traceparent).toBe('00-abc-def-01');
|
|
110
|
-
expect(fields.colo).toBe('LHR');
|
|
111
|
-
expect(fields.country).toBe('GB');
|
|
112
|
-
expect(fields.asn).toBe(13335);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('createWorkersLogger honors explicit requestId override', () => {
|
|
116
|
-
const ctx = createMockContext();
|
|
117
|
-
const request = new Request('https://example.com/health', {
|
|
118
|
-
headers: { 'cf-ray': 'ray-default' },
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const log = createWorkersLogger(request, { requestId: 'manual-id' }, ctx);
|
|
122
|
-
|
|
123
|
-
expect(log.getContext().request).toMatchObject({
|
|
124
|
-
requestId: 'manual-id',
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|
package/src/execution-logger.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type ExecutionLogger,
|
|
3
|
-
type ExecutionLoggerOptions,
|
|
4
|
-
type ExecutionLogSnapshot,
|
|
5
|
-
getExecutionLogger,
|
|
6
|
-
type TraceContext,
|
|
7
|
-
} from 'autotel-edge/logger';
|
|
8
|
-
|
|
9
|
-
export type {
|
|
10
|
-
ExecutionLogger,
|
|
11
|
-
ExecutionLoggerOptions,
|
|
12
|
-
ExecutionLogSnapshot,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export interface WorkersLoggerOptions {
|
|
16
|
-
/** Override derived request id (default: cf-ray header value when present). */
|
|
17
|
-
requestId?: string;
|
|
18
|
-
/** Optional request header allowlist to include in logger context. */
|
|
19
|
-
headers?: string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
23
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function collectHeaders(
|
|
27
|
-
headers: Headers,
|
|
28
|
-
include: string[] | undefined,
|
|
29
|
-
): Record<string, string> | undefined {
|
|
30
|
-
if (!include || include.length === 0) return undefined;
|
|
31
|
-
|
|
32
|
-
const allowlist = new Set(include.map((h) => h.toLowerCase()));
|
|
33
|
-
const out: Record<string, string> = {};
|
|
34
|
-
headers.forEach((value, key) => {
|
|
35
|
-
if (allowlist.has(key.toLowerCase())) {
|
|
36
|
-
out[key] = value;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function pickCfContext(request: Request): Record<string, unknown> {
|
|
44
|
-
const cf = Reflect.get(request, 'cf');
|
|
45
|
-
if (!isRecord(cf)) return {};
|
|
46
|
-
|
|
47
|
-
const out: Record<string, unknown> = {};
|
|
48
|
-
if (typeof cf.colo === 'string') out.colo = cf.colo;
|
|
49
|
-
if (typeof cf.country === 'string') out.country = cf.country;
|
|
50
|
-
if (typeof cf.asn === 'number') out.asn = cf.asn;
|
|
51
|
-
if (typeof cf.city === 'string') out.city = cf.city;
|
|
52
|
-
if (typeof cf.region === 'string') out.region = cf.region;
|
|
53
|
-
return out;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Create an execution logger pre-populated with common request context.
|
|
58
|
-
* Best used from fetch handlers that already run inside autotel span context.
|
|
59
|
-
*/
|
|
60
|
-
export function createWorkersLogger(
|
|
61
|
-
request: Request,
|
|
62
|
-
options: WorkersLoggerOptions = {},
|
|
63
|
-
ctx?: TraceContext,
|
|
64
|
-
): ExecutionLogger {
|
|
65
|
-
const log = getExecutionLogger(ctx);
|
|
66
|
-
const url = new URL(request.url);
|
|
67
|
-
const cfRay = request.headers.get('cf-ray') ?? undefined;
|
|
68
|
-
const traceparent = request.headers.get('traceparent') ?? undefined;
|
|
69
|
-
|
|
70
|
-
log.set({
|
|
71
|
-
request: {
|
|
72
|
-
method: request.method,
|
|
73
|
-
path: url.pathname,
|
|
74
|
-
url: request.url,
|
|
75
|
-
requestId: options.requestId ?? cfRay,
|
|
76
|
-
headers: collectHeaders(request.headers, options.headers),
|
|
77
|
-
},
|
|
78
|
-
cfRay,
|
|
79
|
-
traceparent,
|
|
80
|
-
...pickCfContext(request),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
return log;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function getRequestLogger(
|
|
87
|
-
ctx?: TraceContext,
|
|
88
|
-
options?: ExecutionLoggerOptions,
|
|
89
|
-
): ExecutionLogger {
|
|
90
|
-
return getExecutionLogger(ctx, options);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function getQueueLogger(
|
|
94
|
-
ctx?: TraceContext,
|
|
95
|
-
options?: ExecutionLoggerOptions,
|
|
96
|
-
): ExecutionLogger {
|
|
97
|
-
return getExecutionLogger(ctx, options);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function getWorkflowLogger(
|
|
101
|
-
ctx?: TraceContext,
|
|
102
|
-
options?: ExecutionLoggerOptions,
|
|
103
|
-
): ExecutionLogger {
|
|
104
|
-
return getExecutionLogger(ctx, options);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function getActorLogger(
|
|
108
|
-
ctx?: TraceContext,
|
|
109
|
-
options?: ExecutionLoggerOptions,
|
|
110
|
-
): ExecutionLogger {
|
|
111
|
-
return getExecutionLogger(ctx, options);
|
|
112
|
-
}
|
package/src/global/cache.test.ts
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { instrumentGlobalCache } from './cache';
|
|
3
|
-
import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api';
|
|
4
|
-
|
|
5
|
-
describe('Global Cache Instrumentation', () => {
|
|
6
|
-
let mockTracer: any;
|
|
7
|
-
let mockSpan: any;
|
|
8
|
-
let getTracerSpy: any;
|
|
9
|
-
let originalCaches: typeof globalThis.caches;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Save original caches
|
|
13
|
-
originalCaches = globalThis.caches;
|
|
14
|
-
|
|
15
|
-
mockSpan = {
|
|
16
|
-
spanContext: () => ({
|
|
17
|
-
traceId: 'test-trace-id',
|
|
18
|
-
spanId: 'test-span-id',
|
|
19
|
-
traceFlags: 1,
|
|
20
|
-
}),
|
|
21
|
-
setAttribute: vi.fn(),
|
|
22
|
-
setAttributes: vi.fn(),
|
|
23
|
-
setStatus: vi.fn(),
|
|
24
|
-
recordException: vi.fn(),
|
|
25
|
-
end: vi.fn(),
|
|
26
|
-
isRecording: () => true,
|
|
27
|
-
updateName: vi.fn(),
|
|
28
|
-
addEvent: vi.fn(),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
mockTracer = {
|
|
32
|
-
startActiveSpan: vi.fn((name, options, fn) => {
|
|
33
|
-
return fn(mockSpan);
|
|
34
|
-
}),
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any);
|
|
38
|
-
|
|
39
|
-
// Mock caches API
|
|
40
|
-
const mockCache = {
|
|
41
|
-
match: vi.fn(async (key) => {
|
|
42
|
-
// Simulate cache hit/miss
|
|
43
|
-
const url = key instanceof Request ? key.url : key;
|
|
44
|
-
if (typeof url === 'string' && url.includes('cached')) {
|
|
45
|
-
return new Response('cached data');
|
|
46
|
-
}
|
|
47
|
-
return undefined;
|
|
48
|
-
}),
|
|
49
|
-
put: vi.fn(async () => {}),
|
|
50
|
-
delete: vi.fn(async () => true),
|
|
51
|
-
keys: vi.fn(async () => []),
|
|
52
|
-
add: vi.fn(async () => {}),
|
|
53
|
-
addAll: vi.fn(async () => {}),
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
(globalThis as any).caches = {
|
|
57
|
-
default: mockCache,
|
|
58
|
-
open: vi.fn(async (name: string) => mockCache),
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
afterEach(() => {
|
|
63
|
-
// Restore original caches
|
|
64
|
-
(globalThis as any).caches = originalCaches;
|
|
65
|
-
getTracerSpy.mockRestore();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('instrumentGlobalCache()', () => {
|
|
69
|
-
it('should wrap globalThis.caches', () => {
|
|
70
|
-
const originalCaches = globalThis.caches;
|
|
71
|
-
instrumentGlobalCache();
|
|
72
|
-
|
|
73
|
-
expect(globalThis.caches).not.toBe(originalCaches);
|
|
74
|
-
expect(globalThis.caches.default).toBeDefined();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should instrument caches.default', async () => {
|
|
78
|
-
instrumentGlobalCache();
|
|
79
|
-
|
|
80
|
-
const request = new Request('https://example.com/test');
|
|
81
|
-
await caches.default.match(request);
|
|
82
|
-
|
|
83
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalled();
|
|
84
|
-
|
|
85
|
-
const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
|
|
86
|
-
expect(spanName).toContain('Cache');
|
|
87
|
-
expect(spanName).toContain('default');
|
|
88
|
-
expect(spanName).toContain('match');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('cache.match() instrumentation', () => {
|
|
93
|
-
it('should create span for cache.match()', async () => {
|
|
94
|
-
instrumentGlobalCache();
|
|
95
|
-
|
|
96
|
-
const request = new Request('https://example.com/api/data');
|
|
97
|
-
await caches.default.match(request);
|
|
98
|
-
|
|
99
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalled();
|
|
100
|
-
|
|
101
|
-
const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
|
|
102
|
-
expect(spanName).toBe('Cache default.match');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should record cache hit (result found)', async () => {
|
|
106
|
-
instrumentGlobalCache();
|
|
107
|
-
|
|
108
|
-
const request = new Request('https://example.com/cached/data');
|
|
109
|
-
const result = await caches.default.match(request);
|
|
110
|
-
|
|
111
|
-
expect(result).toBeDefined();
|
|
112
|
-
expect(mockSpan.setAttribute).toHaveBeenCalledWith('cache.hit', true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should record cache miss (result not found)', async () => {
|
|
116
|
-
// Create a mock that explicitly returns undefined
|
|
117
|
-
const missCache = {
|
|
118
|
-
match: vi.fn(async () => undefined),
|
|
119
|
-
put: vi.fn(async () => {}),
|
|
120
|
-
delete: vi.fn(async () => true),
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
(globalThis as any).caches = {
|
|
124
|
-
default: missCache,
|
|
125
|
-
open: vi.fn(),
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
instrumentGlobalCache();
|
|
129
|
-
|
|
130
|
-
const request = new Request('https://example.com/uncached/data');
|
|
131
|
-
const result = await caches.default.match(request);
|
|
132
|
-
|
|
133
|
-
expect(result).toBeUndefined();
|
|
134
|
-
expect(mockSpan.setAttribute).toHaveBeenCalledWith('cache.hit', false);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('should sanitize URL in attributes', async () => {
|
|
138
|
-
instrumentGlobalCache();
|
|
139
|
-
|
|
140
|
-
const request = new Request('https://example.com/api/data?secret=123&token=abc');
|
|
141
|
-
await caches.default.match(request);
|
|
142
|
-
|
|
143
|
-
const options = mockTracer.startActiveSpan.mock.calls[0][1];
|
|
144
|
-
|
|
145
|
-
// URL should be sanitized (query params removed)
|
|
146
|
-
expect(options.attributes['cache.key']).toBe('https://example.com/api/data');
|
|
147
|
-
expect(options.attributes['cache.key']).not.toContain('secret');
|
|
148
|
-
expect(options.attributes['cache.key']).not.toContain('token');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should add cache operation attributes', async () => {
|
|
152
|
-
instrumentGlobalCache();
|
|
153
|
-
|
|
154
|
-
const request = new Request('https://example.com/test');
|
|
155
|
-
await caches.default.match(request);
|
|
156
|
-
|
|
157
|
-
const options = mockTracer.startActiveSpan.mock.calls[0][1];
|
|
158
|
-
expect(options.kind).toBe(SpanKind.CLIENT);
|
|
159
|
-
expect(options.attributes['cache.name']).toBe('default');
|
|
160
|
-
expect(options.attributes['cache.operation']).toBe('match');
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe('cache.put() instrumentation', () => {
|
|
165
|
-
it('should create span for cache.put()', async () => {
|
|
166
|
-
instrumentGlobalCache();
|
|
167
|
-
|
|
168
|
-
const request = new Request('https://example.com/test');
|
|
169
|
-
const response = new Response('data');
|
|
170
|
-
await caches.default.put(request, response);
|
|
171
|
-
|
|
172
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalled();
|
|
173
|
-
|
|
174
|
-
const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
|
|
175
|
-
expect(spanName).toBe('Cache default.put');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should add cache name and operation attributes', async () => {
|
|
179
|
-
instrumentGlobalCache();
|
|
180
|
-
|
|
181
|
-
const request = new Request('https://example.com/test');
|
|
182
|
-
const response = new Response('data');
|
|
183
|
-
await caches.default.put(request, response);
|
|
184
|
-
|
|
185
|
-
const options = mockTracer.startActiveSpan.mock.calls[0][1];
|
|
186
|
-
expect(options.attributes['cache.name']).toBe('default');
|
|
187
|
-
expect(options.attributes['cache.operation']).toBe('put');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should handle errors in cache.put()', async () => {
|
|
191
|
-
const errorCache = {
|
|
192
|
-
...globalThis.caches.default,
|
|
193
|
-
put: vi.fn(async () => {
|
|
194
|
-
throw new Error('Cache error');
|
|
195
|
-
}),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
(globalThis as any).caches = {
|
|
199
|
-
default: errorCache,
|
|
200
|
-
open: vi.fn(),
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
instrumentGlobalCache();
|
|
204
|
-
|
|
205
|
-
const request = new Request('https://example.com/test');
|
|
206
|
-
const response = new Response('data');
|
|
207
|
-
|
|
208
|
-
await expect(caches.default.put(request, response)).rejects.toThrow('Cache error');
|
|
209
|
-
|
|
210
|
-
expect(mockSpan.recordException).toHaveBeenCalled();
|
|
211
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({
|
|
212
|
-
code: SpanStatusCode.ERROR,
|
|
213
|
-
message: 'Cache error',
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
describe('cache.delete() instrumentation', () => {
|
|
219
|
-
it('should create span for cache.delete()', async () => {
|
|
220
|
-
instrumentGlobalCache();
|
|
221
|
-
|
|
222
|
-
const request = new Request('https://example.com/test');
|
|
223
|
-
await caches.default.delete(request);
|
|
224
|
-
|
|
225
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalled();
|
|
226
|
-
|
|
227
|
-
const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
|
|
228
|
-
expect(spanName).toBe('Cache default.delete');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should add cache operation attributes for delete', async () => {
|
|
232
|
-
instrumentGlobalCache();
|
|
233
|
-
|
|
234
|
-
const request = new Request('https://example.com/test');
|
|
235
|
-
await caches.default.delete(request);
|
|
236
|
-
|
|
237
|
-
const options = mockTracer.startActiveSpan.mock.calls[0][1];
|
|
238
|
-
expect(options.attributes['cache.name']).toBe('default');
|
|
239
|
-
expect(options.attributes['cache.operation']).toBe('delete');
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('caches.open() instrumentation', () => {
|
|
244
|
-
it('should wrap caches.open() to instrument named caches', async () => {
|
|
245
|
-
instrumentGlobalCache();
|
|
246
|
-
|
|
247
|
-
const namedCache = await caches.open('my-cache');
|
|
248
|
-
|
|
249
|
-
// After instrumentation, caches.open is wrapped
|
|
250
|
-
// The key is that we can still call it and get a cache back
|
|
251
|
-
expect(namedCache).toBeDefined();
|
|
252
|
-
expect(typeof namedCache.match).toBe('function');
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('should instrument operations on named caches', async () => {
|
|
256
|
-
instrumentGlobalCache();
|
|
257
|
-
|
|
258
|
-
const namedCache = await caches.open('my-custom-cache');
|
|
259
|
-
const request = new Request('https://example.com/test');
|
|
260
|
-
await namedCache.match(request);
|
|
261
|
-
|
|
262
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalled();
|
|
263
|
-
|
|
264
|
-
const spanName = mockTracer.startActiveSpan.mock.calls[0][0];
|
|
265
|
-
expect(spanName).toBe('Cache my-custom-cache.match');
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe('Edge cases', () => {
|
|
270
|
-
it('should handle string keys for match()', async () => {
|
|
271
|
-
instrumentGlobalCache();
|
|
272
|
-
|
|
273
|
-
// Some implementations allow string keys
|
|
274
|
-
await caches.default.match('https://example.com/test');
|
|
275
|
-
|
|
276
|
-
expect(mockTracer.startActiveSpan).toHaveBeenCalled();
|
|
277
|
-
|
|
278
|
-
const options = mockTracer.startActiveSpan.mock.calls[0][1];
|
|
279
|
-
expect(options.attributes['cache.key']).toBe('https://example.com/test');
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('should set OK status on successful operations', async () => {
|
|
283
|
-
instrumentGlobalCache();
|
|
284
|
-
|
|
285
|
-
const request = new Request('https://example.com/test');
|
|
286
|
-
await caches.default.match(request);
|
|
287
|
-
|
|
288
|
-
expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK });
|
|
289
|
-
expect(mockSpan.end).toHaveBeenCalled();
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|