@sentry/nitro 10.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/build/esm/config.js +32 -0
- package/build/esm/config.js.map +1 -0
- package/build/esm/index.js +4 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/instruments/instrumentServer.js +14 -0
- package/build/esm/instruments/instrumentServer.js.map +1 -0
- package/build/esm/module.js +18 -0
- package/build/esm/module.js.map +1 -0
- package/build/esm/package.json +1 -0
- package/build/esm/runtime/hooks/captureErrorHook.js +69 -0
- package/build/esm/runtime/hooks/captureErrorHook.js.map +1 -0
- package/build/esm/runtime/hooks/captureTracingEvents.js +264 -0
- package/build/esm/runtime/hooks/captureTracingEvents.js.map +1 -0
- package/build/esm/runtime/hooks/setServerTimingHeaders.js +29 -0
- package/build/esm/runtime/hooks/setServerTimingHeaders.js.map +1 -0
- package/build/esm/runtime/plugins/server.js +12 -0
- package/build/esm/runtime/plugins/server.js.map +1 -0
- package/build/esm/sdk.js +33 -0
- package/build/esm/sdk.js.map +1 -0
- package/build/esm/sourceMaps.js +192 -0
- package/build/esm/sourceMaps.js.map +1 -0
- package/build/esm/utils/plugin.js +10 -0
- package/build/esm/utils/plugin.js.map +1 -0
- package/build/esm/utils/resolver.js +24 -0
- package/build/esm/utils/resolver.js.map +1 -0
- package/build/types/common/debug-build.d.ts +7 -0
- package/build/types/common/debug-build.d.ts.map +1 -0
- package/build/types/config.d.ts +12 -0
- package/build/types/config.d.ts.map +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/instruments/instrumentServer.d.ts +7 -0
- package/build/types/instruments/instrumentServer.d.ts.map +1 -0
- package/build/types/module.d.ts +7 -0
- package/build/types/module.d.ts.map +1 -0
- package/build/types/runtime/hooks/captureErrorHook.d.ts +6 -0
- package/build/types/runtime/hooks/captureErrorHook.d.ts.map +1 -0
- package/build/types/runtime/hooks/captureTracingEvents.d.ts +5 -0
- package/build/types/runtime/hooks/captureTracingEvents.d.ts.map +1 -0
- package/build/types/runtime/hooks/setServerTimingHeaders.d.ts +7 -0
- package/build/types/runtime/hooks/setServerTimingHeaders.d.ts.map +1 -0
- package/build/types/runtime/plugins/server.d.ts +3 -0
- package/build/types/runtime/plugins/server.d.ts.map +1 -0
- package/build/types/sdk.d.ts +13 -0
- package/build/types/sdk.d.ts.map +1 -0
- package/build/types/sourceMaps.d.ts +17 -0
- package/build/types/sourceMaps.d.ts.map +1 -0
- package/build/types/utils/plugin.d.ts +6 -0
- package/build/types/utils/plugin.d.ts.map +1 -0
- package/build/types/utils/resolver.d.ts +13 -0
- package/build/types/utils/resolver.d.ts.map +1 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Functional Software, Inc. dba Sentry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
9
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
10
|
+
so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
|
|
3
|
+
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
> NOTICE: This package is in beta state and may be subject to breaking changes.
|
|
8
|
+
|
|
9
|
+
# Official Sentry SDK for Nitro
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/@sentry/nitro)
|
|
12
|
+
[](https://www.npmjs.com/package/@sentry/nitro)
|
|
13
|
+
[](https://www.npmjs.com/package/@sentry/nitro)
|
|
14
|
+
|
|
15
|
+
## Links
|
|
16
|
+
|
|
17
|
+
- [Official Nitro SDK Docs](https://docs.sentry.io/platforms/javascript/guides/nitro/)
|
|
18
|
+
- [Example Nitro app](https://github.com/getsentry/sentry-javascript/tree/develop/dev-packages/e2e-tests/test-applications/nitro-3)
|
|
19
|
+
|
|
20
|
+
## Compatibility
|
|
21
|
+
|
|
22
|
+
The minimum supported version of Nitro is `3.0.260415-beta`.
|
|
23
|
+
|
|
24
|
+
## General
|
|
25
|
+
|
|
26
|
+
This package is a wrapper around `@sentry/node` with added instrumentation for Nitro's features like:
|
|
27
|
+
|
|
28
|
+
- HTTP handlers and error capturing.
|
|
29
|
+
- [Middleware instrumentation](https://nitro.build/guide/routing#middleware).
|
|
30
|
+
<!-- - [Database instrumentation](https://nitro.build/guide/database). -->
|
|
31
|
+
<!-- - [KV Storage](https://nitro.build/guide/storage) and [Cache](https://nitro.build/guide/cache) instrumentation. -->
|
|
32
|
+
|
|
33
|
+
## Manual Setup
|
|
34
|
+
|
|
35
|
+
### 1. Prerequisites & Installation
|
|
36
|
+
|
|
37
|
+
1. Install the Sentry Nitro SDK:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Using npm
|
|
41
|
+
npm install @sentry/nitro
|
|
42
|
+
|
|
43
|
+
# Using yarn
|
|
44
|
+
yarn add @sentry/nitro
|
|
45
|
+
|
|
46
|
+
# Using pnpm
|
|
47
|
+
pnpm add @sentry/nitro
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Build-Time Nitro Config Setup
|
|
51
|
+
|
|
52
|
+
1. Import `withSentryConfig` from `@sentry/nitro` and call it with your Nitro config.
|
|
53
|
+
|
|
54
|
+
#### In `nitro.config.ts`
|
|
55
|
+
|
|
56
|
+
If you are using a dedicated `nitro.config.ts` file, you can import `withSentryConfig` from `@sentry/nitro` and call it with your Nitro config.
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import { defineNitroConfig } from 'nitro/config';
|
|
60
|
+
import { withSentryConfig } from '@sentry/nitro';
|
|
61
|
+
|
|
62
|
+
const config = defineNitroConfig({
|
|
63
|
+
// ...
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export default withSentryConfig(config, {
|
|
67
|
+
// Sentry Build Options
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### In `vite.config.ts`
|
|
72
|
+
|
|
73
|
+
If you are using Nitro as a Vite plugin, you can import `withSentryConfig` from `@sentry/nitro` and call it with your Nitro config.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { defineConfig } from 'vite';
|
|
77
|
+
import { nitro } from 'nitro/vite';
|
|
78
|
+
import { withSentryConfig } from '@sentry/nitro';
|
|
79
|
+
|
|
80
|
+
export default defineConfig({
|
|
81
|
+
plugins: [nitro()],
|
|
82
|
+
nitro: withSentryConfig(
|
|
83
|
+
{
|
|
84
|
+
// Nitro options
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
// Sentry Build Options
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Sentry Config Setup
|
|
94
|
+
|
|
95
|
+
Create an `instrument.mjs` file in your project root to initialize the Sentry SDK:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
import * as Sentry from '@sentry/nitro';
|
|
99
|
+
|
|
100
|
+
Sentry.init({
|
|
101
|
+
dsn: '__YOUR_DSN__',
|
|
102
|
+
tracesSampleRate: 1.0,
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then use `--import` in `NODE_OPTIONS` to load the instrumentation before your app code:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
NODE_OPTIONS='--import ./instrument.mjs' npx nitro dev
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This works with any Nitro command (`nitro dev`, `nitro preview`, or a production start script).
|
|
113
|
+
|
|
114
|
+
## Uploading Source Maps
|
|
115
|
+
|
|
116
|
+
The `withSentryConfig` function automatically configures source map uploading when the `authToken`, `org`, and `project`
|
|
117
|
+
options are provided:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
export default withSentryConfig(config, {
|
|
121
|
+
org: 'your-sentry-org',
|
|
122
|
+
project: 'your-sentry-project',
|
|
123
|
+
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Troubleshoot
|
|
128
|
+
|
|
129
|
+
If you encounter any issues with error tracking or integrations, refer to the official [Sentry Nitro SDK documentation](https://docs.sentry.io/platforms/javascript/guides/nitro/). If the documentation does not provide the necessary information, consider opening an issue on GitHub.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createNitroModule } from './module.js';
|
|
2
|
+
import { configureSourcemapSettings } from './sourceMaps.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Modifies the passed in Nitro configuration with automatic build-time instrumentation.
|
|
6
|
+
*/
|
|
7
|
+
function withSentryConfig(config, sentryOptions) {
|
|
8
|
+
return setupSentryNitroModule(config, sentryOptions);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sets up the Sentry Nitro module, useful for meta framework integrations.
|
|
13
|
+
*/
|
|
14
|
+
function setupSentryNitroModule(
|
|
15
|
+
config,
|
|
16
|
+
moduleOptions,
|
|
17
|
+
_serverConfigFile,
|
|
18
|
+
) {
|
|
19
|
+
if (!config.tracingChannel) {
|
|
20
|
+
config.tracingChannel = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { sentryEnabledSourcemaps } = configureSourcemapSettings(config, moduleOptions);
|
|
24
|
+
|
|
25
|
+
config.modules = config.modules || [];
|
|
26
|
+
config.modules.push(createNitroModule(moduleOptions, sentryEnabledSourcemaps));
|
|
27
|
+
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { setupSentryNitroModule, withSentryConfig };
|
|
32
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../../src/config.ts"],"sourcesContent":["import type { BuildTimeOptionsBase } from '@sentry/core';\nimport type { NitroConfig } from 'nitro/types';\nimport { createNitroModule } from './module';\nimport { configureSourcemapSettings } from './sourceMaps';\n\nexport type SentryNitroOptions = BuildTimeOptionsBase;\n\n/**\n * Modifies the passed in Nitro configuration with automatic build-time instrumentation.\n */\nexport function withSentryConfig(config: NitroConfig, sentryOptions?: SentryNitroOptions): NitroConfig {\n return setupSentryNitroModule(config, sentryOptions);\n}\n\n/**\n * Sets up the Sentry Nitro module, useful for meta framework integrations.\n */\nexport function setupSentryNitroModule(\n config: NitroConfig,\n moduleOptions?: SentryNitroOptions,\n _serverConfigFile?: string,\n): NitroConfig {\n if (!config.tracingChannel) {\n config.tracingChannel = true;\n }\n\n const { sentryEnabledSourcemaps } = configureSourcemapSettings(config, moduleOptions);\n\n config.modules = config.modules || [];\n config.modules.push(createNitroModule(moduleOptions, sentryEnabledSourcemaps));\n\n return config;\n}\n"],"names":[],"mappings":";;;AAOA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,MAAM,EAAe,aAAa,EAAoC;AACvG,EAAE,OAAO,sBAAsB,CAAC,MAAM,EAAE,aAAa,CAAC;AACtD;;AAEA;AACA;AACA;AACO,SAAS,sBAAsB;AACtC,EAAE,MAAM;AACR,EAAE,aAAa;AACf,EAAE,iBAAiB;AACnB,EAAe;AACf,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE;AAC9B,IAAI,MAAM,CAAC,cAAA,GAAiB,IAAI;AAChC,EAAE;;AAEF,EAAE,MAAM,EAAE,uBAAA,EAAwB,GAAI,0BAA0B,CAAC,MAAM,EAAE,aAAa,CAAC;;AAEvF,EAAE,MAAM,CAAC,OAAA,GAAU,MAAM,CAAC,OAAA,IAAW,EAAE;AACvC,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,uBAAuB,CAAC,CAAC;;AAEhF,EAAE,OAAO,MAAM;AACf;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { addPlugin } from '../utils/plugin.js';
|
|
2
|
+
import { createResolver } from '../utils/resolver.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sets up the Nitro server instrumentation plugin
|
|
6
|
+
* @param nitro - The Nitro instance.
|
|
7
|
+
*/
|
|
8
|
+
function instrumentServer(nitro) {
|
|
9
|
+
const moduleResolver = createResolver(import.meta.url);
|
|
10
|
+
addPlugin(nitro, moduleResolver.resolve('../runtime/plugins/server'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { instrumentServer };
|
|
14
|
+
//# sourceMappingURL=instrumentServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrumentServer.js","sources":["../../../src/instruments/instrumentServer.ts"],"sourcesContent":["import type { Nitro } from 'nitro/types';\nimport { addPlugin } from '../utils/plugin';\nimport { createResolver } from '../utils/resolver';\n\n/**\n * Sets up the Nitro server instrumentation plugin\n * @param nitro - The Nitro instance.\n */\nexport function instrumentServer(nitro: Nitro): void {\n const moduleResolver = createResolver(import.meta.url);\n addPlugin(nitro, moduleResolver.resolve('../runtime/plugins/server'));\n}\n"],"names":[],"mappings":";;;AAIA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,KAAK,EAAe;AACrD,EAAE,MAAM,cAAA,GAAiB,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AACxD,EAAE,SAAS,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;AACvE;;;;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { instrumentServer } from './instruments/instrumentServer.js';
|
|
2
|
+
import { setupSourceMaps } from './sourceMaps.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Nitro module to setup the Sentry SDK.
|
|
6
|
+
*/
|
|
7
|
+
function createNitroModule(sentryOptions, sentryEnabledSourcemaps) {
|
|
8
|
+
return {
|
|
9
|
+
name: 'sentry',
|
|
10
|
+
setup: nitro => {
|
|
11
|
+
instrumentServer(nitro);
|
|
12
|
+
setupSourceMaps(nitro, sentryOptions, sentryEnabledSourcemaps);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { createNitroModule };
|
|
18
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sources":["../../src/module.ts"],"sourcesContent":["import type { NitroModule } from 'nitro/types';\nimport type { SentryNitroOptions } from './config';\nimport { instrumentServer } from './instruments/instrumentServer';\nimport { setupSourceMaps } from './sourceMaps';\n\n/**\n * Creates a Nitro module to setup the Sentry SDK.\n */\nexport function createNitroModule(sentryOptions?: SentryNitroOptions, sentryEnabledSourcemaps?: boolean): NitroModule {\n return {\n name: 'sentry',\n setup: nitro => {\n instrumentServer(nitro);\n setupSourceMaps(nitro, sentryOptions, sentryEnabledSourcemaps);\n },\n };\n}\n"],"names":[],"mappings":";;;AAKA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,aAAa,EAAuB,uBAAuB,EAAyB;AACtH,EAAE,OAAO;AACT,IAAI,IAAI,EAAE,QAAQ;AAClB,IAAI,KAAK,EAAE,KAAA,IAAS;AACpB,MAAM,gBAAgB,CAAC,KAAK,CAAC;AAC7B,MAAM,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,uBAAuB,CAAC;AACpE,IAAI,CAAC;AACL,GAAG;AACH;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module","version":"10.51.0","sideEffects":false}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getClient, parseUrl, getCurrentScope, getActiveSpan, getRootSpan, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, captureException, flushIfServerless } from '@sentry/core';
|
|
2
|
+
import { HTTPError } from 'h3';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extracts the relevant context information from the error context (HTTPEvent in Nitro Error)
|
|
6
|
+
* and creates a structured context object.
|
|
7
|
+
*/
|
|
8
|
+
function extractErrorContext(errorContext) {
|
|
9
|
+
const ctx = {};
|
|
10
|
+
|
|
11
|
+
if (!errorContext) {
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (errorContext.event) {
|
|
16
|
+
ctx.method = errorContext.event.req.method;
|
|
17
|
+
ctx.path = parseUrl(errorContext.event.req.url).path;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Array.isArray(errorContext.tags)) {
|
|
21
|
+
ctx.tags = errorContext.tags;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return ctx;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Hook that can be added in a Nitro plugin. It captures an error and sends it to Sentry.
|
|
29
|
+
*/
|
|
30
|
+
async function captureErrorHook(error, errorContext) {
|
|
31
|
+
const sentryClient = getClient();
|
|
32
|
+
const sentryClientOptions = sentryClient?.getOptions();
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
sentryClientOptions &&
|
|
36
|
+
'enableNitroErrorHandler' in sentryClientOptions &&
|
|
37
|
+
sentryClientOptions.enableNitroErrorHandler === false
|
|
38
|
+
) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Do not report HTTPErrors with 3xx or 4xx status codes
|
|
43
|
+
if (HTTPError.isError(error) && error.status >= 300 && error.status < 500) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const method = errorContext.event?.req.method ?? '';
|
|
48
|
+
const path = errorContext.event?.req.url ? parseUrl(errorContext.event.req.url).path : null;
|
|
49
|
+
|
|
50
|
+
if (path) {
|
|
51
|
+
getCurrentScope().setTransactionName(`${method} ${path}`);
|
|
52
|
+
const activeSpan = getActiveSpan();
|
|
53
|
+
const activeRootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;
|
|
54
|
+
activeRootSpan?.updateName(`${method} ${path}`);
|
|
55
|
+
activeRootSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const structuredContext = extractErrorContext(errorContext);
|
|
59
|
+
|
|
60
|
+
captureException(error, {
|
|
61
|
+
captureContext: { contexts: { nitro: structuredContext } },
|
|
62
|
+
mechanism: { handled: false, type: 'auto.function.nitro.captureErrorHook' },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await flushIfServerless();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { captureErrorHook };
|
|
69
|
+
//# sourceMappingURL=captureErrorHook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureErrorHook.js","sources":["../../../../src/runtime/hooks/captureErrorHook.ts"],"sourcesContent":["import {\n captureException,\n flushIfServerless,\n getActiveSpan,\n getClient,\n getCurrentScope,\n getRootSpan,\n parseUrl,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n} from '@sentry/core';\nimport { HTTPError } from 'h3';\nimport type { CapturedErrorContext } from 'nitro/types';\n\n/**\n * Extracts the relevant context information from the error context (HTTPEvent in Nitro Error)\n * and creates a structured context object.\n */\nfunction extractErrorContext(errorContext: CapturedErrorContext | undefined): Record<string, unknown> {\n const ctx: Record<string, unknown> = {};\n\n if (!errorContext) {\n return ctx;\n }\n\n if (errorContext.event) {\n ctx.method = errorContext.event.req.method;\n ctx.path = parseUrl(errorContext.event.req.url).path;\n }\n\n if (Array.isArray(errorContext.tags)) {\n ctx.tags = errorContext.tags;\n }\n\n return ctx;\n}\n\n/**\n * Hook that can be added in a Nitro plugin. It captures an error and sends it to Sentry.\n */\nexport async function captureErrorHook(error: Error, errorContext: CapturedErrorContext): Promise<void> {\n const sentryClient = getClient();\n const sentryClientOptions = sentryClient?.getOptions();\n\n if (\n sentryClientOptions &&\n 'enableNitroErrorHandler' in sentryClientOptions &&\n sentryClientOptions.enableNitroErrorHandler === false\n ) {\n return;\n }\n\n // Do not report HTTPErrors with 3xx or 4xx status codes\n if (HTTPError.isError(error) && error.status >= 300 && error.status < 500) {\n return;\n }\n\n const method = errorContext.event?.req.method ?? '';\n const path = errorContext.event?.req.url ? parseUrl(errorContext.event.req.url).path : null;\n\n if (path) {\n getCurrentScope().setTransactionName(`${method} ${path}`);\n const activeSpan = getActiveSpan();\n const activeRootSpan = activeSpan ? getRootSpan(activeSpan) : undefined;\n activeRootSpan?.updateName(`${method} ${path}`);\n activeRootSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n }\n\n const structuredContext = extractErrorContext(errorContext);\n\n captureException(error, {\n captureContext: { contexts: { nitro: structuredContext } },\n mechanism: { handled: false, type: 'auto.function.nitro.captureErrorHook' },\n });\n\n await flushIfServerless();\n}\n"],"names":[],"mappings":";;;AAaA;AACA;AACA;AACA;AACA,SAAS,mBAAmB,CAAC,YAAY,EAA6D;AACtG,EAAE,MAAM,GAAG,GAA4B,EAAE;;AAEzC,EAAE,IAAI,CAAC,YAAY,EAAE;AACrB,IAAI,OAAO,GAAG;AACd,EAAE;;AAEF,EAAE,IAAI,YAAY,CAAC,KAAK,EAAE;AAC1B,IAAI,GAAG,CAAC,MAAA,GAAS,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM;AAC9C,IAAI,GAAG,CAAC,IAAA,GAAO,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI;AACxD,EAAE;;AAEF,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;AACxC,IAAI,GAAG,CAAC,IAAA,GAAO,YAAY,CAAC,IAAI;AAChC,EAAE;;AAEF,EAAE,OAAO,GAAG;AACZ;;AAEA;AACA;AACA;AACO,eAAe,gBAAgB,CAAC,KAAK,EAAS,YAAY,EAAuC;AACxG,EAAE,MAAM,YAAA,GAAe,SAAS,EAAE;AAClC,EAAE,MAAM,mBAAA,GAAsB,YAAY,EAAE,UAAU,EAAE;;AAExD,EAAE;AACF,IAAI,mBAAA;AACJ,IAAI,yBAAA,IAA6B,mBAAA;AACjC,IAAI,mBAAmB,CAAC,uBAAA,KAA4B;AACpD,IAAI;AACJ,IAAI;AACJ,EAAE;;AAEF;AACA,EAAE,IAAI,SAAS,CAAC,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,MAAA,IAAU,OAAO,KAAK,CAAC,MAAA,GAAS,GAAG,EAAE;AAC7E,IAAI;AACJ,EAAE;;AAEF,EAAE,MAAM,MAAA,GAAS,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,MAAA,IAAU,EAAE;AACrD,EAAE,MAAM,IAAA,GAAO,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,GAAA,GAAM,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAA,GAAO,IAAI;;AAE7F,EAAE,IAAI,IAAI,EAAE;AACZ,IAAI,eAAe,EAAE,CAAC,kBAAkB,CAAC,CAAC,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA,CAAA,CAAA,CAAA;AACA,IAAA,MAAA,UAAA,GAAA,aAAA,EAAA;AACA,IAAA,MAAA,cAAA,GAAA,UAAA,GAAA,WAAA,CAAA,UAAA,CAAA,GAAA,SAAA;AACA,IAAA,cAAA,EAAA,UAAA,CAAA,CAAA,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA,CAAA,CAAA,CAAA;AACA,IAAA,cAAA,EAAA,YAAA,CAAA,gCAAA,EAAA,OAAA,CAAA;AACA,EAAA;;AAEA,EAAA,MAAA,iBAAA,GAAA,mBAAA,CAAA,YAAA,CAAA;;AAEA,EAAA,gBAAA,CAAA,KAAA,EAAA;AACA,IAAA,cAAA,EAAA,EAAA,QAAA,EAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,EAAA;AACA,IAAA,SAAA,EAAA,EAAA,OAAA,EAAA,KAAA,EAAA,IAAA,EAAA,sCAAA,EAAA;AACA,GAAA,CAAA;;AAEA,EAAA,MAAA,iBAAA,EAAA;AACA;;;;"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { GLOBAL_OBJ, parseStringToURLObject, getHttpSpanDetailsFromUrlObject, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getRootSpan, updateSpanName, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getClient, httpHeadersToSpanAttributes, getActiveSpan, captureException, SPAN_STATUS_ERROR, setHttpStatus } from '@sentry/core';
|
|
2
|
+
import { tracingChannel } from '@sentry/opentelemetry/tracing-channel';
|
|
3
|
+
import { setServerTimingHeaders } from './setServerTimingHeaders.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Global object with the trace channels
|
|
7
|
+
*/
|
|
8
|
+
const globalWithTraceChannels = GLOBAL_OBJ
|
|
9
|
+
|
|
10
|
+
;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Captures tracing events emitted by Nitro tracing channels.
|
|
14
|
+
*/
|
|
15
|
+
function captureTracingEvents() {
|
|
16
|
+
if (globalWithTraceChannels.__SENTRY_NITRO_HTTP_CHANNELS_INSTRUMENTED__) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
setupH3TracingChannels();
|
|
21
|
+
setupSrvxTracingChannels();
|
|
22
|
+
globalWithTraceChannels.__SENTRY_NITRO_HTTP_CHANNELS_INSTRUMENTED__ = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* No-op function to satisfy the tracing channel subscribe callbacks
|
|
27
|
+
*/
|
|
28
|
+
const NOOP = () => {};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the HTTP status code from a tracing channel result.
|
|
32
|
+
* The result is the return value of the traced handler, which is a Response for srvx
|
|
33
|
+
* and may or may not be a Response for h3.
|
|
34
|
+
*/
|
|
35
|
+
function getResponseStatusCode(result) {
|
|
36
|
+
if (result && typeof result === 'object' && 'status' in result && typeof result.status === 'number') {
|
|
37
|
+
return result.status;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function onTraceEnd(data) {
|
|
43
|
+
const statusCode = getResponseStatusCode(data.result);
|
|
44
|
+
if (data._sentrySpan && statusCode !== undefined) {
|
|
45
|
+
setHttpStatus(data._sentrySpan, statusCode);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
data._sentrySpan?.end();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function onTraceError(data) {
|
|
52
|
+
captureException(data.error, { mechanism: { type: 'auto.http.nitro.onTraceError', handled: false } });
|
|
53
|
+
data._sentrySpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
54
|
+
data._sentrySpan?.end();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extracts the parameterized route pattern from the h3 event context.
|
|
59
|
+
*/
|
|
60
|
+
function getParameterizedRoute(event) {
|
|
61
|
+
const matchedRoute = event.context?.matchedRoute;
|
|
62
|
+
if (!matchedRoute) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const routePath = matchedRoute.route;
|
|
67
|
+
|
|
68
|
+
// Skip catch-all routes as they're not useful for transaction grouping
|
|
69
|
+
if (!routePath || routePath === '/**') {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return routePath;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function setupH3TracingChannels() {
|
|
77
|
+
const h3Channel = tracingChannel('h3.request', data => {
|
|
78
|
+
const parsedUrl = parseStringToURLObject(data.event.url.href);
|
|
79
|
+
const routePattern = getParameterizedRoute(data.event);
|
|
80
|
+
|
|
81
|
+
const [spanName, urlAttributes] = getHttpSpanDetailsFromUrlObject(
|
|
82
|
+
parsedUrl,
|
|
83
|
+
'server',
|
|
84
|
+
'auto.http.nitro.h3',
|
|
85
|
+
{ method: data.event.req.method },
|
|
86
|
+
routePattern,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return startSpanManual(
|
|
90
|
+
{
|
|
91
|
+
name: spanName,
|
|
92
|
+
attributes: {
|
|
93
|
+
...urlAttributes,
|
|
94
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nitro.h3',
|
|
95
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: data?.type === 'middleware' ? 'middleware.nitro' : 'http.server',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
span => {
|
|
99
|
+
setParameterizedRouteAttributes(span, data.event);
|
|
100
|
+
|
|
101
|
+
return span;
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
h3Channel.subscribe({
|
|
107
|
+
start: (data) => {
|
|
108
|
+
setServerTimingHeaders(data.event);
|
|
109
|
+
},
|
|
110
|
+
asyncStart: NOOP,
|
|
111
|
+
end: NOOP,
|
|
112
|
+
asyncEnd: (data) => {
|
|
113
|
+
onTraceEnd(data);
|
|
114
|
+
|
|
115
|
+
if (!data._sentrySpan) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update the root span (srvx transaction) with the parameterized route name.
|
|
120
|
+
// The srvx span is created before h3 resolves the route, so it initially has the raw URL.
|
|
121
|
+
// Note: data.type is always 'middleware' in asyncEnd regardless of handler type,
|
|
122
|
+
// so we rely on getParameterizedRoute() to filter out catch-all routes instead.
|
|
123
|
+
const rootSpan = getRootSpan(data._sentrySpan);
|
|
124
|
+
if (rootSpan && rootSpan !== data._sentrySpan) {
|
|
125
|
+
const routePattern = getParameterizedRoute(data.event);
|
|
126
|
+
if (routePattern) {
|
|
127
|
+
const method = data.event.req.method || 'GET';
|
|
128
|
+
updateSpanName(rootSpan, `${method} ${routePattern}`);
|
|
129
|
+
rootSpan.setAttributes({
|
|
130
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
|
|
131
|
+
'http.route': routePattern,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
error: onTraceError,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function setupSrvxTracingChannels() {
|
|
141
|
+
// Store the parent span per-request so middleware and fetch share the same parent.
|
|
142
|
+
// WeakMap ensures per-request isolation in concurrent environments and automatic cleanup.
|
|
143
|
+
const requestParentSpans = new WeakMap();
|
|
144
|
+
|
|
145
|
+
const fetchChannel = tracingChannel('srvx.request', data => {
|
|
146
|
+
const parsedUrl = data.request._url ? parseStringToURLObject(data.request._url.href) : undefined;
|
|
147
|
+
const [spanName, urlAttributes] = getHttpSpanDetailsFromUrlObject(parsedUrl, 'server', 'auto.http.nitro.srvx', {
|
|
148
|
+
method: data.request.method,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const sendDefaultPii = getClient()?.getOptions().sendDefaultPii ?? false;
|
|
152
|
+
const headerAttributes = httpHeadersToSpanAttributes(
|
|
153
|
+
Object.fromEntries(data.request.headers.entries()),
|
|
154
|
+
sendDefaultPii,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return startSpanManual(
|
|
158
|
+
{
|
|
159
|
+
name: spanName,
|
|
160
|
+
attributes: {
|
|
161
|
+
...urlAttributes,
|
|
162
|
+
...headerAttributes,
|
|
163
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nitro.srvx',
|
|
164
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: data.middleware ? 'middleware.nitro' : 'http.server',
|
|
165
|
+
'server.port': data.server.options.port,
|
|
166
|
+
},
|
|
167
|
+
// Use the same parent span as middleware to make them siblings
|
|
168
|
+
parentSpan: requestParentSpans.get(data.request) || undefined,
|
|
169
|
+
},
|
|
170
|
+
span => span,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Subscribe to events (span already created in bindStore)
|
|
175
|
+
fetchChannel.subscribe({
|
|
176
|
+
start: () => {},
|
|
177
|
+
asyncStart: () => {},
|
|
178
|
+
end: () => {},
|
|
179
|
+
asyncEnd: data => {
|
|
180
|
+
onTraceEnd(data);
|
|
181
|
+
|
|
182
|
+
// Clean up parent span reference after the fetch handler completes.
|
|
183
|
+
requestParentSpans.delete(data.request);
|
|
184
|
+
},
|
|
185
|
+
error: data => {
|
|
186
|
+
onTraceError(data);
|
|
187
|
+
// Clean up parent span reference on error too
|
|
188
|
+
requestParentSpans.delete(data.request);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const middlewareChannel = tracingChannel('srvx.middleware', data => {
|
|
193
|
+
// For the first middleware, capture the current parent span per-request
|
|
194
|
+
if (data.middleware?.index === 0) {
|
|
195
|
+
const activeSpan = getActiveSpan();
|
|
196
|
+
if (activeSpan) {
|
|
197
|
+
requestParentSpans.set(data.request, activeSpan);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const parsedUrl = data.request._url ? parseStringToURLObject(data.request._url.href) : undefined;
|
|
202
|
+
const [, urlAttributes] = getHttpSpanDetailsFromUrlObject(parsedUrl, 'server', 'auto.http.nitro.srvx', {
|
|
203
|
+
method: data.request.method,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Create span as a child of the original parent, not the previous middleware
|
|
207
|
+
return startSpanManual(
|
|
208
|
+
{
|
|
209
|
+
name: `${data.middleware?.handler.name ?? 'unknown'} - ${data.request.method} ${data.request._url?.pathname}`,
|
|
210
|
+
attributes: {
|
|
211
|
+
...urlAttributes,
|
|
212
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nitro.srvx',
|
|
213
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'middleware.nitro',
|
|
214
|
+
},
|
|
215
|
+
parentSpan: requestParentSpans.get(data.request) || undefined,
|
|
216
|
+
},
|
|
217
|
+
span => span,
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Subscribe to events (span already created in bindStore)
|
|
222
|
+
middlewareChannel.subscribe({
|
|
223
|
+
start: () => {},
|
|
224
|
+
asyncStart: () => {},
|
|
225
|
+
end: () => {},
|
|
226
|
+
asyncEnd: onTraceEnd,
|
|
227
|
+
error: onTraceError,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Sets the parameterized route attributes on the span.
|
|
233
|
+
*/
|
|
234
|
+
function setParameterizedRouteAttributes(span, event) {
|
|
235
|
+
const rootSpan = getRootSpan(span);
|
|
236
|
+
if (!rootSpan) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const matchedRoutePath = getParameterizedRoute(event);
|
|
241
|
+
if (!matchedRoutePath) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
rootSpan.setAttributes({
|
|
246
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
|
|
247
|
+
'http.route': matchedRoutePath,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const params = event.context?.params;
|
|
251
|
+
|
|
252
|
+
if (params && typeof params === 'object') {
|
|
253
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
254
|
+
// Based on this convention: https://getsentry.github.io/sentry-conventions/generated/attributes/url.html#urlpathparameterkey
|
|
255
|
+
rootSpan.setAttributes({
|
|
256
|
+
[`url.path.parameter.${key}`]: String(value),
|
|
257
|
+
[`params.${key}`]: String(value),
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export { captureTracingEvents };
|
|
264
|
+
//# sourceMappingURL=captureTracingEvents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureTracingEvents.js","sources":["../../../../src/runtime/hooks/captureTracingEvents.ts"],"sourcesContent":["import {\n captureException,\n getActiveSpan,\n getClient,\n getHttpSpanDetailsFromUrlObject,\n getRootSpan,\n GLOBAL_OBJ,\n httpHeadersToSpanAttributes,\n parseStringToURLObject,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n setHttpStatus,\n type Span,\n SPAN_STATUS_ERROR,\n startSpanManual,\n updateSpanName,\n} from '@sentry/core';\nimport { tracingChannel, type TracingChannelContextWithSpan } from '@sentry/opentelemetry/tracing-channel';\nimport type { TracingRequestEvent as H3TracingRequestEvent } from 'h3/tracing';\nimport type { RequestEvent as SrvxRequestEvent } from 'srvx/tracing';\nimport { setServerTimingHeaders } from './setServerTimingHeaders';\n\n/**\n * Global object with the trace channels\n */\nconst globalWithTraceChannels = GLOBAL_OBJ as typeof GLOBAL_OBJ & {\n __SENTRY_NITRO_HTTP_CHANNELS_INSTRUMENTED__: boolean;\n};\n\n/**\n * Captures tracing events emitted by Nitro tracing channels.\n */\nexport function captureTracingEvents(): void {\n if (globalWithTraceChannels.__SENTRY_NITRO_HTTP_CHANNELS_INSTRUMENTED__) {\n return;\n }\n\n setupH3TracingChannels();\n setupSrvxTracingChannels();\n globalWithTraceChannels.__SENTRY_NITRO_HTTP_CHANNELS_INSTRUMENTED__ = true;\n}\n\n/**\n * No-op function to satisfy the tracing channel subscribe callbacks\n */\nconst NOOP = (): void => {};\n\n/**\n * Extracts the HTTP status code from a tracing channel result.\n * The result is the return value of the traced handler, which is a Response for srvx\n * and may or may not be a Response for h3.\n */\nfunction getResponseStatusCode(result: unknown): number | undefined {\n if (result && typeof result === 'object' && 'status' in result && typeof result.status === 'number') {\n return result.status;\n }\n return undefined;\n}\n\nfunction onTraceEnd(data: TracingChannelContextWithSpan<{ result?: unknown }>): void {\n const statusCode = getResponseStatusCode(data.result);\n if (data._sentrySpan && statusCode !== undefined) {\n setHttpStatus(data._sentrySpan, statusCode);\n }\n\n data._sentrySpan?.end();\n}\n\nfunction onTraceError(data: TracingChannelContextWithSpan<{ error: unknown }>): void {\n captureException(data.error, { mechanism: { type: 'auto.http.nitro.onTraceError', handled: false } });\n data._sentrySpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n data._sentrySpan?.end();\n}\n\n/**\n * Extracts the parameterized route pattern from the h3 event context.\n */\nfunction getParameterizedRoute(event: H3TracingRequestEvent['event']): string | undefined {\n const matchedRoute = event.context?.matchedRoute;\n if (!matchedRoute) {\n return undefined;\n }\n\n const routePath = matchedRoute.route;\n\n // Skip catch-all routes as they're not useful for transaction grouping\n if (!routePath || routePath === '/**') {\n return undefined;\n }\n\n return routePath;\n}\n\nfunction setupH3TracingChannels(): void {\n const h3Channel = tracingChannel<H3TracingRequestEvent>('h3.request', data => {\n const parsedUrl = parseStringToURLObject(data.event.url.href);\n const routePattern = getParameterizedRoute(data.event);\n\n const [spanName, urlAttributes] = getHttpSpanDetailsFromUrlObject(\n parsedUrl,\n 'server',\n 'auto.http.nitro.h3',\n { method: data.event.req.method },\n routePattern,\n );\n\n return startSpanManual(\n {\n name: spanName,\n attributes: {\n ...urlAttributes,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nitro.h3',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: data?.type === 'middleware' ? 'middleware.nitro' : 'http.server',\n },\n },\n span => {\n setParameterizedRouteAttributes(span, data.event);\n\n return span;\n },\n );\n });\n\n h3Channel.subscribe({\n start: (data: H3TracingRequestEvent) => {\n setServerTimingHeaders(data.event);\n },\n asyncStart: NOOP,\n end: NOOP,\n asyncEnd: (data: TracingChannelContextWithSpan<H3TracingRequestEvent>) => {\n onTraceEnd(data);\n\n if (!data._sentrySpan) {\n return;\n }\n\n // Update the root span (srvx transaction) with the parameterized route name.\n // The srvx span is created before h3 resolves the route, so it initially has the raw URL.\n // Note: data.type is always 'middleware' in asyncEnd regardless of handler type,\n // so we rely on getParameterizedRoute() to filter out catch-all routes instead.\n const rootSpan = getRootSpan(data._sentrySpan);\n if (rootSpan && rootSpan !== data._sentrySpan) {\n const routePattern = getParameterizedRoute(data.event);\n if (routePattern) {\n const method = data.event.req.method || 'GET';\n updateSpanName(rootSpan, `${method} ${routePattern}`);\n rootSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n 'http.route': routePattern,\n });\n }\n }\n },\n error: onTraceError,\n });\n}\n\nfunction setupSrvxTracingChannels(): void {\n // Store the parent span per-request so middleware and fetch share the same parent.\n // WeakMap ensures per-request isolation in concurrent environments and automatic cleanup.\n const requestParentSpans = new WeakMap<Request, Span>();\n\n const fetchChannel = tracingChannel<SrvxRequestEvent>('srvx.request', data => {\n const parsedUrl = data.request._url ? parseStringToURLObject(data.request._url.href) : undefined;\n const [spanName, urlAttributes] = getHttpSpanDetailsFromUrlObject(parsedUrl, 'server', 'auto.http.nitro.srvx', {\n method: data.request.method,\n });\n\n const sendDefaultPii = getClient()?.getOptions().sendDefaultPii ?? false;\n const headerAttributes = httpHeadersToSpanAttributes(\n Object.fromEntries(data.request.headers.entries()),\n sendDefaultPii,\n );\n\n return startSpanManual(\n {\n name: spanName,\n attributes: {\n ...urlAttributes,\n ...headerAttributes,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nitro.srvx',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: data.middleware ? 'middleware.nitro' : 'http.server',\n 'server.port': data.server.options.port,\n },\n // Use the same parent span as middleware to make them siblings\n parentSpan: requestParentSpans.get(data.request) || undefined,\n },\n span => span,\n );\n });\n\n // Subscribe to events (span already created in bindStore)\n fetchChannel.subscribe({\n start: () => {},\n asyncStart: () => {},\n end: () => {},\n asyncEnd: data => {\n onTraceEnd(data);\n\n // Clean up parent span reference after the fetch handler completes.\n requestParentSpans.delete(data.request);\n },\n error: data => {\n onTraceError(data);\n // Clean up parent span reference on error too\n requestParentSpans.delete(data.request);\n },\n });\n\n const middlewareChannel = tracingChannel<SrvxRequestEvent>('srvx.middleware', data => {\n // For the first middleware, capture the current parent span per-request\n if (data.middleware?.index === 0) {\n const activeSpan = getActiveSpan();\n if (activeSpan) {\n requestParentSpans.set(data.request, activeSpan);\n }\n }\n\n const parsedUrl = data.request._url ? parseStringToURLObject(data.request._url.href) : undefined;\n const [, urlAttributes] = getHttpSpanDetailsFromUrlObject(parsedUrl, 'server', 'auto.http.nitro.srvx', {\n method: data.request.method,\n });\n\n // Create span as a child of the original parent, not the previous middleware\n return startSpanManual(\n {\n name: `${data.middleware?.handler.name ?? 'unknown'} - ${data.request.method} ${data.request._url?.pathname}`,\n attributes: {\n ...urlAttributes,\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.nitro.srvx',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'middleware.nitro',\n },\n parentSpan: requestParentSpans.get(data.request) || undefined,\n },\n span => span,\n );\n });\n\n // Subscribe to events (span already created in bindStore)\n middlewareChannel.subscribe({\n start: () => {},\n asyncStart: () => {},\n end: () => {},\n asyncEnd: onTraceEnd,\n error: onTraceError,\n });\n}\n\n/**\n * Sets the parameterized route attributes on the span.\n */\nfunction setParameterizedRouteAttributes(span: Span, event: H3TracingRequestEvent['event']): void {\n const rootSpan = getRootSpan(span);\n if (!rootSpan) {\n return;\n }\n\n const matchedRoutePath = getParameterizedRoute(event);\n if (!matchedRoutePath) {\n return;\n }\n\n rootSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n 'http.route': matchedRoutePath,\n });\n\n const params = event.context?.params;\n\n if (params && typeof params === 'object') {\n Object.entries(params).forEach(([key, value]) => {\n // Based on this convention: https://getsentry.github.io/sentry-conventions/generated/attributes/url.html#urlpathparameterkey\n rootSpan.setAttributes({\n [`url.path.parameter.${key}`]: String(value),\n [`params.${key}`]: String(value),\n });\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getTraceData } from '@sentry/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sets Server-Timing response headers for trace propagation to the client.
|
|
5
|
+
* The browser SDK reads these via the Performance API to connect pageload traces.
|
|
6
|
+
*/
|
|
7
|
+
function setServerTimingHeaders(event) {
|
|
8
|
+
if (event.context._sentryServerTimingSet) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const headers = event.res?.headers;
|
|
13
|
+
if (!headers) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const traceData = getTraceData();
|
|
18
|
+
if (traceData['sentry-trace']) {
|
|
19
|
+
headers.append('Server-Timing', `sentry-trace;desc="${traceData['sentry-trace']}"`);
|
|
20
|
+
}
|
|
21
|
+
if (traceData.baggage) {
|
|
22
|
+
headers.append('Server-Timing', `baggage;desc="${traceData.baggage}"`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
event.context._sentryServerTimingSet = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { setServerTimingHeaders };
|
|
29
|
+
//# sourceMappingURL=setServerTimingHeaders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setServerTimingHeaders.js","sources":["../../../../src/runtime/hooks/setServerTimingHeaders.ts"],"sourcesContent":["import { getTraceData } from '@sentry/core';\nimport type { TracingRequestEvent as H3TracingRequestEvent } from 'h3/tracing';\n\n/**\n * Sets Server-Timing response headers for trace propagation to the client.\n * The browser SDK reads these via the Performance API to connect pageload traces.\n */\nexport function setServerTimingHeaders(event: H3TracingRequestEvent['event']): void {\n if (event.context._sentryServerTimingSet) {\n return;\n }\n\n const headers = event.res?.headers;\n if (!headers) {\n return;\n }\n\n const traceData = getTraceData();\n if (traceData['sentry-trace']) {\n headers.append('Server-Timing', `sentry-trace;desc=\"${traceData['sentry-trace']}\"`);\n }\n if (traceData.baggage) {\n headers.append('Server-Timing', `baggage;desc=\"${traceData.baggage}\"`);\n }\n\n event.context._sentryServerTimingSet = true;\n}\n"],"names":[],"mappings":";;AAGA;AACA;AACA;AACA;AACO,SAAS,sBAAsB,CAAC,KAAK,EAAwC;AACpF,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC,sBAAsB,EAAE;AAC5C,IAAI;AACJ,EAAE;;AAEF,EAAE,MAAM,OAAA,GAAU,KAAK,CAAC,GAAG,EAAE,OAAO;AACpC,EAAE,IAAI,CAAC,OAAO,EAAE;AAChB,IAAI;AACJ,EAAE;;AAEF,EAAE,MAAM,SAAA,GAAY,YAAY,EAAE;AAClC,EAAE,IAAI,SAAS,CAAC,cAAc,CAAC,EAAE;AACjC,IAAI,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,mBAAmB,EAAE,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AACvF,EAAE;AACF,EAAE,IAAI,SAAS,CAAC,OAAO,EAAE;AACzB,IAAI,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,cAAc,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1E,EAAE;;AAEF,EAAE,KAAK,CAAC,OAAO,CAAC,sBAAA,GAAyB,IAAI;AAC7C;;;;"}
|