mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
fix(sentry): route fatal logs like errors
SENTRY_LOG_LEVELS accepted 'fatal', but SentryService had no fatal override, so Nest's ConsoleLogger.fatal printed the message and Sentry never saw it (also before this branch). A fatal log now becomes an event whenever fatal or error is captured, and is a breadcrumb otherwise. The level list is a Set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
69a293175a
commit
f7659fd4fb
@@ -57,6 +57,19 @@ describe('SentryService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends fatal logs to Sentry whenever errors are captured', () => {
|
||||
new SentryService({ dsn: DSN, logLevels: ['error'] }).fatal('database unreachable', 'Bootstrap');
|
||||
|
||||
expect(Sentry.captureMessage).toHaveBeenCalledWith(expect.stringContaining('database unreachable'), 'fatal');
|
||||
});
|
||||
|
||||
it('keeps fatal logs as breadcrumbs when neither fatal nor error is captured', () => {
|
||||
new SentryService({ dsn: DSN, logLevels: ['warn'] }).fatal('database unreachable', 'Bootstrap');
|
||||
|
||||
expect(Sentry.captureMessage).not.toHaveBeenCalled();
|
||||
expect(Sentry.addBreadcrumb).toHaveBeenCalledWith(expect.objectContaining({ level: 'fatal' }));
|
||||
});
|
||||
|
||||
it('captures every level listed, e.g. SENTRY_LOG_LEVELS=error,warn', () => {
|
||||
const logger = new SentryService({ dsn: DSN, logLevels: ['error', 'warn'] });
|
||||
|
||||
|
||||
@@ -191,6 +191,28 @@ export class SentryService extends ConsoleLogger implements OnApplicationShutdow
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fatal log is at least as severe as an error, so it becomes an event whenever `fatal` or `error`
|
||||
* is captured. Without this override Nest's ConsoleLogger.fatal printed it and Sentry never saw it.
|
||||
*
|
||||
* @param message
|
||||
* @param context
|
||||
*/
|
||||
fatal(message: string, context?: string) {
|
||||
message = `${this.app} ${message}`;
|
||||
try {
|
||||
super.fatal(message, context);
|
||||
if (!this.isEnabled()) return;
|
||||
if (this.captures('fatal') || this.captures('error')) {
|
||||
Sentry.captureMessage(message, 'fatal');
|
||||
} else {
|
||||
Sentry.addBreadcrumb({ message, level: 'fatal', data: { context } });
|
||||
}
|
||||
} catch (err) {
|
||||
// do nothing to avoid blocking the application
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
|
||||
@@ -9,6 +9,10 @@ describe('parseSentryLogLevels', () => {
|
||||
expect(parseSentryLogLevels(' Error , WARN ')).toEqual(['error', 'warn']);
|
||||
});
|
||||
|
||||
it('accepts fatal, which SentryService routes like error', () => {
|
||||
expect(parseSentryLogLevels('fatal')).toEqual(['fatal']);
|
||||
});
|
||||
|
||||
it('ignores unknown names and duplicates but keeps the valid ones', () => {
|
||||
expect(parseSentryLogLevels('error,info,error,log')).toEqual(['error', 'log']);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LogLevel } from '@nestjs/common';
|
||||
|
||||
const NEST_LOG_LEVELS: readonly LogLevel[] = ['log', 'error', 'warn', 'debug', 'verbose', 'fatal'];
|
||||
const NEST_LOG_LEVELS: ReadonlySet<string> = new Set<LogLevel>(['log', 'error', 'warn', 'debug', 'verbose', 'fatal']);
|
||||
|
||||
/**
|
||||
* The Nest log levels that become Sentry events, from a comma-separated list such as `error,warn`
|
||||
@@ -12,6 +12,6 @@ export function parseSentryLogLevels(value: string | undefined): LogLevel[] {
|
||||
const levels = (value ?? '')
|
||||
.split(',')
|
||||
.map((level) => level.trim().toLowerCase())
|
||||
.filter((level): level is LogLevel => NEST_LOG_LEVELS.includes(level as LogLevel));
|
||||
.filter((level): level is LogLevel => NEST_LOG_LEVELS.has(level));
|
||||
return levels.length ? [...new Set(levels)] : ['error'];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user