feat: init logging system

This commit is contained in:
2026-09-05 16:50:15 +03:30
parent 287091724d
commit f90b802419
8 changed files with 384 additions and 5 deletions
+5
View File
@@ -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: [
{
+36 -4
View File
@@ -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 {}
+1
View File
@@ -9,4 +9,5 @@ export default registerAs('app', () => ({
swaggerServers: process.env.SWAGGER_SERVERS?.split(',').map((origin) =>
origin.trim(),
),
logLevel: process.env.LOG_LEVEL,
}));
+1
View File
@@ -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
View File
@@ -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');