Skip to content

Commit

Permalink
feat(nestjs): Add @sentry/nestjs (#12613)
Browse files Browse the repository at this point in the history
Implements basic nest js package as wrapper on top of node sdk

---------

Co-authored-by: Luca Forstner <luca.forstner@sentry.io>
  • Loading branch information
nicohrubec and lforst authored Jun 26, 2024
1 parent 98fda49 commit 8c94548
Show file tree
Hide file tree
Showing 34 changed files with 267 additions and 18 deletions.
3 changes: 3 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ targets:
- name: npm
id: '@sentry/bun'
includeNames: /^sentry-bun-\d.*\.tgz$/
- name: npm
id: '@sentry/nestjs'
includeNames: /^sentry-nestjs-\d.*\.tgz$/

## 6. Fullstack/Meta Frameworks (depending on Node and Browser or Framework SDKs)
- name: npm
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ jobs:
'generic-ts3.8',
'node-fastify',
'node-hapi',
'node-nestjs',
'nestjs',
'node-exports-test-app',
'node-koa',
'node-connect',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "node-nestjs",
"name": "nestjs",
"version": "0.0.1",
"private": true,
"scripts": {
Expand All @@ -18,7 +18,7 @@
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@sentry/node": "latest || *",
"@sentry/nestjs": "latest || *",
"@sentry/types": "latest || *",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common';
import * as Sentry from '@sentry/node';
import * as Sentry from '@sentry/nestjs';
import { makeHttpRequest } from './utils';

@Injectable()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as Sentry from '@sentry/node';
import * as Sentry from '@sentry/nestjs';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import './instrument';

// Import other modules
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
import * as Sentry from '@sentry/node';
import * as Sentry from '@sentry/nestjs';
import { AppModule1, AppModule2 } from './app.module';

const app1Port = 3030;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'node-nestjs',
proxyServerName: 'nestjs',
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { waitForError } from '@sentry-internal/test-utils';

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { SpanJSON } from '@sentry/types';
test('Propagates trace for outgoing http requests', async ({ baseURL }) => {
const id = crypto.randomUUID();

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

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

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

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

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

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

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

test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => {
const inboundTransactionPromise = waitForTransaction('node-nestjs', transactionEvent => {
const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => {
return (
transactionEvent?.contexts?.trace?.op === 'http.server' &&
transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test('Sends an API route transaction', async ({ baseURL }) => {
const pageloadTransactionEventPromise = waitForTransaction('node-nestjs', transactionEvent => {
const pageloadTransactionEventPromise = waitForTransaction('nestjs', transactionEvent => {
return (
transactionEvent?.contexts?.trace?.op === 'http.server' &&
transactionEvent?.transaction === 'GET /test-transaction'
Expand Down
6 changes: 6 additions & 0 deletions dev-packages/e2e-tests/verdaccio-config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ packages:
unpublish: $all
# proxy: npmjs # Don't proxy for E2E tests!

'@sentry/nestjs':
access: $all
publish: $all
unpublish: $all
# proxy: npmjs # Don't proxy for E2E tests!

'@sentry/nextjs':
access: $all
publish: $all
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"packages/gatsby",
"packages/google-cloud-serverless",
"packages/integration-shims",
"packages/nestjs",
"packages/nextjs",
"packages/node",
"packages/nuxt",
Expand Down
6 changes: 6 additions & 0 deletions packages/nestjs/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
env: {
node: true,
},
extends: ['../../.eslintrc.js'],
};
21 changes: 21 additions & 0 deletions packages/nestjs/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Functional Software, Inc. dba Sentry

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
43 changes: 43 additions & 0 deletions packages/nestjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<p align="center">
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84">
</a>
</p>

# Official Sentry SDK for NestJS (EXPERIMENTAL)

[![npm version](https://img.shields.io/npm/v/@sentry/nestjs.svg)](https://www.npmjs.com/package/@sentry/nestjs)
[![npm dm](https://img.shields.io/npm/dm/@sentry/nestjs.svg)](https://www.npmjs.com/package/@sentry/nestjs)
[![npm dt](https://img.shields.io/npm/dt/@sentry/nestjs.svg)](https://www.npmjs.com/package/@sentry/nestjs)

This SDK is considered **experimental and in an alpha state**. It may experience breaking changes. Please reach out on
[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback or concerns.

## Installation

```bash
npm install @sentry/nestjs

# Or yarn
yarn add @sentry/nestjs
```

## Usage

```js
// CJS Syntax
const Sentry = require('@sentry/nestjs');
// ESM Syntax
import * as Sentry from '@sentry/nestjs';

Sentry.init({
dsn: '__DSN__',
// ...
});
```

Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**.

## Links

- [Official SDK Docs](https://docs.sentry.io/platforms/javascript/guides/nestjs/)
71 changes: 71 additions & 0 deletions packages/nestjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "@sentry/nestjs",
"version": "8.12.0",
"description": "Official Sentry SDK for NestJS",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs",
"author": "Sentry",
"license": "MIT",
"engines": {
"node": ">=16"
},
"files": [
"/build",
"LICENSE",
"README.md"
],
"main": "build/cjs/nestjs/index.js",
"module": "build/esm/nestjs/index.js",
"types": "build/types/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./build/types/index.d.ts",
"default": "./build/esm/index.js"
},
"require": {
"types": "./build/types/index.d.ts",
"default": "./build/cjs/index.js"
}
}
},
"typesVersions": {
"<4.9": {
"build/types/index.d.ts": [
"build/types-ts3.8/index.d.ts"
]
}
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@sentry/core": "8.12.0",
"@sentry/node": "8.12.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
"build:dev": "yarn build",
"build:transpile": "rollup -c rollup.npm.config.mjs",
"build:types": "run-s build:types:core build:types:downlevel",
"build:types:core": "tsc -p tsconfig.types.json",
"build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8",
"build:watch": "run-p build:transpile:watch build:types:watch",
"build:dev:watch": "yarn build:watch",
"build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch",
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:tarball": "npm pack",
"circularDepCheck": "madge --circular src/index.ts",
"clean": "rimraf build coverage sentry-node-*.tgz",
"fix": "eslint . --format stylish --fix",
"lint": "eslint . --format stylish",
"test": "vitest run",
"test:watch": "vitest --watch",
"yalc:publish": "yalc publish --push --sig"
},
"volta": {
"extends": "../../package.json"
},
"sideEffects": false
}
3 changes: 3 additions & 0 deletions packages/nestjs/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';

export default makeNPMConfigVariants(makeBaseNPMConfig());
3 changes: 3 additions & 0 deletions packages/nestjs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from '@sentry/node';

export { init } from './sdk';
16 changes: 16 additions & 0 deletions packages/nestjs/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { applySdkMetadata } from '@sentry/core';
import type { NodeClient, NodeOptions } from '@sentry/node';
import { init as nodeInit } from '@sentry/node';

/**
* Initializes the NestJS SDK
*/
export function init(options: NodeOptions | undefined = {}): NodeClient | undefined {
const opts: NodeOptions = {
...options,
};

applySdkMetadata(opts, 'nestjs');

return nodeInit(opts);
}
34 changes: 34 additions & 0 deletions packages/nestjs/test/sdk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as SentryNode from '@sentry/node';
import { SDK_VERSION } from '@sentry/utils';

import { vi } from 'vitest';
import { init as nestInit } from '../src/sdk';

const nodeInit = vi.spyOn(SentryNode, 'init');
const PUBLIC_DSN = 'https://username@domain/123';

describe('Initialize Nest SDK', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('has the correct metadata', () => {
const client = nestInit({
dsn: PUBLIC_DSN,
});

const expectedMetadata = {
_metadata: {
sdk: {
name: 'sentry.javascript.nestjs',
packages: [{ name: 'npm:@sentry/nestjs', version: SDK_VERSION }],
version: SDK_VERSION,
},
},
};

expect(client).not.toBeUndefined();
expect(nodeInit).toHaveBeenCalledTimes(1);
expect(nodeInit).toHaveBeenLastCalledWith(expect.objectContaining(expectedMetadata));
});
});
7 changes: 7 additions & 0 deletions packages/nestjs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",

"include": ["src/**/*"],

"compilerOptions": {}
}
12 changes: 12 additions & 0 deletions packages/nestjs/tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",

"include": ["test/**/*", "vite.config.ts"],

"compilerOptions": {
// should include all types from `./tsconfig.json` plus types for all test frameworks used
"types": ["vitest/globals"]

// other package-specific, test-specific options
}
}
Loading

0 comments on commit 8c94548

Please sign in to comment.