@sentry/elysia 10.53.1 → 10.55.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/sdk.js +8 -34
- package/build/cjs/sdk.js.map +1 -1
- package/build/cjs/withElysia.js +55 -146
- package/build/cjs/withElysia.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/sdk.js +8 -34
- package/build/esm/sdk.js.map +1 -1
- package/build/esm/withElysia.js +55 -146
- package/build/esm/withElysia.js.map +1 -1
- package/build/types/withElysia.d.ts +2 -2
- package/build/types/withElysia.d.ts.map +1 -1
- package/build/types-ts3.8/withElysia.d.ts +2 -2
- package/package.json +3 -3
package/build/cjs/sdk.js
CHANGED
|
@@ -4,53 +4,27 @@ const os = require('node:os');
|
|
|
4
4
|
const bun = require('@sentry/bun');
|
|
5
5
|
const core = require('@sentry/core');
|
|
6
6
|
|
|
7
|
-
/** Get the default integrations for the Elysia SDK. */
|
|
8
7
|
function getDefaultIntegrations(_options) {
|
|
9
|
-
|
|
10
|
-
// Elysia already produces an HTTP server span, so we don't need Bun's competing root span.
|
|
11
|
-
return bun.getDefaultIntegrations(_options).filter(i => i.name !== bun.bunServerIntegration().name);
|
|
8
|
+
return bun.getDefaultIntegrations(_options).filter((i) => i.name !== bun.bunServerIntegration().name);
|
|
12
9
|
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Get the runtime name and version.
|
|
16
|
-
*/
|
|
17
10
|
function getRuntime() {
|
|
18
|
-
if (typeof Bun !==
|
|
19
|
-
return { name:
|
|
11
|
+
if (typeof Bun !== "undefined") {
|
|
12
|
+
return { name: "bun", version: Bun.version };
|
|
20
13
|
}
|
|
21
|
-
|
|
22
|
-
return { name: 'node', version: process.version };
|
|
14
|
+
return { name: "node", version: process.version };
|
|
23
15
|
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Initializes the Sentry Elysia SDK.
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```javascript
|
|
30
|
-
* import * as Sentry from '@sentry/elysia';
|
|
31
|
-
*
|
|
32
|
-
* Sentry.init({
|
|
33
|
-
* dsn: '__DSN__',
|
|
34
|
-
* tracesSampleRate: 1.0,
|
|
35
|
-
* });
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
16
|
function init(userOptions = {}) {
|
|
39
17
|
const options = {
|
|
40
18
|
...userOptions,
|
|
41
|
-
platform:
|
|
19
|
+
platform: "javascript",
|
|
42
20
|
runtime: getRuntime(),
|
|
43
|
-
serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname()
|
|
21
|
+
serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname()
|
|
44
22
|
};
|
|
45
|
-
|
|
46
|
-
core.applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]);
|
|
47
|
-
|
|
23
|
+
core.applySdkMetadata(userOptions, "elysia", ["elysia", options.runtime.name]);
|
|
48
24
|
options.transport = options.transport || bun.makeFetchTransport;
|
|
49
|
-
|
|
50
|
-
if (options.defaultIntegrations === undefined) {
|
|
25
|
+
if (options.defaultIntegrations === void 0) {
|
|
51
26
|
options.defaultIntegrations = getDefaultIntegrations(options);
|
|
52
27
|
}
|
|
53
|
-
|
|
54
28
|
return bun.init(options);
|
|
55
29
|
}
|
|
56
30
|
|
package/build/cjs/sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","sources":["../../src/sdk.ts"],"sourcesContent":["import * as os from 'node:os';\nimport {\n bunServerIntegration,\n getDefaultIntegrations as getBunDefaultIntegrations,\n makeFetchTransport,\n} from '@sentry/bun';\nimport type { Integration, Options } from '@sentry/core';\nimport { applySdkMetadata } from '@sentry/core';\nimport { init as initNode, type NodeClient } from '@sentry/bun';\nimport type { ElysiaOptions } from './types';\n\n/** Get the default integrations for the Elysia SDK. */\nexport function getDefaultIntegrations(_options: Options): Integration[] {\n // Filter out bunServerIntegration\n // Elysia already produces an HTTP server span, so we don't need Bun's competing root span.\n return getBunDefaultIntegrations(_options).filter(i => i.name !== bunServerIntegration().name);\n}\n\n/**\n * Get the runtime name and version.\n */\nfunction getRuntime(): { name: string; version: string } {\n if (typeof Bun !== 'undefined') {\n return { name: 'bun', version: Bun.version };\n }\n\n return { name: 'node', version: process.version };\n}\n\n/**\n * Initializes the Sentry Elysia SDK.\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n *\n * Sentry.init({\n * dsn: '__DSN__',\n * tracesSampleRate: 1.0,\n * });\n * ```\n */\nexport function init(userOptions: ElysiaOptions = {}): NodeClient | undefined {\n const options = {\n ...userOptions,\n platform: 'javascript',\n runtime: getRuntime(),\n serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(),\n };\n\n applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]);\n\n options.transport = options.transport || makeFetchTransport;\n\n if (options.defaultIntegrations === undefined) {\n options.defaultIntegrations = getDefaultIntegrations(options);\n }\n\n return initNode(options);\n}\n"],"names":["getBunDefaultIntegrations","bunServerIntegration","applySdkMetadata","makeFetchTransport","initNode"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"sdk.js","sources":["../../src/sdk.ts"],"sourcesContent":["import * as os from 'node:os';\nimport {\n bunServerIntegration,\n getDefaultIntegrations as getBunDefaultIntegrations,\n makeFetchTransport,\n} from '@sentry/bun';\nimport type { Integration, Options } from '@sentry/core';\nimport { applySdkMetadata } from '@sentry/core';\nimport { init as initNode, type NodeClient } from '@sentry/bun';\nimport type { ElysiaOptions } from './types';\n\n/** Get the default integrations for the Elysia SDK. */\nexport function getDefaultIntegrations(_options: Options): Integration[] {\n // Filter out bunServerIntegration\n // Elysia already produces an HTTP server span, so we don't need Bun's competing root span.\n return getBunDefaultIntegrations(_options).filter(i => i.name !== bunServerIntegration().name);\n}\n\n/**\n * Get the runtime name and version.\n */\nfunction getRuntime(): { name: string; version: string } {\n if (typeof Bun !== 'undefined') {\n return { name: 'bun', version: Bun.version };\n }\n\n return { name: 'node', version: process.version };\n}\n\n/**\n * Initializes the Sentry Elysia SDK.\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n *\n * Sentry.init({\n * dsn: '__DSN__',\n * tracesSampleRate: 1.0,\n * });\n * ```\n */\nexport function init(userOptions: ElysiaOptions = {}): NodeClient | undefined {\n const options = {\n ...userOptions,\n platform: 'javascript',\n runtime: getRuntime(),\n serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(),\n };\n\n applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]);\n\n options.transport = options.transport || makeFetchTransport;\n\n if (options.defaultIntegrations === undefined) {\n options.defaultIntegrations = getDefaultIntegrations(options);\n }\n\n return initNode(options);\n}\n"],"names":["getBunDefaultIntegrations","bunServerIntegration","applySdkMetadata","makeFetchTransport","initNode"],"mappings":";;;;;;AAYO,SAAS,uBAAuB,QAAA,EAAkC;AAGvE,EAAA,OAAOA,0BAAA,CAA0B,QAAQ,CAAA,CAAE,MAAA,CAAO,OAAK,CAAA,CAAE,IAAA,KAASC,wBAAA,EAAqB,CAAE,IAAI,CAAA;AAC/F;AAKA,SAAS,UAAA,GAAgD;AACvD,EAAA,IAAI,OAAO,QAAQ,WAAA,EAAa;AAC9B,IAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,IAAI,OAAA,EAAQ;AAAA,EAC7C;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,QAAQ,OAAA,EAAQ;AAClD;AAeO,SAAS,IAAA,CAAK,WAAA,GAA6B,EAAC,EAA2B;AAC5E,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,GAAG,WAAA;AAAA,IACH,QAAA,EAAU,YAAA;AAAA,IACV,SAAS,UAAA,EAAW;AAAA,IACpB,UAAA,EAAY,YAAY,UAAA,IAAc,MAAA,CAAO,QAAQ,GAAA,CAAI,WAAA,IAAe,GAAG,QAAA;AAAS,GACtF;AAEA,EAAAC,qBAAA,CAAiB,aAAa,QAAA,EAAU,CAAC,UAAU,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAC,CAAA;AAExE,EAAA,OAAA,CAAQ,SAAA,GAAY,QAAQ,SAAA,IAAaC,sBAAA;AAEzC,EAAA,IAAI,OAAA,CAAQ,wBAAwB,MAAA,EAAW;AAC7C,IAAA,OAAA,CAAQ,mBAAA,GAAsB,uBAAuB,OAAO,CAAA;AAAA,EAC9D;AAEA,EAAA,OAAOC,SAAS,OAAO,CAAA;AACzB;;;;;"}
|
package/build/cjs/withElysia.js
CHANGED
|
@@ -2,195 +2,125 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
|
|
3
3
|
const core = require('@sentry/core');
|
|
4
4
|
|
|
5
|
-
const ELYSIA_ORIGIN =
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Map Elysia lifecycle phase names to Sentry span ops.
|
|
9
|
-
*/
|
|
5
|
+
const ELYSIA_ORIGIN = "auto.http.elysia";
|
|
10
6
|
const ELYSIA_LIFECYCLE_OP_MAP = {
|
|
11
|
-
Request:
|
|
12
|
-
Parse:
|
|
13
|
-
Transform:
|
|
14
|
-
BeforeHandle:
|
|
15
|
-
Handle:
|
|
16
|
-
AfterHandle:
|
|
17
|
-
MapResponse:
|
|
18
|
-
AfterResponse:
|
|
19
|
-
Error:
|
|
7
|
+
Request: "middleware.elysia",
|
|
8
|
+
Parse: "middleware.elysia",
|
|
9
|
+
Transform: "middleware.elysia",
|
|
10
|
+
BeforeHandle: "middleware.elysia",
|
|
11
|
+
Handle: "request_handler.elysia",
|
|
12
|
+
AfterHandle: "middleware.elysia",
|
|
13
|
+
MapResponse: "middleware.elysia",
|
|
14
|
+
AfterResponse: "middleware.elysia",
|
|
15
|
+
Error: "middleware.elysia"
|
|
20
16
|
};
|
|
21
|
-
|
|
22
17
|
function isBun() {
|
|
23
|
-
return typeof Bun !==
|
|
18
|
+
return typeof Bun !== "undefined";
|
|
24
19
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* Per-request storage for the root span reference.
|
|
28
|
-
* .wrap() captures the root span and .trace() reads it.
|
|
29
|
-
* This is necessary because Elysia's .trace() callbacks may run in a different
|
|
30
|
-
* async context where getActiveSpan() returns undefined.
|
|
31
|
-
*/
|
|
32
|
-
const rootSpanForRequest = new WeakMap();
|
|
33
|
-
|
|
34
|
-
const instrumentedApps = new WeakSet();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Updates the root span and isolation scope with the parameterized route name.
|
|
38
|
-
*/
|
|
20
|
+
const rootSpanForRequest = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
const instrumentedApps = /* @__PURE__ */ new WeakSet();
|
|
39
22
|
function updateRouteTransactionName(request, method, route) {
|
|
40
23
|
const transactionName = `${method} ${route}`;
|
|
41
|
-
|
|
42
|
-
// Try the stored root span first (reliable across async contexts),
|
|
43
|
-
// then fall back to getActiveSpan() for cases where async context is preserved.
|
|
44
24
|
const rootSpan = rootSpanForRequest.get(request);
|
|
45
25
|
if (rootSpan) {
|
|
46
26
|
core.updateSpanName(rootSpan, transactionName);
|
|
47
|
-
rootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
|
27
|
+
rootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route");
|
|
48
28
|
} else {
|
|
49
29
|
const activeSpan = core.getActiveSpan();
|
|
50
30
|
if (activeSpan) {
|
|
51
31
|
const root = core.getRootSpan(activeSpan);
|
|
52
32
|
core.updateSpanName(root, transactionName);
|
|
53
|
-
root.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
|
33
|
+
root.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route");
|
|
54
34
|
}
|
|
55
35
|
}
|
|
56
|
-
|
|
57
36
|
core.getIsolationScope().setTransactionName(transactionName);
|
|
58
37
|
}
|
|
59
|
-
|
|
60
38
|
function defaultShouldHandleError(context) {
|
|
61
39
|
const status = context.set.status;
|
|
62
|
-
if (status ===
|
|
40
|
+
if (status === void 0) {
|
|
63
41
|
return true;
|
|
64
42
|
}
|
|
65
43
|
const statusCode = parseInt(String(status), 10);
|
|
66
44
|
if (Number.isNaN(statusCode)) {
|
|
67
45
|
return true;
|
|
68
46
|
}
|
|
69
|
-
// Capture server errors (5xx) and unusual status codes (<= 299 in an error handler).
|
|
70
|
-
// 3xx and 4xx are not captured by default (client errors / redirects).
|
|
71
47
|
return statusCode >= 500 || statusCode <= 299;
|
|
72
48
|
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Instruments a single Elysia lifecycle phase by creating a Sentry span for it,
|
|
76
|
-
* and child spans for each individual handler within the phase.
|
|
77
|
-
*
|
|
78
|
-
* @param rootSpan - The root server span to parent lifecycle spans under.
|
|
79
|
-
* Must be passed explicitly because Elysia's .trace() listener callbacks run
|
|
80
|
-
* in a different async context where getActiveSpan() returns undefined.
|
|
81
|
-
*/
|
|
82
49
|
function instrumentLifecyclePhase(phaseName, listener, rootSpan) {
|
|
83
50
|
const op = ELYSIA_LIFECYCLE_OP_MAP[phaseName];
|
|
84
51
|
if (!op) {
|
|
85
52
|
return;
|
|
86
53
|
}
|
|
87
|
-
|
|
88
|
-
void listener(process => {
|
|
54
|
+
void listener((process) => {
|
|
89
55
|
const phaseSpan = core.startInactiveSpan({
|
|
90
56
|
name: phaseName,
|
|
91
57
|
parentSpan: rootSpan,
|
|
92
58
|
attributes: {
|
|
93
59
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
94
|
-
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
95
|
-
}
|
|
60
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
61
|
+
}
|
|
96
62
|
});
|
|
97
|
-
|
|
98
|
-
// Create child spans for individual handlers within this phase.
|
|
99
|
-
// Named functions get their name, arrow functions get 'anonymous'.
|
|
100
63
|
if (process.total > 0) {
|
|
101
|
-
void process.onEvent(child => {
|
|
102
|
-
const handlerName = child.name ||
|
|
64
|
+
void process.onEvent((child) => {
|
|
65
|
+
const handlerName = child.name || "anonymous";
|
|
103
66
|
const childSpan = core.startInactiveSpan({
|
|
104
67
|
name: handlerName,
|
|
105
68
|
parentSpan: phaseSpan,
|
|
106
69
|
attributes: {
|
|
107
70
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
108
|
-
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
109
|
-
}
|
|
71
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
72
|
+
}
|
|
110
73
|
});
|
|
111
|
-
|
|
112
74
|
void child.onStop(() => {
|
|
113
75
|
childSpan.end();
|
|
114
76
|
});
|
|
115
77
|
});
|
|
116
78
|
}
|
|
117
|
-
|
|
118
79
|
void process.onStop(() => {
|
|
119
80
|
phaseSpan.end();
|
|
120
81
|
});
|
|
121
82
|
});
|
|
122
83
|
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Integrate Sentry with an Elysia app for error handling, request context,
|
|
126
|
-
* and tracing. Returns the app instance for chaining.
|
|
127
|
-
*
|
|
128
|
-
* Should be called at the **start** of the chain before defining routes.
|
|
129
|
-
*
|
|
130
|
-
* @param app The Elysia instance
|
|
131
|
-
* @param options Configuration options
|
|
132
|
-
* @returns The same Elysia instance for chaining
|
|
133
|
-
*
|
|
134
|
-
* @example
|
|
135
|
-
* ```javascript
|
|
136
|
-
* import * as Sentry from '@sentry/elysia';
|
|
137
|
-
* import { Elysia } from 'elysia';
|
|
138
|
-
*
|
|
139
|
-
* Sentry.withElysia(new Elysia())
|
|
140
|
-
* .get('/', () => 'Hello World')
|
|
141
|
-
* .listen(3000);
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
84
|
function withElysia(app, options = {}) {
|
|
145
85
|
if (instrumentedApps.has(app)) {
|
|
146
86
|
return app;
|
|
147
87
|
}
|
|
148
88
|
instrumentedApps.add(app);
|
|
149
|
-
|
|
150
|
-
// Use .wrap() to capture or create the root span for each request.
|
|
151
|
-
// This is necessary because Elysia's .trace() callbacks run in a different
|
|
152
|
-
// async context where getActiveSpan() returns undefined. By storing the root
|
|
153
|
-
// span in a WeakMap keyed by the Request object, we can retrieve it in .trace().
|
|
154
|
-
// HigherOrderFunction type is not exported from elysia's main entry point,
|
|
155
|
-
// so we type the callback parameters directly.
|
|
156
89
|
app.wrap((fn, request) => {
|
|
157
90
|
if (isBun()) {
|
|
158
|
-
// On Bun there is no HTTP instrumentation, so we create a root span ourselves.
|
|
159
|
-
// Scope setup must happen inside the returned function so that it's active
|
|
160
|
-
// when Elysia calls the handler (not during .wrap() registration).
|
|
161
91
|
return (...args) => {
|
|
162
92
|
return core.withIsolationScope(() => {
|
|
163
93
|
return core.continueTrace(
|
|
164
94
|
{
|
|
165
|
-
sentryTrace: request.headers.get(
|
|
166
|
-
baggage: request.headers.get(
|
|
95
|
+
sentryTrace: request.headers.get("sentry-trace") || "",
|
|
96
|
+
baggage: request.headers.get("baggage")
|
|
167
97
|
},
|
|
168
98
|
() => {
|
|
169
99
|
return core.startSpanManual(
|
|
170
100
|
{
|
|
171
|
-
op:
|
|
101
|
+
op: "http.server",
|
|
172
102
|
name: `${request.method} ${new URL(request.url).pathname}`,
|
|
173
103
|
attributes: {
|
|
174
104
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,
|
|
175
|
-
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]:
|
|
176
|
-
}
|
|
105
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url"
|
|
106
|
+
}
|
|
177
107
|
},
|
|
178
|
-
rootSpan => {
|
|
108
|
+
(rootSpan) => {
|
|
179
109
|
rootSpanForRequest.set(request, rootSpan);
|
|
180
110
|
try {
|
|
181
111
|
const result = fn(...args);
|
|
182
112
|
if (result instanceof Promise) {
|
|
183
113
|
return result.then(
|
|
184
|
-
res => {
|
|
114
|
+
(res) => {
|
|
185
115
|
rootSpanForRequest.delete(request);
|
|
186
116
|
rootSpan.end();
|
|
187
117
|
return res;
|
|
188
118
|
},
|
|
189
|
-
err => {
|
|
119
|
+
(err) => {
|
|
190
120
|
rootSpanForRequest.delete(request);
|
|
191
121
|
rootSpan.end();
|
|
192
122
|
throw err;
|
|
193
|
-
}
|
|
123
|
+
}
|
|
194
124
|
);
|
|
195
125
|
}
|
|
196
126
|
rootSpanForRequest.delete(request);
|
|
@@ -201,97 +131,76 @@ function withElysia(app, options = {}) {
|
|
|
201
131
|
rootSpan.end();
|
|
202
132
|
throw err;
|
|
203
133
|
}
|
|
204
|
-
}
|
|
134
|
+
}
|
|
205
135
|
);
|
|
206
|
-
}
|
|
136
|
+
}
|
|
207
137
|
);
|
|
208
138
|
});
|
|
209
139
|
};
|
|
210
140
|
}
|
|
211
|
-
|
|
212
|
-
// On Node.js, the HTTP instrumentation already creates a root span.
|
|
213
|
-
// We just capture its reference so .trace() can use it.
|
|
214
141
|
const activeSpan = core.getActiveSpan();
|
|
215
142
|
if (activeSpan) {
|
|
216
143
|
rootSpanForRequest.set(request, core.getRootSpan(activeSpan));
|
|
217
144
|
}
|
|
218
145
|
return fn;
|
|
219
146
|
});
|
|
220
|
-
|
|
221
|
-
// Use .trace() ONLY for span creation. The trace API is observational —
|
|
222
|
-
// callbacks fire after phases complete, so they can't reliably mutate
|
|
223
|
-
// response headers or capture errors. All SDK logic stays in real hooks.
|
|
224
|
-
const traceHandler = lifecycle => {
|
|
147
|
+
const traceHandler = (lifecycle) => {
|
|
225
148
|
const rootSpan = rootSpanForRequest.get(lifecycle.context.request);
|
|
226
|
-
|
|
227
149
|
const phases = [
|
|
228
|
-
[
|
|
229
|
-
[
|
|
230
|
-
[
|
|
231
|
-
[
|
|
232
|
-
[
|
|
233
|
-
[
|
|
234
|
-
[
|
|
235
|
-
[
|
|
236
|
-
[
|
|
150
|
+
["Request", lifecycle.onRequest],
|
|
151
|
+
["Parse", lifecycle.onParse],
|
|
152
|
+
["Transform", lifecycle.onTransform],
|
|
153
|
+
["BeforeHandle", lifecycle.onBeforeHandle],
|
|
154
|
+
["Handle", lifecycle.onHandle],
|
|
155
|
+
["AfterHandle", lifecycle.onAfterHandle],
|
|
156
|
+
["MapResponse", lifecycle.onMapResponse],
|
|
157
|
+
["AfterResponse", lifecycle.onAfterResponse],
|
|
158
|
+
["Error", lifecycle.onError]
|
|
237
159
|
];
|
|
238
|
-
|
|
239
160
|
for (const [phaseName, listener] of phases) {
|
|
240
161
|
if (listener) {
|
|
241
162
|
instrumentLifecyclePhase(phaseName, listener, rootSpan);
|
|
242
163
|
}
|
|
243
164
|
}
|
|
244
165
|
};
|
|
245
|
-
|
|
246
|
-
app.trace({ as: 'global' }, traceHandler);
|
|
247
|
-
|
|
248
|
-
// SDK logic uses real lifecycle hooks — these show up as handler spans
|
|
249
|
-
// in the trace (named sentryOnRequest etc.), but that's the correct
|
|
250
|
-
// tradeoff: the trace API can't reliably mutate state or capture errors.
|
|
251
|
-
|
|
166
|
+
app.trace({ as: "global" }, traceHandler);
|
|
252
167
|
app.onRequest(function sentryOnRequest(context) {
|
|
253
168
|
core.getIsolationScope().setSDKProcessingMetadata({
|
|
254
|
-
normalizedRequest: core.winterCGRequestToRequestData(context.request)
|
|
169
|
+
normalizedRequest: core.winterCGRequestToRequestData(context.request)
|
|
255
170
|
});
|
|
256
171
|
});
|
|
257
|
-
|
|
258
|
-
|
|
172
|
+
const elysiaApp = app;
|
|
173
|
+
elysiaApp.onAfterHandle({ as: "global" }, function sentryOnAfterHandle(context) {
|
|
259
174
|
if (context.route) {
|
|
260
175
|
updateRouteTransactionName(context.request, context.request.method, context.route);
|
|
261
176
|
}
|
|
262
|
-
|
|
263
177
|
const traceData = core.getTraceData();
|
|
264
|
-
if (traceData[
|
|
265
|
-
context.set.headers[
|
|
178
|
+
if (traceData["sentry-trace"]) {
|
|
179
|
+
context.set.headers["sentry-trace"] = traceData["sentry-trace"];
|
|
266
180
|
}
|
|
267
181
|
if (traceData.baggage) {
|
|
268
|
-
context.set.headers[
|
|
182
|
+
context.set.headers["baggage"] = traceData.baggage;
|
|
269
183
|
}
|
|
270
184
|
});
|
|
271
|
-
|
|
272
|
-
app.onError({ as: 'global' }, function sentryOnError(context) {
|
|
185
|
+
elysiaApp.onError({ as: "global" }, function sentryOnError(context) {
|
|
273
186
|
if (context.route) {
|
|
274
187
|
updateRouteTransactionName(context.request, context.request.method, context.route);
|
|
275
188
|
}
|
|
276
|
-
|
|
277
|
-
// Set error status on root span
|
|
278
189
|
const rootSpan = rootSpanForRequest.get(context.request);
|
|
279
190
|
if (rootSpan) {
|
|
280
191
|
const statusCode = parseInt(String(context.set.status), 10);
|
|
281
192
|
core.setHttpStatus(rootSpan, Number.isNaN(statusCode) ? 500 : statusCode);
|
|
282
193
|
}
|
|
283
|
-
|
|
284
194
|
const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;
|
|
285
195
|
if (shouldHandleError(context)) {
|
|
286
196
|
core.captureException(context.error, {
|
|
287
197
|
mechanism: {
|
|
288
|
-
type:
|
|
289
|
-
handled: false
|
|
290
|
-
}
|
|
198
|
+
type: "auto.http.elysia.on_error",
|
|
199
|
+
handled: false
|
|
200
|
+
}
|
|
291
201
|
});
|
|
292
202
|
}
|
|
293
203
|
});
|
|
294
|
-
|
|
295
204
|
return app;
|
|
296
205
|
}
|
|
297
206
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withElysia.js","sources":["../../src/withElysia.ts"],"sourcesContent":["import type { Span } from '@sentry/core';\nimport {\n captureException,\n continueTrace,\n getActiveSpan,\n getIsolationScope,\n getRootSpan,\n getTraceData,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n setHttpStatus,\n startInactiveSpan,\n startSpanManual,\n updateSpanName,\n winterCGRequestToRequestData,\n withIsolationScope,\n} from '@sentry/core';\nimport type { Elysia, ErrorContext, TraceHandler, TraceListener } from 'elysia';\n\ninterface ElysiaHandlerOptions {\n shouldHandleError?: (context: ErrorContext) => boolean;\n}\n\nconst ELYSIA_ORIGIN = 'auto.http.elysia';\n\n/**\n * Map Elysia lifecycle phase names to Sentry span ops.\n */\nconst ELYSIA_LIFECYCLE_OP_MAP: Record<string, string> = {\n Request: 'middleware.elysia',\n Parse: 'middleware.elysia',\n Transform: 'middleware.elysia',\n BeforeHandle: 'middleware.elysia',\n Handle: 'request_handler.elysia',\n AfterHandle: 'middleware.elysia',\n MapResponse: 'middleware.elysia',\n AfterResponse: 'middleware.elysia',\n Error: 'middleware.elysia',\n};\n\nfunction isBun(): boolean {\n return typeof Bun !== 'undefined';\n}\n\n/**\n * Per-request storage for the root span reference.\n * .wrap() captures the root span and .trace() reads it.\n * This is necessary because Elysia's .trace() callbacks may run in a different\n * async context where getActiveSpan() returns undefined.\n */\nconst rootSpanForRequest = new WeakMap<Request, Span>();\n\nconst instrumentedApps = new WeakSet<Elysia>();\n\n/**\n * Updates the root span and isolation scope with the parameterized route name.\n */\nfunction updateRouteTransactionName(request: Request, method: string, route: string): void {\n const transactionName = `${method} ${route}`;\n\n // Try the stored root span first (reliable across async contexts),\n // then fall back to getActiveSpan() for cases where async context is preserved.\n const rootSpan = rootSpanForRequest.get(request);\n if (rootSpan) {\n updateSpanName(rootSpan, transactionName);\n rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n } else {\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n const root = getRootSpan(activeSpan);\n updateSpanName(root, transactionName);\n root.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n }\n\n getIsolationScope().setTransactionName(transactionName);\n}\n\nfunction defaultShouldHandleError(context: ErrorContext): boolean {\n const status = context.set.status;\n if (status === undefined) {\n return true;\n }\n const statusCode = parseInt(String(status), 10);\n if (Number.isNaN(statusCode)) {\n return true;\n }\n // Capture server errors (5xx) and unusual status codes (<= 299 in an error handler).\n // 3xx and 4xx are not captured by default (client errors / redirects).\n return statusCode >= 500 || statusCode <= 299;\n}\n\n/**\n * Instruments a single Elysia lifecycle phase by creating a Sentry span for it,\n * and child spans for each individual handler within the phase.\n *\n * @param rootSpan - The root server span to parent lifecycle spans under.\n * Must be passed explicitly because Elysia's .trace() listener callbacks run\n * in a different async context where getActiveSpan() returns undefined.\n */\nfunction instrumentLifecyclePhase(phaseName: string, listener: TraceListener, rootSpan: Span | undefined): void {\n const op = ELYSIA_LIFECYCLE_OP_MAP[phaseName];\n if (!op) {\n return;\n }\n\n void listener(process => {\n const phaseSpan = startInactiveSpan({\n name: phaseName,\n parentSpan: rootSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n // Create child spans for individual handlers within this phase.\n // Named functions get their name, arrow functions get 'anonymous'.\n if (process.total > 0) {\n void process.onEvent(child => {\n const handlerName = child.name || 'anonymous';\n const childSpan = startInactiveSpan({\n name: handlerName,\n parentSpan: phaseSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n void child.onStop(() => {\n childSpan.end();\n });\n });\n }\n\n void process.onStop(() => {\n phaseSpan.end();\n });\n });\n}\n\n/**\n * Integrate Sentry with an Elysia app for error handling, request context,\n * and tracing. Returns the app instance for chaining.\n *\n * Should be called at the **start** of the chain before defining routes.\n *\n * @param app The Elysia instance\n * @param options Configuration options\n * @returns The same Elysia instance for chaining\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n * import { Elysia } from 'elysia';\n *\n * Sentry.withElysia(new Elysia())\n * .get('/', () => 'Hello World')\n * .listen(3000);\n * ```\n */\nexport function withElysia<T extends Elysia>(app: T, options: ElysiaHandlerOptions = {}): T {\n if (instrumentedApps.has(app)) {\n return app;\n }\n instrumentedApps.add(app);\n\n // Use .wrap() to capture or create the root span for each request.\n // This is necessary because Elysia's .trace() callbacks run in a different\n // async context where getActiveSpan() returns undefined. By storing the root\n // span in a WeakMap keyed by the Request object, we can retrieve it in .trace().\n // HigherOrderFunction type is not exported from elysia's main entry point,\n // so we type the callback parameters directly.\n app.wrap((fn: (...args: unknown[]) => unknown, request: Request) => {\n if (isBun()) {\n // On Bun there is no HTTP instrumentation, so we create a root span ourselves.\n // Scope setup must happen inside the returned function so that it's active\n // when Elysia calls the handler (not during .wrap() registration).\n return (...args: unknown[]) => {\n return withIsolationScope(() => {\n return continueTrace(\n {\n sentryTrace: request.headers.get('sentry-trace') || '',\n baggage: request.headers.get('baggage'),\n },\n () => {\n return startSpanManual(\n {\n op: 'http.server',\n name: `${request.method} ${new URL(request.url).pathname}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n },\n },\n rootSpan => {\n rootSpanForRequest.set(request, rootSpan);\n try {\n const result = fn(...args);\n if (result instanceof Promise) {\n return result.then(\n res => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return res;\n },\n err => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n },\n );\n }\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return result;\n } catch (err) {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n }\n },\n );\n },\n );\n });\n };\n }\n\n // On Node.js, the HTTP instrumentation already creates a root span.\n // We just capture its reference so .trace() can use it.\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n rootSpanForRequest.set(request, getRootSpan(activeSpan));\n }\n return fn;\n });\n\n // Use .trace() ONLY for span creation. The trace API is observational —\n // callbacks fire after phases complete, so they can't reliably mutate\n // response headers or capture errors. All SDK logic stays in real hooks.\n const traceHandler: TraceHandler = lifecycle => {\n const rootSpan = rootSpanForRequest.get(lifecycle.context.request);\n\n const phases: [string, TraceListener][] = [\n ['Request', lifecycle.onRequest],\n ['Parse', lifecycle.onParse],\n ['Transform', lifecycle.onTransform],\n ['BeforeHandle', lifecycle.onBeforeHandle],\n ['Handle', lifecycle.onHandle],\n ['AfterHandle', lifecycle.onAfterHandle],\n ['MapResponse', lifecycle.onMapResponse],\n ['AfterResponse', lifecycle.onAfterResponse],\n ['Error', lifecycle.onError],\n ];\n\n for (const [phaseName, listener] of phases) {\n if (listener) {\n instrumentLifecyclePhase(phaseName, listener, rootSpan);\n }\n }\n };\n\n app.trace({ as: 'global' }, traceHandler);\n\n // SDK logic uses real lifecycle hooks — these show up as handler spans\n // in the trace (named sentryOnRequest etc.), but that's the correct\n // tradeoff: the trace API can't reliably mutate state or capture errors.\n\n app.onRequest(function sentryOnRequest(context) {\n getIsolationScope().setSDKProcessingMetadata({\n normalizedRequest: winterCGRequestToRequestData(context.request),\n });\n });\n\n app.onAfterHandle({ as: 'global' }, function sentryOnAfterHandle(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n const traceData = getTraceData();\n if (traceData['sentry-trace']) {\n context.set.headers['sentry-trace'] = traceData['sentry-trace'];\n }\n if (traceData.baggage) {\n context.set.headers['baggage'] = traceData.baggage;\n }\n });\n\n app.onError({ as: 'global' }, function sentryOnError(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n // Set error status on root span\n const rootSpan = rootSpanForRequest.get(context.request);\n if (rootSpan) {\n const statusCode = parseInt(String(context.set.status), 10);\n setHttpStatus(rootSpan, Number.isNaN(statusCode) ? 500 : statusCode);\n }\n\n const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;\n if (shouldHandleError(context)) {\n captureException(context.error, {\n mechanism: {\n type: 'auto.http.elysia.on_error',\n handled: false,\n },\n });\n }\n });\n\n return app;\n}\n"],"names":["updateSpanName","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","getActiveSpan","getRootSpan","getIsolationScope","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","withIsolationScope","continueTrace","startSpanManual","winterCGRequestToRequestData","getTraceData","setHttpStatus","captureException"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"withElysia.js","sources":["../../src/withElysia.ts"],"sourcesContent":["import type { Span } from '@sentry/core';\nimport {\n captureException,\n continueTrace,\n getActiveSpan,\n getIsolationScope,\n getRootSpan,\n getTraceData,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n setHttpStatus,\n startInactiveSpan,\n startSpanManual,\n updateSpanName,\n winterCGRequestToRequestData,\n withIsolationScope,\n} from '@sentry/core';\nimport type { AnyElysia, Elysia, ErrorContext, TraceHandler, TraceListener } from 'elysia';\n\ninterface ElysiaHandlerOptions {\n shouldHandleError?: (context: ErrorContext) => boolean;\n}\n\nconst ELYSIA_ORIGIN = 'auto.http.elysia';\n\n/**\n * Map Elysia lifecycle phase names to Sentry span ops.\n */\nconst ELYSIA_LIFECYCLE_OP_MAP: Record<string, string> = {\n Request: 'middleware.elysia',\n Parse: 'middleware.elysia',\n Transform: 'middleware.elysia',\n BeforeHandle: 'middleware.elysia',\n Handle: 'request_handler.elysia',\n AfterHandle: 'middleware.elysia',\n MapResponse: 'middleware.elysia',\n AfterResponse: 'middleware.elysia',\n Error: 'middleware.elysia',\n};\n\nfunction isBun(): boolean {\n return typeof Bun !== 'undefined';\n}\n\n/**\n * Per-request storage for the root span reference.\n * .wrap() captures the root span and .trace() reads it.\n * This is necessary because Elysia's .trace() callbacks may run in a different\n * async context where getActiveSpan() returns undefined.\n */\nconst rootSpanForRequest = new WeakMap<Request, Span>();\n\nconst instrumentedApps = new WeakSet<Elysia>();\n\n/**\n * Updates the root span and isolation scope with the parameterized route name.\n */\nfunction updateRouteTransactionName(request: Request, method: string, route: string): void {\n const transactionName = `${method} ${route}`;\n\n // Try the stored root span first (reliable across async contexts),\n // then fall back to getActiveSpan() for cases where async context is preserved.\n const rootSpan = rootSpanForRequest.get(request);\n if (rootSpan) {\n updateSpanName(rootSpan, transactionName);\n rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n } else {\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n const root = getRootSpan(activeSpan);\n updateSpanName(root, transactionName);\n root.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n }\n\n getIsolationScope().setTransactionName(transactionName);\n}\n\nfunction defaultShouldHandleError(context: ErrorContext): boolean {\n const status = context.set.status;\n if (status === undefined) {\n return true;\n }\n const statusCode = parseInt(String(status), 10);\n if (Number.isNaN(statusCode)) {\n return true;\n }\n // Capture server errors (5xx) and unusual status codes (<= 299 in an error handler).\n // 3xx and 4xx are not captured by default (client errors / redirects).\n return statusCode >= 500 || statusCode <= 299;\n}\n\n/**\n * Instruments a single Elysia lifecycle phase by creating a Sentry span for it,\n * and child spans for each individual handler within the phase.\n *\n * @param rootSpan - The root server span to parent lifecycle spans under.\n * Must be passed explicitly because Elysia's .trace() listener callbacks run\n * in a different async context where getActiveSpan() returns undefined.\n */\nfunction instrumentLifecyclePhase(phaseName: string, listener: TraceListener, rootSpan: Span | undefined): void {\n const op = ELYSIA_LIFECYCLE_OP_MAP[phaseName];\n if (!op) {\n return;\n }\n\n void listener(process => {\n const phaseSpan = startInactiveSpan({\n name: phaseName,\n parentSpan: rootSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n // Create child spans for individual handlers within this phase.\n // Named functions get their name, arrow functions get 'anonymous'.\n if (process.total > 0) {\n void process.onEvent(child => {\n const handlerName = child.name || 'anonymous';\n const childSpan = startInactiveSpan({\n name: handlerName,\n parentSpan: phaseSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n void child.onStop(() => {\n childSpan.end();\n });\n });\n }\n\n void process.onStop(() => {\n phaseSpan.end();\n });\n });\n}\n\n/**\n * Integrate Sentry with an Elysia app for error handling, request context,\n * and tracing. Returns the app instance for chaining.\n *\n * Should be called at the **start** of the chain before defining routes.\n *\n * @param app The Elysia instance\n * @param options Configuration options\n * @returns The same Elysia instance for chaining\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n * import { Elysia } from 'elysia';\n *\n * Sentry.withElysia(new Elysia())\n * .get('/', () => 'Hello World')\n * .listen(3000);\n * ```\n */\n// Using the AnyElysia type here to allow users to pass in the full set of Elysia\n// options without type errors. The `T extends Elysia` type is too narrow to allow\n// users to pass in dynamic options like `prefix` without breaking the type system.\n// See Elysia type definition and its usage of `const in out` which forces the string\n// template literals to be fully consistent for e.g. `prefix`.\nexport function withElysia<T extends AnyElysia>(app: T, options: ElysiaHandlerOptions = {}): T {\n if (instrumentedApps.has(app)) {\n return app;\n }\n instrumentedApps.add(app);\n\n // Use .wrap() to capture or create the root span for each request.\n // This is necessary because Elysia's .trace() callbacks run in a different\n // async context where getActiveSpan() returns undefined. By storing the root\n // span in a WeakMap keyed by the Request object, we can retrieve it in .trace().\n // HigherOrderFunction type is not exported from elysia's main entry point,\n // so we type the callback parameters directly.\n app.wrap((fn: (...args: unknown[]) => unknown, request: Request) => {\n if (isBun()) {\n // On Bun there is no HTTP instrumentation, so we create a root span ourselves.\n // Scope setup must happen inside the returned function so that it's active\n // when Elysia calls the handler (not during .wrap() registration).\n return (...args: unknown[]) => {\n return withIsolationScope(() => {\n return continueTrace(\n {\n sentryTrace: request.headers.get('sentry-trace') || '',\n baggage: request.headers.get('baggage'),\n },\n () => {\n return startSpanManual(\n {\n op: 'http.server',\n name: `${request.method} ${new URL(request.url).pathname}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n },\n },\n rootSpan => {\n rootSpanForRequest.set(request, rootSpan);\n try {\n const result = fn(...args);\n if (result instanceof Promise) {\n return result.then(\n res => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return res;\n },\n err => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n },\n );\n }\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return result;\n } catch (err) {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n }\n },\n );\n },\n );\n });\n };\n }\n\n // On Node.js, the HTTP instrumentation already creates a root span.\n // We just capture its reference so .trace() can use it.\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n rootSpanForRequest.set(request, getRootSpan(activeSpan));\n }\n return fn;\n });\n\n // Use .trace() ONLY for span creation. The trace API is observational —\n // callbacks fire after phases complete, so they can't reliably mutate\n // response headers or capture errors. All SDK logic stays in real hooks.\n const traceHandler: TraceHandler = lifecycle => {\n const rootSpan = rootSpanForRequest.get(lifecycle.context.request);\n\n const phases: [string, TraceListener][] = [\n ['Request', lifecycle.onRequest],\n ['Parse', lifecycle.onParse],\n ['Transform', lifecycle.onTransform],\n ['BeforeHandle', lifecycle.onBeforeHandle],\n ['Handle', lifecycle.onHandle],\n ['AfterHandle', lifecycle.onAfterHandle],\n ['MapResponse', lifecycle.onMapResponse],\n ['AfterResponse', lifecycle.onAfterResponse],\n ['Error', lifecycle.onError],\n ];\n\n for (const [phaseName, listener] of phases) {\n if (listener) {\n instrumentLifecyclePhase(phaseName, listener, rootSpan);\n }\n }\n };\n\n app.trace({ as: 'global' }, traceHandler);\n\n // SDK logic uses real lifecycle hooks — these show up as handler spans\n // in the trace (named sentryOnRequest etc.), but that's the correct\n // tradeoff: the trace API can't reliably mutate state or capture errors.\n\n app.onRequest(function sentryOnRequest(context) {\n getIsolationScope().setSDKProcessingMetadata({\n normalizedRequest: winterCGRequestToRequestData(context.request),\n });\n });\n\n // Cast from AnyElysia to Elysia so that onAfterHandle/onError callback\n // parameters resolve to typed contexts (e.g. ErrorContext for shouldHandleError).\n // `AnyElysia` is needed on the user-facing `app` type to accept user instances with\n // arbitrary generic args.\n // The cast is safe because AnyElysia IS Elysia<any, any, ...>.\n const elysiaApp = app as unknown as Elysia;\n\n elysiaApp.onAfterHandle({ as: 'global' }, function sentryOnAfterHandle(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n const traceData = getTraceData();\n if (traceData['sentry-trace']) {\n context.set.headers['sentry-trace'] = traceData['sentry-trace'];\n }\n if (traceData.baggage) {\n context.set.headers['baggage'] = traceData.baggage;\n }\n });\n\n elysiaApp.onError({ as: 'global' }, function sentryOnError(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n // Set error status on root span\n const rootSpan = rootSpanForRequest.get(context.request);\n if (rootSpan) {\n const statusCode = parseInt(String(context.set.status), 10);\n setHttpStatus(rootSpan, Number.isNaN(statusCode) ? 500 : statusCode);\n }\n\n const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;\n if (shouldHandleError(context)) {\n captureException(context.error, {\n mechanism: {\n type: 'auto.http.elysia.on_error',\n handled: false,\n },\n });\n }\n });\n\n return app;\n}\n"],"names":["updateSpanName","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","getActiveSpan","getRootSpan","getIsolationScope","startInactiveSpan","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","withIsolationScope","continueTrace","startSpanManual","winterCGRequestToRequestData","getTraceData","setHttpStatus","captureException"],"mappings":"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"}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"10.
|
|
1
|
+
{"type":"module","version":"10.55.0","sideEffects":false}
|
package/build/esm/sdk.js
CHANGED
|
@@ -2,53 +2,27 @@ import * as os from 'node:os';
|
|
|
2
2
|
import { getDefaultIntegrations as getDefaultIntegrations$1, bunServerIntegration, makeFetchTransport, init as init$1 } from '@sentry/bun';
|
|
3
3
|
import { applySdkMetadata } from '@sentry/core';
|
|
4
4
|
|
|
5
|
-
/** Get the default integrations for the Elysia SDK. */
|
|
6
5
|
function getDefaultIntegrations(_options) {
|
|
7
|
-
|
|
8
|
-
// Elysia already produces an HTTP server span, so we don't need Bun's competing root span.
|
|
9
|
-
return getDefaultIntegrations$1(_options).filter(i => i.name !== bunServerIntegration().name);
|
|
6
|
+
return getDefaultIntegrations$1(_options).filter((i) => i.name !== bunServerIntegration().name);
|
|
10
7
|
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get the runtime name and version.
|
|
14
|
-
*/
|
|
15
8
|
function getRuntime() {
|
|
16
|
-
if (typeof Bun !==
|
|
17
|
-
return { name:
|
|
9
|
+
if (typeof Bun !== "undefined") {
|
|
10
|
+
return { name: "bun", version: Bun.version };
|
|
18
11
|
}
|
|
19
|
-
|
|
20
|
-
return { name: 'node', version: process.version };
|
|
12
|
+
return { name: "node", version: process.version };
|
|
21
13
|
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Initializes the Sentry Elysia SDK.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```javascript
|
|
28
|
-
* import * as Sentry from '@sentry/elysia';
|
|
29
|
-
*
|
|
30
|
-
* Sentry.init({
|
|
31
|
-
* dsn: '__DSN__',
|
|
32
|
-
* tracesSampleRate: 1.0,
|
|
33
|
-
* });
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
14
|
function init(userOptions = {}) {
|
|
37
15
|
const options = {
|
|
38
16
|
...userOptions,
|
|
39
|
-
platform:
|
|
17
|
+
platform: "javascript",
|
|
40
18
|
runtime: getRuntime(),
|
|
41
|
-
serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname()
|
|
19
|
+
serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname()
|
|
42
20
|
};
|
|
43
|
-
|
|
44
|
-
applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]);
|
|
45
|
-
|
|
21
|
+
applySdkMetadata(userOptions, "elysia", ["elysia", options.runtime.name]);
|
|
46
22
|
options.transport = options.transport || makeFetchTransport;
|
|
47
|
-
|
|
48
|
-
if (options.defaultIntegrations === undefined) {
|
|
23
|
+
if (options.defaultIntegrations === void 0) {
|
|
49
24
|
options.defaultIntegrations = getDefaultIntegrations(options);
|
|
50
25
|
}
|
|
51
|
-
|
|
52
26
|
return init$1(options);
|
|
53
27
|
}
|
|
54
28
|
|
package/build/esm/sdk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","sources":["../../src/sdk.ts"],"sourcesContent":["import * as os from 'node:os';\nimport {\n bunServerIntegration,\n getDefaultIntegrations as getBunDefaultIntegrations,\n makeFetchTransport,\n} from '@sentry/bun';\nimport type { Integration, Options } from '@sentry/core';\nimport { applySdkMetadata } from '@sentry/core';\nimport { init as initNode, type NodeClient } from '@sentry/bun';\nimport type { ElysiaOptions } from './types';\n\n/** Get the default integrations for the Elysia SDK. */\nexport function getDefaultIntegrations(_options: Options): Integration[] {\n // Filter out bunServerIntegration\n // Elysia already produces an HTTP server span, so we don't need Bun's competing root span.\n return getBunDefaultIntegrations(_options).filter(i => i.name !== bunServerIntegration().name);\n}\n\n/**\n * Get the runtime name and version.\n */\nfunction getRuntime(): { name: string; version: string } {\n if (typeof Bun !== 'undefined') {\n return { name: 'bun', version: Bun.version };\n }\n\n return { name: 'node', version: process.version };\n}\n\n/**\n * Initializes the Sentry Elysia SDK.\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n *\n * Sentry.init({\n * dsn: '__DSN__',\n * tracesSampleRate: 1.0,\n * });\n * ```\n */\nexport function init(userOptions: ElysiaOptions = {}): NodeClient | undefined {\n const options = {\n ...userOptions,\n platform: 'javascript',\n runtime: getRuntime(),\n serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(),\n };\n\n applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]);\n\n options.transport = options.transport || makeFetchTransport;\n\n if (options.defaultIntegrations === undefined) {\n options.defaultIntegrations = getDefaultIntegrations(options);\n }\n\n return initNode(options);\n}\n"],"names":["getBunDefaultIntegrations","initNode"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"sdk.js","sources":["../../src/sdk.ts"],"sourcesContent":["import * as os from 'node:os';\nimport {\n bunServerIntegration,\n getDefaultIntegrations as getBunDefaultIntegrations,\n makeFetchTransport,\n} from '@sentry/bun';\nimport type { Integration, Options } from '@sentry/core';\nimport { applySdkMetadata } from '@sentry/core';\nimport { init as initNode, type NodeClient } from '@sentry/bun';\nimport type { ElysiaOptions } from './types';\n\n/** Get the default integrations for the Elysia SDK. */\nexport function getDefaultIntegrations(_options: Options): Integration[] {\n // Filter out bunServerIntegration\n // Elysia already produces an HTTP server span, so we don't need Bun's competing root span.\n return getBunDefaultIntegrations(_options).filter(i => i.name !== bunServerIntegration().name);\n}\n\n/**\n * Get the runtime name and version.\n */\nfunction getRuntime(): { name: string; version: string } {\n if (typeof Bun !== 'undefined') {\n return { name: 'bun', version: Bun.version };\n }\n\n return { name: 'node', version: process.version };\n}\n\n/**\n * Initializes the Sentry Elysia SDK.\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n *\n * Sentry.init({\n * dsn: '__DSN__',\n * tracesSampleRate: 1.0,\n * });\n * ```\n */\nexport function init(userOptions: ElysiaOptions = {}): NodeClient | undefined {\n const options = {\n ...userOptions,\n platform: 'javascript',\n runtime: getRuntime(),\n serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(),\n };\n\n applySdkMetadata(userOptions, 'elysia', ['elysia', options.runtime.name]);\n\n options.transport = options.transport || makeFetchTransport;\n\n if (options.defaultIntegrations === undefined) {\n options.defaultIntegrations = getDefaultIntegrations(options);\n }\n\n return initNode(options);\n}\n"],"names":["getBunDefaultIntegrations","initNode"],"mappings":";;;;AAYO,SAAS,uBAAuB,QAAA,EAAkC;AAGvE,EAAA,OAAOA,wBAAA,CAA0B,QAAQ,CAAA,CAAE,MAAA,CAAO,OAAK,CAAA,CAAE,IAAA,KAAS,oBAAA,EAAqB,CAAE,IAAI,CAAA;AAC/F;AAKA,SAAS,UAAA,GAAgD;AACvD,EAAA,IAAI,OAAO,QAAQ,WAAA,EAAa;AAC9B,IAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,IAAI,OAAA,EAAQ;AAAA,EAC7C;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,QAAQ,OAAA,EAAQ;AAClD;AAeO,SAAS,IAAA,CAAK,WAAA,GAA6B,EAAC,EAA2B;AAC5E,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,GAAG,WAAA;AAAA,IACH,QAAA,EAAU,YAAA;AAAA,IACV,SAAS,UAAA,EAAW;AAAA,IACpB,UAAA,EAAY,YAAY,UAAA,IAAc,MAAA,CAAO,QAAQ,GAAA,CAAI,WAAA,IAAe,GAAG,QAAA;AAAS,GACtF;AAEA,EAAA,gBAAA,CAAiB,aAAa,QAAA,EAAU,CAAC,UAAU,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAC,CAAA;AAExE,EAAA,OAAA,CAAQ,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AAEzC,EAAA,IAAI,OAAA,CAAQ,wBAAwB,MAAA,EAAW;AAC7C,IAAA,OAAA,CAAQ,mBAAA,GAAsB,uBAAuB,OAAO,CAAA;AAAA,EAC9D;AAEA,EAAA,OAAOC,OAAS,OAAO,CAAA;AACzB;;;;"}
|
package/build/esm/withElysia.js
CHANGED
|
@@ -1,194 +1,124 @@
|
|
|
1
1
|
import { withIsolationScope, continueTrace, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, getRootSpan, getIsolationScope, winterCGRequestToRequestData, getTraceData, setHttpStatus, captureException, updateSpanName, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
|
|
2
2
|
|
|
3
|
-
const ELYSIA_ORIGIN =
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Map Elysia lifecycle phase names to Sentry span ops.
|
|
7
|
-
*/
|
|
3
|
+
const ELYSIA_ORIGIN = "auto.http.elysia";
|
|
8
4
|
const ELYSIA_LIFECYCLE_OP_MAP = {
|
|
9
|
-
Request:
|
|
10
|
-
Parse:
|
|
11
|
-
Transform:
|
|
12
|
-
BeforeHandle:
|
|
13
|
-
Handle:
|
|
14
|
-
AfterHandle:
|
|
15
|
-
MapResponse:
|
|
16
|
-
AfterResponse:
|
|
17
|
-
Error:
|
|
5
|
+
Request: "middleware.elysia",
|
|
6
|
+
Parse: "middleware.elysia",
|
|
7
|
+
Transform: "middleware.elysia",
|
|
8
|
+
BeforeHandle: "middleware.elysia",
|
|
9
|
+
Handle: "request_handler.elysia",
|
|
10
|
+
AfterHandle: "middleware.elysia",
|
|
11
|
+
MapResponse: "middleware.elysia",
|
|
12
|
+
AfterResponse: "middleware.elysia",
|
|
13
|
+
Error: "middleware.elysia"
|
|
18
14
|
};
|
|
19
|
-
|
|
20
15
|
function isBun() {
|
|
21
|
-
return typeof Bun !==
|
|
16
|
+
return typeof Bun !== "undefined";
|
|
22
17
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* Per-request storage for the root span reference.
|
|
26
|
-
* .wrap() captures the root span and .trace() reads it.
|
|
27
|
-
* This is necessary because Elysia's .trace() callbacks may run in a different
|
|
28
|
-
* async context where getActiveSpan() returns undefined.
|
|
29
|
-
*/
|
|
30
|
-
const rootSpanForRequest = new WeakMap();
|
|
31
|
-
|
|
32
|
-
const instrumentedApps = new WeakSet();
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Updates the root span and isolation scope with the parameterized route name.
|
|
36
|
-
*/
|
|
18
|
+
const rootSpanForRequest = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
const instrumentedApps = /* @__PURE__ */ new WeakSet();
|
|
37
20
|
function updateRouteTransactionName(request, method, route) {
|
|
38
21
|
const transactionName = `${method} ${route}`;
|
|
39
|
-
|
|
40
|
-
// Try the stored root span first (reliable across async contexts),
|
|
41
|
-
// then fall back to getActiveSpan() for cases where async context is preserved.
|
|
42
22
|
const rootSpan = rootSpanForRequest.get(request);
|
|
43
23
|
if (rootSpan) {
|
|
44
24
|
updateSpanName(rootSpan, transactionName);
|
|
45
|
-
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
|
25
|
+
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route");
|
|
46
26
|
} else {
|
|
47
27
|
const activeSpan = getActiveSpan();
|
|
48
28
|
if (activeSpan) {
|
|
49
29
|
const root = getRootSpan(activeSpan);
|
|
50
30
|
updateSpanName(root, transactionName);
|
|
51
|
-
root.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
|
|
31
|
+
root.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "route");
|
|
52
32
|
}
|
|
53
33
|
}
|
|
54
|
-
|
|
55
34
|
getIsolationScope().setTransactionName(transactionName);
|
|
56
35
|
}
|
|
57
|
-
|
|
58
36
|
function defaultShouldHandleError(context) {
|
|
59
37
|
const status = context.set.status;
|
|
60
|
-
if (status ===
|
|
38
|
+
if (status === void 0) {
|
|
61
39
|
return true;
|
|
62
40
|
}
|
|
63
41
|
const statusCode = parseInt(String(status), 10);
|
|
64
42
|
if (Number.isNaN(statusCode)) {
|
|
65
43
|
return true;
|
|
66
44
|
}
|
|
67
|
-
// Capture server errors (5xx) and unusual status codes (<= 299 in an error handler).
|
|
68
|
-
// 3xx and 4xx are not captured by default (client errors / redirects).
|
|
69
45
|
return statusCode >= 500 || statusCode <= 299;
|
|
70
46
|
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Instruments a single Elysia lifecycle phase by creating a Sentry span for it,
|
|
74
|
-
* and child spans for each individual handler within the phase.
|
|
75
|
-
*
|
|
76
|
-
* @param rootSpan - The root server span to parent lifecycle spans under.
|
|
77
|
-
* Must be passed explicitly because Elysia's .trace() listener callbacks run
|
|
78
|
-
* in a different async context where getActiveSpan() returns undefined.
|
|
79
|
-
*/
|
|
80
47
|
function instrumentLifecyclePhase(phaseName, listener, rootSpan) {
|
|
81
48
|
const op = ELYSIA_LIFECYCLE_OP_MAP[phaseName];
|
|
82
49
|
if (!op) {
|
|
83
50
|
return;
|
|
84
51
|
}
|
|
85
|
-
|
|
86
|
-
void listener(process => {
|
|
52
|
+
void listener((process) => {
|
|
87
53
|
const phaseSpan = startInactiveSpan({
|
|
88
54
|
name: phaseName,
|
|
89
55
|
parentSpan: rootSpan,
|
|
90
56
|
attributes: {
|
|
91
57
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
92
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
93
|
-
}
|
|
58
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
59
|
+
}
|
|
94
60
|
});
|
|
95
|
-
|
|
96
|
-
// Create child spans for individual handlers within this phase.
|
|
97
|
-
// Named functions get their name, arrow functions get 'anonymous'.
|
|
98
61
|
if (process.total > 0) {
|
|
99
|
-
void process.onEvent(child => {
|
|
100
|
-
const handlerName = child.name ||
|
|
62
|
+
void process.onEvent((child) => {
|
|
63
|
+
const handlerName = child.name || "anonymous";
|
|
101
64
|
const childSpan = startInactiveSpan({
|
|
102
65
|
name: handlerName,
|
|
103
66
|
parentSpan: phaseSpan,
|
|
104
67
|
attributes: {
|
|
105
68
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
106
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
107
|
-
}
|
|
69
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN
|
|
70
|
+
}
|
|
108
71
|
});
|
|
109
|
-
|
|
110
72
|
void child.onStop(() => {
|
|
111
73
|
childSpan.end();
|
|
112
74
|
});
|
|
113
75
|
});
|
|
114
76
|
}
|
|
115
|
-
|
|
116
77
|
void process.onStop(() => {
|
|
117
78
|
phaseSpan.end();
|
|
118
79
|
});
|
|
119
80
|
});
|
|
120
81
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Integrate Sentry with an Elysia app for error handling, request context,
|
|
124
|
-
* and tracing. Returns the app instance for chaining.
|
|
125
|
-
*
|
|
126
|
-
* Should be called at the **start** of the chain before defining routes.
|
|
127
|
-
*
|
|
128
|
-
* @param app The Elysia instance
|
|
129
|
-
* @param options Configuration options
|
|
130
|
-
* @returns The same Elysia instance for chaining
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```javascript
|
|
134
|
-
* import * as Sentry from '@sentry/elysia';
|
|
135
|
-
* import { Elysia } from 'elysia';
|
|
136
|
-
*
|
|
137
|
-
* Sentry.withElysia(new Elysia())
|
|
138
|
-
* .get('/', () => 'Hello World')
|
|
139
|
-
* .listen(3000);
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
82
|
function withElysia(app, options = {}) {
|
|
143
83
|
if (instrumentedApps.has(app)) {
|
|
144
84
|
return app;
|
|
145
85
|
}
|
|
146
86
|
instrumentedApps.add(app);
|
|
147
|
-
|
|
148
|
-
// Use .wrap() to capture or create the root span for each request.
|
|
149
|
-
// This is necessary because Elysia's .trace() callbacks run in a different
|
|
150
|
-
// async context where getActiveSpan() returns undefined. By storing the root
|
|
151
|
-
// span in a WeakMap keyed by the Request object, we can retrieve it in .trace().
|
|
152
|
-
// HigherOrderFunction type is not exported from elysia's main entry point,
|
|
153
|
-
// so we type the callback parameters directly.
|
|
154
87
|
app.wrap((fn, request) => {
|
|
155
88
|
if (isBun()) {
|
|
156
|
-
// On Bun there is no HTTP instrumentation, so we create a root span ourselves.
|
|
157
|
-
// Scope setup must happen inside the returned function so that it's active
|
|
158
|
-
// when Elysia calls the handler (not during .wrap() registration).
|
|
159
89
|
return (...args) => {
|
|
160
90
|
return withIsolationScope(() => {
|
|
161
91
|
return continueTrace(
|
|
162
92
|
{
|
|
163
|
-
sentryTrace: request.headers.get(
|
|
164
|
-
baggage: request.headers.get(
|
|
93
|
+
sentryTrace: request.headers.get("sentry-trace") || "",
|
|
94
|
+
baggage: request.headers.get("baggage")
|
|
165
95
|
},
|
|
166
96
|
() => {
|
|
167
97
|
return startSpanManual(
|
|
168
98
|
{
|
|
169
|
-
op:
|
|
99
|
+
op: "http.server",
|
|
170
100
|
name: `${request.method} ${new URL(request.url).pathname}`,
|
|
171
101
|
attributes: {
|
|
172
102
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,
|
|
173
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]:
|
|
174
|
-
}
|
|
103
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url"
|
|
104
|
+
}
|
|
175
105
|
},
|
|
176
|
-
rootSpan => {
|
|
106
|
+
(rootSpan) => {
|
|
177
107
|
rootSpanForRequest.set(request, rootSpan);
|
|
178
108
|
try {
|
|
179
109
|
const result = fn(...args);
|
|
180
110
|
if (result instanceof Promise) {
|
|
181
111
|
return result.then(
|
|
182
|
-
res => {
|
|
112
|
+
(res) => {
|
|
183
113
|
rootSpanForRequest.delete(request);
|
|
184
114
|
rootSpan.end();
|
|
185
115
|
return res;
|
|
186
116
|
},
|
|
187
|
-
err => {
|
|
117
|
+
(err) => {
|
|
188
118
|
rootSpanForRequest.delete(request);
|
|
189
119
|
rootSpan.end();
|
|
190
120
|
throw err;
|
|
191
|
-
}
|
|
121
|
+
}
|
|
192
122
|
);
|
|
193
123
|
}
|
|
194
124
|
rootSpanForRequest.delete(request);
|
|
@@ -199,97 +129,76 @@ function withElysia(app, options = {}) {
|
|
|
199
129
|
rootSpan.end();
|
|
200
130
|
throw err;
|
|
201
131
|
}
|
|
202
|
-
}
|
|
132
|
+
}
|
|
203
133
|
);
|
|
204
|
-
}
|
|
134
|
+
}
|
|
205
135
|
);
|
|
206
136
|
});
|
|
207
137
|
};
|
|
208
138
|
}
|
|
209
|
-
|
|
210
|
-
// On Node.js, the HTTP instrumentation already creates a root span.
|
|
211
|
-
// We just capture its reference so .trace() can use it.
|
|
212
139
|
const activeSpan = getActiveSpan();
|
|
213
140
|
if (activeSpan) {
|
|
214
141
|
rootSpanForRequest.set(request, getRootSpan(activeSpan));
|
|
215
142
|
}
|
|
216
143
|
return fn;
|
|
217
144
|
});
|
|
218
|
-
|
|
219
|
-
// Use .trace() ONLY for span creation. The trace API is observational —
|
|
220
|
-
// callbacks fire after phases complete, so they can't reliably mutate
|
|
221
|
-
// response headers or capture errors. All SDK logic stays in real hooks.
|
|
222
|
-
const traceHandler = lifecycle => {
|
|
145
|
+
const traceHandler = (lifecycle) => {
|
|
223
146
|
const rootSpan = rootSpanForRequest.get(lifecycle.context.request);
|
|
224
|
-
|
|
225
147
|
const phases = [
|
|
226
|
-
[
|
|
227
|
-
[
|
|
228
|
-
[
|
|
229
|
-
[
|
|
230
|
-
[
|
|
231
|
-
[
|
|
232
|
-
[
|
|
233
|
-
[
|
|
234
|
-
[
|
|
148
|
+
["Request", lifecycle.onRequest],
|
|
149
|
+
["Parse", lifecycle.onParse],
|
|
150
|
+
["Transform", lifecycle.onTransform],
|
|
151
|
+
["BeforeHandle", lifecycle.onBeforeHandle],
|
|
152
|
+
["Handle", lifecycle.onHandle],
|
|
153
|
+
["AfterHandle", lifecycle.onAfterHandle],
|
|
154
|
+
["MapResponse", lifecycle.onMapResponse],
|
|
155
|
+
["AfterResponse", lifecycle.onAfterResponse],
|
|
156
|
+
["Error", lifecycle.onError]
|
|
235
157
|
];
|
|
236
|
-
|
|
237
158
|
for (const [phaseName, listener] of phases) {
|
|
238
159
|
if (listener) {
|
|
239
160
|
instrumentLifecyclePhase(phaseName, listener, rootSpan);
|
|
240
161
|
}
|
|
241
162
|
}
|
|
242
163
|
};
|
|
243
|
-
|
|
244
|
-
app.trace({ as: 'global' }, traceHandler);
|
|
245
|
-
|
|
246
|
-
// SDK logic uses real lifecycle hooks — these show up as handler spans
|
|
247
|
-
// in the trace (named sentryOnRequest etc.), but that's the correct
|
|
248
|
-
// tradeoff: the trace API can't reliably mutate state or capture errors.
|
|
249
|
-
|
|
164
|
+
app.trace({ as: "global" }, traceHandler);
|
|
250
165
|
app.onRequest(function sentryOnRequest(context) {
|
|
251
166
|
getIsolationScope().setSDKProcessingMetadata({
|
|
252
|
-
normalizedRequest: winterCGRequestToRequestData(context.request)
|
|
167
|
+
normalizedRequest: winterCGRequestToRequestData(context.request)
|
|
253
168
|
});
|
|
254
169
|
});
|
|
255
|
-
|
|
256
|
-
|
|
170
|
+
const elysiaApp = app;
|
|
171
|
+
elysiaApp.onAfterHandle({ as: "global" }, function sentryOnAfterHandle(context) {
|
|
257
172
|
if (context.route) {
|
|
258
173
|
updateRouteTransactionName(context.request, context.request.method, context.route);
|
|
259
174
|
}
|
|
260
|
-
|
|
261
175
|
const traceData = getTraceData();
|
|
262
|
-
if (traceData[
|
|
263
|
-
context.set.headers[
|
|
176
|
+
if (traceData["sentry-trace"]) {
|
|
177
|
+
context.set.headers["sentry-trace"] = traceData["sentry-trace"];
|
|
264
178
|
}
|
|
265
179
|
if (traceData.baggage) {
|
|
266
|
-
context.set.headers[
|
|
180
|
+
context.set.headers["baggage"] = traceData.baggage;
|
|
267
181
|
}
|
|
268
182
|
});
|
|
269
|
-
|
|
270
|
-
app.onError({ as: 'global' }, function sentryOnError(context) {
|
|
183
|
+
elysiaApp.onError({ as: "global" }, function sentryOnError(context) {
|
|
271
184
|
if (context.route) {
|
|
272
185
|
updateRouteTransactionName(context.request, context.request.method, context.route);
|
|
273
186
|
}
|
|
274
|
-
|
|
275
|
-
// Set error status on root span
|
|
276
187
|
const rootSpan = rootSpanForRequest.get(context.request);
|
|
277
188
|
if (rootSpan) {
|
|
278
189
|
const statusCode = parseInt(String(context.set.status), 10);
|
|
279
190
|
setHttpStatus(rootSpan, Number.isNaN(statusCode) ? 500 : statusCode);
|
|
280
191
|
}
|
|
281
|
-
|
|
282
192
|
const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;
|
|
283
193
|
if (shouldHandleError(context)) {
|
|
284
194
|
captureException(context.error, {
|
|
285
195
|
mechanism: {
|
|
286
|
-
type:
|
|
287
|
-
handled: false
|
|
288
|
-
}
|
|
196
|
+
type: "auto.http.elysia.on_error",
|
|
197
|
+
handled: false
|
|
198
|
+
}
|
|
289
199
|
});
|
|
290
200
|
}
|
|
291
201
|
});
|
|
292
|
-
|
|
293
202
|
return app;
|
|
294
203
|
}
|
|
295
204
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withElysia.js","sources":["../../src/withElysia.ts"],"sourcesContent":["import type { Span } from '@sentry/core';\nimport {\n captureException,\n continueTrace,\n getActiveSpan,\n getIsolationScope,\n getRootSpan,\n getTraceData,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n setHttpStatus,\n startInactiveSpan,\n startSpanManual,\n updateSpanName,\n winterCGRequestToRequestData,\n withIsolationScope,\n} from '@sentry/core';\nimport type { Elysia, ErrorContext, TraceHandler, TraceListener } from 'elysia';\n\ninterface ElysiaHandlerOptions {\n shouldHandleError?: (context: ErrorContext) => boolean;\n}\n\nconst ELYSIA_ORIGIN = 'auto.http.elysia';\n\n/**\n * Map Elysia lifecycle phase names to Sentry span ops.\n */\nconst ELYSIA_LIFECYCLE_OP_MAP: Record<string, string> = {\n Request: 'middleware.elysia',\n Parse: 'middleware.elysia',\n Transform: 'middleware.elysia',\n BeforeHandle: 'middleware.elysia',\n Handle: 'request_handler.elysia',\n AfterHandle: 'middleware.elysia',\n MapResponse: 'middleware.elysia',\n AfterResponse: 'middleware.elysia',\n Error: 'middleware.elysia',\n};\n\nfunction isBun(): boolean {\n return typeof Bun !== 'undefined';\n}\n\n/**\n * Per-request storage for the root span reference.\n * .wrap() captures the root span and .trace() reads it.\n * This is necessary because Elysia's .trace() callbacks may run in a different\n * async context where getActiveSpan() returns undefined.\n */\nconst rootSpanForRequest = new WeakMap<Request, Span>();\n\nconst instrumentedApps = new WeakSet<Elysia>();\n\n/**\n * Updates the root span and isolation scope with the parameterized route name.\n */\nfunction updateRouteTransactionName(request: Request, method: string, route: string): void {\n const transactionName = `${method} ${route}`;\n\n // Try the stored root span first (reliable across async contexts),\n // then fall back to getActiveSpan() for cases where async context is preserved.\n const rootSpan = rootSpanForRequest.get(request);\n if (rootSpan) {\n updateSpanName(rootSpan, transactionName);\n rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n } else {\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n const root = getRootSpan(activeSpan);\n updateSpanName(root, transactionName);\n root.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n }\n\n getIsolationScope().setTransactionName(transactionName);\n}\n\nfunction defaultShouldHandleError(context: ErrorContext): boolean {\n const status = context.set.status;\n if (status === undefined) {\n return true;\n }\n const statusCode = parseInt(String(status), 10);\n if (Number.isNaN(statusCode)) {\n return true;\n }\n // Capture server errors (5xx) and unusual status codes (<= 299 in an error handler).\n // 3xx and 4xx are not captured by default (client errors / redirects).\n return statusCode >= 500 || statusCode <= 299;\n}\n\n/**\n * Instruments a single Elysia lifecycle phase by creating a Sentry span for it,\n * and child spans for each individual handler within the phase.\n *\n * @param rootSpan - The root server span to parent lifecycle spans under.\n * Must be passed explicitly because Elysia's .trace() listener callbacks run\n * in a different async context where getActiveSpan() returns undefined.\n */\nfunction instrumentLifecyclePhase(phaseName: string, listener: TraceListener, rootSpan: Span | undefined): void {\n const op = ELYSIA_LIFECYCLE_OP_MAP[phaseName];\n if (!op) {\n return;\n }\n\n void listener(process => {\n const phaseSpan = startInactiveSpan({\n name: phaseName,\n parentSpan: rootSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n // Create child spans for individual handlers within this phase.\n // Named functions get their name, arrow functions get 'anonymous'.\n if (process.total > 0) {\n void process.onEvent(child => {\n const handlerName = child.name || 'anonymous';\n const childSpan = startInactiveSpan({\n name: handlerName,\n parentSpan: phaseSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n void child.onStop(() => {\n childSpan.end();\n });\n });\n }\n\n void process.onStop(() => {\n phaseSpan.end();\n });\n });\n}\n\n/**\n * Integrate Sentry with an Elysia app for error handling, request context,\n * and tracing. Returns the app instance for chaining.\n *\n * Should be called at the **start** of the chain before defining routes.\n *\n * @param app The Elysia instance\n * @param options Configuration options\n * @returns The same Elysia instance for chaining\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n * import { Elysia } from 'elysia';\n *\n * Sentry.withElysia(new Elysia())\n * .get('/', () => 'Hello World')\n * .listen(3000);\n * ```\n */\nexport function withElysia<T extends Elysia>(app: T, options: ElysiaHandlerOptions = {}): T {\n if (instrumentedApps.has(app)) {\n return app;\n }\n instrumentedApps.add(app);\n\n // Use .wrap() to capture or create the root span for each request.\n // This is necessary because Elysia's .trace() callbacks run in a different\n // async context where getActiveSpan() returns undefined. By storing the root\n // span in a WeakMap keyed by the Request object, we can retrieve it in .trace().\n // HigherOrderFunction type is not exported from elysia's main entry point,\n // so we type the callback parameters directly.\n app.wrap((fn: (...args: unknown[]) => unknown, request: Request) => {\n if (isBun()) {\n // On Bun there is no HTTP instrumentation, so we create a root span ourselves.\n // Scope setup must happen inside the returned function so that it's active\n // when Elysia calls the handler (not during .wrap() registration).\n return (...args: unknown[]) => {\n return withIsolationScope(() => {\n return continueTrace(\n {\n sentryTrace: request.headers.get('sentry-trace') || '',\n baggage: request.headers.get('baggage'),\n },\n () => {\n return startSpanManual(\n {\n op: 'http.server',\n name: `${request.method} ${new URL(request.url).pathname}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n },\n },\n rootSpan => {\n rootSpanForRequest.set(request, rootSpan);\n try {\n const result = fn(...args);\n if (result instanceof Promise) {\n return result.then(\n res => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return res;\n },\n err => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n },\n );\n }\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return result;\n } catch (err) {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n }\n },\n );\n },\n );\n });\n };\n }\n\n // On Node.js, the HTTP instrumentation already creates a root span.\n // We just capture its reference so .trace() can use it.\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n rootSpanForRequest.set(request, getRootSpan(activeSpan));\n }\n return fn;\n });\n\n // Use .trace() ONLY for span creation. The trace API is observational —\n // callbacks fire after phases complete, so they can't reliably mutate\n // response headers or capture errors. All SDK logic stays in real hooks.\n const traceHandler: TraceHandler = lifecycle => {\n const rootSpan = rootSpanForRequest.get(lifecycle.context.request);\n\n const phases: [string, TraceListener][] = [\n ['Request', lifecycle.onRequest],\n ['Parse', lifecycle.onParse],\n ['Transform', lifecycle.onTransform],\n ['BeforeHandle', lifecycle.onBeforeHandle],\n ['Handle', lifecycle.onHandle],\n ['AfterHandle', lifecycle.onAfterHandle],\n ['MapResponse', lifecycle.onMapResponse],\n ['AfterResponse', lifecycle.onAfterResponse],\n ['Error', lifecycle.onError],\n ];\n\n for (const [phaseName, listener] of phases) {\n if (listener) {\n instrumentLifecyclePhase(phaseName, listener, rootSpan);\n }\n }\n };\n\n app.trace({ as: 'global' }, traceHandler);\n\n // SDK logic uses real lifecycle hooks — these show up as handler spans\n // in the trace (named sentryOnRequest etc.), but that's the correct\n // tradeoff: the trace API can't reliably mutate state or capture errors.\n\n app.onRequest(function sentryOnRequest(context) {\n getIsolationScope().setSDKProcessingMetadata({\n normalizedRequest: winterCGRequestToRequestData(context.request),\n });\n });\n\n app.onAfterHandle({ as: 'global' }, function sentryOnAfterHandle(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n const traceData = getTraceData();\n if (traceData['sentry-trace']) {\n context.set.headers['sentry-trace'] = traceData['sentry-trace'];\n }\n if (traceData.baggage) {\n context.set.headers['baggage'] = traceData.baggage;\n }\n });\n\n app.onError({ as: 'global' }, function sentryOnError(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n // Set error status on root span\n const rootSpan = rootSpanForRequest.get(context.request);\n if (rootSpan) {\n const statusCode = parseInt(String(context.set.status), 10);\n setHttpStatus(rootSpan, Number.isNaN(statusCode) ? 500 : statusCode);\n }\n\n const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;\n if (shouldHandleError(context)) {\n captureException(context.error, {\n mechanism: {\n type: 'auto.http.elysia.on_error',\n handled: false,\n },\n });\n }\n });\n\n return app;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"withElysia.js","sources":["../../src/withElysia.ts"],"sourcesContent":["import type { Span } from '@sentry/core';\nimport {\n captureException,\n continueTrace,\n getActiveSpan,\n getIsolationScope,\n getRootSpan,\n getTraceData,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n setHttpStatus,\n startInactiveSpan,\n startSpanManual,\n updateSpanName,\n winterCGRequestToRequestData,\n withIsolationScope,\n} from '@sentry/core';\nimport type { AnyElysia, Elysia, ErrorContext, TraceHandler, TraceListener } from 'elysia';\n\ninterface ElysiaHandlerOptions {\n shouldHandleError?: (context: ErrorContext) => boolean;\n}\n\nconst ELYSIA_ORIGIN = 'auto.http.elysia';\n\n/**\n * Map Elysia lifecycle phase names to Sentry span ops.\n */\nconst ELYSIA_LIFECYCLE_OP_MAP: Record<string, string> = {\n Request: 'middleware.elysia',\n Parse: 'middleware.elysia',\n Transform: 'middleware.elysia',\n BeforeHandle: 'middleware.elysia',\n Handle: 'request_handler.elysia',\n AfterHandle: 'middleware.elysia',\n MapResponse: 'middleware.elysia',\n AfterResponse: 'middleware.elysia',\n Error: 'middleware.elysia',\n};\n\nfunction isBun(): boolean {\n return typeof Bun !== 'undefined';\n}\n\n/**\n * Per-request storage for the root span reference.\n * .wrap() captures the root span and .trace() reads it.\n * This is necessary because Elysia's .trace() callbacks may run in a different\n * async context where getActiveSpan() returns undefined.\n */\nconst rootSpanForRequest = new WeakMap<Request, Span>();\n\nconst instrumentedApps = new WeakSet<Elysia>();\n\n/**\n * Updates the root span and isolation scope with the parameterized route name.\n */\nfunction updateRouteTransactionName(request: Request, method: string, route: string): void {\n const transactionName = `${method} ${route}`;\n\n // Try the stored root span first (reliable across async contexts),\n // then fall back to getActiveSpan() for cases where async context is preserved.\n const rootSpan = rootSpanForRequest.get(request);\n if (rootSpan) {\n updateSpanName(rootSpan, transactionName);\n rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n } else {\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n const root = getRootSpan(activeSpan);\n updateSpanName(root, transactionName);\n root.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n }\n\n getIsolationScope().setTransactionName(transactionName);\n}\n\nfunction defaultShouldHandleError(context: ErrorContext): boolean {\n const status = context.set.status;\n if (status === undefined) {\n return true;\n }\n const statusCode = parseInt(String(status), 10);\n if (Number.isNaN(statusCode)) {\n return true;\n }\n // Capture server errors (5xx) and unusual status codes (<= 299 in an error handler).\n // 3xx and 4xx are not captured by default (client errors / redirects).\n return statusCode >= 500 || statusCode <= 299;\n}\n\n/**\n * Instruments a single Elysia lifecycle phase by creating a Sentry span for it,\n * and child spans for each individual handler within the phase.\n *\n * @param rootSpan - The root server span to parent lifecycle spans under.\n * Must be passed explicitly because Elysia's .trace() listener callbacks run\n * in a different async context where getActiveSpan() returns undefined.\n */\nfunction instrumentLifecyclePhase(phaseName: string, listener: TraceListener, rootSpan: Span | undefined): void {\n const op = ELYSIA_LIFECYCLE_OP_MAP[phaseName];\n if (!op) {\n return;\n }\n\n void listener(process => {\n const phaseSpan = startInactiveSpan({\n name: phaseName,\n parentSpan: rootSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n // Create child spans for individual handlers within this phase.\n // Named functions get their name, arrow functions get 'anonymous'.\n if (process.total > 0) {\n void process.onEvent(child => {\n const handlerName = child.name || 'anonymous';\n const childSpan = startInactiveSpan({\n name: handlerName,\n parentSpan: phaseSpan,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n },\n });\n\n void child.onStop(() => {\n childSpan.end();\n });\n });\n }\n\n void process.onStop(() => {\n phaseSpan.end();\n });\n });\n}\n\n/**\n * Integrate Sentry with an Elysia app for error handling, request context,\n * and tracing. Returns the app instance for chaining.\n *\n * Should be called at the **start** of the chain before defining routes.\n *\n * @param app The Elysia instance\n * @param options Configuration options\n * @returns The same Elysia instance for chaining\n *\n * @example\n * ```javascript\n * import * as Sentry from '@sentry/elysia';\n * import { Elysia } from 'elysia';\n *\n * Sentry.withElysia(new Elysia())\n * .get('/', () => 'Hello World')\n * .listen(3000);\n * ```\n */\n// Using the AnyElysia type here to allow users to pass in the full set of Elysia\n// options without type errors. The `T extends Elysia` type is too narrow to allow\n// users to pass in dynamic options like `prefix` without breaking the type system.\n// See Elysia type definition and its usage of `const in out` which forces the string\n// template literals to be fully consistent for e.g. `prefix`.\nexport function withElysia<T extends AnyElysia>(app: T, options: ElysiaHandlerOptions = {}): T {\n if (instrumentedApps.has(app)) {\n return app;\n }\n instrumentedApps.add(app);\n\n // Use .wrap() to capture or create the root span for each request.\n // This is necessary because Elysia's .trace() callbacks run in a different\n // async context where getActiveSpan() returns undefined. By storing the root\n // span in a WeakMap keyed by the Request object, we can retrieve it in .trace().\n // HigherOrderFunction type is not exported from elysia's main entry point,\n // so we type the callback parameters directly.\n app.wrap((fn: (...args: unknown[]) => unknown, request: Request) => {\n if (isBun()) {\n // On Bun there is no HTTP instrumentation, so we create a root span ourselves.\n // Scope setup must happen inside the returned function so that it's active\n // when Elysia calls the handler (not during .wrap() registration).\n return (...args: unknown[]) => {\n return withIsolationScope(() => {\n return continueTrace(\n {\n sentryTrace: request.headers.get('sentry-trace') || '',\n baggage: request.headers.get('baggage'),\n },\n () => {\n return startSpanManual(\n {\n op: 'http.server',\n name: `${request.method} ${new URL(request.url).pathname}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ELYSIA_ORIGIN,\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n },\n },\n rootSpan => {\n rootSpanForRequest.set(request, rootSpan);\n try {\n const result = fn(...args);\n if (result instanceof Promise) {\n return result.then(\n res => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return res;\n },\n err => {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n },\n );\n }\n rootSpanForRequest.delete(request);\n rootSpan.end();\n return result;\n } catch (err) {\n rootSpanForRequest.delete(request);\n rootSpan.end();\n throw err;\n }\n },\n );\n },\n );\n });\n };\n }\n\n // On Node.js, the HTTP instrumentation already creates a root span.\n // We just capture its reference so .trace() can use it.\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n rootSpanForRequest.set(request, getRootSpan(activeSpan));\n }\n return fn;\n });\n\n // Use .trace() ONLY for span creation. The trace API is observational —\n // callbacks fire after phases complete, so they can't reliably mutate\n // response headers or capture errors. All SDK logic stays in real hooks.\n const traceHandler: TraceHandler = lifecycle => {\n const rootSpan = rootSpanForRequest.get(lifecycle.context.request);\n\n const phases: [string, TraceListener][] = [\n ['Request', lifecycle.onRequest],\n ['Parse', lifecycle.onParse],\n ['Transform', lifecycle.onTransform],\n ['BeforeHandle', lifecycle.onBeforeHandle],\n ['Handle', lifecycle.onHandle],\n ['AfterHandle', lifecycle.onAfterHandle],\n ['MapResponse', lifecycle.onMapResponse],\n ['AfterResponse', lifecycle.onAfterResponse],\n ['Error', lifecycle.onError],\n ];\n\n for (const [phaseName, listener] of phases) {\n if (listener) {\n instrumentLifecyclePhase(phaseName, listener, rootSpan);\n }\n }\n };\n\n app.trace({ as: 'global' }, traceHandler);\n\n // SDK logic uses real lifecycle hooks — these show up as handler spans\n // in the trace (named sentryOnRequest etc.), but that's the correct\n // tradeoff: the trace API can't reliably mutate state or capture errors.\n\n app.onRequest(function sentryOnRequest(context) {\n getIsolationScope().setSDKProcessingMetadata({\n normalizedRequest: winterCGRequestToRequestData(context.request),\n });\n });\n\n // Cast from AnyElysia to Elysia so that onAfterHandle/onError callback\n // parameters resolve to typed contexts (e.g. ErrorContext for shouldHandleError).\n // `AnyElysia` is needed on the user-facing `app` type to accept user instances with\n // arbitrary generic args.\n // The cast is safe because AnyElysia IS Elysia<any, any, ...>.\n const elysiaApp = app as unknown as Elysia;\n\n elysiaApp.onAfterHandle({ as: 'global' }, function sentryOnAfterHandle(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n const traceData = getTraceData();\n if (traceData['sentry-trace']) {\n context.set.headers['sentry-trace'] = traceData['sentry-trace'];\n }\n if (traceData.baggage) {\n context.set.headers['baggage'] = traceData.baggage;\n }\n });\n\n elysiaApp.onError({ as: 'global' }, function sentryOnError(context) {\n if (context.route) {\n updateRouteTransactionName(context.request, context.request.method, context.route);\n }\n\n // Set error status on root span\n const rootSpan = rootSpanForRequest.get(context.request);\n if (rootSpan) {\n const statusCode = parseInt(String(context.set.status), 10);\n setHttpStatus(rootSpan, Number.isNaN(statusCode) ? 500 : statusCode);\n }\n\n const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;\n if (shouldHandleError(context)) {\n captureException(context.error, {\n mechanism: {\n type: 'auto.http.elysia.on_error',\n handled: false,\n },\n });\n }\n });\n\n return app;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyElysia, ErrorContext } from 'elysia';
|
|
2
2
|
interface ElysiaHandlerOptions {
|
|
3
3
|
shouldHandleError?: (context: ErrorContext) => boolean;
|
|
4
4
|
}
|
|
@@ -22,6 +22,6 @@ interface ElysiaHandlerOptions {
|
|
|
22
22
|
* .listen(3000);
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
export declare function withElysia<T extends
|
|
25
|
+
export declare function withElysia<T extends AnyElysia>(app: T, options?: ElysiaHandlerOptions): T;
|
|
26
26
|
export {};
|
|
27
27
|
//# sourceMappingURL=withElysia.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withElysia.d.ts","sourceRoot":"","sources":["../../src/withElysia.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"withElysia.d.ts","sourceRoot":"","sources":["../../src/withElysia.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,SAAS,EAAU,YAAY,EAA+B,MAAM,QAAQ,CAAC;AAE3F,UAAU,oBAAoB;IAC5B,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC;CACxD;AAyHD;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,GAAE,oBAAyB,GAAG,CAAC,CA+J7F"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyElysia, ErrorContext } from 'elysia';
|
|
2
2
|
interface ElysiaHandlerOptions {
|
|
3
3
|
shouldHandleError?: (context: ErrorContext) => boolean;
|
|
4
4
|
}
|
|
@@ -22,6 +22,6 @@ interface ElysiaHandlerOptions {
|
|
|
22
22
|
* .listen(3000);
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
export declare function withElysia<T extends
|
|
25
|
+
export declare function withElysia<T extends AnyElysia>(app: T, options?: ElysiaHandlerOptions): T;
|
|
26
26
|
export {};
|
|
27
27
|
//# sourceMappingURL=withElysia.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/elysia",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.55.0",
|
|
4
4
|
"description": "Official Sentry SDK for Elysia",
|
|
5
5
|
"repository": "git://github.com/getsentry/sentry-javascript.git",
|
|
6
6
|
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/elysia",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@sentry/bun": "10.
|
|
43
|
-
"@sentry/core": "10.
|
|
42
|
+
"@sentry/bun": "10.55.0",
|
|
43
|
+
"@sentry/core": "10.55.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"elysia": "^1.4.0"
|