Skip to content

Commit 8c94548

Browse files
nicohrubeclforst
andauthored
feat(nestjs): Add @sentry/nestjs (#12613)
Implements basic nest js package as wrapper on top of node sdk --------- Co-authored-by: Luca Forstner <[email protected]>
1 parent 98fda49 commit 8c94548

34 files changed

+267
-18
lines changed

.craft.yml

+3
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ targets:
9797
- name: npm
9898
id: '@sentry/bun'
9999
includeNames: /^sentry-bun-\d.*\.tgz$/
100+
- name: npm
101+
id: '@sentry/nestjs'
102+
includeNames: /^sentry-nestjs-\d.*\.tgz$/
100103

101104
## 6. Fullstack/Meta Frameworks (depending on Node and Browser or Framework SDKs)
102105
- name: npm

.github/workflows/build.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,7 @@ jobs:
10671067
'generic-ts3.8',
10681068
'node-fastify',
10691069
'node-hapi',
1070-
'node-nestjs',
1070+
'nestjs',
10711071
'node-exports-test-app',
10721072
'node-koa',
10731073
'node-connect',

dev-packages/e2e-tests/test-applications/node-nestjs/package.json renamed to dev-packages/e2e-tests/test-applications/nestjs/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "node-nestjs",
2+
"name": "nestjs",
33
"version": "0.0.1",
44
"private": true,
55
"scripts": {
@@ -18,7 +18,7 @@
1818
"@nestjs/common": "^10.0.0",
1919
"@nestjs/core": "^10.0.0",
2020
"@nestjs/platform-express": "^10.0.0",
21-
"@sentry/node": "latest || *",
21+
"@sentry/nestjs": "latest || *",
2222
"@sentry/types": "latest || *",
2323
"reflect-metadata": "^0.2.0",
2424
"rxjs": "^7.8.1"

dev-packages/e2e-tests/test-applications/node-nestjs/src/app.service.ts renamed to dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@nestjs/common';
2-
import * as Sentry from '@sentry/node';
2+
import * as Sentry from '@sentry/nestjs';
33
import { makeHttpRequest } from './utils';
44

55
@Injectable()

dev-packages/e2e-tests/test-applications/node-nestjs/src/instrument.ts renamed to dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as Sentry from '@sentry/node';
1+
import * as Sentry from '@sentry/nestjs';
22

33
Sentry.init({
44
environment: 'qa', // dynamic sampling bias to keep transactions

dev-packages/e2e-tests/test-applications/node-nestjs/src/main.ts renamed to dev-packages/e2e-tests/test-applications/nestjs/src/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import './instrument';
33

44
// Import other modules
55
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
6-
import * as Sentry from '@sentry/node';
6+
import * as Sentry from '@sentry/nestjs';
77
import { AppModule1, AppModule2 } from './app.module';
88

99
const app1Port = 3030;

dev-packages/e2e-tests/test-applications/node-nestjs/start-event-proxy.mjs renamed to dev-packages/e2e-tests/test-applications/nestjs/start-event-proxy.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils';
22

33
startEventProxyServer({
44
port: 3031,
5-
proxyServerName: 'node-nestjs',
5+
proxyServerName: 'nestjs',
66
});

dev-packages/e2e-tests/test-applications/node-nestjs/tests/errors.test.ts renamed to dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
22
import { waitForError } from '@sentry-internal/test-utils';
33

44
test('Sends exception to Sentry', async ({ baseURL }) => {
5-
const errorEventPromise = waitForError('node-nestjs', event => {
5+
const errorEventPromise = waitForError('nestjs', event => {
66
return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123';
77
});
88

dev-packages/e2e-tests/test-applications/node-nestjs/tests/propagation.test.ts renamed to dev-packages/e2e-tests/test-applications/nestjs/tests/propagation.test.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import { SpanJSON } from '@sentry/types';
66
test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
77
const id = crypto.randomUUID();
88

9-
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
9+
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
1010
return (
1111
transactionEvent.contexts?.trace?.op === 'http.server' &&
1212
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}`
1313
);
1414
});
1515

16-
const outboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
16+
const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
1717
return (
1818
transactionEvent.contexts?.trace?.op === 'http.server' &&
1919
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}`
@@ -121,14 +121,14 @@ test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
121121
test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
122122
const id = crypto.randomUUID();
123123

124-
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
124+
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
125125
return (
126126
transactionEvent?.contexts?.trace?.op === 'http.server' &&
127127
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}`
128128
);
129129
});
130130

131-
const outboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
131+
const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
132132
return (
133133
transactionEvent?.contexts?.trace?.op === 'http.server' &&
134134
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}`
@@ -234,7 +234,7 @@ test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => {
234234
});
235235

236236
test('Propagates trace for outgoing external http requests', async ({ baseURL }) => {
237-
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
237+
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
238238
return (
239239
transactionEvent?.contexts?.trace?.op === 'http.server' &&
240240
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed`
@@ -271,7 +271,7 @@ test('Propagates trace for outgoing external http requests', async ({ baseURL })
271271
});
272272

273273
test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => {
274-
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
274+
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
275275
return (
276276
transactionEvent?.contexts?.trace?.op === 'http.server' &&
277277
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed`
@@ -295,7 +295,7 @@ test('Does not propagate outgoing http requests not covered by tracePropagationT
295295
});
296296

297297
test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => {
298-
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
298+
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
299299
return (
300300
transactionEvent?.contexts?.trace?.op === 'http.server' &&
301301
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed`
@@ -332,7 +332,7 @@ test('Propagates trace for outgoing external fetch requests', async ({ baseURL }
332332
});
333333

334334
test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => {
335-
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
335+
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
336336
return (
337337
transactionEvent?.contexts?.trace?.op === 'http.server' &&
338338
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed`

dev-packages/e2e-tests/test-applications/node-nestjs/tests/transactions.test.ts renamed to dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
22
import { waitForTransaction } from '@sentry-internal/test-utils';
33

44
test('Sends an API route transaction', async ({ baseURL }) => {
5-
const pageloadTransactionEventPromise = waitForTransaction('node-nestjs', transactionEvent => {
5+
const pageloadTransactionEventPromise = waitForTransaction('nestjs', transactionEvent => {
66
return (
77
transactionEvent?.contexts?.trace?.op === 'http.server' &&
88
transactionEvent?.transaction === 'GET /test-transaction'

dev-packages/e2e-tests/verdaccio-config/config.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ packages:
8080
unpublish: $all
8181
# proxy: npmjs # Don't proxy for E2E tests!
8282

83+
'@sentry/nestjs':
84+
access: $all
85+
publish: $all
86+
unpublish: $all
87+
# proxy: npmjs # Don't proxy for E2E tests!
88+
8389
'@sentry/nextjs':
8490
access: $all
8591
publish: $all

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"packages/gatsby",
6161
"packages/google-cloud-serverless",
6262
"packages/integration-shims",
63+
"packages/nestjs",
6364
"packages/nextjs",
6465
"packages/node",
6566
"packages/nuxt",

packages/nestjs/.eslintrc.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
env: {
3+
node: true,
4+
},
5+
extends: ['../../.eslintrc.js'],
6+
};

packages/nestjs/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 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.

packages/nestjs/README.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
# Official Sentry SDK for NestJS (EXPERIMENTAL)
8+
9+
[![npm version](https://img.shields.io/npm/v/@sentry/nestjs.svg)](https://www.npmjs.com/package/@sentry/nestjs)
10+
[![npm dm](https://img.shields.io/npm/dm/@sentry/nestjs.svg)](https://www.npmjs.com/package/@sentry/nestjs)
11+
[![npm dt](https://img.shields.io/npm/dt/@sentry/nestjs.svg)](https://www.npmjs.com/package/@sentry/nestjs)
12+
13+
This SDK is considered **experimental and in an alpha state**. It may experience breaking changes. Please reach out on
14+
[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback or concerns.
15+
16+
## Installation
17+
18+
```bash
19+
npm install @sentry/nestjs
20+
21+
# Or yarn
22+
yarn add @sentry/nestjs
23+
```
24+
25+
## Usage
26+
27+
```js
28+
// CJS Syntax
29+
const Sentry = require('@sentry/nestjs');
30+
// ESM Syntax
31+
import * as Sentry from '@sentry/nestjs';
32+
33+
Sentry.init({
34+
dsn: '__DSN__',
35+
// ...
36+
});
37+
```
38+
39+
Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**.
40+
41+
## Links
42+
43+
- [Official SDK Docs](https://docs.sentry.io/platforms/javascript/guides/nestjs/)

packages/nestjs/package.json

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"name": "@sentry/nestjs",
3+
"version": "8.12.0",
4+
"description": "Official Sentry SDK for NestJS",
5+
"repository": "git://github.com/getsentry/sentry-javascript.git",
6+
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs",
7+
"author": "Sentry",
8+
"license": "MIT",
9+
"engines": {
10+
"node": ">=16"
11+
},
12+
"files": [
13+
"/build",
14+
"LICENSE",
15+
"README.md"
16+
],
17+
"main": "build/cjs/nestjs/index.js",
18+
"module": "build/esm/nestjs/index.js",
19+
"types": "build/types/index.d.ts",
20+
"exports": {
21+
"./package.json": "./package.json",
22+
".": {
23+
"import": {
24+
"types": "./build/types/index.d.ts",
25+
"default": "./build/esm/index.js"
26+
},
27+
"require": {
28+
"types": "./build/types/index.d.ts",
29+
"default": "./build/cjs/index.js"
30+
}
31+
}
32+
},
33+
"typesVersions": {
34+
"<4.9": {
35+
"build/types/index.d.ts": [
36+
"build/types-ts3.8/index.d.ts"
37+
]
38+
}
39+
},
40+
"publishConfig": {
41+
"access": "public"
42+
},
43+
"dependencies": {
44+
"@sentry/core": "8.12.0",
45+
"@sentry/node": "8.12.0"
46+
},
47+
"scripts": {
48+
"build": "run-p build:transpile build:types",
49+
"build:dev": "yarn build",
50+
"build:transpile": "rollup -c rollup.npm.config.mjs",
51+
"build:types": "run-s build:types:core build:types:downlevel",
52+
"build:types:core": "tsc -p tsconfig.types.json",
53+
"build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8",
54+
"build:watch": "run-p build:transpile:watch build:types:watch",
55+
"build:dev:watch": "yarn build:watch",
56+
"build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch",
57+
"build:types:watch": "tsc -p tsconfig.types.json --watch",
58+
"build:tarball": "npm pack",
59+
"circularDepCheck": "madge --circular src/index.ts",
60+
"clean": "rimraf build coverage sentry-node-*.tgz",
61+
"fix": "eslint . --format stylish --fix",
62+
"lint": "eslint . --format stylish",
63+
"test": "vitest run",
64+
"test:watch": "vitest --watch",
65+
"yalc:publish": "yalc publish --push --sig"
66+
},
67+
"volta": {
68+
"extends": "../../package.json"
69+
},
70+
"sideEffects": false
71+
}

packages/nestjs/rollup.npm.config.mjs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
2+
3+
export default makeNPMConfigVariants(makeBaseNPMConfig());

packages/nestjs/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from '@sentry/node';
2+
3+
export { init } from './sdk';

packages/nestjs/src/sdk.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { applySdkMetadata } from '@sentry/core';
2+
import type { NodeClient, NodeOptions } from '@sentry/node';
3+
import { init as nodeInit } from '@sentry/node';
4+
5+
/**
6+
* Initializes the NestJS SDK
7+
*/
8+
export function init(options: NodeOptions | undefined = {}): NodeClient | undefined {
9+
const opts: NodeOptions = {
10+
...options,
11+
};
12+
13+
applySdkMetadata(opts, 'nestjs');
14+
15+
return nodeInit(opts);
16+
}

packages/nestjs/test/sdk.test.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as SentryNode from '@sentry/node';
2+
import { SDK_VERSION } from '@sentry/utils';
3+
4+
import { vi } from 'vitest';
5+
import { init as nestInit } from '../src/sdk';
6+
7+
const nodeInit = vi.spyOn(SentryNode, 'init');
8+
const PUBLIC_DSN = 'https://username@domain/123';
9+
10+
describe('Initialize Nest SDK', () => {
11+
beforeEach(() => {
12+
vi.clearAllMocks();
13+
});
14+
15+
it('has the correct metadata', () => {
16+
const client = nestInit({
17+
dsn: PUBLIC_DSN,
18+
});
19+
20+
const expectedMetadata = {
21+
_metadata: {
22+
sdk: {
23+
name: 'sentry.javascript.nestjs',
24+
packages: [{ name: 'npm:@sentry/nestjs', version: SDK_VERSION }],
25+
version: SDK_VERSION,
26+
},
27+
},
28+
};
29+
30+
expect(client).not.toBeUndefined();
31+
expect(nodeInit).toHaveBeenCalledTimes(1);
32+
expect(nodeInit).toHaveBeenLastCalledWith(expect.objectContaining(expectedMetadata));
33+
});
34+
});

packages/nestjs/tsconfig.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
4+
"include": ["src/**/*"],
5+
6+
"compilerOptions": {}
7+
}

packages/nestjs/tsconfig.test.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
4+
"include": ["test/**/*", "vite.config.ts"],
5+
6+
"compilerOptions": {
7+
// should include all types from `./tsconfig.json` plus types for all test frameworks used
8+
"types": ["vitest/globals"]
9+
10+
// other package-specific, test-specific options
11+
}
12+
}

0 commit comments

Comments
 (0)