Complete Sentry integration (#1546)

This commit is contained in:
Charles Bochet 2023-09-11 15:07:30 -07:00 committed by GitHub
parent 35bcef5090
commit 7621854d4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 164 additions and 67 deletions

View File

@ -22,5 +22,6 @@ SIGN_IN_PREFILLED=true
# SUPPORT_DRIVER=front
# SUPPORT_FRONT_HMAC_KEY=replace_me_with_front_chat_verification_secret
# SUPPORT_FRONT_CHAT_ID=replace_me_with_front_chat_id
# LOGGER_DRIVER=console
# SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
# LOGGER_DRIVER=console
# LOG_LEVEL=error,warn

View File

@ -75,8 +75,7 @@ export class UserResolver {
select,
});
assert(user, 'User not found');
return user;
throw new Error('test2');
}
@UseFilters(ExceptionFilter)

View File

@ -0,0 +1,58 @@
import { plainToClass } from 'class-transformer';
import { CastToLogLevelArray } from 'src/integrations/environment/decorators/cast-to-log-level-array.decorator';
class TestClass {
@CastToLogLevelArray()
logLevels?: any;
}
describe('CastToLogLevelArray Decorator', () => {
it('should cast "log" to ["log"]', () => {
const transformedClass = plainToClass(TestClass, { logLevels: 'log' });
expect(transformedClass.logLevels).toStrictEqual(['log']);
});
it('should cast "error" to ["error"]', () => {
const transformedClass = plainToClass(TestClass, { logLevels: 'error' });
expect(transformedClass.logLevels).toStrictEqual(['error']);
});
it('should cast "warn" to ["warn"]', () => {
const transformedClass = plainToClass(TestClass, { logLevels: 'warn' });
expect(transformedClass.logLevels).toStrictEqual(['warn']);
});
it('should cast "debug" to ["debug"]', () => {
const transformedClass = plainToClass(TestClass, { logLevels: 'debug' });
expect(transformedClass.logLevels).toStrictEqual(['debug']);
});
it('should cast "verbose" to ["verbose"]', () => {
const transformedClass = plainToClass(TestClass, { logLevels: 'verbose' });
expect(transformedClass.logLevels).toStrictEqual(['verbose']);
});
it('should cast "verbose,error,warn" to ["verbose", "error", "warn"]', () => {
const transformedClass = plainToClass(TestClass, {
logLevels: 'verbose,error,warn',
});
expect(transformedClass.logLevels).toStrictEqual([
'verbose',
'error',
'warn',
]);
});
it('should cast "toto" to undefined', () => {
const transformedClass = plainToClass(TestClass, { logLevels: 'toto' });
expect(transformedClass.logLevels).toBeUndefined();
});
it('should cast "verbose,error,toto" to undefined', () => {
const transformedClass = plainToClass(TestClass, {
logLevels: 'verbose,error,toto',
});
expect(transformedClass.logLevels).toBeUndefined();
});
});

View File

@ -0,0 +1,20 @@
import { Transform } from 'class-transformer';
export function CastToLogLevelArray() {
return Transform(({ value }: { value: string }) => toLogLevelArray(value));
}
const toLogLevelArray = (value: any) => {
if (typeof value === 'string') {
const rawLogLevels = value.split(',').map((level) => level.trim());
const isInvalid = rawLogLevels.some(
(level) => !['log', 'error', 'warn', 'debug', 'verbose'].includes(level),
);
if (!isInvalid) {
return rawLogLevels;
}
}
return undefined;
};

View File

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Injectable } from '@nestjs/common';
import { Injectable, LogLevel } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AwsRegion } from './interfaces/aws-region.interface';
import { StorageType } from './interfaces/storage.interface';
import { SupportDriver } from './interfaces/support.interface';
import { LoggerType } from './interfaces/logger.interface';
import { LoggerDriver } from './interfaces/logger.interface';
@Injectable()
export class EnvironmentService {
@ -122,13 +122,23 @@ export class EnvironmentService {
return this.configService.get<string>('SUPPORT_FRONT_HMAC_KEY');
}
getLoggerDriver(): string {
return (
this.configService.get<string>('LOGGER_DRIVER') ?? LoggerDriver.Console
);
}
getLogLevels(): LogLevel[] {
return (
this.configService.get<LogLevel[]>('LOG_LEVELS') ?? [
'log',
'error',
'warn',
]
);
}
getSentryDSN(): string | undefined {
return this.configService.get<string>('SENTRY_DSN');
}
getLoggerDriver(): string | undefined {
return (
this.configService.get<string>('LOGGER_DRIVER') ?? LoggerType.Console
);
}
}

View File

@ -1,3 +1,5 @@
import { LogLevel } from '@nestjs/common';
import { plainToClass } from 'class-transformer';
import {
IsEnum,
@ -19,6 +21,8 @@ import { IsAWSRegion } from './decorators/is-aws-region.decorator';
import { CastToBoolean } from './decorators/cast-to-boolean.decorator';
import { SupportDriver } from './interfaces/support.interface';
import { CastToPositiveNumber } from './decorators/cast-to-positive-number.decorator';
import { LoggerDriver } from './interfaces/logger.interface';
import { CastToLogLevelArray } from './decorators/cast-to-log-level-array.decorator';
export class EnvironmentVariables {
// Misc
@ -125,6 +129,18 @@ export class EnvironmentVariables {
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
@IsString()
SUPPORT_FRONT_HMAC_KEY?: string;
@IsEnum(LoggerDriver)
@IsOptional()
LOGGER_DRIVER?: LoggerDriver;
@CastToLogLevelArray()
@IsOptional()
LOG_LEVELS?: LogLevel[];
@ValidateIf((env) => env.LOGGER_DRIVER === LoggerDriver.Sentry)
@IsString()
SENTRY_DSN?: string;
}
export function validate(config: Record<string, unknown>) {

View File

@ -1,4 +1,4 @@
export enum LoggerType {
export enum LoggerDriver {
Console = 'console',
Sentry = 'sentry',
}

View File

@ -8,8 +8,8 @@ import { FileStorageModule } from './file-storage/file-storage.module';
import { FileStorageModuleOptions } from './file-storage/interfaces';
import { StorageType } from './environment/interfaces/storage.interface';
import { LoggerModule } from './logger/logger.module';
import { LoggerType } from './environment/interfaces/logger.interface';
import { LoggerModuleOptions } from './logger/interfaces';
import { LoggerDriver } from './environment/interfaces/logger.interface';
/**
* FileStorage Module factory
@ -63,15 +63,15 @@ const loggerModuleFactory = async (
): Promise<LoggerModuleOptions> => {
const type = environmentService.getLoggerDriver();
switch (type) {
case LoggerType.Console: {
case LoggerDriver.Console: {
return {
type: LoggerType.Console,
type: LoggerDriver.Console,
options: null,
};
}
case LoggerType.Sentry: {
case LoggerDriver.Sentry: {
return {
type: LoggerType.Sentry,
type: LoggerDriver.Sentry,
options: {
sentryDNS: environmentService.getSentryDSN() ?? '',
},

View File

@ -15,53 +15,39 @@ export class SentryDriver implements LoggerService {
});
}
log(message: any, category: string) {
Sentry.addBreadcrumb({
message,
level: 'log',
data: {
category,
},
});
private logLevels = ['log', 'error', 'warning', 'debug', 'info'];
setLogLevels(levels: string[]) {
this.logLevels = levels;
}
error(message: any, category: string) {
Sentry.addBreadcrumb({
message,
level: 'error',
data: {
category,
},
});
log(message: any) {
if (this.logLevels.includes('log')) {
Sentry.captureMessage(message, { level: 'log' });
}
}
warn(message: any, category: string) {
Sentry.addBreadcrumb({
message,
level: 'error',
data: {
category,
},
});
error(message: any) {
if (this.logLevels.includes('error')) {
Sentry.captureMessage(message, { level: 'error' });
}
}
debug?(message: any, category: string) {
Sentry.addBreadcrumb({
message,
level: 'debug',
data: {
category,
},
});
warn(message: any) {
if (this.logLevels.includes('warn')) {
Sentry.captureMessage(message, { level: 'warning' });
}
}
verbose?(message: any, category: string) {
Sentry.addBreadcrumb({
message,
level: 'info',
data: {
category,
},
});
debug?(message: any) {
if (this.logLevels.includes('debug')) {
Sentry.captureMessage(message, { level: 'debug' });
}
}
verbose?(message: any) {
if (this.logLevels.includes('verbose')) {
Sentry.captureMessage(message, { level: 'info' });
}
}
}

View File

@ -1,14 +1,14 @@
import { LoggerType } from 'src/integrations/environment/interfaces/logger.interface';
import { LoggerDriver } from 'src/integrations/environment/interfaces/logger.interface';
export interface SentryDriverFactoryOptions {
type: LoggerType.Sentry;
type: LoggerDriver.Sentry;
options: {
sentryDNS: string;
};
}
export interface ConsoleDriverFactoryOptions {
type: LoggerType.Console;
type: LoggerDriver.Console;
options: null;
}

View File

@ -1,6 +1,6 @@
import { DynamicModule, Global, ConsoleLogger } from '@nestjs/common';
import { LoggerType } from 'src/integrations/environment/interfaces/logger.interface';
import { LoggerDriver } from 'src/integrations/environment/interfaces/logger.interface';
import { LoggerService } from './logger.service';
import { LoggerModuleOptions } from './interfaces';
@ -15,7 +15,7 @@ export class LoggerModule {
const provider = {
provide: LOGGER_DRIVER,
useValue:
options.type === LoggerType.Console
options.type === LoggerDriver.Console
? new ConsoleLogger()
: new SentryDriver(options.options),
};
@ -32,7 +32,7 @@ export class LoggerModule {
provide: LOGGER_DRIVER,
useFactory: async (...args: any[]) => {
const config = await options.useFactory(...args);
return config?.type === LoggerType.Console
return config?.type === LoggerDriver.Console
? new ConsoleLogger()
: new SentryDriver(config.options);
},

View File

@ -1,14 +1,15 @@
import {
Inject,
Injectable,
LoggerService as ConsoleLoggerService,
LogLevel,
LoggerService as LoggerServiceInterface,
} from '@nestjs/common';
import { LOGGER_DRIVER } from './logger.constants';
@Injectable()
export class LoggerService implements ConsoleLoggerService {
constructor(@Inject(LOGGER_DRIVER) private driver: ConsoleLoggerService) {}
export class LoggerService implements LoggerServiceInterface {
constructor(@Inject(LOGGER_DRIVER) private driver: LoggerServiceInterface) {}
log(message: any, category: string, ...optionalParams: any[]) {
this.driver.log.apply(this.driver, [message, category, ...optionalParams]);
@ -41,4 +42,8 @@ export class LoggerService implements ConsoleLoggerService {
...optionalParams,
]);
}
setLogLevels(levels: LogLevel[]) {
this.driver.setLogLevels?.apply(this.driver, [levels]);
}
}

View File

@ -9,6 +9,7 @@ import { AppModule } from './app.module';
import { settings } from './constants/settings';
import { LoggerService } from './integrations/logger/logger.service';
import { EnvironmentService } from './integrations/environment/environment.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
@ -35,8 +36,9 @@ async function bootstrap() {
);
const loggerService = app.get(LoggerService);
app.useLogger(loggerService);
app.useLogger(app.get(EnvironmentService).getLogLevels());
await app.listen(3000);
await app.listen(app.get(EnvironmentService).getPort());
}
bootstrap();