@sentry/cloudflare 10.68.0 → 10.70.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/build/cjs/client.js +5 -0
- package/build/cjs/client.js.map +1 -1
- package/build/cjs/durableobject.js +210 -97
- package/build/cjs/durableobject.js.map +1 -1
- package/build/cjs/flush.js.map +1 -1
- package/build/cjs/index.js +3 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/instrumentations/agents/index.js +16 -0
- package/build/cjs/instrumentations/agents/index.js.map +1 -0
- package/build/cjs/instrumentations/agents/instrumentAgentCallableRpc.js +52 -0
- package/build/cjs/instrumentations/agents/instrumentAgentCallableRpc.js.map +1 -0
- package/build/cjs/instrumentations/agents/instrumentAgentRequestConversation.js +19 -0
- package/build/cjs/instrumentations/agents/instrumentAgentRequestConversation.js.map +1 -0
- package/build/cjs/instrumentations/agents/instrumentChatAgentConversation.js +31 -0
- package/build/cjs/instrumentations/agents/instrumentChatAgentConversation.js.map +1 -0
- package/build/cjs/instrumentations/agents/types.js +77 -0
- package/build/cjs/instrumentations/agents/types.js.map +1 -0
- package/build/cjs/instrumentations/instrumentDurableObjectStorage.js +6 -0
- package/build/cjs/instrumentations/instrumentDurableObjectStorage.js.map +1 -1
- package/build/cjs/instrumentations/instrumentSqlStorage.js +1 -1
- package/build/cjs/instrumentations/instrumentSqlStorage.js.map +1 -1
- package/build/cjs/integrations/spotlight.js +64 -0
- package/build/cjs/integrations/spotlight.js.map +1 -0
- package/build/cjs/nodejs_compat/index.js +5 -2
- package/build/cjs/nodejs_compat/index.js.map +1 -1
- package/build/cjs/options.js +14 -1
- package/build/cjs/options.js.map +1 -1
- package/build/cjs/request.js +3 -2
- package/build/cjs/request.js.map +1 -1
- package/build/cjs/sdk.js +8 -0
- package/build/cjs/sdk.js.map +1 -1
- package/build/cjs/utils/internalSqlQuery.js +13 -7
- package/build/cjs/utils/internalSqlQuery.js.map +1 -1
- package/build/cjs/utils/internalStorageKey.js +44 -0
- package/build/cjs/utils/internalStorageKey.js.map +1 -0
- package/build/cjs/utils/invocationScope.js +12 -0
- package/build/cjs/utils/invocationScope.js.map +1 -0
- package/build/cjs/utils/rpcMeta.js +4 -0
- package/build/cjs/utils/rpcMeta.js.map +1 -1
- package/build/cjs/vite/agentClass.js +114 -0
- package/build/cjs/vite/agentClass.js.map +1 -0
- package/build/cjs/vite/autoInstrument.js +17 -4
- package/build/cjs/vite/autoInstrument.js.map +1 -1
- package/build/cjs/vite/index.js +1 -1
- package/build/cjs/vite/index.js.map +1 -1
- package/build/cjs/vite/moduleShape.js +231 -0
- package/build/cjs/vite/moduleShape.js.map +1 -0
- package/build/cjs/vite/transform.js +6 -1
- package/build/cjs/vite/transform.js.map +1 -1
- package/build/cjs/vite/wranglerConfig.js +1 -1
- package/build/cjs/vite/wranglerConfig.js.map +1 -1
- package/build/cjs/workflows.js +1 -1
- package/build/cjs/workflows.js.map +1 -1
- package/build/cjs/wrapMethodWithSentry.js +2 -3
- package/build/cjs/wrapMethodWithSentry.js.map +1 -1
- package/build/esm/client.js +6 -1
- package/build/esm/client.js.map +1 -1
- package/build/esm/durableobject.js +211 -101
- package/build/esm/durableobject.js.map +1 -1
- package/build/esm/flush.js.map +1 -1
- package/build/esm/index.js +2 -1
- package/build/esm/index.js.map +1 -1
- package/build/esm/instrumentations/agents/index.js +14 -0
- package/build/esm/instrumentations/agents/index.js.map +1 -0
- package/build/esm/instrumentations/agents/instrumentAgentCallableRpc.js +50 -0
- package/build/esm/instrumentations/agents/instrumentAgentCallableRpc.js.map +1 -0
- package/build/esm/instrumentations/agents/instrumentAgentRequestConversation.js +17 -0
- package/build/esm/instrumentations/agents/instrumentAgentRequestConversation.js.map +1 -0
- package/build/esm/instrumentations/agents/instrumentChatAgentConversation.js +29 -0
- package/build/esm/instrumentations/agents/instrumentChatAgentConversation.js.map +1 -0
- package/build/esm/instrumentations/agents/types.js +69 -0
- package/build/esm/instrumentations/agents/types.js.map +1 -0
- package/build/esm/instrumentations/instrumentDurableObjectStorage.js +7 -1
- package/build/esm/instrumentations/instrumentDurableObjectStorage.js.map +1 -1
- package/build/esm/instrumentations/instrumentSqlStorage.js +1 -1
- package/build/esm/instrumentations/instrumentSqlStorage.js.map +1 -1
- package/build/esm/integrations/spotlight.js +61 -0
- package/build/esm/integrations/spotlight.js.map +1 -0
- package/build/esm/nodejs_compat/index.js +2 -1
- package/build/esm/nodejs_compat/index.js.map +1 -1
- package/build/esm/options.js +14 -1
- package/build/esm/options.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/request.js +4 -3
- package/build/esm/request.js.map +1 -1
- package/build/esm/sdk.js +8 -0
- package/build/esm/sdk.js.map +1 -1
- package/build/esm/utils/internalSqlQuery.js +13 -7
- package/build/esm/utils/internalSqlQuery.js.map +1 -1
- package/build/esm/utils/internalStorageKey.js +41 -0
- package/build/esm/utils/internalStorageKey.js.map +1 -0
- package/build/esm/utils/invocationScope.js +10 -0
- package/build/esm/utils/invocationScope.js.map +1 -0
- package/build/esm/utils/rpcMeta.js +4 -1
- package/build/esm/utils/rpcMeta.js.map +1 -1
- package/build/esm/utils/traceLinks.js +1 -1
- package/build/esm/vite/agentClass.js +111 -0
- package/build/esm/vite/agentClass.js.map +1 -0
- package/build/esm/vite/autoInstrument.js +17 -4
- package/build/esm/vite/autoInstrument.js.map +1 -1
- package/build/esm/vite/index.js +1 -1
- package/build/esm/vite/index.js.map +1 -1
- package/build/esm/vite/moduleShape.js +227 -0
- package/build/esm/vite/moduleShape.js.map +1 -0
- package/build/esm/vite/transform.js +6 -1
- package/build/esm/vite/transform.js.map +1 -1
- package/build/esm/vite/wranglerConfig.js +1 -1
- package/build/esm/vite/wranglerConfig.js.map +1 -1
- package/build/esm/workflows.js +2 -2
- package/build/esm/workflows.js.map +1 -1
- package/build/esm/wrapMethodWithSentry.js +3 -4
- package/build/esm/wrapMethodWithSentry.js.map +1 -1
- package/build/types/client.d.ts +37 -0
- package/build/types/client.d.ts.map +1 -1
- package/build/types/durableobject.d.ts +90 -0
- package/build/types/durableobject.d.ts.map +1 -1
- package/build/types/flush.d.ts +1 -1
- package/build/types/flush.d.ts.map +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/instrumentations/agents/index.d.ts +25 -0
- package/build/types/instrumentations/agents/index.d.ts.map +1 -0
- package/build/types/instrumentations/agents/instrumentAgentCallableRpc.d.ts +14 -0
- package/build/types/instrumentations/agents/instrumentAgentCallableRpc.d.ts.map +1 -0
- package/build/types/instrumentations/agents/instrumentAgentRequestConversation.d.ts +16 -0
- package/build/types/instrumentations/agents/instrumentAgentRequestConversation.d.ts.map +1 -0
- package/build/types/instrumentations/agents/instrumentChatAgentConversation.d.ts +23 -0
- package/build/types/instrumentations/agents/instrumentChatAgentConversation.d.ts.map +1 -0
- package/build/types/instrumentations/agents/types.d.ts +94 -0
- package/build/types/instrumentations/agents/types.d.ts.map +1 -0
- package/build/types/instrumentations/instrumentDurableObjectStorage.d.ts.map +1 -1
- package/build/types/integrations/spotlight.d.ts +22 -0
- package/build/types/integrations/spotlight.d.ts.map +1 -0
- package/build/types/options.d.ts.map +1 -1
- package/build/types/request.d.ts.map +1 -1
- package/build/types/sdk.d.ts.map +1 -1
- package/build/types/utils/internalSqlQuery.d.ts +4 -2
- package/build/types/utils/internalSqlQuery.d.ts.map +1 -1
- package/build/types/utils/internalStorageKey.d.ts +22 -0
- package/build/types/utils/internalStorageKey.d.ts.map +1 -0
- package/build/types/utils/invocationScope.d.ts +24 -0
- package/build/types/utils/invocationScope.d.ts.map +1 -0
- package/build/types/utils/rpcMeta.d.ts +8 -0
- package/build/types/utils/rpcMeta.d.ts.map +1 -1
- package/build/types/vite/agentClass.d.ts +41 -0
- package/build/types/vite/agentClass.d.ts.map +1 -0
- package/build/types/vite/autoInstrument.d.ts +7 -6
- package/build/types/vite/autoInstrument.d.ts.map +1 -1
- package/build/types/vite/index.d.ts +14 -4
- package/build/types/vite/index.d.ts.map +1 -1
- package/build/types/vite/moduleShape.d.ts +52 -0
- package/build/types/vite/moduleShape.d.ts.map +1 -0
- package/build/types/vite/transform.d.ts +6 -1
- package/build/types/vite/transform.d.ts.map +1 -1
- package/build/types/wrapMethodWithSentry.d.ts +1 -1
- package/build/types/wrapMethodWithSentry.d.ts.map +1 -1
- package/build/types-ts3.8/client.d.ts +37 -0
- package/build/types-ts3.8/durableobject.d.ts +90 -0
- package/build/types-ts3.8/flush.d.ts +1 -1
- package/build/types-ts3.8/index.d.ts +2 -1
- package/build/types-ts3.8/instrumentations/agents/index.d.ts +25 -0
- package/build/types-ts3.8/instrumentations/agents/instrumentAgentCallableRpc.d.ts +14 -0
- package/build/types-ts3.8/instrumentations/agents/instrumentAgentRequestConversation.d.ts +16 -0
- package/build/types-ts3.8/instrumentations/agents/instrumentChatAgentConversation.d.ts +23 -0
- package/build/types-ts3.8/instrumentations/agents/types.d.ts +94 -0
- package/build/types-ts3.8/integrations/spotlight.d.ts +22 -0
- package/build/types-ts3.8/utils/internalSqlQuery.d.ts +4 -2
- package/build/types-ts3.8/utils/internalStorageKey.d.ts +22 -0
- package/build/types-ts3.8/utils/invocationScope.d.ts +24 -0
- package/build/types-ts3.8/utils/rpcMeta.d.ts +8 -0
- package/build/types-ts3.8/vite/agentClass.d.ts +41 -0
- package/build/types-ts3.8/vite/autoInstrument.d.ts +7 -6
- package/build/types-ts3.8/vite/index.d.ts +14 -4
- package/build/types-ts3.8/vite/moduleShape.d.ts +52 -0
- package/build/types-ts3.8/vite/transform.d.ts +6 -1
- package/build/types-ts3.8/wrapMethodWithSentry.d.ts +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const debugBuild = require('../../debug-build.js');
|
|
5
|
+
const types = require('./types.js');
|
|
6
|
+
|
|
7
|
+
function instrumentAgentCallableRpc(obj) {
|
|
8
|
+
const original = obj.onMessage;
|
|
9
|
+
if (typeof original !== "function") {
|
|
10
|
+
debugBuild.DEBUG_BUILD && core.debug.log("[Sentry] Agent `onMessage` not found \u2014 callable RPC span instrumentation skipped.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
obj.onMessage = new Proxy(original, {
|
|
14
|
+
apply(target, thisArg, args) {
|
|
15
|
+
const method = extractCallableMethod(args[1]);
|
|
16
|
+
if (!method) {
|
|
17
|
+
return Reflect.apply(target, thisArg, args);
|
|
18
|
+
}
|
|
19
|
+
return core.startSpan(
|
|
20
|
+
{
|
|
21
|
+
name: method,
|
|
22
|
+
attributes: {
|
|
23
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: "rpc",
|
|
24
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: types.AGENT_SPAN_ORIGIN,
|
|
25
|
+
...types.getAgentAttributes(thisArg)
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
async () => {
|
|
29
|
+
await types.setAgentConversationId(thisArg);
|
|
30
|
+
return Reflect.apply(target, thisArg, args);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function extractCallableMethod(message) {
|
|
37
|
+
const text = typeof message === "string" ? message : message instanceof ArrayBuffer ? new TextDecoder().decode(message) : void 0;
|
|
38
|
+
if (!text) {
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(text);
|
|
43
|
+
if (parsed.type === "rpc" && typeof parsed.method === "string" && Array.isArray(parsed.args)) {
|
|
44
|
+
return parsed.method;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.instrumentAgentCallableRpc = instrumentAgentCallableRpc;
|
|
52
|
+
//# sourceMappingURL=instrumentAgentCallableRpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentAgentCallableRpc.js","sources":["../../../../src/instrumentations/agents/instrumentAgentCallableRpc.ts"],"sourcesContent":["import { debug, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport { AGENT_SPAN_ORIGIN, type AgentInternals, getAgentAttributes, setAgentConversationId } from './types';\n\n/**\n * Wraps the Agent's `onMessage` handler to create a span for each `@callable()` RPC invocation.\n * RPC requests arrive as WebSocket messages, so this span nests under the active transaction for\n * the WebSocket message (on Cloudflare, the instrumented Durable Object `webSocketMessage` hook).\n *\n * Also sets the conversation id on the scope for the duration of the call: callable methods are the\n * unit of work for plain (non-chat) agents, which run LLM calls just like chat turns do. It is\n * awaited so the id is on the scope before the method body creates any span; the `agents`\n * `onMessage` own property this wraps is already `async`, so the promise return is nothing new to\n * the WebSocket dispatch upstream.\n */\nexport function instrumentAgentCallableRpc(obj: AgentInternals): void {\n const original = obj.onMessage;\n if (typeof original !== 'function') {\n DEBUG_BUILD && debug.log('[Sentry] Agent `onMessage` not found — callable RPC span instrumentation skipped.');\n return;\n }\n\n obj.onMessage = new Proxy(original, {\n apply(target, thisArg: AgentInternals, args: unknown[]): unknown {\n const method = extractCallableMethod(args[1]);\n\n if (!method) {\n return Reflect.apply(target, thisArg, args);\n }\n\n return startSpan(\n {\n name: method,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'rpc',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: AGENT_SPAN_ORIGIN,\n ...getAgentAttributes(thisArg),\n },\n },\n async () => {\n await setAgentConversationId(thisArg);\n return Reflect.apply(target, thisArg, args);\n },\n );\n },\n });\n}\n\n/** Extracts the RPC method name from a WebSocket message, mirroring the SDK's `isRPCRequest`. */\nfunction extractCallableMethod(message: unknown): string | undefined {\n const text =\n typeof message === 'string'\n ? message\n : message instanceof ArrayBuffer\n ? new TextDecoder().decode(message)\n : undefined;\n\n if (!text) {\n return undefined;\n }\n\n try {\n const parsed = JSON.parse(text) as { type?: unknown; method?: unknown; args?: unknown };\n if (parsed.type === 'rpc' && typeof parsed.method === 'string' && Array.isArray(parsed.args)) {\n return parsed.method;\n }\n } catch {\n // Not JSON, or not an RPC request — no span.\n }\n\n return undefined;\n}\n"],"names":["DEBUG_BUILD","debug","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","AGENT_SPAN_ORIGIN","getAgentAttributes","setAgentConversationId"],"mappings":";;;;;;AAeO,SAAS,2BAA2B,GAAA,EAA2B;AACpE,EAAA,MAAM,WAAW,GAAA,CAAI,SAAA;AACrB,EAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AAClC,IAAAA,sBAAA,IAAeC,UAAA,CAAM,IAAI,wFAAmF,CAAA;AAC5G,IAAA;AAAA,EACF;AAEA,EAAA,GAAA,CAAI,SAAA,GAAY,IAAI,KAAA,CAAM,QAAA,EAAU;AAAA,IAClC,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAyB,IAAA,EAA0B;AAC/D,MAAA,MAAM,MAAA,GAAS,qBAAA,CAAsB,IAAA,CAAK,CAAC,CAAC,CAAA;AAE5C,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAS,IAAI,CAAA;AAAA,MAC5C;AAEA,MAAA,OAAOC,cAAA;AAAA,QACL;AAAA,UACE,IAAA,EAAM,MAAA;AAAA,UACN,UAAA,EAAY;AAAA,YACV,CAACC,iCAA4B,GAAG,KAAA;AAAA,YAChC,CAACC,qCAAgC,GAAGC,uBAAA;AAAA,YACpC,GAAGC,yBAAmB,OAAO;AAAA;AAC/B,SACF;AAAA,QACA,YAAY;AACV,UAAA,MAAMC,6BAAuB,OAAO,CAAA;AACpC,UAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAS,IAAI,CAAA;AAAA,QAC5C;AAAA,OACF;AAAA,IACF;AAAA,GACD,CAAA;AACH;AAGA,SAAS,sBAAsB,OAAA,EAAsC;AACnE,EAAA,MAAM,IAAA,GACJ,OAAO,OAAA,KAAY,QAAA,GACf,OAAA,GACA,OAAA,YAAmB,WAAA,GACjB,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,OAAO,CAAA,GAChC,MAAA;AAER,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,IAAA,IAAI,MAAA,CAAO,IAAA,KAAS,KAAA,IAAS,OAAO,MAAA,CAAO,MAAA,KAAW,QAAA,IAAY,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAA,EAAG;AAC5F,MAAA,OAAO,MAAA,CAAO,MAAA;AAAA,IAChB;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AAEA,EAAA,OAAO,MAAA;AACT;;;;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const types = require('./types.js');
|
|
4
|
+
|
|
5
|
+
function instrumentAgentRequestConversation(obj) {
|
|
6
|
+
const original = obj.onRequest;
|
|
7
|
+
if (typeof original !== "function") {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
obj.onRequest = new Proxy(original, {
|
|
11
|
+
async apply(target, thisArg, args) {
|
|
12
|
+
await types.setAgentConversationId(thisArg);
|
|
13
|
+
return Reflect.apply(target, thisArg, args);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.instrumentAgentRequestConversation = instrumentAgentRequestConversation;
|
|
19
|
+
//# sourceMappingURL=instrumentAgentRequestConversation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentAgentRequestConversation.js","sources":["../../../../src/instrumentations/agents/instrumentAgentRequestConversation.ts"],"sourcesContent":["import { type AgentInternals, setAgentConversationId } from './types';\n\n/**\n * Correlates the AI spans of an HTTP-driven agent turn with a conversation id on the active scope.\n *\n * `onRequest` is the third unit of agent work, alongside chat turns and `@callable()` RPC: the\n * `agents` router sends every non-WebSocket request to it, which is how REST endpoints and webhooks\n * reach an agent.\n *\n * `agents` installs `onRequest` as an own property in the `Agent` constructor (as it does\n * `onMessage`), and we instrument after construction, so wrapping the own property is what the\n * router ends up calling. That own property is already `async`, and partyserver's `fetch` awaits it,\n * so returning a promise from this wrapper — to get the conversation id onto the scope before the\n * request handler creates any span — keeps the existing contract.\n */\nexport function instrumentAgentRequestConversation(obj: AgentInternals): void {\n const original = obj.onRequest;\n\n if (typeof original !== 'function') {\n return;\n }\n\n obj.onRequest = new Proxy(original, {\n async apply(target, thisArg: AgentInternals, args: unknown[]): Promise<unknown> {\n await setAgentConversationId(thisArg);\n\n return Reflect.apply(target, thisArg, args);\n },\n });\n}\n"],"names":["setAgentConversationId"],"mappings":";;;;AAeO,SAAS,mCAAmC,GAAA,EAA2B;AAC5E,EAAA,MAAM,WAAW,GAAA,CAAI,SAAA;AAErB,EAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AAClC,IAAA;AAAA,EACF;AAEA,EAAA,GAAA,CAAI,SAAA,GAAY,IAAI,KAAA,CAAM,QAAA,EAAU;AAAA,IAClC,MAAM,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAyB,IAAA,EAAmC;AAC9E,MAAA,MAAMA,6BAAuB,OAAO,CAAA;AAEpC,MAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAS,IAAI,CAAA;AAAA,IAC5C;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const types = require('./types.js');
|
|
5
|
+
|
|
6
|
+
function instrumentChatAgentConversation(obj) {
|
|
7
|
+
const originalEmit = obj._emit;
|
|
8
|
+
if (typeof originalEmit === "function") {
|
|
9
|
+
obj._emit = new Proxy(originalEmit, {
|
|
10
|
+
apply(target, thisArg, args) {
|
|
11
|
+
if (args[0] === "message:clear") {
|
|
12
|
+
types.storeAgentConversationId(thisArg, core.uuid4());
|
|
13
|
+
}
|
|
14
|
+
return Reflect.apply(target, thisArg, args);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const original = obj.onChatMessage;
|
|
19
|
+
if (typeof original !== "function") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
obj.onChatMessage = new Proxy(original, {
|
|
23
|
+
async apply(target, thisArg, args) {
|
|
24
|
+
await types.setAgentConversationId(thisArg);
|
|
25
|
+
return Reflect.apply(target, thisArg, args);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
exports.instrumentChatAgentConversation = instrumentChatAgentConversation;
|
|
31
|
+
//# sourceMappingURL=instrumentChatAgentConversation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentChatAgentConversation.js","sources":["../../../../src/instrumentations/agents/instrumentChatAgentConversation.ts"],"sourcesContent":["import { uuid4 } from '@sentry/core';\nimport { type AgentInternals, setAgentConversationId, storeAgentConversationId } from './types';\n\n/**\n * For chat agents (`AIChatAgent` from `@cloudflare/ai-chat`), correlates each chat turn's AI spans\n * with a conversation id on the active scope.\n *\n * In the Agents model one agent instance is one long-lived conversation, so the conversation id is\n * minted once per instance and persisted to Durable Object storage, which is what carries it across\n * hibernation (that destroys the in-memory instance). When the user clears the chat, they expect a\n * fresh conversation — but recreating the Durable Object for that would also drop the MCP/OAuth\n * state stored per instance (GitHub/Sentry sign-in). To get a fresh conversation id *without*\n * losing that state, we rotate the persisted id when the SDK reports a cleared chat.\n *\n * The id itself is not attached to spans here — `setAgentConversationId` resolves it, and the SDK's\n * `conversationIdIntegration` picks it off the scope at `spanStart` to stamp\n * `gen_ai.conversation.id` onto the AI spans created inside the turn (e.g. by the Workers AI\n * instrumentation), which correlates a turn's model and tool calls.\n *\n * Plain (non-chat) `Agent`s do not define `onChatMessage`, so they are skipped here — their unit\n * of work is the callable RPC method, where `instrumentAgentCallableRpc` sets the conversation id\n * instead.\n */\nexport function instrumentChatAgentConversation(obj: AgentInternals): void {\n // Rotate the conversation id when the chat is cleared. `_emit` is the central choke-point through\n // which all `agents:*` observability events are published, and it already exists on the base\n // `Agent` class — so this hook composes with the RPC instrumentation, which keys off the same\n // surface. We shadow it with an own property so the original stays reachable on the prototype.\n const originalEmit = obj._emit;\n\n if (typeof originalEmit === 'function') {\n obj._emit = new Proxy(originalEmit, {\n apply(target, thisArg: AgentInternals, args: [string, Record<string, unknown>?]) {\n if (args[0] === 'message:clear') {\n storeAgentConversationId(thisArg, uuid4());\n }\n\n return Reflect.apply(target, thisArg, args);\n },\n });\n }\n\n const original = obj.onChatMessage;\n\n if (typeof original !== 'function') {\n return;\n }\n\n obj.onChatMessage = new Proxy(original, {\n async apply(target, thisArg: AgentInternals, args: unknown[]): Promise<unknown> {\n await setAgentConversationId(thisArg);\n\n return Reflect.apply(target, thisArg, args);\n },\n });\n}\n"],"names":["storeAgentConversationId","uuid4","setAgentConversationId"],"mappings":";;;;;AAuBO,SAAS,gCAAgC,GAAA,EAA2B;AAKzE,EAAA,MAAM,eAAe,GAAA,CAAI,KAAA;AAEzB,EAAA,IAAI,OAAO,iBAAiB,UAAA,EAAY;AACtC,IAAA,GAAA,CAAI,KAAA,GAAQ,IAAI,KAAA,CAAM,YAAA,EAAc;AAAA,MAClC,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAyB,IAAA,EAA0C;AAC/E,QAAA,IAAI,IAAA,CAAK,CAAC,CAAA,KAAM,eAAA,EAAiB;AAC/B,UAAAA,8BAAA,CAAyB,OAAA,EAASC,YAAO,CAAA;AAAA,QAC3C;AAEA,QAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAS,IAAI,CAAA;AAAA,MAC5C;AAAA,KACD,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,WAAW,GAAA,CAAI,aAAA;AAErB,EAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AAClC,IAAA;AAAA,EACF;AAEA,EAAA,GAAA,CAAI,aAAA,GAAgB,IAAI,KAAA,CAAM,QAAA,EAAU;AAAA,IACtC,MAAM,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAyB,IAAA,EAAmC;AAC9E,MAAA,MAAMC,6BAAuB,OAAO,CAAA;AAEpC,MAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,MAAA,EAAQ,OAAA,EAAS,IAAI,CAAA;AAAA,IAC5C;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const debugBuild = require('../../debug-build.js');
|
|
5
|
+
|
|
6
|
+
const GEN_AI_AGENT_NAME_ATTRIBUTE = "gen_ai.agent.name";
|
|
7
|
+
const AGENT_SPAN_ORIGIN = "auto.faas.cloudflare.agents";
|
|
8
|
+
const AGENT_CONVERSATION_ID_STORAGE_KEY = "__SENTRY_AGENT_CONVERSATION_ID__";
|
|
9
|
+
const AGENT_CONVERSATION_ID_SYMBOL = /* @__PURE__ */ Symbol("sentryAgentConversationId");
|
|
10
|
+
const AGENT_APPLIED_CONVERSATION_ID_SYMBOL = /* @__PURE__ */ Symbol("sentryAgentAppliedConversationId");
|
|
11
|
+
function getAgentAttributes(instance) {
|
|
12
|
+
const attributes = {};
|
|
13
|
+
const agentClass = instance._ParentClass?.name;
|
|
14
|
+
if (typeof agentClass === "string" && agentClass) {
|
|
15
|
+
attributes[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentClass;
|
|
16
|
+
}
|
|
17
|
+
return attributes;
|
|
18
|
+
}
|
|
19
|
+
function storeAgentConversationId(instance, conversationId) {
|
|
20
|
+
instance[AGENT_CONVERSATION_ID_SYMBOL] = conversationId;
|
|
21
|
+
const storage = resolveStorage(instance);
|
|
22
|
+
if (!storage) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
storage.put(AGENT_CONVERSATION_ID_STORAGE_KEY, conversationId).catch((error) => debugBuild.DEBUG_BUILD && core.debug.log("[Sentry] Failed to persist agent conversation id", error));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
debugBuild.DEBUG_BUILD && core.debug.log("[Sentry] Failed to persist agent conversation id", error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function setAgentConversationId(instance) {
|
|
32
|
+
const isolationScope = core.getIsolationScope();
|
|
33
|
+
const existing = isolationScope.getScopeData().conversationId ?? core.getCurrentScope().getScopeData().conversationId;
|
|
34
|
+
if (existing && existing !== instance[AGENT_APPLIED_CONVERSATION_ID_SYMBOL]) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const conversationId = instance[AGENT_CONVERSATION_ID_SYMBOL] ?? await loadAgentConversationId(instance);
|
|
38
|
+
instance[AGENT_APPLIED_CONVERSATION_ID_SYMBOL] = conversationId;
|
|
39
|
+
isolationScope.setConversationId(conversationId);
|
|
40
|
+
}
|
|
41
|
+
async function loadAgentConversationId(instance) {
|
|
42
|
+
let stored;
|
|
43
|
+
let readFailed = false;
|
|
44
|
+
try {
|
|
45
|
+
stored = await resolveStorage(instance)?.get(AGENT_CONVERSATION_ID_STORAGE_KEY);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
readFailed = true;
|
|
48
|
+
debugBuild.DEBUG_BUILD && core.debug.log("[Sentry] Failed to read agent conversation id from storage", error);
|
|
49
|
+
}
|
|
50
|
+
const cached = instance[AGENT_CONVERSATION_ID_SYMBOL];
|
|
51
|
+
if (cached !== void 0) {
|
|
52
|
+
return cached;
|
|
53
|
+
}
|
|
54
|
+
if (typeof stored === "string" && stored) {
|
|
55
|
+
instance[AGENT_CONVERSATION_ID_SYMBOL] = stored;
|
|
56
|
+
return stored;
|
|
57
|
+
}
|
|
58
|
+
const conversationId = core.uuid4();
|
|
59
|
+
if (readFailed) {
|
|
60
|
+
instance[AGENT_CONVERSATION_ID_SYMBOL] = conversationId;
|
|
61
|
+
} else {
|
|
62
|
+
storeAgentConversationId(instance, conversationId);
|
|
63
|
+
}
|
|
64
|
+
return conversationId;
|
|
65
|
+
}
|
|
66
|
+
function resolveStorage(instance) {
|
|
67
|
+
return instance.ctx?.originalStorage ?? instance.ctx?.storage;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
exports.AGENT_APPLIED_CONVERSATION_ID_SYMBOL = AGENT_APPLIED_CONVERSATION_ID_SYMBOL;
|
|
71
|
+
exports.AGENT_CONVERSATION_ID_STORAGE_KEY = AGENT_CONVERSATION_ID_STORAGE_KEY;
|
|
72
|
+
exports.AGENT_CONVERSATION_ID_SYMBOL = AGENT_CONVERSATION_ID_SYMBOL;
|
|
73
|
+
exports.AGENT_SPAN_ORIGIN = AGENT_SPAN_ORIGIN;
|
|
74
|
+
exports.getAgentAttributes = getAgentAttributes;
|
|
75
|
+
exports.setAgentConversationId = setAgentConversationId;
|
|
76
|
+
exports.storeAgentConversationId = storeAgentConversationId;
|
|
77
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../src/instrumentations/agents/types.ts"],"sourcesContent":["import type { DurableObjectStorage } from '@cloudflare/workers-types';\nimport { debug, getCurrentScope, getIsolationScope, uuid4 } from '@sentry/core';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport type { InstrumentedDurableObjectState } from '../../wrapMethodWithSentry';\n\n// v10 predates `@sentry/conventions` and doesn't expose this from `@sentry/core`, so keep the\n// standard semantic attribute name locally.\nconst GEN_AI_AGENT_NAME_ATTRIBUTE = 'gen_ai.agent.name';\n\nexport const AGENT_SPAN_ORIGIN = 'auto.faas.cloudflare.agents';\n\n/** DO storage key under which the conversation id is persisted so it survives hibernation. */\nexport const AGENT_CONVERSATION_ID_STORAGE_KEY = '__SENTRY_AGENT_CONVERSATION_ID__';\n\n/**\n * Instance keys for our conversation-id bookkeeping, keyed by symbol so the state stays invisible\n * to anything enumerating the user-owned agent instance (`Object.keys`, `JSON.stringify`, spread).\n * Exported because the exported `AgentInternals` interface references them.\n */\nexport const AGENT_CONVERSATION_ID_SYMBOL: unique symbol = Symbol('sentryAgentConversationId');\nexport const AGENT_APPLIED_CONVERSATION_ID_SYMBOL: unique symbol = Symbol('sentryAgentAppliedConversationId');\n\n/**\n * The subset of the `agents` `Agent` instance internals that we instrument. These are runtime\n * implementation details of the `agents` package (v0.13.x) rather than part of its public type\n * surface, so every access is guarded and wrapping degrades gracefully if a name changes upstream.\n */\nexport interface AgentInternals {\n /** Central choke-point through which all `agents:*` observability events are published. */\n _emit?: (type: string, payload?: Record<string, unknown>) => void;\n /** WebSocket message handler; dispatches `@callable()` RPC requests. */\n onMessage?: (...args: unknown[]) => unknown;\n /**\n * Chat-turn handler. Only `AIChatAgent` (from `@cloudflare/ai-chat`) defines this; a plain `Agent`\n * does not, so its presence discriminates a chat agent.\n */\n onChatMessage?: (...args: unknown[]) => unknown;\n /** HTTP request handler; the router sends every non-WebSocket request here. */\n onRequest?: (...args: unknown[]) => unknown;\n /** The user's Agent class (used by the SDK for the observability event `agent` field). */\n _ParentClass?: { name?: string };\n /** The Agent instance name, reported as the `cloudflare.agent.name` span attribute. */\n name?: string;\n /**\n * The Durable Object state of the instance. Present on every real Agent; guarded like the other\n * internals so partially-mocked instances keep working.\n */\n ctx?: InstrumentedDurableObjectState;\n /**\n * The current conversation id for this agent instance. It is cached in memory after being loaded\n * from Durable Object storage and updated when the conversation rotates. `undefined` means storage\n * has not been read yet.\n */\n [AGENT_CONVERSATION_ID_SYMBOL]?: string;\n /**\n * The conversation id this instrumentation most recently applied to the isolation scope. It lets\n * subsequent units of work distinguish an SDK-applied id, which may be replaced after rotation,\n * from an id explicitly set by the user, which must be preserved.\n */\n [AGENT_APPLIED_CONVERSATION_ID_SYMBOL]?: string;\n}\n\n/** Reads best-effort agent identity attributes from the instance, tolerating missing internals. */\nexport function getAgentAttributes(instance: AgentInternals): Record<string, string> {\n const attributes: Record<string, string> = {};\n\n const agentClass = instance._ParentClass?.name;\n if (typeof agentClass === 'string' && agentClass) {\n attributes[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentClass;\n }\n\n return attributes;\n}\n\n/**\n * Persists the conversation id to Durable Object storage, so it survives hibernation, and updates\n * the in-memory cache synchronously so the current wake is immediately consistent even if the\n * write fails. Uses the async storage API, which exists on KV- and SQLite-backed DOs alike.\n * The write is fire-and-forget: DO storage writes are tracked by the runtime and land without\n * awaiting (`waitUntil` is a no-op in Durable Objects), and the catch keeps a rejection from\n * surfacing as unhandled — a failed write just means the next wake starts a new conversation,\n * which must never throw into user code. Uses the original uninstrumented storage so internal\n * bookkeeping doesn't create spans.\n */\nexport function storeAgentConversationId(instance: AgentInternals, conversationId: string): void {\n instance[AGENT_CONVERSATION_ID_SYMBOL] = conversationId;\n\n const storage = resolveStorage(instance);\n if (!storage) {\n return;\n }\n\n try {\n storage\n .put(AGENT_CONVERSATION_ID_STORAGE_KEY, conversationId)\n .catch(error => DEBUG_BUILD && debug.log('[Sentry] Failed to persist agent conversation id', error));\n } catch (error) {\n DEBUG_BUILD && debug.log('[Sentry] Failed to persist agent conversation id', error);\n }\n}\n\n/**\n * Sets the agent instance's conversation id on the active scope for the duration of the\n * surrounding unit of work (chat turn, callable RPC call, HTTP request). In the Agents model one\n * instance is one long-lived conversation, so the id is minted once per instance and persisted to\n * DO storage — for chat and plain agents alike, since plain agents run LLM calls too (e.g. inside\n * `@callable()` methods). The instance `name` is deliberately not used: it is caller-chosen and can\n * be a stable, guessable, or shared value, whereas a conversation id should identify exactly one\n * conversation. Clearing the chat rotates it (see `storeAgentConversationId`) so subsequent LLM\n * calls group under the fresh conversation.\n *\n * Every handler wrapper awaits this before invoking the original, so the id is on the scope before\n * the unit of work — and any `gen_ai` span it creates — starts. Only the first unit of work per wake\n * pays the storage read; the rest resolve from the in-memory cache. Awaiting is safe on all three\n * paths because the `agents` `Agent` constructor already replaces `onMessage` and `onRequest` with\n * `async` wrappers of its own, and `onChatMessage` is async by contract, so every caller upstream\n * already handles a promise.\n *\n * An id the user set explicitly outranks this inferred one, in either order: a `setConversationId()`\n * call that already happened is detected here and left alone, and one made inside the handler lands\n * on the same scope afterwards and therefore wins. That is why the write targets the isolation scope\n * — it is the scope the public `Sentry.setConversationId()` writes to, and `conversationIdIntegration`\n * prefers the current scope over it, so writing there would make a user's call unoverridable.\n *\n * `conversationIdIntegration` reads the id off the scope at `spanStart` and stamps\n * `gen_ai.conversation.id` onto AI spans created within the unit of work, correlating its model and\n * tool calls.\n */\nexport async function setAgentConversationId(instance: AgentInternals): Promise<void> {\n const isolationScope = getIsolationScope();\n const existing = isolationScope.getScopeData().conversationId ?? getCurrentScope().getScopeData().conversationId;\n\n // An id that isn't the one we put there ourselves came from the user — never override it.\n if (existing && existing !== instance[AGENT_APPLIED_CONVERSATION_ID_SYMBOL]) {\n return;\n }\n\n const conversationId = instance[AGENT_CONVERSATION_ID_SYMBOL] ?? (await loadAgentConversationId(instance));\n\n instance[AGENT_APPLIED_CONVERSATION_ID_SYMBOL] = conversationId;\n isolationScope.setConversationId(conversationId);\n}\n\n/**\n * Reads the persisted id into the instance cache, minting and persisting a fresh one when storage\n * holds none, so subsequent units of work resolve from memory. The async `get` exists on KV- and\n * SQLite-backed DOs alike, so this one path covers both.\n *\n * Anything already cached by the time the read lands wins over the read: that covers a rotation\n * (cleared chat) racing the read, and a second concurrent unit of work that must not mint and\n * persist a competing id for the same conversation.\n */\nasync function loadAgentConversationId(instance: AgentInternals): Promise<string> {\n let stored: unknown;\n let readFailed = false;\n\n try {\n stored = await resolveStorage(instance)?.get<string>(AGENT_CONVERSATION_ID_STORAGE_KEY);\n } catch (error) {\n readFailed = true;\n DEBUG_BUILD && debug.log('[Sentry] Failed to read agent conversation id from storage', error);\n }\n\n const cached = instance[AGENT_CONVERSATION_ID_SYMBOL];\n\n if (cached !== undefined) {\n return cached;\n }\n\n if (typeof stored === 'string' && stored) {\n instance[AGENT_CONVERSATION_ID_SYMBOL] = stored;\n\n return stored;\n }\n\n const conversationId = uuid4();\n\n if (readFailed) {\n // Cache without persisting: a read that failed may still have an id behind it, and overwriting\n // it would split the conversation for good instead of just for this wake.\n instance[AGENT_CONVERSATION_ID_SYMBOL] = conversationId;\n } else {\n storeAgentConversationId(instance, conversationId);\n }\n\n return conversationId;\n}\n\n/**\n * Resolves the uninstrumented DO storage for internal reads/writes, so they don't create spans of\n * their own. Falls back to the instance's regular storage when the uninstrumented one isn't\n * exposed (e.g. when `instrumentCloudflareAgent` is used directly, without `instrumentContext`).\n */\nfunction resolveStorage(instance: AgentInternals): DurableObjectStorage | undefined {\n return instance.ctx?.originalStorage ?? instance.ctx?.storage;\n}\n"],"names":["DEBUG_BUILD","debug","getIsolationScope","getCurrentScope","uuid4"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const core = require('@sentry/core');
|
|
4
|
+
const internalStorageKey = require('../utils/internalStorageKey.js');
|
|
4
5
|
const traceLinks = require('../utils/traceLinks.js');
|
|
5
6
|
const instrumentDurableObjectSyncKvStorage = require('./instrumentDurableObjectSyncKvStorage.js');
|
|
6
7
|
const instrumentSqlStorage = require('./instrumentSqlStorage.js');
|
|
@@ -24,6 +25,11 @@ function instrumentDurableObjectStorage(storage, waitUntil) {
|
|
|
24
25
|
return original.bind(target);
|
|
25
26
|
}
|
|
26
27
|
return function(...args) {
|
|
28
|
+
const allowlist = core.getClient()?.getOptions()?.durableObjectStorageSpanAllowlist;
|
|
29
|
+
const keys = internalStorageKey.getStorageKeys(methodName, args);
|
|
30
|
+
if (keys && keys.length > 0 && keys.every((key) => internalStorageKey.targetsCloudflareInternalKey(key, allowlist))) {
|
|
31
|
+
return original.apply(target, args);
|
|
32
|
+
}
|
|
27
33
|
return core.startSpan(
|
|
28
34
|
{
|
|
29
35
|
// Use underscore naming to match Cloudflare's native instrumentation (e.g., "durable_object_storage_get")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentDurableObjectStorage.js","sources":["../../../src/instrumentations/instrumentDurableObjectStorage.ts"],"sourcesContent":["import type { DurableObjectStorage, SyncKvStorage, SqlStorage } from '@cloudflare/workers-types';\nimport { isThenable, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core';\nimport { storeSpanContext } from '../utils/traceLinks';\nimport { instrumentDurableObjectSyncKvStorage } from './instrumentDurableObjectSyncKvStorage';\nimport { instrumentSqlStorage } from './instrumentSqlStorage';\n\nconst STORAGE_METHODS_TO_INSTRUMENT = ['get', 'put', 'delete', 'list', 'setAlarm', 'getAlarm', 'deleteAlarm'] as const;\n\ntype StorageMethod = (typeof STORAGE_METHODS_TO_INSTRUMENT)[number];\n\ntype WaitUntil = (promise: Promise<unknown>) => void;\n\n/**\n * Instruments DurableObjectStorage methods with Sentry spans.\n *\n * Wraps the following async methods:\n * - get, put, delete, list (KV API)\n * - setAlarm, getAlarm, deleteAlarm (Alarm API)\n *\n * When setAlarm is called, it also stores the current span context so that when\n * the alarm fires later, it can link back to the trace that called setAlarm.\n *\n * @param storage - The DurableObjectStorage instance to instrument\n * @param waitUntil - Optional waitUntil function to defer span context storage\n * @returns An instrumented DurableObjectStorage instance\n */\nexport function instrumentDurableObjectStorage(\n storage: DurableObjectStorage,\n waitUntil?: WaitUntil,\n): DurableObjectStorage {\n return new Proxy(storage, {\n get(target, prop, _receiver) {\n // Use `target` as the receiver instead of the proxy (`_receiver`).\n // Native workerd getters (e.g., `storage.sql`) validate `this` via\n // internal slots. Passing the proxy as receiver breaks that check,\n // causing \"Illegal invocation: function called with incorrect `this`\n // reference\" errors.\n const original = Reflect.get(target, prop, target);\n\n if (prop === 'kv' && original != null && 'get' in original && 'put' in original) {\n return instrumentDurableObjectSyncKvStorage(original as SyncKvStorage);\n }\n\n if (prop === 'sql' && original != null && 'databaseSize' in original && 'exec' in original) {\n return instrumentSqlStorage(original as SqlStorage);\n }\n\n if (typeof original !== 'function') {\n return original;\n }\n\n const methodName = prop as string;\n if (!STORAGE_METHODS_TO_INSTRUMENT.includes(methodName as StorageMethod)) {\n return (original as (...args: unknown[]) => unknown).bind(target);\n }\n\n return function (this: unknown, ...args: unknown[]) {\n return startSpan(\n {\n // Use underscore naming to match Cloudflare's native instrumentation (e.g., \"durable_object_storage_get\")\n name: `durable_object_storage_${methodName}`,\n op: 'db',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.cloudflare.durable_object',\n 'db.system.name': 'cloudflare.durable_object.storage',\n 'db.operation.name': methodName,\n },\n },\n () => {\n const teardown = async (): Promise<void> => {\n // When setAlarm is called, store the current span context so that when the alarm\n // fires later, it can link back to the trace that called setAlarm.\n // We use the original (uninstrumented) storage (target) to avoid creating a span\n // for this internal operation. The storage is deferred via waitUntil to not block.\n if (methodName === 'setAlarm') {\n storeSpanContext(target, 'alarm');\n }\n };\n\n const result = (original as (...args: unknown[]) => unknown).apply(target, args);\n\n if (!isThenable(result)) {\n waitUntil?.(teardown());\n\n return result;\n }\n\n return result.then(\n res => {\n waitUntil?.(teardown());\n return res;\n },\n e => {\n throw e;\n },\n );\n },\n );\n };\n },\n });\n}\n"],"names":["instrumentDurableObjectSyncKvStorage","instrumentSqlStorage","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","storeSpanContext","isThenable"],"mappings":"
|
|
1
|
+
{"version":3,"file":"instrumentDurableObjectStorage.js","sources":["../../../src/instrumentations/instrumentDurableObjectStorage.ts"],"sourcesContent":["import type { DurableObjectStorage, SyncKvStorage, SqlStorage } from '@cloudflare/workers-types';\nimport { getClient, isThenable, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core';\nimport type { CloudflareClientOptions } from '../client';\nimport { getStorageKeys, targetsCloudflareInternalKey } from '../utils/internalStorageKey';\nimport { storeSpanContext } from '../utils/traceLinks';\nimport { instrumentDurableObjectSyncKvStorage } from './instrumentDurableObjectSyncKvStorage';\nimport { instrumentSqlStorage } from './instrumentSqlStorage';\n\nconst STORAGE_METHODS_TO_INSTRUMENT = ['get', 'put', 'delete', 'list', 'setAlarm', 'getAlarm', 'deleteAlarm'] as const;\n\ntype StorageMethod = (typeof STORAGE_METHODS_TO_INSTRUMENT)[number];\n\ntype WaitUntil = (promise: Promise<unknown>) => void;\n\n/**\n * Instruments DurableObjectStorage methods with Sentry spans.\n *\n * Wraps the following async methods:\n * - get, put, delete, list (KV API)\n * - setAlarm, getAlarm, deleteAlarm (Alarm API)\n *\n * When setAlarm is called, it also stores the current span context so that when\n * the alarm fires later, it can link back to the trace that called setAlarm.\n *\n * @param storage - The DurableObjectStorage instance to instrument\n * @param waitUntil - Optional waitUntil function to defer span context storage\n * @returns An instrumented DurableObjectStorage instance\n */\nexport function instrumentDurableObjectStorage(\n storage: DurableObjectStorage,\n waitUntil?: WaitUntil,\n): DurableObjectStorage {\n return new Proxy(storage, {\n get(target, prop, _receiver) {\n // Use `target` as the receiver instead of the proxy (`_receiver`).\n // Native workerd getters (e.g., `storage.sql`) validate `this` via\n // internal slots. Passing the proxy as receiver breaks that check,\n // causing \"Illegal invocation: function called with incorrect `this`\n // reference\" errors.\n const original = Reflect.get(target, prop, target);\n\n if (prop === 'kv' && original != null && 'get' in original && 'put' in original) {\n return instrumentDurableObjectSyncKvStorage(original as SyncKvStorage);\n }\n\n if (prop === 'sql' && original != null && 'databaseSize' in original && 'exec' in original) {\n return instrumentSqlStorage(original as SqlStorage);\n }\n\n if (typeof original !== 'function') {\n return original;\n }\n\n const methodName = prop as string;\n if (!STORAGE_METHODS_TO_INSTRUMENT.includes(methodName as StorageMethod)) {\n return (original as (...args: unknown[]) => unknown).bind(target);\n }\n\n return function (this: unknown, ...args: unknown[]) {\n // KV entries managed by the DO framework itself (agents/partyserver state) are bookkeeping\n // rather than user work — skip the span, mirroring how `cf_` SQL tables are treated.\n const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)\n ?.durableObjectStorageSpanAllowlist;\n const keys = getStorageKeys(methodName, args);\n if (keys && keys.length > 0 && keys.every(key => targetsCloudflareInternalKey(key, allowlist))) {\n return (original as (...a: unknown[]) => unknown).apply(target, args);\n }\n\n return startSpan(\n {\n // Use underscore naming to match Cloudflare's native instrumentation (e.g., \"durable_object_storage_get\")\n name: `durable_object_storage_${methodName}`,\n op: 'db',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.cloudflare.durable_object',\n 'db.system.name': 'cloudflare.durable_object.storage',\n 'db.operation.name': methodName,\n },\n },\n () => {\n const teardown = async (): Promise<void> => {\n // When setAlarm is called, store the current span context so that when the alarm\n // fires later, it can link back to the trace that called setAlarm.\n // We use the original (uninstrumented) storage (target) to avoid creating a span\n // for this internal operation. The storage is deferred via waitUntil to not block.\n if (methodName === 'setAlarm') {\n storeSpanContext(target, 'alarm');\n }\n };\n\n const result = (original as (...args: unknown[]) => unknown).apply(target, args);\n\n if (!isThenable(result)) {\n waitUntil?.(teardown());\n\n return result;\n }\n\n return result.then(\n res => {\n waitUntil?.(teardown());\n return res;\n },\n e => {\n throw e;\n },\n );\n },\n );\n };\n },\n });\n}\n"],"names":["instrumentDurableObjectSyncKvStorage","instrumentSqlStorage","getClient","getStorageKeys","targetsCloudflareInternalKey","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","storeSpanContext","isThenable"],"mappings":";;;;;;;;AAQA,MAAM,6BAAA,GAAgC,CAAC,KAAA,EAAO,KAAA,EAAO,UAAU,MAAA,EAAQ,UAAA,EAAY,YAAY,aAAa,CAAA;AAoBrG,SAAS,8BAAA,CACd,SACA,SAAA,EACsB;AACtB,EAAA,OAAO,IAAI,MAAM,OAAA,EAAS;AAAA,IACxB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAW;AAM3B,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,MAAM,MAAM,CAAA;AAEjD,MAAA,IAAI,SAAS,IAAA,IAAQ,QAAA,IAAY,QAAQ,KAAA,IAAS,QAAA,IAAY,SAAS,QAAA,EAAU;AAC/E,QAAA,OAAOA,0EAAqC,QAAyB,CAAA;AAAA,MACvE;AAEA,MAAA,IAAI,SAAS,KAAA,IAAS,QAAA,IAAY,QAAQ,cAAA,IAAkB,QAAA,IAAY,UAAU,QAAA,EAAU;AAC1F,QAAA,OAAOC,0CAAqB,QAAsB,CAAA;AAAA,MACpD;AAEA,MAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AAClC,QAAA,OAAO,QAAA;AAAA,MACT;AAEA,MAAA,MAAM,UAAA,GAAa,IAAA;AACnB,MAAA,IAAI,CAAC,6BAAA,CAA8B,QAAA,CAAS,UAA2B,CAAA,EAAG;AACxE,QAAA,OAAQ,QAAA,CAA6C,KAAK,MAAM,CAAA;AAAA,MAClE;AAEA,MAAA,OAAO,YAA4B,IAAA,EAAiB;AAGlD,QAAA,MAAM,SAAA,GAAaC,cAAA,EAAU,EAAG,UAAA,EAAW,EACvC,iCAAA;AACJ,QAAA,MAAM,IAAA,GAAOC,iCAAA,CAAe,UAAA,EAAY,IAAI,CAAA;AAC5C,QAAA,IAAI,IAAA,IAAQ,IAAA,CAAK,MAAA,GAAS,CAAA,IAAK,IAAA,CAAK,KAAA,CAAM,CAAA,GAAA,KAAOC,+CAAA,CAA6B,GAAA,EAAK,SAAS,CAAC,CAAA,EAAG;AAC9F,UAAA,OAAQ,QAAA,CAA0C,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAA;AAAA,QACtE;AAEA,QAAA,OAAOC,cAAA;AAAA,UACL;AAAA;AAAA,YAEE,IAAA,EAAM,0BAA0B,UAAU,CAAA,CAAA;AAAA,YAC1C,EAAA,EAAI,IAAA;AAAA,YACJ,UAAA,EAAY;AAAA,cACV,CAACC,qCAAgC,GAAG,mCAAA;AAAA,cACpC,gBAAA,EAAkB,mCAAA;AAAA,cAClB,mBAAA,EAAqB;AAAA;AACvB,WACF;AAAA,UACA,MAAM;AACJ,YAAA,MAAM,WAAW,YAA2B;AAK1C,cAAA,IAAI,eAAe,UAAA,EAAY;AAC7B,gBAAAC,2BAAA,CAAiB,QAAQ,OAAO,CAAA;AAAA,cAClC;AAAA,YACF,CAAA;AAEA,YAAA,MAAM,MAAA,GAAU,QAAA,CAA6C,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAA;AAE/E,YAAA,IAAI,CAACC,eAAA,CAAW,MAAM,CAAA,EAAG;AACvB,cAAA,SAAA,GAAY,UAAU,CAAA;AAEtB,cAAA,OAAO,MAAA;AAAA,YACT;AAEA,YAAA,OAAO,MAAA,CAAO,IAAA;AAAA,cACZ,CAAA,GAAA,KAAO;AACL,gBAAA,SAAA,GAAY,UAAU,CAAA;AACtB,gBAAA,OAAO,GAAA;AAAA,cACT,CAAA;AAAA,cACA,CAAA,CAAA,KAAK;AACH,gBAAA,MAAM,CAAA;AAAA,cACR;AAAA,aACF;AAAA,UACF;AAAA,SACF;AAAA,MACF,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -15,7 +15,7 @@ function instrumentSqlStorage(sql) {
|
|
|
15
15
|
const sanitizedQuery = core._INTERNAL_sanitizeSqlQuery(query);
|
|
16
16
|
const querySummary = core._INTERNAL_getSqlQuerySummary(sanitizedQuery);
|
|
17
17
|
const allowlist = core.getClient()?.getOptions()?.durableObjectSqlSpanAllowlist;
|
|
18
|
-
if (internalSqlQuery.targetsCloudflareInternalTable(querySummary, allowlist)) {
|
|
18
|
+
if (internalSqlQuery.targetsCloudflareInternalTable(querySummary, allowlist, sanitizedQuery)) {
|
|
19
19
|
return original.apply(target, args);
|
|
20
20
|
}
|
|
21
21
|
return core.startSpan(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentSqlStorage.js","sources":["../../../src/instrumentations/instrumentSqlStorage.ts"],"sourcesContent":["import type { SqlStorage } from '@cloudflare/workers-types';\nimport {\n _INTERNAL_getSqlQuerySummary,\n _INTERNAL_sanitizeSqlQuery,\n getClient,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n startSpan,\n} from '@sentry/core';\nimport type { CloudflareClientOptions } from '../client';\nimport { targetsCloudflareInternalTable } from '../utils/internalSqlQuery';\n\n/**\n * Instruments the Durable Object SqlStorage `exec` method with Sentry spans.\n *\n * @param sql - The SqlStorage instance to instrument\n * @returns An instrumented SqlStorage instance\n */\nexport function instrumentSqlStorage(sql: SqlStorage): SqlStorage {\n return new Proxy(sql, {\n get(target, prop, _receiver) {\n const original = Reflect.get(target, prop, target);\n\n if (prop !== 'exec' || typeof original !== 'function') {\n return original;\n }\n\n return function (this: unknown, ...args: unknown[]) {\n const [query, ...bindings] = args as [string, ...unknown[]];\n\n const sanitizedQuery = _INTERNAL_sanitizeSqlQuery(query);\n const querySummary = _INTERNAL_getSqlQuerySummary(sanitizedQuery);\n\n const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)\n ?.durableObjectSqlSpanAllowlist;\n\n if (targetsCloudflareInternalTable(querySummary, allowlist)) {\n return (original as (...a: unknown[]) => ReturnType<SqlStorage['exec']>).apply(target, args);\n }\n\n return startSpan(\n {\n op: 'db.query',\n name: querySummary || sanitizedQuery,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.cloudflare.durable_object.sql',\n 'db.system.name': 'cloudflare-durable-object-sql',\n 'db.operation.name': 'exec',\n 'db.query.text': sanitizedQuery,\n 'db.query.summary': querySummary,\n 'cloudflare.durable_object.query.bindings': bindings.length,\n },\n },\n () => (original as (...a: unknown[]) => ReturnType<SqlStorage['exec']>).apply(target, args),\n );\n };\n },\n });\n}\n"],"names":["_INTERNAL_sanitizeSqlQuery","_INTERNAL_getSqlQuerySummary","getClient","targetsCloudflareInternalTable","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN"],"mappings":";;;;;AAiBO,SAAS,qBAAqB,GAAA,EAA6B;AAChE,EAAA,OAAO,IAAI,MAAM,GAAA,EAAK;AAAA,IACpB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAW;AAC3B,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,MAAM,MAAM,CAAA;AAEjD,MAAA,IAAI,IAAA,KAAS,MAAA,IAAU,OAAO,QAAA,KAAa,UAAA,EAAY;AACrD,QAAA,OAAO,QAAA;AAAA,MACT;AAEA,MAAA,OAAO,YAA4B,IAAA,EAAiB;AAClD,QAAA,MAAM,CAAC,KAAA,EAAO,GAAG,QAAQ,CAAA,GAAI,IAAA;AAE7B,QAAA,MAAM,cAAA,GAAiBA,gCAA2B,KAAK,CAAA;AACvD,QAAA,MAAM,YAAA,GAAeC,kCAA6B,cAAc,CAAA;AAEhE,QAAA,MAAM,SAAA,GAAaC,cAAA,EAAU,EAAG,UAAA,EAAW,EACvC,6BAAA;AAEJ,QAAA,IAAIC,+CAAA,CAA+B,YAAA,EAAc,
|
|
1
|
+
{"version":3,"file":"instrumentSqlStorage.js","sources":["../../../src/instrumentations/instrumentSqlStorage.ts"],"sourcesContent":["import type { SqlStorage } from '@cloudflare/workers-types';\nimport {\n _INTERNAL_getSqlQuerySummary,\n _INTERNAL_sanitizeSqlQuery,\n getClient,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n startSpan,\n} from '@sentry/core';\nimport type { CloudflareClientOptions } from '../client';\nimport { targetsCloudflareInternalTable } from '../utils/internalSqlQuery';\n\n/**\n * Instruments the Durable Object SqlStorage `exec` method with Sentry spans.\n *\n * @param sql - The SqlStorage instance to instrument\n * @returns An instrumented SqlStorage instance\n */\nexport function instrumentSqlStorage(sql: SqlStorage): SqlStorage {\n return new Proxy(sql, {\n get(target, prop, _receiver) {\n const original = Reflect.get(target, prop, target);\n\n if (prop !== 'exec' || typeof original !== 'function') {\n return original;\n }\n\n return function (this: unknown, ...args: unknown[]) {\n const [query, ...bindings] = args as [string, ...unknown[]];\n\n const sanitizedQuery = _INTERNAL_sanitizeSqlQuery(query);\n const querySummary = _INTERNAL_getSqlQuerySummary(sanitizedQuery);\n\n const allowlist = (getClient()?.getOptions() as CloudflareClientOptions | undefined)\n ?.durableObjectSqlSpanAllowlist;\n\n if (targetsCloudflareInternalTable(querySummary, allowlist, sanitizedQuery)) {\n return (original as (...a: unknown[]) => ReturnType<SqlStorage['exec']>).apply(target, args);\n }\n\n return startSpan(\n {\n op: 'db.query',\n name: querySummary || sanitizedQuery,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.db.cloudflare.durable_object.sql',\n 'db.system.name': 'cloudflare-durable-object-sql',\n 'db.operation.name': 'exec',\n 'db.query.text': sanitizedQuery,\n 'db.query.summary': querySummary,\n 'cloudflare.durable_object.query.bindings': bindings.length,\n },\n },\n () => (original as (...a: unknown[]) => ReturnType<SqlStorage['exec']>).apply(target, args),\n );\n };\n },\n });\n}\n"],"names":["_INTERNAL_sanitizeSqlQuery","_INTERNAL_getSqlQuerySummary","getClient","targetsCloudflareInternalTable","startSpan","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN"],"mappings":";;;;;AAiBO,SAAS,qBAAqB,GAAA,EAA6B;AAChE,EAAA,OAAO,IAAI,MAAM,GAAA,EAAK;AAAA,IACpB,GAAA,CAAI,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAW;AAC3B,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,MAAM,MAAM,CAAA;AAEjD,MAAA,IAAI,IAAA,KAAS,MAAA,IAAU,OAAO,QAAA,KAAa,UAAA,EAAY;AACrD,QAAA,OAAO,QAAA;AAAA,MACT;AAEA,MAAA,OAAO,YAA4B,IAAA,EAAiB;AAClD,QAAA,MAAM,CAAC,KAAA,EAAO,GAAG,QAAQ,CAAA,GAAI,IAAA;AAE7B,QAAA,MAAM,cAAA,GAAiBA,gCAA2B,KAAK,CAAA;AACvD,QAAA,MAAM,YAAA,GAAeC,kCAA6B,cAAc,CAAA;AAEhE,QAAA,MAAM,SAAA,GAAaC,cAAA,EAAU,EAAG,UAAA,EAAW,EACvC,6BAAA;AAEJ,QAAA,IAAIC,+CAAA,CAA+B,YAAA,EAAc,SAAA,EAAW,cAAc,CAAA,EAAG;AAC3E,UAAA,OAAQ,QAAA,CAAiE,KAAA,CAAM,MAAA,EAAQ,IAAI,CAAA;AAAA,QAC7F;AAEA,QAAA,OAAOC,cAAA;AAAA,UACL;AAAA,YACE,EAAA,EAAI,UAAA;AAAA,YACJ,MAAM,YAAA,IAAgB,cAAA;AAAA,YACtB,UAAA,EAAY;AAAA,cACV,CAACC,qCAAgC,GAAG,uCAAA;AAAA,cACpC,gBAAA,EAAkB,+BAAA;AAAA,cAClB,mBAAA,EAAqB,MAAA;AAAA,cACrB,eAAA,EAAiB,cAAA;AAAA,cACjB,kBAAA,EAAoB,YAAA;AAAA,cACpB,4CAA4C,QAAA,CAAS;AAAA;AACvD,WACF;AAAA,UACA,MAAO,QAAA,CAAiE,KAAA,CAAM,MAAA,EAAQ,IAAI;AAAA,SAC5F;AAAA,MACF,CAAA;AAAA,IACF;AAAA,GACD,CAAA;AACH;;;;"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const debugBuild = require('../debug-build.js');
|
|
5
|
+
|
|
6
|
+
const INTEGRATION_NAME = "Spotlight";
|
|
7
|
+
const _spotlightIntegration = ((options = {}) => {
|
|
8
|
+
const sidecarUrl = options.sidecarUrl || "http://localhost:8969/stream";
|
|
9
|
+
return {
|
|
10
|
+
name: INTEGRATION_NAME,
|
|
11
|
+
setup(client) {
|
|
12
|
+
debugBuild.DEBUG_BUILD && core.debug.log("[Spotlight] Using Sidecar URL", sidecarUrl);
|
|
13
|
+
setupSidecarForwarding(client, sidecarUrl);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
const spotlightIntegration = core.defineIntegration(_spotlightIntegration);
|
|
18
|
+
function setupSidecarForwarding(client, sidecarUrl) {
|
|
19
|
+
const parsedUrl = parseSidecarUrl(sidecarUrl);
|
|
20
|
+
if (!parsedUrl) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let failCount = 0;
|
|
24
|
+
client.on("beforeEnvelope", (envelope) => {
|
|
25
|
+
if (failCount > 3) {
|
|
26
|
+
debugBuild.DEBUG_BUILD && core.debug.warn("[Spotlight] Disabled Sentry -> Spotlight forwarding due to too many failed requests");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const body = core.serializeEnvelope(envelope);
|
|
30
|
+
core.suppressTracing(() => {
|
|
31
|
+
fetch(parsedUrl.href, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
body,
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/x-sentry-envelope"
|
|
36
|
+
}
|
|
37
|
+
}).then(
|
|
38
|
+
(res) => {
|
|
39
|
+
res.text().catch(() => {
|
|
40
|
+
});
|
|
41
|
+
if (res.status >= 200 && res.status < 400) {
|
|
42
|
+
failCount = 0;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
() => {
|
|
46
|
+
failCount++;
|
|
47
|
+
debugBuild.DEBUG_BUILD && core.debug.warn("[Spotlight] Failed to send envelope to Spotlight Sidecar");
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function parseSidecarUrl(url) {
|
|
54
|
+
try {
|
|
55
|
+
return new URL(url);
|
|
56
|
+
} catch {
|
|
57
|
+
debugBuild.DEBUG_BUILD && core.debug.warn(`[Spotlight] Invalid sidecar URL: ${url}`);
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exports.INTEGRATION_NAME = INTEGRATION_NAME;
|
|
63
|
+
exports.spotlightIntegration = spotlightIntegration;
|
|
64
|
+
//# sourceMappingURL=spotlight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spotlight.js","sources":["../../../src/integrations/spotlight.ts"],"sourcesContent":["import type { Client, Envelope, IntegrationFn } from '@sentry/core';\nimport { debug, defineIntegration, serializeEnvelope, suppressTracing } from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\ntype SpotlightConnectionOptions = {\n /**\n * Set this if the Spotlight Sidecar is not running on localhost:8969.\n * By default, the URL is set to http://localhost:8969/stream\n */\n sidecarUrl?: string;\n};\n\nexport const INTEGRATION_NAME = 'Spotlight' as const;\n\nconst _spotlightIntegration = ((options: Partial<SpotlightConnectionOptions> = {}) => {\n const sidecarUrl = options.sidecarUrl || 'http://localhost:8969/stream';\n\n return {\n name: INTEGRATION_NAME,\n setup(client) {\n DEBUG_BUILD && debug.log('[Spotlight] Using Sidecar URL', sidecarUrl);\n setupSidecarForwarding(client, sidecarUrl);\n },\n };\n}) satisfies IntegrationFn;\n\n/**\n * Use this integration to send errors and transactions to Spotlight.\n *\n * Learn more about spotlight at https://spotlightjs.com\n *\n * Important: This integration is intended for local development only.\n * Each forwarded envelope counts as a Worker subrequest (50 free / 1000 paid\n * per invocation), so it should not be enabled in production.\n */\nexport const spotlightIntegration = defineIntegration(_spotlightIntegration);\n\nfunction setupSidecarForwarding(client: Client, sidecarUrl: string): void {\n const parsedUrl = parseSidecarUrl(sidecarUrl);\n if (!parsedUrl) {\n return;\n }\n\n let failCount = 0;\n\n client.on('beforeEnvelope', (envelope: Envelope) => {\n if (failCount > 3) {\n DEBUG_BUILD && debug.warn('[Spotlight] Disabled Sentry -> Spotlight forwarding due to too many failed requests');\n return;\n }\n\n const body = serializeEnvelope(envelope);\n\n suppressTracing(() => {\n fetch(parsedUrl.href, {\n method: 'POST',\n body,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n },\n }).then(\n res => {\n // Consume the response body to satisfy Cloudflare Workers' requirement\n // that all fetch response bodies are read or cancelled.\n res.text().catch(() => {\n // no-op\n });\n\n if (res.status >= 200 && res.status < 400) {\n failCount = 0;\n }\n },\n () => {\n failCount++;\n DEBUG_BUILD && debug.warn('[Spotlight] Failed to send envelope to Spotlight Sidecar');\n },\n );\n });\n });\n}\n\nfunction parseSidecarUrl(url: string): URL | undefined {\n try {\n return new URL(url);\n } catch {\n DEBUG_BUILD && debug.warn(`[Spotlight] Invalid sidecar URL: ${url}`);\n return undefined;\n }\n}\n"],"names":["DEBUG_BUILD","debug","defineIntegration","serializeEnvelope","suppressTracing"],"mappings":";;;;;AAYO,MAAM,gBAAA,GAAmB;AAEhC,MAAM,qBAAA,IAAyB,CAAC,OAAA,GAA+C,EAAC,KAAM;AACpF,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,8BAAA;AAEzC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,gBAAA;AAAA,IACN,MAAM,MAAA,EAAQ;AACZ,MAAAA,sBAAA,IAAeC,UAAA,CAAM,GAAA,CAAI,+BAAA,EAAiC,UAAU,CAAA;AACpE,MAAA,sBAAA,CAAuB,QAAQ,UAAU,CAAA;AAAA,IAC3C;AAAA,GACF;AACF,CAAA,CAAA;AAWO,MAAM,oBAAA,GAAuBC,uBAAkB,qBAAqB;AAE3E,SAAS,sBAAA,CAAuB,QAAgB,UAAA,EAA0B;AACxE,EAAA,MAAM,SAAA,GAAY,gBAAgB,UAAU,CAAA;AAC5C,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,SAAA,GAAY,CAAA;AAEhB,EAAA,MAAA,CAAO,EAAA,CAAG,gBAAA,EAAkB,CAAC,QAAA,KAAuB;AAClD,IAAA,IAAI,YAAY,CAAA,EAAG;AACjB,MAAAF,sBAAA,IAAeC,UAAA,CAAM,KAAK,qFAAqF,CAAA;AAC/G,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAOE,uBAAkB,QAAQ,CAAA;AAEvC,IAAAC,oBAAA,CAAgB,MAAM;AACpB,MAAA,KAAA,CAAM,UAAU,IAAA,EAAM;AAAA,QACpB,MAAA,EAAQ,MAAA;AAAA,QACR,IAAA;AAAA,QACA,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB,OACD,CAAA,CAAE,IAAA;AAAA,QACD,CAAA,GAAA,KAAO;AAGL,UAAA,GAAA,CAAI,IAAA,EAAK,CAAE,KAAA,CAAM,MAAM;AAAA,UAEvB,CAAC,CAAA;AAED,UAAA,IAAI,GAAA,CAAI,MAAA,IAAU,GAAA,IAAO,GAAA,CAAI,SAAS,GAAA,EAAK;AACzC,YAAA,SAAA,GAAY,CAAA;AAAA,UACd;AAAA,QACF,CAAA;AAAA,QACA,MAAM;AACJ,UAAA,SAAA,EAAA;AACA,UAAAJ,sBAAA,IAAeC,UAAA,CAAM,KAAK,0DAA0D,CAAA;AAAA,QACtF;AAAA,OACF;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAEA,SAAS,gBAAgB,GAAA,EAA8B;AACrD,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,IAAI,GAAG,CAAA;AAAA,EACpB,CAAA,CAAA,MAAQ;AACN,IAAAD,sBAAA,IAAeC,UAAA,CAAM,IAAA,CAAK,CAAA,iCAAA,EAAoC,GAAG,CAAA,CAAE,CAAA;AACnE,IAAA,OAAO,MAAA;AAAA,EACT;AACF;;;;;"}
|
|
@@ -9,11 +9,12 @@ const fetch = require('../integrations/fetch.js');
|
|
|
9
9
|
const sdk = require('../sdk.js');
|
|
10
10
|
const hono = require('../integrations/hono.js');
|
|
11
11
|
const httpServer = require('../integrations/httpServer.js');
|
|
12
|
-
const instrumentD1 = require('../instrumentations/worker/instrumentD1.js');
|
|
13
12
|
const durableobject = require('../durableobject.js');
|
|
13
|
+
const instrumentD1 = require('../instrumentations/worker/instrumentD1.js');
|
|
14
14
|
const workflows = require('../workflows.js');
|
|
15
15
|
const pagesPlugin = require('../pages-plugin.js');
|
|
16
16
|
const async = require('../async.js');
|
|
17
|
+
const spotlight = require('../integrations/spotlight.js');
|
|
17
18
|
const withSentry = require('../withSentry.js');
|
|
18
19
|
const request = require('../request.js');
|
|
19
20
|
|
|
@@ -117,11 +118,13 @@ exports.fetchIntegration = fetch.fetchIntegration;
|
|
|
117
118
|
exports.getDefaultIntegrations = sdk.getDefaultIntegrations;
|
|
118
119
|
exports.honoIntegration = hono.honoIntegration;
|
|
119
120
|
exports.httpServerIntegration = httpServer.httpServerIntegration;
|
|
120
|
-
exports.
|
|
121
|
+
exports.instrumentAgentWithSentry = durableobject.instrumentAgentWithSentry;
|
|
121
122
|
exports.instrumentDurableObjectWithSentry = durableobject.instrumentDurableObjectWithSentry;
|
|
123
|
+
exports.instrumentD1WithSentry = instrumentD1.instrumentD1WithSentry;
|
|
122
124
|
exports.instrumentWorkflowWithSentry = workflows.instrumentWorkflowWithSentry;
|
|
123
125
|
exports.sentryPagesPlugin = pagesPlugin.sentryPagesPlugin;
|
|
124
126
|
exports.setAsyncLocalStorageAsyncContextStrategy = async.setAsyncLocalStorageAsyncContextStrategy;
|
|
127
|
+
exports.spotlightIntegration = spotlight.spotlightIntegration;
|
|
125
128
|
exports.withSentry = withSentry.withSentry;
|
|
126
129
|
exports.wrapRequestHandler = request.wrapRequestHandler;
|
|
127
130
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/build/cjs/options.js
CHANGED
|
@@ -14,6 +14,7 @@ function getFinalOptions(userOptions = {}, env) {
|
|
|
14
14
|
}
|
|
15
15
|
const release = "SENTRY_RELEASE" in env && typeof env.SENTRY_RELEASE === "string" ? env.SENTRY_RELEASE : "CF_VERSION_METADATA" in env && isVersionMetadata(env.CF_VERSION_METADATA) ? env.CF_VERSION_METADATA.id : void 0;
|
|
16
16
|
const tracesSampleRate = userOptions.tracesSampleRate ?? parseFloat(getEnvVar(env, "SENTRY_TRACES_SAMPLE_RATE") ?? "");
|
|
17
|
+
const spotlight = getSpotlightFromEnv(userOptions.spotlight, getEnvVar(env, "SENTRY_SPOTLIGHT"));
|
|
17
18
|
return {
|
|
18
19
|
release,
|
|
19
20
|
...userOptions,
|
|
@@ -21,9 +22,21 @@ function getFinalOptions(userOptions = {}, env) {
|
|
|
21
22
|
environment: userOptions.environment ?? getEnvVar(env, "SENTRY_ENVIRONMENT"),
|
|
22
23
|
tracesSampleRate: isFinite(tracesSampleRate) ? tracesSampleRate : void 0,
|
|
23
24
|
debug: userOptions.debug ?? core.envToBool(getEnvVar(env, "SENTRY_DEBUG")),
|
|
24
|
-
tunnel: userOptions.tunnel ?? getEnvVar(env, "SENTRY_TUNNEL")
|
|
25
|
+
tunnel: userOptions.tunnel ?? getEnvVar(env, "SENTRY_TUNNEL"),
|
|
26
|
+
spotlight
|
|
25
27
|
};
|
|
26
28
|
}
|
|
29
|
+
function getSpotlightFromEnv(optionsSpotlight, envVar) {
|
|
30
|
+
if (optionsSpotlight === false) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (typeof optionsSpotlight === "string") {
|
|
34
|
+
return optionsSpotlight;
|
|
35
|
+
}
|
|
36
|
+
const envBool = core.envToBool(envVar, { strict: true });
|
|
37
|
+
const envUrl = envBool === null && envVar ? envVar : void 0;
|
|
38
|
+
return optionsSpotlight === true ? envUrl ?? true : envBool ?? envUrl;
|
|
39
|
+
}
|
|
27
40
|
|
|
28
41
|
exports.getFinalOptions = getFinalOptions;
|
|
29
42
|
//# sourceMappingURL=options.js.map
|
package/build/cjs/options.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sources":["../../src/options.ts"],"sourcesContent":["import { isObjectLike, envToBool } from '@sentry/core';\nimport type { CloudflareOptions } from './client';\n\n/**\n * Cloudflare's version metadata binding structure.\n * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/\n */\ninterface CfVersionMetadata {\n id: string;\n tag?: string;\n timestamp?: string;\n}\n\n/**\n * Checks if the value is a valid CF_VERSION_METADATA binding.\n */\nfunction isVersionMetadata(value: unknown): value is CfVersionMetadata {\n return isObjectLike(value) && 'id' in value && typeof value.id === 'string';\n}\n\nfunction getEnvVar<T extends Record<string, unknown>>(env: unknown, varName: keyof T): string | undefined {\n return isObjectLike(env) && varName in env && typeof (env as T)[varName] === 'string'\n ? ((env as T)[varName] as string)\n : undefined;\n}\n\n/**\n * Merges the options passed in from the user with the options we read from\n * the Cloudflare `env` environment variable object.\n *\n * Release is determined with the following priority (highest to lowest):\n * 1. User-provided release option\n * 2. SENTRY_RELEASE environment variable\n * 3. CF_VERSION_METADATA.id binding (if configured in the wrangler config)\n *\n * @param userOptions - The options passed in from the user.\n * @param env - The environment variables.\n *\n * @returns The final options.\n */\nexport function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknown): CloudflareOptions {\n if (typeof env !== 'object' || env === null) {\n return userOptions;\n }\n\n // Priority: userOptions.release > SENTRY_RELEASE > CF_VERSION_METADATA.id\n const release =\n 'SENTRY_RELEASE' in env && typeof env.SENTRY_RELEASE === 'string'\n ? env.SENTRY_RELEASE\n : 'CF_VERSION_METADATA' in env && isVersionMetadata(env.CF_VERSION_METADATA)\n ? env.CF_VERSION_METADATA.id\n : undefined;\n\n const tracesSampleRate =\n userOptions.tracesSampleRate ?? parseFloat(getEnvVar(env, 'SENTRY_TRACES_SAMPLE_RATE') ?? '');\n\n return {\n release,\n ...userOptions,\n dsn: userOptions.dsn ?? getEnvVar(env, 'SENTRY_DSN'),\n environment: userOptions.environment ?? getEnvVar(env, 'SENTRY_ENVIRONMENT'),\n tracesSampleRate: isFinite(tracesSampleRate) ? tracesSampleRate : undefined,\n debug: userOptions.debug ?? envToBool(getEnvVar(env, 'SENTRY_DEBUG')),\n tunnel: userOptions.tunnel ?? getEnvVar(env, 'SENTRY_TUNNEL'),\n };\n}\n"],"names":["isObjectLike","envToBool"],"mappings":";;;;AAgBA,SAAS,kBAAkB,KAAA,EAA4C;AACrE,EAAA,OAAOA,kBAAa,KAAK,CAAA,IAAK,QAAQ,KAAA,IAAS,OAAO,MAAM,EAAA,KAAO,QAAA;AACrE;AAEA,SAAS,SAAA,CAA6C,KAAc,OAAA,EAAsC;AACxG,EAAA,OAAOA,iBAAA,CAAa,GAAG,CAAA,IAAK,OAAA,IAAW,GAAA,IAAO,OAAQ,GAAA,CAAU,OAAO,CAAA,KAAM,QAAA,GACvE,GAAA,CAAU,OAAO,CAAA,GACnB,MAAA;AACN;AAgBO,SAAS,eAAA,CAAgB,WAAA,GAAiC,EAAC,EAAG,GAAA,EAAiC;AACpG,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,IAAA,EAAM;AAC3C,IAAA,OAAO,WAAA;AAAA,EACT;AAGA,EAAA,MAAM,UACJ,gBAAA,IAAoB,GAAA,IAAO,OAAO,GAAA,CAAI,mBAAmB,QAAA,GACrD,GAAA,CAAI,cAAA,GACJ,qBAAA,IAAyB,OAAO,iBAAA,CAAkB,GAAA,CAAI,mBAAmB,CAAA,GACvE,GAAA,CAAI,oBAAoB,EAAA,GACxB,MAAA;AAER,EAAA,MAAM,gBAAA,GACJ,YAAY,gBAAA,IAAoB,UAAA,CAAW,UAAU,GAAA,EAAK,2BAA2B,KAAK,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"options.js","sources":["../../src/options.ts"],"sourcesContent":["import { isObjectLike, envToBool } from '@sentry/core';\nimport type { CloudflareOptions } from './client';\n\n/**\n * Cloudflare's version metadata binding structure.\n * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/\n */\ninterface CfVersionMetadata {\n id: string;\n tag?: string;\n timestamp?: string;\n}\n\n/**\n * Checks if the value is a valid CF_VERSION_METADATA binding.\n */\nfunction isVersionMetadata(value: unknown): value is CfVersionMetadata {\n return isObjectLike(value) && 'id' in value && typeof value.id === 'string';\n}\n\nfunction getEnvVar<T extends Record<string, unknown>>(env: unknown, varName: keyof T): string | undefined {\n return isObjectLike(env) && varName in env && typeof (env as T)[varName] === 'string'\n ? ((env as T)[varName] as string)\n : undefined;\n}\n\n/**\n * Merges the options passed in from the user with the options we read from\n * the Cloudflare `env` environment variable object.\n *\n * Release is determined with the following priority (highest to lowest):\n * 1. User-provided release option\n * 2. SENTRY_RELEASE environment variable\n * 3. CF_VERSION_METADATA.id binding (if configured in the wrangler config)\n *\n * @param userOptions - The options passed in from the user.\n * @param env - The environment variables.\n *\n * @returns The final options.\n */\nexport function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknown): CloudflareOptions {\n if (typeof env !== 'object' || env === null) {\n return userOptions;\n }\n\n // Priority: userOptions.release > SENTRY_RELEASE > CF_VERSION_METADATA.id\n const release =\n 'SENTRY_RELEASE' in env && typeof env.SENTRY_RELEASE === 'string'\n ? env.SENTRY_RELEASE\n : 'CF_VERSION_METADATA' in env && isVersionMetadata(env.CF_VERSION_METADATA)\n ? env.CF_VERSION_METADATA.id\n : undefined;\n\n const tracesSampleRate =\n userOptions.tracesSampleRate ?? parseFloat(getEnvVar(env, 'SENTRY_TRACES_SAMPLE_RATE') ?? '');\n\n // Spotlight precedence (mirrors node-core's getSpotlightConfig):\n // - false or explicit string from options: use as-is\n // - true: enable, but prefer a custom URL from the env var if set\n // - undefined: defer entirely to the env var (bool or URL)\n const spotlight = getSpotlightFromEnv(userOptions.spotlight, getEnvVar(env, 'SENTRY_SPOTLIGHT'));\n\n return {\n release,\n ...userOptions,\n dsn: userOptions.dsn ?? getEnvVar(env, 'SENTRY_DSN'),\n environment: userOptions.environment ?? getEnvVar(env, 'SENTRY_ENVIRONMENT'),\n tracesSampleRate: isFinite(tracesSampleRate) ? tracesSampleRate : undefined,\n debug: userOptions.debug ?? envToBool(getEnvVar(env, 'SENTRY_DEBUG')),\n tunnel: userOptions.tunnel ?? getEnvVar(env, 'SENTRY_TUNNEL'),\n spotlight,\n };\n}\n\n/**\n * Resolve the spotlight option from a user-supplied value and an env binding string.\n * Mirrors node-core's `getSpotlightConfig` precedence:\n * - `false` or explicit string from options → use as-is\n * - `true` → enable, but prefer a custom URL from the env var if set\n * - `undefined` → defer entirely to the env var (bool or URL)\n */\nfunction getSpotlightFromEnv(\n optionsSpotlight: boolean | string | undefined,\n envVar: string | undefined,\n): boolean | string | undefined {\n if (optionsSpotlight === false) {\n return false;\n }\n if (typeof optionsSpotlight === 'string') {\n return optionsSpotlight;\n }\n\n // optionsSpotlight is true or undefined\n const envBool = envToBool(envVar, { strict: true });\n const envUrl = envBool === null && envVar ? envVar : undefined;\n\n return optionsSpotlight === true\n ? (envUrl ?? true) // true: use env URL if present, otherwise true\n : (envBool ?? envUrl); // undefined: use env var (bool or URL)\n}\n"],"names":["isObjectLike","envToBool"],"mappings":";;;;AAgBA,SAAS,kBAAkB,KAAA,EAA4C;AACrE,EAAA,OAAOA,kBAAa,KAAK,CAAA,IAAK,QAAQ,KAAA,IAAS,OAAO,MAAM,EAAA,KAAO,QAAA;AACrE;AAEA,SAAS,SAAA,CAA6C,KAAc,OAAA,EAAsC;AACxG,EAAA,OAAOA,iBAAA,CAAa,GAAG,CAAA,IAAK,OAAA,IAAW,GAAA,IAAO,OAAQ,GAAA,CAAU,OAAO,CAAA,KAAM,QAAA,GACvE,GAAA,CAAU,OAAO,CAAA,GACnB,MAAA;AACN;AAgBO,SAAS,eAAA,CAAgB,WAAA,GAAiC,EAAC,EAAG,GAAA,EAAiC;AACpG,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,IAAA,EAAM;AAC3C,IAAA,OAAO,WAAA;AAAA,EACT;AAGA,EAAA,MAAM,UACJ,gBAAA,IAAoB,GAAA,IAAO,OAAO,GAAA,CAAI,mBAAmB,QAAA,GACrD,GAAA,CAAI,cAAA,GACJ,qBAAA,IAAyB,OAAO,iBAAA,CAAkB,GAAA,CAAI,mBAAmB,CAAA,GACvE,GAAA,CAAI,oBAAoB,EAAA,GACxB,MAAA;AAER,EAAA,MAAM,gBAAA,GACJ,YAAY,gBAAA,IAAoB,UAAA,CAAW,UAAU,GAAA,EAAK,2BAA2B,KAAK,EAAE,CAAA;AAM9F,EAAA,MAAM,YAAY,mBAAA,CAAoB,WAAA,CAAY,WAAW,SAAA,CAAU,GAAA,EAAK,kBAAkB,CAAC,CAAA;AAE/F,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,GAAG,WAAA;AAAA,IACH,GAAA,EAAK,WAAA,CAAY,GAAA,IAAO,SAAA,CAAU,KAAK,YAAY,CAAA;AAAA,IACnD,WAAA,EAAa,WAAA,CAAY,WAAA,IAAe,SAAA,CAAU,KAAK,oBAAoB,CAAA;AAAA,IAC3E,gBAAA,EAAkB,QAAA,CAAS,gBAAgB,CAAA,GAAI,gBAAA,GAAmB,MAAA;AAAA,IAClE,OAAO,WAAA,CAAY,KAAA,IAASC,eAAU,SAAA,CAAU,GAAA,EAAK,cAAc,CAAC,CAAA;AAAA,IACpE,MAAA,EAAQ,WAAA,CAAY,MAAA,IAAU,SAAA,CAAU,KAAK,eAAe,CAAA;AAAA,IAC5D;AAAA,GACF;AACF;AASA,SAAS,mBAAA,CACP,kBACA,MAAA,EAC8B;AAC9B,EAAA,IAAI,qBAAqB,KAAA,EAAO;AAC9B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,qBAAqB,QAAA,EAAU;AACxC,IAAA,OAAO,gBAAA;AAAA,EACT;AAGA,EAAA,MAAM,UAAUA,cAAA,CAAU,MAAA,EAAQ,EAAE,MAAA,EAAQ,MAAM,CAAA;AAClD,EAAA,MAAM,MAAA,GAAS,OAAA,KAAY,IAAA,IAAQ,MAAA,GAAS,MAAA,GAAS,MAAA;AAErD,EAAA,OAAO,gBAAA,KAAqB,IAAA,GACvB,MAAA,IAAU,IAAA,GACV,OAAA,IAAW,MAAA;AAClB;;;;"}
|
package/build/cjs/request.js
CHANGED
|
@@ -5,16 +5,17 @@ const httpServer = require('./integrations/httpServer.js');
|
|
|
5
5
|
const flush = require('./flush.js');
|
|
6
6
|
const scopeUtils = require('./scope-utils.js');
|
|
7
7
|
const sdk = require('./sdk.js');
|
|
8
|
+
const invocationScope = require('./utils/invocationScope.js');
|
|
8
9
|
const streaming = require('./utils/streaming.js');
|
|
9
10
|
|
|
10
11
|
function getRequestErrorMechanismType(context) {
|
|
11
12
|
return context && "storage" in context ? "auto.faas.cloudflare.durable_object" : "auto.http.cloudflare";
|
|
12
13
|
}
|
|
13
14
|
function wrapRequestHandler(wrapperOptions, handler) {
|
|
14
|
-
return
|
|
15
|
+
return invocationScope.withInvocationIsolationScope(async (isolationScope) => {
|
|
15
16
|
const { options, request, captureErrors = true } = wrapperOptions;
|
|
16
17
|
const context = wrapperOptions.context;
|
|
17
|
-
const waitUntil = context ? flush.getOriginalWaitUntil(context)
|
|
18
|
+
const waitUntil = context ? flush.getOriginalWaitUntil(context).bind(context) : void 0;
|
|
18
19
|
const errorMechanismType = getRequestErrorMechanismType(context);
|
|
19
20
|
const client = sdk.init({ ...options, ctx: context });
|
|
20
21
|
isolationScope.setClient(client);
|
package/build/cjs/request.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sources":["../../src/request.ts"],"sourcesContent":["import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types';\nimport {\n captureException,\n continueTrace,\n getClient,\n getHttpSpanDetailsFromUrlObject,\n httpHeadersToSpanAttributes,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n setHttpStatus,\n startSpanManual,\n winterCGHeadersToDict,\n withIsolationScope,\n} from '@sentry/core';\nimport { captureIncomingRequestBody } from './integrations/httpServer';\nimport type { CloudflareOptions } from './client';\nimport type { ExecutionContextCompat } from './executionContext';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils';\nimport { init } from './sdk';\nimport { classifyResponseStreaming } from './utils/streaming';\n\nfunction getRequestErrorMechanismType(context: ExecutionContextCompat | undefined): string {\n // Durable Object fetch handlers use DO state as context (see instrumentDurableObjectWithSentry)\n return context && 'storage' in context ? 'auto.faas.cloudflare.durable_object' : 'auto.http.cloudflare';\n}\n\ninterface RequestHandlerWrapperOptions {\n options: CloudflareOptions;\n request: Request<unknown, IncomingRequestCfProperties<unknown> | CfProperties<unknown>>;\n context: ExecutionContextCompat | undefined;\n /**\n * If true, errors will be captured, rethrown and sent to Sentry.\n * Otherwise, errors are rethrown but not captured.\n *\n * You most likely don't want to set this to `false`, if you use `wrapRequestHandler` directly.\n * This is primarily meant as an escape hatch for higher-level SDKs relying on additional error\n * capturing mechanisms where this wrapper captures errors too early or too generally.\n *\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Wraps a cloudflare request handler in Sentry instrumentation\n */\nexport function wrapRequestHandler(\n wrapperOptions: RequestHandlerWrapperOptions,\n handler: (...args: unknown[]) => Response | Promise<Response>,\n): Promise<Response> {\n return withIsolationScope(async isolationScope => {\n const { options, request, captureErrors = true } = wrapperOptions;\n const context = wrapperOptions.context;\n\n // Use getOriginalWaitUntil to get the un-instrumented waitUntil function.\n // This is crucial to avoid deadlock: the flush lock mechanism wraps waitUntil\n // to track pending tasks. If we use the instrumented version for flushAndDispose,\n // it acquires the lock, then flushAndDispose tries to wait for the same lock,\n // creating a deadlock.\n const waitUntil = context ? getOriginalWaitUntil(context)?.bind(context) : undefined;\n const errorMechanismType = getRequestErrorMechanismType(context);\n\n const client = init({ ...options, ctx: context });\n isolationScope.setClient(client);\n\n const urlObject = parseStringToURLObject(request.url);\n const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request);\n\n const contentLength = request.headers.get('content-length');\n if (contentLength) {\n attributes['http.request.body.size'] = parseInt(contentLength, 10);\n }\n\n const userAgentHeader = request.headers.get('user-agent');\n if (userAgentHeader) {\n attributes['user_agent.original'] = userAgentHeader;\n }\n\n Object.assign(\n attributes,\n httpHeadersToSpanAttributes(\n winterCGHeadersToDict(request.headers),\n getClient()?.getDataCollectionOptions() ?? false,\n ),\n );\n\n attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';\n\n addCloudResourceContext(isolationScope);\n addRequest(isolationScope, request);\n if (request.cf) {\n addCultureContext(isolationScope, request.cf);\n\n if (typeof request.cf.httpProtocol === 'string') {\n attributes['network.protocol.name'] = request.cf.httpProtocol;\n }\n }\n\n // Do not capture spans for OPTIONS and HEAD requests\n if (request.method === 'OPTIONS' || request.method === 'HEAD') {\n try {\n return await handler();\n } catch (e) {\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n throw e;\n } finally {\n waitUntil?.(flushAndDispose(client));\n }\n }\n\n if (client) {\n await captureIncomingRequestBody(client, request);\n }\n\n return continueTrace(\n { sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') },\n () => {\n // Note: This span will not have a duration unless I/O happens in the handler. This is\n // because of how the cloudflare workers runtime works.\n // See: https://developers.cloudflare.com/workers/runtime-apis/performance/\n\n // Use startSpanManual to control when span ends (needed for streaming responses)\n return startSpanManual({ name, attributes }, async span => {\n let res: Response;\n\n try {\n res = await handler();\n setHttpStatus(span, res.status);\n\n // After the handler runs, the span name might have been updated by nested instrumentation\n // (e.g., Remix parameterizing routes). The span should already have the correct name\n // from that instrumentation, so we don't need to do anything here.\n } catch (e) {\n span.end();\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n waitUntil?.(flushAndDispose(client));\n throw e;\n }\n\n // Classify response to detect actual streaming\n const classification = classifyResponseStreaming(res);\n\n if (classification.isStreaming && res.body) {\n try {\n let ended = false;\n\n const endSpanOnce = (): void => {\n if (ended) return;\n\n ended = true;\n span.end();\n waitUntil?.(flushAndDispose(client));\n };\n\n const transform = new TransformStream({\n flush() {\n // Source stream completed normally.\n endSpanOnce();\n },\n cancel() {\n // Client disconnected (or downstream cancelled). The `cancel`\n // is being called while the response is still considered\n // active, so this is a safe place to end the span.\n endSpanOnce();\n },\n });\n\n return new Response(res.body.pipeThrough(transform), {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n } catch {\n span.end();\n waitUntil?.(flushAndDispose(client));\n return res;\n }\n }\n\n // Non-streaming response - end span immediately and return original\n span.end();\n\n // Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive.\n // This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers\n // will still be called and may need the client to capture errors.\n if (res.status === 101) {\n waitUntil?.(client?.flush(2000));\n } else {\n waitUntil?.(flushAndDispose(client));\n }\n return res;\n });\n },\n );\n });\n}\n"],"names":["withIsolationScope","getOriginalWaitUntil","init","parseStringToURLObject","getHttpSpanDetailsFromUrlObject","httpHeadersToSpanAttributes","winterCGHeadersToDict","getClient","SEMANTIC_ATTRIBUTE_SENTRY_OP","addCloudResourceContext","addRequest","addCultureContext","captureException","flushAndDispose","captureIncomingRequestBody","continueTrace","startSpanManual","setHttpStatus","classifyResponseStreaming"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request.js","sources":["../../src/request.ts"],"sourcesContent":["import type { CfProperties, IncomingRequestCfProperties } from '@cloudflare/workers-types';\nimport {\n captureException,\n continueTrace,\n getClient,\n getHttpSpanDetailsFromUrlObject,\n httpHeadersToSpanAttributes,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n setHttpStatus,\n startSpanManual,\n winterCGHeadersToDict,\n} from '@sentry/core';\nimport { captureIncomingRequestBody } from './integrations/httpServer';\nimport type { CloudflareOptions } from './client';\nimport type { ExecutionContextCompat } from './executionContext';\nimport { flushAndDispose, getOriginalWaitUntil } from './flush';\nimport { addCloudResourceContext, addCultureContext, addRequest } from './scope-utils';\nimport { init } from './sdk';\nimport { withInvocationIsolationScope } from './utils/invocationScope';\nimport { classifyResponseStreaming } from './utils/streaming';\n\nfunction getRequestErrorMechanismType(context: ExecutionContextCompat | undefined): string {\n // Durable Object fetch handlers use DO state as context (see instrumentDurableObjectWithSentry)\n return context && 'storage' in context ? 'auto.faas.cloudflare.durable_object' : 'auto.http.cloudflare';\n}\n\ninterface RequestHandlerWrapperOptions {\n options: CloudflareOptions;\n request: Request<unknown, IncomingRequestCfProperties<unknown> | CfProperties<unknown>>;\n context: ExecutionContextCompat | undefined;\n /**\n * If true, errors will be captured, rethrown and sent to Sentry.\n * Otherwise, errors are rethrown but not captured.\n *\n * You most likely don't want to set this to `false`, if you use `wrapRequestHandler` directly.\n * This is primarily meant as an escape hatch for higher-level SDKs relying on additional error\n * capturing mechanisms where this wrapper captures errors too early or too generally.\n *\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Wraps a cloudflare request handler in Sentry instrumentation\n */\nexport function wrapRequestHandler(\n wrapperOptions: RequestHandlerWrapperOptions,\n handler: (...args: unknown[]) => Response | Promise<Response>,\n): Promise<Response> {\n return withInvocationIsolationScope(async isolationScope => {\n const { options, request, captureErrors = true } = wrapperOptions;\n const context = wrapperOptions.context;\n\n // Use getOriginalWaitUntil to get the un-instrumented waitUntil function.\n // This is crucial to avoid deadlock: the flush lock mechanism wraps waitUntil\n // to track pending tasks. If we use the instrumented version for flushAndDispose,\n // it acquires the lock, then flushAndDispose tries to wait for the same lock,\n // creating a deadlock.\n const waitUntil = context ? getOriginalWaitUntil(context).bind(context) : undefined;\n const errorMechanismType = getRequestErrorMechanismType(context);\n\n const client = init({ ...options, ctx: context });\n isolationScope.setClient(client);\n\n const urlObject = parseStringToURLObject(request.url);\n const [name, attributes] = getHttpSpanDetailsFromUrlObject(urlObject, 'server', 'auto.http.cloudflare', request);\n\n const contentLength = request.headers.get('content-length');\n if (contentLength) {\n attributes['http.request.body.size'] = parseInt(contentLength, 10);\n }\n\n const userAgentHeader = request.headers.get('user-agent');\n if (userAgentHeader) {\n attributes['user_agent.original'] = userAgentHeader;\n }\n\n Object.assign(\n attributes,\n httpHeadersToSpanAttributes(\n winterCGHeadersToDict(request.headers),\n getClient()?.getDataCollectionOptions() ?? false,\n ),\n );\n\n attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';\n\n addCloudResourceContext(isolationScope);\n addRequest(isolationScope, request);\n if (request.cf) {\n addCultureContext(isolationScope, request.cf);\n\n if (typeof request.cf.httpProtocol === 'string') {\n attributes['network.protocol.name'] = request.cf.httpProtocol;\n }\n }\n\n // Do not capture spans for OPTIONS and HEAD requests\n if (request.method === 'OPTIONS' || request.method === 'HEAD') {\n try {\n return await handler();\n } catch (e) {\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n throw e;\n } finally {\n waitUntil?.(flushAndDispose(client));\n }\n }\n\n if (client) {\n await captureIncomingRequestBody(client, request);\n }\n\n return continueTrace(\n { sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') },\n () => {\n // Note: This span will not have a duration unless I/O happens in the handler. This is\n // because of how the cloudflare workers runtime works.\n // See: https://developers.cloudflare.com/workers/runtime-apis/performance/\n\n // Use startSpanManual to control when span ends (needed for streaming responses)\n return startSpanManual({ name, attributes }, async span => {\n let res: Response;\n\n try {\n res = await handler();\n setHttpStatus(span, res.status);\n\n // After the handler runs, the span name might have been updated by nested instrumentation\n // (e.g., Remix parameterizing routes). The span should already have the correct name\n // from that instrumentation, so we don't need to do anything here.\n } catch (e) {\n span.end();\n if (captureErrors) {\n captureException(e, { mechanism: { handled: false, type: errorMechanismType } });\n }\n waitUntil?.(flushAndDispose(client));\n throw e;\n }\n\n // Classify response to detect actual streaming\n const classification = classifyResponseStreaming(res);\n\n if (classification.isStreaming && res.body) {\n try {\n let ended = false;\n\n const endSpanOnce = (): void => {\n if (ended) return;\n\n ended = true;\n span.end();\n waitUntil?.(flushAndDispose(client));\n };\n\n const transform = new TransformStream({\n flush() {\n // Source stream completed normally.\n endSpanOnce();\n },\n cancel() {\n // Client disconnected (or downstream cancelled). The `cancel`\n // is being called while the response is still considered\n // active, so this is a safe place to end the span.\n endSpanOnce();\n },\n });\n\n return new Response(res.body.pipeThrough(transform), {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n } catch {\n span.end();\n waitUntil?.(flushAndDispose(client));\n return res;\n }\n }\n\n // Non-streaming response - end span immediately and return original\n span.end();\n\n // Don't dispose for protocol upgrades (101 Switching Protocols) - the connection stays alive.\n // This includes WebSocket upgrades where webSocketMessage/webSocketClose handlers\n // will still be called and may need the client to capture errors.\n if (res.status === 101) {\n waitUntil?.(client?.flush(2000));\n } else {\n waitUntil?.(flushAndDispose(client));\n }\n return res;\n });\n },\n );\n });\n}\n"],"names":["withInvocationIsolationScope","getOriginalWaitUntil","init","parseStringToURLObject","getHttpSpanDetailsFromUrlObject","httpHeadersToSpanAttributes","winterCGHeadersToDict","getClient","SEMANTIC_ATTRIBUTE_SENTRY_OP","addCloudResourceContext","addRequest","addCultureContext","captureException","flushAndDispose","captureIncomingRequestBody","continueTrace","startSpanManual","setHttpStatus","classifyResponseStreaming"],"mappings":"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"}
|
package/build/cjs/sdk.js
CHANGED
|
@@ -6,6 +6,7 @@ const flush = require('./flush.js');
|
|
|
6
6
|
const httpServer = require('./integrations/httpServer.js');
|
|
7
7
|
const fetch = require('./integrations/fetch.js');
|
|
8
8
|
const hono = require('./integrations/hono.js');
|
|
9
|
+
const spotlight = require('./integrations/spotlight.js');
|
|
9
10
|
const tracer = require('./opentelemetry/tracer.js');
|
|
10
11
|
const transport = require('./transport.js');
|
|
11
12
|
const stacktrace = require('./vendor/stacktrace.js');
|
|
@@ -54,6 +55,13 @@ function init(options) {
|
|
|
54
55
|
transport: options.transport || transport.makeCloudflareTransport,
|
|
55
56
|
flushLock
|
|
56
57
|
};
|
|
58
|
+
if (options.spotlight && !clientOptions.integrations.some(({ name }) => name === spotlight.INTEGRATION_NAME)) {
|
|
59
|
+
clientOptions.integrations.push(
|
|
60
|
+
spotlight.spotlightIntegration({
|
|
61
|
+
sidecarUrl: typeof options.spotlight === "string" ? options.spotlight : void 0
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
57
65
|
if (!options.skipOpenTelemetrySetup) {
|
|
58
66
|
tracer.setupOpenTelemetryTracer();
|
|
59
67
|
}
|