Skip to content

Commit

Permalink
feat: update sentry error handling (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwwinter committed Mar 20, 2024
1 parent 52cf588 commit 5ba4bdb
Show file tree
Hide file tree
Showing 9 changed files with 1,643 additions and 2,198 deletions.
3 changes: 1 addition & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
CORS=*
SENTRY_DSN=
KEYCLOAK_URL=https://keycloak.aam-digital.net
KEYCLOAK_ADMIN=admin
KEYCLOAK_PASSWORD=PASSWORD
KEYCLOAK_PASSWORD=PASSWORD
5 changes: 4 additions & 1 deletion nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"],
"assets": ["assets/*"]
"assets": [
"assets/*",
{ "include": "./config/*.yaml", "outDir": "./dist", "watchAssets": true }
]
}
}
3,651 changes: 1,507 additions & 2,144 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 3 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/swagger": "^6.0.5",
"@ntegral/nestjs-sentry": "^3.0.7",
"@sentry/node": "^7.11.1",
"@sentry/node": "^7.102.1",
"@sentry/tracing": "^7.102.1",
"js-yaml": "4.1.0",
"jwt-decode": "^3.1.2",
"passport-http-bearer": "^1.0.1",
"reflect-metadata": "^0.1.13",
Expand Down Expand Up @@ -60,13 +61,6 @@
"tsconfig-paths": "4.0.0",
"typescript": "^4.3.5"
},
"overrides": {
"@ntegral/nestjs-sentry": {
"@nestjs/common": "$@nestjs/common",
"@nestjs/core": "$@nestjs/core",
"@sentry/node": "$@sentry/node"
}
},
"jest": {
"moduleFileExtensions": [
"js",
Expand Down
45 changes: 6 additions & 39 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,17 @@
import { Module } from '@nestjs/common';
import { AuthModule } from './auth/auth.module';
import { AccountModule } from './account/account.module';
import { SeverityLevel } from '@sentry/node';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SentryInterceptor, SentryModule } from '@ntegral/nestjs-sentry';
import { APP_INTERCEPTOR } from '@nestjs/core';

const lowSeverityLevels: SeverityLevel[] = ['log', 'info'];
import { ConfigModule } from '@nestjs/config';
import { AppConfiguration } from './config/configuration';

@Module({
providers: [
{ provide: APP_INTERCEPTOR, useFactory: () => new SentryInterceptor() },
],
imports: [
AuthModule,
AccountModule,
ConfigModule.forRoot({ isGlobal: true }),
SentryModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
if (!configService.get('SENTRY_DSN')) {
return;
}

return {
dsn: configService.get('SENTRY_DSN'),
debug: true,
environment: 'prod',
release: 'accounts@' + process.env.npm_package_version,
whitelistUrls: [/https?:\/\/(.*)\.?aam-digital\.com/],
initialScope: {
tags: {
// ID of the docker container in which this is run
hostname: process.env.HOSTNAME || 'unknown',
},
},
beforeSend: (event) => {
if (lowSeverityLevels.includes(event.level)) {
return null;
} else {
return event;
}
},
};
},
ConfigModule.forRoot({
isGlobal: true,
ignoreEnvFile: false,
load: [AppConfiguration],
}),
],
})
Expand Down
8 changes: 8 additions & 0 deletions src/config/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Logger Configuration
# values can be overwritten in .env file

SENTRY:
ENABLED: false
INSTANCE_NAME: local-development # can be personalised in .env -> local-development-<your-name>
ENVIRONMENT: local # local | development | production
DSN: ''
38 changes: 38 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import { join } from 'path';

const CONFIG_FILENAME = 'app.yaml';

/**
* loads local CONFIG_FILENAME file and provides them in NestJs Config Service
* See: /src/config/app.yaml
*/
export function AppConfiguration(): Record<string, string> {
return flatten(
yaml.load(readFileSync(join(__dirname, CONFIG_FILENAME), 'utf8')) as Record<
string,
string
>,
);
}

/**
* Recursively create a flat key-value object where keys contain nested keys as prefixes
*/
function flatten(
obj: any,
prefix = '',
delimiter = '_',
): Record<string, string> {
return Object.keys(obj).reduce((acc: any, k: string) => {
const pre = prefix.length ? prefix + delimiter : '';

if (typeof obj[k] === 'object')
Object.assign(acc, flatten(obj[k], pre + k));
else {
acc[pre + k] = obj[k];
}
return acc;
}, {});
}
11 changes: 8 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { SentryService } from '@ntegral/nestjs-sentry';
import { ConfigService } from '@nestjs/config';
import { AppConfiguration } from './config/configuration';
import { configureSentry } from './sentry.configuration';

async function bootstrap() {
// load ConfigService instance to access .env and app.yaml values
const configService = new ConfigService(AppConfiguration());

const app = await NestFactory.create(AppModule);

app.enableCors({ origin: process.env.CORS });
Expand All @@ -20,8 +25,8 @@ async function bootstrap() {
swaggerOptions: { persistAuthorization: true },
});

app.useLogger(SentryService.SentryServiceInstance());
configureSentry(app, configService);

await app.listen(3000);
await app.listen(process.env.PORT || 3000);
}
bootstrap();
68 changes: 68 additions & 0 deletions src/sentry.configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as Sentry from '@sentry/node';
import { ConfigService } from '@nestjs/config';
import { ArgumentsHost, INestApplication } from '@nestjs/common';
import { BaseExceptionFilter, HttpAdapterHost } from '@nestjs/core';

export class SentryConfiguration {
ENABLED = false;
DSN = '';
INSTANCE_NAME = '';
ENVIRONMENT = '';
}

function loadSentryConfiguration(
configService: ConfigService,
): SentryConfiguration {
return {
ENABLED: configService.getOrThrow('SENTRY_ENABLED'),
DSN: configService.getOrThrow('SENTRY_DSN'),
INSTANCE_NAME: configService.getOrThrow('SENTRY_INSTANCE_NAME'),
ENVIRONMENT: configService.getOrThrow('SENTRY_ENVIRONMENT'),
};
}

export function configureSentry(
app: INestApplication,
configService: ConfigService,
): void {
const sentryConfiguration = loadSentryConfiguration(configService);
if (sentryConfiguration.ENABLED) {
configureLoggingSentry(app, sentryConfiguration);
}
}

export class SentryFilter extends BaseExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
Sentry.captureException(exception);
super.catch(exception, host);
}
}

function configureLoggingSentry(
app: INestApplication,
sentryConfiguration: SentryConfiguration,
): void {
Sentry.init({
debug: true,
serverName: sentryConfiguration.INSTANCE_NAME,
environment: sentryConfiguration.ENVIRONMENT,
dsn: sentryConfiguration.DSN,
integrations: [
// enable HTTP calls tracing
new Sentry.Integrations.Console(),
new Sentry.Integrations.Http({ tracing: true }),
new Sentry.Integrations.Express(),
],
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions
// Set sampling rate for profiling - this is relative to tracesSampleRate
profilesSampleRate: 1.0,
});

app.use(Sentry.Handlers.errorHandler());
app.use(Sentry.Handlers.tracingHandler());
app.use(Sentry.Handlers.requestHandler());

const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new SentryFilter(httpAdapter));
}

0 comments on commit 5ba4bdb

Please sign in to comment.