diff --git a/be/src/app.module.ts b/be/src/app.module.ts index d059e904..b5d7246e 100644 --- a/be/src/app.module.ts +++ b/be/src/app.module.ts @@ -3,8 +3,18 @@ import { UserModule } from "./user/user.module"; import { TypeOrmModule } from "@nestjs/typeorm"; import { typeORMConfig } from "./configs/typeorm.config"; import { AuthModule } from "./auth/auth.module"; +import { CustomLoggerService } from "./custom.logger"; +import { APP_INTERCEPTOR } from '@nestjs/core'; +import { LoggingInterceptor } from "./logger.interceptor"; @Module({ imports: [UserModule, TypeOrmModule.forRoot(typeORMConfig), AuthModule], + providers: [ + CustomLoggerService, + { + provide: APP_INTERCEPTOR, + useClass: LoggingInterceptor, + }, + ], }) -export class AppModule {} +export class AppModule { } diff --git a/be/src/custom.logger.ts b/be/src/custom.logger.ts new file mode 100644 index 00000000..17d9d579 --- /dev/null +++ b/be/src/custom.logger.ts @@ -0,0 +1,55 @@ +import { Injectable, LoggerService } from '@nestjs/common'; +import * as winston from 'winston'; + +@Injectable() +export class CustomLoggerService implements LoggerService { + private logger: winston.Logger; + + constructor() { + this.logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.printf(({ level, message, timestamp, stack = '' }) => { + return `${timestamp} ${level}: ${message} ${stack} `; + }), + ), + transports: [ + new winston.transports.File({ filename: 'error.log', level: 'error' }), + new winston.transports.File({ filename: 'combined.log' }), + ], + + }); + + if (process.env.NODE_ENV !== 'production') { + this.logger.add(new winston.transports.Console({ + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + winston.format.printf(({ level, message, timestamp }) => { + return `${timestamp} ${level}: ${message}`; + }) + ), + })); + } + } + + log(message: string) { + this.logger.info(message); + } + + error(message: string, trace: string) { + this.logger.error({ message, stack: trace }); + } + + warn(message: string) { + this.logger.warn(message); + } + + debug(message: string) { + this.logger.debug(message); + } + + verbose(message: string) { + this.logger.verbose(message); + } +} diff --git a/be/src/logger.interceptor.ts b/be/src/logger.interceptor.ts new file mode 100644 index 00000000..213cc981 --- /dev/null +++ b/be/src/logger.interceptor.ts @@ -0,0 +1,41 @@ +// logging.interceptor.ts +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, + HttpStatus +} from '@nestjs/common'; +import { Observable, throwError } from 'rxjs'; +import { tap, catchError } from 'rxjs/operators'; +import { CustomLoggerService } from './custom.logger'; + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + constructor(private logger: CustomLoggerService) { } + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const now = Date.now(); + const request = context.switchToHttp().getRequest(); + const response = context.switchToHttp().getResponse(); + const { method, url } = request; + const clientIp = request.ip || request.headers['x-forwarded-for']; + + return next + .handle() + .pipe( + tap(() => { + const { statusCode } = response; + const delay = Date.now() - now; + this.logger.log(`[Success] ${clientIp} ${method} ${url} ${statusCode} ${delay}ms`); + }), + catchError((error) => { + const status = error.status || HttpStatus.INTERNAL_SERVER_ERROR; + this.logger.error(`[Error] ${clientIp} ${method} ${url} ${status} ${error.message}`, error.stack); + + return throwError(error); + }), + ); + } + +}