Complete Sentry integration (#1546)
This commit is contained in:
@ -22,5 +22,6 @@ SIGN_IN_PREFILLED=true
|
|||||||
# SUPPORT_DRIVER=front
|
# SUPPORT_DRIVER=front
|
||||||
# SUPPORT_FRONT_HMAC_KEY=replace_me_with_front_chat_verification_secret
|
# SUPPORT_FRONT_HMAC_KEY=replace_me_with_front_chat_verification_secret
|
||||||
# SUPPORT_FRONT_CHAT_ID=replace_me_with_front_chat_id
|
# SUPPORT_FRONT_CHAT_ID=replace_me_with_front_chat_id
|
||||||
# SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
|
||||||
# LOGGER_DRIVER=console
|
# LOGGER_DRIVER=console
|
||||||
|
# SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
||||||
|
# LOG_LEVEL=error,warn
|
||||||
@ -75,8 +75,7 @@ export class UserResolver {
|
|||||||
select,
|
select,
|
||||||
});
|
});
|
||||||
assert(user, 'User not found');
|
assert(user, 'User not found');
|
||||||
|
throw new Error('test2');
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseFilters(ExceptionFilter)
|
@UseFilters(ExceptionFilter)
|
||||||
|
|||||||
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -1,11 +1,11 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* 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 { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
import { AwsRegion } from './interfaces/aws-region.interface';
|
import { AwsRegion } from './interfaces/aws-region.interface';
|
||||||
import { StorageType } from './interfaces/storage.interface';
|
import { StorageType } from './interfaces/storage.interface';
|
||||||
import { SupportDriver } from './interfaces/support.interface';
|
import { SupportDriver } from './interfaces/support.interface';
|
||||||
import { LoggerType } from './interfaces/logger.interface';
|
import { LoggerDriver } from './interfaces/logger.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EnvironmentService {
|
export class EnvironmentService {
|
||||||
@ -122,13 +122,23 @@ export class EnvironmentService {
|
|||||||
return this.configService.get<string>('SUPPORT_FRONT_HMAC_KEY');
|
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 {
|
getSentryDSN(): string | undefined {
|
||||||
return this.configService.get<string>('SENTRY_DSN');
|
return this.configService.get<string>('SENTRY_DSN');
|
||||||
}
|
}
|
||||||
|
|
||||||
getLoggerDriver(): string | undefined {
|
|
||||||
return (
|
|
||||||
this.configService.get<string>('LOGGER_DRIVER') ?? LoggerType.Console
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { LogLevel } from '@nestjs/common';
|
||||||
|
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsEnum,
|
IsEnum,
|
||||||
@ -19,6 +21,8 @@ import { IsAWSRegion } from './decorators/is-aws-region.decorator';
|
|||||||
import { CastToBoolean } from './decorators/cast-to-boolean.decorator';
|
import { CastToBoolean } from './decorators/cast-to-boolean.decorator';
|
||||||
import { SupportDriver } from './interfaces/support.interface';
|
import { SupportDriver } from './interfaces/support.interface';
|
||||||
import { CastToPositiveNumber } from './decorators/cast-to-positive-number.decorator';
|
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 {
|
export class EnvironmentVariables {
|
||||||
// Misc
|
// Misc
|
||||||
@ -125,6 +129,18 @@ export class EnvironmentVariables {
|
|||||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||||
@IsString()
|
@IsString()
|
||||||
SUPPORT_FRONT_HMAC_KEY?: string;
|
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>) {
|
export function validate(config: Record<string, unknown>) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export enum LoggerType {
|
export enum LoggerDriver {
|
||||||
Console = 'console',
|
Console = 'console',
|
||||||
Sentry = 'sentry',
|
Sentry = 'sentry',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import { FileStorageModule } from './file-storage/file-storage.module';
|
|||||||
import { FileStorageModuleOptions } from './file-storage/interfaces';
|
import { FileStorageModuleOptions } from './file-storage/interfaces';
|
||||||
import { StorageType } from './environment/interfaces/storage.interface';
|
import { StorageType } from './environment/interfaces/storage.interface';
|
||||||
import { LoggerModule } from './logger/logger.module';
|
import { LoggerModule } from './logger/logger.module';
|
||||||
import { LoggerType } from './environment/interfaces/logger.interface';
|
|
||||||
import { LoggerModuleOptions } from './logger/interfaces';
|
import { LoggerModuleOptions } from './logger/interfaces';
|
||||||
|
import { LoggerDriver } from './environment/interfaces/logger.interface';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileStorage Module factory
|
* FileStorage Module factory
|
||||||
@ -63,15 +63,15 @@ const loggerModuleFactory = async (
|
|||||||
): Promise<LoggerModuleOptions> => {
|
): Promise<LoggerModuleOptions> => {
|
||||||
const type = environmentService.getLoggerDriver();
|
const type = environmentService.getLoggerDriver();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case LoggerType.Console: {
|
case LoggerDriver.Console: {
|
||||||
return {
|
return {
|
||||||
type: LoggerType.Console,
|
type: LoggerDriver.Console,
|
||||||
options: null,
|
options: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case LoggerType.Sentry: {
|
case LoggerDriver.Sentry: {
|
||||||
return {
|
return {
|
||||||
type: LoggerType.Sentry,
|
type: LoggerDriver.Sentry,
|
||||||
options: {
|
options: {
|
||||||
sentryDNS: environmentService.getSentryDSN() ?? '',
|
sentryDNS: environmentService.getSentryDSN() ?? '',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -15,53 +15,39 @@ export class SentryDriver implements LoggerService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
log(message: any, category: string) {
|
private logLevels = ['log', 'error', 'warning', 'debug', 'info'];
|
||||||
Sentry.addBreadcrumb({
|
|
||||||
message,
|
setLogLevels(levels: string[]) {
|
||||||
level: 'log',
|
this.logLevels = levels;
|
||||||
data: {
|
|
||||||
category,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message: any, category: string) {
|
log(message: any) {
|
||||||
Sentry.addBreadcrumb({
|
if (this.logLevels.includes('log')) {
|
||||||
message,
|
Sentry.captureMessage(message, { level: 'log' });
|
||||||
level: 'error',
|
}
|
||||||
data: {
|
|
||||||
category,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(message: any, category: string) {
|
error(message: any) {
|
||||||
Sentry.addBreadcrumb({
|
if (this.logLevels.includes('error')) {
|
||||||
message,
|
Sentry.captureMessage(message, { level: 'error' });
|
||||||
level: 'error',
|
}
|
||||||
data: {
|
|
||||||
category,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug?(message: any, category: string) {
|
warn(message: any) {
|
||||||
Sentry.addBreadcrumb({
|
if (this.logLevels.includes('warn')) {
|
||||||
message,
|
Sentry.captureMessage(message, { level: 'warning' });
|
||||||
level: 'debug',
|
}
|
||||||
data: {
|
|
||||||
category,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
verbose?(message: any, category: string) {
|
debug?(message: any) {
|
||||||
Sentry.addBreadcrumb({
|
if (this.logLevels.includes('debug')) {
|
||||||
message,
|
Sentry.captureMessage(message, { level: 'debug' });
|
||||||
level: 'info',
|
}
|
||||||
data: {
|
}
|
||||||
category,
|
|
||||||
},
|
verbose?(message: any) {
|
||||||
});
|
if (this.logLevels.includes('verbose')) {
|
||||||
|
Sentry.captureMessage(message, { level: 'info' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
export interface SentryDriverFactoryOptions {
|
||||||
type: LoggerType.Sentry;
|
type: LoggerDriver.Sentry;
|
||||||
options: {
|
options: {
|
||||||
sentryDNS: string;
|
sentryDNS: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConsoleDriverFactoryOptions {
|
export interface ConsoleDriverFactoryOptions {
|
||||||
type: LoggerType.Console;
|
type: LoggerDriver.Console;
|
||||||
options: null;
|
options: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { DynamicModule, Global, ConsoleLogger } from '@nestjs/common';
|
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 { LoggerService } from './logger.service';
|
||||||
import { LoggerModuleOptions } from './interfaces';
|
import { LoggerModuleOptions } from './interfaces';
|
||||||
@ -15,7 +15,7 @@ export class LoggerModule {
|
|||||||
const provider = {
|
const provider = {
|
||||||
provide: LOGGER_DRIVER,
|
provide: LOGGER_DRIVER,
|
||||||
useValue:
|
useValue:
|
||||||
options.type === LoggerType.Console
|
options.type === LoggerDriver.Console
|
||||||
? new ConsoleLogger()
|
? new ConsoleLogger()
|
||||||
: new SentryDriver(options.options),
|
: new SentryDriver(options.options),
|
||||||
};
|
};
|
||||||
@ -32,7 +32,7 @@ export class LoggerModule {
|
|||||||
provide: LOGGER_DRIVER,
|
provide: LOGGER_DRIVER,
|
||||||
useFactory: async (...args: any[]) => {
|
useFactory: async (...args: any[]) => {
|
||||||
const config = await options.useFactory(...args);
|
const config = await options.useFactory(...args);
|
||||||
return config?.type === LoggerType.Console
|
return config?.type === LoggerDriver.Console
|
||||||
? new ConsoleLogger()
|
? new ConsoleLogger()
|
||||||
: new SentryDriver(config.options);
|
: new SentryDriver(config.options);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
LoggerService as ConsoleLoggerService,
|
LogLevel,
|
||||||
|
LoggerService as LoggerServiceInterface,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { LOGGER_DRIVER } from './logger.constants';
|
import { LOGGER_DRIVER } from './logger.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoggerService implements ConsoleLoggerService {
|
export class LoggerService implements LoggerServiceInterface {
|
||||||
constructor(@Inject(LOGGER_DRIVER) private driver: ConsoleLoggerService) {}
|
constructor(@Inject(LOGGER_DRIVER) private driver: LoggerServiceInterface) {}
|
||||||
|
|
||||||
log(message: any, category: string, ...optionalParams: any[]) {
|
log(message: any, category: string, ...optionalParams: any[]) {
|
||||||
this.driver.log.apply(this.driver, [message, category, ...optionalParams]);
|
this.driver.log.apply(this.driver, [message, category, ...optionalParams]);
|
||||||
@ -41,4 +42,8 @@ export class LoggerService implements ConsoleLoggerService {
|
|||||||
...optionalParams,
|
...optionalParams,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLogLevels(levels: LogLevel[]) {
|
||||||
|
this.driver.setLogLevels?.apply(this.driver, [levels]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { AppModule } from './app.module';
|
|||||||
|
|
||||||
import { settings } from './constants/settings';
|
import { settings } from './constants/settings';
|
||||||
import { LoggerService } from './integrations/logger/logger.service';
|
import { LoggerService } from './integrations/logger/logger.service';
|
||||||
|
import { EnvironmentService } from './integrations/environment/environment.service';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, {
|
const app = await NestFactory.create(AppModule, {
|
||||||
@ -35,8 +36,9 @@ async function bootstrap() {
|
|||||||
);
|
);
|
||||||
const loggerService = app.get(LoggerService);
|
const loggerService = app.get(LoggerService);
|
||||||
app.useLogger(loggerService);
|
app.useLogger(loggerService);
|
||||||
|
app.useLogger(app.get(EnvironmentService).getLogLevels());
|
||||||
|
|
||||||
await app.listen(3000);
|
await app.listen(app.get(EnvironmentService).getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user