From 7a748d5cdc3bad960ae83b651f3c0ecf9d4f9682 Mon Sep 17 00:00:00 2001 From: Timon Masberg Date: Thu, 1 Feb 2024 12:01:01 +0100 Subject: [PATCH] fix(api-observability): log unhandled exceptions caught in exceptions filter --- ...ec.ts => sentry-exceptions.filter.spec.ts} | 33 +++++++++++-------- .../lib/filters/sentry-exceptions.filter.ts | 30 ++++++++++++----- 2 files changed, 41 insertions(+), 22 deletions(-) rename libs/api/observability/src/lib/filters/{exceptions.filter.spec.ts => sentry-exceptions.filter.spec.ts} (66%) diff --git a/libs/api/observability/src/lib/filters/exceptions.filter.spec.ts b/libs/api/observability/src/lib/filters/sentry-exceptions.filter.spec.ts similarity index 66% rename from libs/api/observability/src/lib/filters/exceptions.filter.spec.ts rename to libs/api/observability/src/lib/filters/sentry-exceptions.filter.spec.ts index 24429532..3b0d2b8c 100644 --- a/libs/api/observability/src/lib/filters/exceptions.filter.spec.ts +++ b/libs/api/observability/src/lib/filters/sentry-exceptions.filter.spec.ts @@ -1,22 +1,25 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { Logger } from '@nestjs/common'; import * as Sentry from '@sentry/node'; import { PresentableException } from '@kordis/api/shared'; import { SentryExceptionsFilter } from './sentry-exceptions.filter'; -describe('ExceptionsFilter', () => { +describe('SentryExceptionsFilter', () => { let sentryExceptionsFilter: SentryExceptionsFilter; let addBreadcrumbMock: jest.Mock; let captureExceptionMock: jest.Mock; - - beforeEach(() => { + let logger: DeepMocked; + beforeEach(async () => { addBreadcrumbMock = jest.fn(); captureExceptionMock = jest.fn(); + logger = createMock(); (Sentry.addBreadcrumb as jest.Mock) = addBreadcrumbMock; (Sentry.captureException as jest.Mock) = captureExceptionMock; - sentryExceptionsFilter = new SentryExceptionsFilter(); + sentryExceptionsFilter = new SentryExceptionsFilter(logger); }); afterEach(() => { @@ -36,15 +39,11 @@ describe('ExceptionsFilter', () => { sentryExceptionsFilter.catch(presentableException); - expect(addBreadcrumbMock).toHaveBeenCalledTimes(1); - expect(addBreadcrumbMock).toHaveBeenCalledWith({ - level: 'error', - message: 'message', - data: { - name: 'Error', - code: 'code', - stack: expect.any(String), - }, + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith('message', undefined, { + name: 'Error', + code: 'code', + stack: expect.any(String), }); expect(captureExceptionMock).not.toHaveBeenCalled(); }); @@ -58,6 +57,14 @@ describe('ExceptionsFilter', () => { expect(captureExceptionMock).toHaveBeenCalledWith(exception, { level: 'error', }); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith( + 'Caught unhandled exception that was not presentable', + undefined, + { + exception, + }, + ); expect(addBreadcrumbMock).not.toHaveBeenCalled(); }); }); diff --git a/libs/api/observability/src/lib/filters/sentry-exceptions.filter.ts b/libs/api/observability/src/lib/filters/sentry-exceptions.filter.ts index 62a52efd..626603c5 100644 --- a/libs/api/observability/src/lib/filters/sentry-exceptions.filter.ts +++ b/libs/api/observability/src/lib/filters/sentry-exceptions.filter.ts @@ -1,23 +1,35 @@ -import { Catch, ExceptionFilter } from '@nestjs/common'; +import { Catch, ExceptionFilter, Logger } from '@nestjs/common'; import * as Sentry from '@sentry/node'; import { PresentableException } from '@kordis/api/shared'; +import { KordisLogger } from '../services/kordis-logger.interface'; + @Catch() export class SentryExceptionsFilter implements ExceptionFilter { + readonly logger: KordisLogger; + + constructor(_logger: Logger) { + this.logger = _logger; + } + catch(exception: unknown): void { if (exception instanceof PresentableException) { // if this is a presentable error, such as a validation error, we don't want to log it as an error but rather as an information to have the context for possible future debugging - Sentry.addBreadcrumb({ - level: 'error', - message: exception.message, - data: { - code: exception.code, - name: exception.name, - stack: exception.stack, - }, + this.logger.error(exception.message, undefined, { + code: exception.code, + name: exception.name, + stack: exception.stack, }); } else { + this.logger.error( + 'Caught unhandled exception that was not presentable', + undefined, + { + exception, + }, + ); + Sentry.captureException(exception, { level: 'error', });