-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathinstrumentDurableObjectStorage.ts
More file actions
87 lines (76 loc) · 3.12 KB
/
instrumentDurableObjectStorage.ts
File metadata and controls
87 lines (76 loc) · 3.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import type { DurableObjectStorage } from '@cloudflare/workers-types';
import { isThenable, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core';
import { storeSpanContext } from '../utils/traceLinks';
const STORAGE_METHODS_TO_INSTRUMENT = ['get', 'put', 'delete', 'list', 'setAlarm', 'getAlarm', 'deleteAlarm'] as const;
type StorageMethod = (typeof STORAGE_METHODS_TO_INSTRUMENT)[number];
type WaitUntil = (promise: Promise<unknown>) => void;
/**
* Instruments DurableObjectStorage methods with Sentry spans.
*
* Wraps the following async methods:
* - get, put, delete, list (KV API)
* - setAlarm, getAlarm, deleteAlarm (Alarm API)
*
* When setAlarm is called, it also stores the current span context so that when
* the alarm fires later, it can link back to the trace that called setAlarm.
*
* @param storage - The DurableObjectStorage instance to instrument
* @param waitUntil - Optional waitUntil function to defer span context storage
* @returns An instrumented DurableObjectStorage instance
*/
export function instrumentDurableObjectStorage(
storage: DurableObjectStorage,
waitUntil?: WaitUntil,
): DurableObjectStorage {
return new Proxy(storage, {
get(target, prop, receiver) {
const original = Reflect.get(target, prop, receiver);
if (typeof original !== 'function') {
return original;
}
const methodName = prop as string;
if (!STORAGE_METHODS_TO_INSTRUMENT.includes(methodName as StorageMethod)) {
return (original as (...args: unknown[]) => unknown).bind(target);
}
return function (this: unknown, ...args: unknown[]) {
return startSpan(
{
// Use underscore naming to match Cloudflare's native instrumentation (e.g., "durable_object_storage_get")
name: `durable_object_storage_${methodName}`,
op: 'db',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.cloudflare.durable_object',
'db.system.name': 'cloudflare.durable_object.storage',
'db.operation.name': methodName,
},
},
() => {
const teardown = async (): Promise<void> => {
// When setAlarm is called, store the current span context so that when the alarm
// fires later, it can link back to the trace that called setAlarm.
// We use the original (uninstrumented) storage (target) to avoid creating a span
// for this internal operation. The storage is deferred via waitUntil to not block.
if (methodName === 'setAlarm') {
await storeSpanContext(target, 'alarm');
}
};
const result = (original as (...args: unknown[]) => unknown).apply(target, args);
if (!isThenable(result)) {
waitUntil?.(teardown());
return result;
}
return result.then(
res => {
waitUntil?.(teardown());
return res;
},
e => {
throw e;
},
);
},
);
};
},
});
}