feat: init logging system
This commit is contained in:
@@ -23,6 +23,7 @@ import { ScoringModule } from './modules/scoring/scoring.module';
|
||||
import { AdminModule } from './modules/admin/admin.module';
|
||||
import { AppThrottlerGuard } from './common/guards/app-throttler.guard';
|
||||
import { ThrottlerModule } from '@nestjs/throttler';
|
||||
import { LoggingModule } from './common/modules/logging/logging.module';
|
||||
|
||||
const ENV = process.env.NODE_ENV;
|
||||
|
||||
@@ -79,6 +80,9 @@ const ENV = process.env.NODE_ENV;
|
||||
// ]),
|
||||
ThrottlerModule.forRoot([]),
|
||||
|
||||
// Logging
|
||||
LoggingModule,
|
||||
|
||||
/**
|
||||
* Local Modules
|
||||
*/
|
||||
@@ -92,6 +96,7 @@ const ENV = process.env.NODE_ENV;
|
||||
SubmissionsModule,
|
||||
ScoringModule,
|
||||
AdminModule,
|
||||
LoggingModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import type { Request, Response } from 'express';
|
||||
import { I18nService } from 'nestjs-i18n';
|
||||
import { PinoLogger } from 'nestjs-pino';
|
||||
|
||||
@Catch()
|
||||
export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
@@ -16,6 +17,11 @@ export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
* Inject I18n Service
|
||||
*/
|
||||
private readonly i18nService: I18nService,
|
||||
|
||||
/**
|
||||
* Inject Pino Logger
|
||||
*/
|
||||
private readonly logger: PinoLogger,
|
||||
) {}
|
||||
|
||||
catch(exception: any, host: ArgumentsHost) {
|
||||
@@ -40,16 +46,42 @@ export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
args: (exceptionResponse as any).params,
|
||||
});
|
||||
|
||||
this.logException(request, status, exception);
|
||||
|
||||
response.status(status).json({
|
||||
success: false,
|
||||
statusCode: status,
|
||||
message,
|
||||
path: request.url,
|
||||
timestamp: new Date().toISOString(),
|
||||
// ...(process.env.NODE_ENV !== 'production' && {
|
||||
raw: exceptionResponse,
|
||||
stack: exception instanceof Error ? exception.stack : undefined,
|
||||
// }),
|
||||
...(process.env.NODE_ENV !== 'production' && {
|
||||
raw: exceptionResponse,
|
||||
stack: exception instanceof Error ? exception.stack : undefined,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
logException(request: Request, status: number, exception?: any) {
|
||||
if (status >= 500) {
|
||||
this.logger.error(
|
||||
{
|
||||
err: exception,
|
||||
stack: exception instanceof Error ? exception.stack : undefined,
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
statusCode: status,
|
||||
},
|
||||
'Unhandled server exception',
|
||||
);
|
||||
} else {
|
||||
this.logger.warn(
|
||||
{
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
statusCode: status,
|
||||
},
|
||||
'HTTP exception',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { LoggerModule } from 'nestjs-pino';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
LoggerModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
pinoHttp: {
|
||||
level: configService.get<string>('app.logLevel') ?? 'info',
|
||||
genReqId: (req) => {
|
||||
const requestId = req.headers['x-request-id'];
|
||||
|
||||
if (typeof requestId === 'string' && requestId.length > 0) {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
return crypto.randomUUID();
|
||||
},
|
||||
redact: {
|
||||
paths: [
|
||||
'req.headers.authorization',
|
||||
'req.headers.cookie',
|
||||
'req.headers["set-cookie"]',
|
||||
'password',
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
'otp',
|
||||
],
|
||||
censor: '[REDACTED]',
|
||||
},
|
||||
|
||||
// serializers: {
|
||||
// req: (req) => ({
|
||||
// method: req.method,
|
||||
// url: req.url,
|
||||
// remoteAddress: req.remoteAddress,
|
||||
// }),
|
||||
|
||||
// res: (res) => ({
|
||||
// statusCode: res.statusCode,
|
||||
// }),
|
||||
// },
|
||||
|
||||
customProps: (req) => ({
|
||||
requestId: req.id,
|
||||
}),
|
||||
|
||||
customLogLevel: (req, res, error) => {
|
||||
if (error || res.statusCode >= 500) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
if (res.statusCode >= 400) {
|
||||
return 'warn';
|
||||
}
|
||||
|
||||
return 'info';
|
||||
},
|
||||
|
||||
transport:
|
||||
configService.get<string>('app.environment') !== 'production'
|
||||
? {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
singleLine: true,
|
||||
translateTime: 'SYS:standard',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
],
|
||||
exports: [LoggerModule],
|
||||
})
|
||||
export class LoggingModule {}
|
||||
@@ -9,4 +9,5 @@ export default registerAs('app', () => ({
|
||||
swaggerServers: process.env.SWAGGER_SERVERS?.split(',').map((origin) =>
|
||||
origin.trim(),
|
||||
),
|
||||
logLevel: process.env.LOG_LEVEL,
|
||||
}));
|
||||
|
||||
@@ -22,4 +22,5 @@ export default Joi.object({
|
||||
CORS_ORIGINS: Joi.string().required(),
|
||||
CORS_SUBDOMAIN: Joi.string().required(),
|
||||
SWAGGER_SERVERS: Joi.string().required(),
|
||||
LOG_LEVEL: Joi.string().default('info'),
|
||||
});
|
||||
|
||||
+4
-1
@@ -5,9 +5,12 @@ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { ResponseInterceptor } from './common/interceptors/response.interceptor';
|
||||
import helmet from 'helmet';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Logger } from 'nestjs-pino';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create(AppModule, { bufferLogs: true });
|
||||
|
||||
app.useLogger(app.get(Logger));
|
||||
|
||||
app.setGlobalPrefix('v1');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user