nestjs内置日志模块Logger
关闭整个 logger 日志
src\main.ts
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create( AppModule, // 关闭整个 logger 日志 { logger: false, }, ); await app.listen(process.env.PORT ?? 3000); } bootstrap();设置统一接口前缀
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 设置全局前缀 app.setGlobalPrefix('api/v1'); await app.listen(process.env.PORT ?? 3000); } bootstrap();src\user\user.controller.ts
@Controller('user') export class UserController { private logger = new Logger(UserController.name); // 查询日志 @Get('/logsByGroup') async getLogsByGroup(): Promise<any> { const res = await this.userService.findLogsByGroup(1); return res; }全自动高性能日志模块:Pino、日志滚动pino-roll
安装依赖
"nestjs-pino": "^3.1.1", "pino-pretty": "^9.1.1", "pino-roll": "1.0.0-rc.1",src\app.module.ts
全局配置 pino 日志模块
LoggerModule.forRoot({ pinoHttp: { transport: { targets: [ process.env.NODE_ENV === 'development' ? { level: 'info', target: 'pino-pretty', options: { colorize: true, }, } : { level: 'info', target: 'pino-roll', options: { file: join('logs','log.txt'), frequency: 'daily', size: '10M', mkdir: true, }, }, ], }, }, })src\user\user.controller.ts
使用安装包
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable prettier/prettier */ import { Controller, Get, Patch, Post, Delete, Param } from '@nestjs/common'; import { UserService } from './user.service'; import { User } from 'src/entities/User'; import { log } from 'console'; import { Logger } from 'nestjs-pino'; @Controller('user') export class UserController { constructor(private readonly userService: UserService, private logger: Logger ) { console.log('UserController constructor called'); this.logger.log('UserController constructor called'); // 记录日志 } @Get('users') geUsers() { this.logger.log('getUsers method called'); return this.userService.findAll(); }开发和生产环境打印的日志。
高度集成的日志模块:winston
安装包
"nest-winston": "^1.8.0", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.7.1",src\main.ts
winston 日志配置
/* eslint-disable @typescript-eslint/no-unsafe-call */ import { NestFactory } from '@nestjs/core'; import { createLogger } from 'winston'; import { AppModule } from './app.module'; import * as winston from 'winston'; import 'winston-daily-rotate-file'; import { utilities, WinstonModule } from 'nest-winston'; async function bootstrap() { try { // 配置 winston 日志记录器 const instance = createLogger({ transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), utilities.format.nestLike(), ), }), // 配置 daily rotate file transport new winston.transports.DailyRotateFile({ level: 'warn', dirname: 'logs', filename: 'application-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', format: winston.format.combine( winston.format.timestamp(), winston.format.simple(), ), }), new winston.transports.DailyRotateFile({ level: 'info', dirname: 'logs', filename: 'info-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', }), ], }); const app = await NestFactory.create(AppModule, { logger: WinstonModule.createLogger({ instance, }), }); app.setGlobalPrefix('api/v1'); // Set a global prefix for all routes (optional) const port = process.env.PORT ?? 3000; await app.listen(port); console.log(`Application is running on: http://localhost:${port}/api/v1`); } catch (error) { console.error('Failed to start application:', error); process.exit(1); } } void bootstrap();src\user\user.module.ts
注入 WinstonModule 模块
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable prettier/prettier */ import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from '../entities/User'; import { UserService } from './user.service'; import { Logs } from '../entities/Logs'; import { WinstonModule } from 'nest-winston'; @Module({ imports: [ TypeOrmModule.forFeature([User, Logs]), WinstonModule.forRoot({}), // 导入 WinstonModule 使 WINSTON_MODULE_PROVIDER 可用 ], controllers: [UserController], providers: [UserService], }) export class UserModule {}src\user\user.controller.ts
使用 winston 打印日志
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable prettier/prettier */ import { Controller, Get, Patch, Post, Delete, Param, Inject } from '@nestjs/common'; import { UserService } from './user.service'; import { User } from 'src/entities/User'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger } from 'winston'; @Controller('user') export class UserController { constructor( private readonly userService: UserService, // 注入 Winston 日志实例 @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, ) { } @Get('users') geUsers() { // 日志分级使用 this.logger.info('访问首页接口'); // 普通信息 this.logger.warn('接口存在潜在风险'); // 警告 this.logger.error('接口发生异常', { stack: '错误堆栈' }); // 错误 return this.userService.findAll(); }全局异常过滤器:配合winston记录日志(作业全局Filters)
src\filters\http-exception.filter.ts
自定义异常请求过滤器
import { Catch, ExceptionFilter, HttpException } from '@nestjs/common'; import type { LoggerService } from '@nestjs/common'; // 全局异常捕获过滤器 @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { constructor(private logger: LoggerService) {} catch(exception: any, host: any) { // 获取请求上下文 const ctx = host.switchToHttp(); // 获取请求和响应对象 const response = ctx.getResponse(); // 获取请求对象 const request = ctx.getRequest(); // 获取异常状态码 const status = exception.getStatus(); this.logger.error( `HTTP Status: ${status} Error Message: ${exception.message}`, ); response.status(status).json({ code: status, timestamp: new Date().toISOString(), path: request.url, message: exception.message || 'Internal server error', }); } }src\main.ts
配置全局异常过滤器
/* eslint-disable @typescript-eslint/no-unsafe-call */ import { NestFactory } from '@nestjs/core'; import { createLogger } from 'winston'; import { AppModule } from './app.module'; import * as winston from 'winston'; import 'winston-daily-rotate-file'; import { utilities, WinstonModule } from 'nest-winston'; import { HttpExceptionFilter } from './filters/http-exception-filter'; async function bootstrap() { try { // 配置 winston 日志记录器 const instance = createLogger({ transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), utilities.format.nestLike(), ), }), // 配置 daily rotate file transport new winston.transports.DailyRotateFile({ level: 'warn', dirname: 'logs', filename: 'application-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', format: winston.format.combine( winston.format.timestamp(), winston.format.simple(), ), }), new winston.transports.DailyRotateFile({ level: 'info', dirname: 'logs', filename: 'info-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', }), new winston.transports.DailyRotateFile({ level: 'error', dirname: 'logs', filename: 'error-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', }), ], }); const logger = WinstonModule.createLogger({ instance, }); const app = await NestFactory.create(AppModule, { logger, }); // 全局异常过滤器 app.useGlobalFilters(new HttpExceptionFilter(logger)); app.setGlobalPrefix('api/v1'); // Set a global prefix for all routes (optional) const port = process.env.PORT ?? 3000; await app.listen(port); console.log(`Application is running on: http://localhost:${port}/api/v1`); } catch (error) { console.error('Failed to start application:', error); process.exit(1); } } void bootstrap();src\user\user.controller.ts
模拟异常情况
// 查询用户列表 @Get() async getUsers() { const user = { isAdmin: false }; if (!user.isAdmin) { throw new UnauthorizedException('只有管理员才能访问此资源'); } this.logger.log('获取用户列表'); return this.userService.getUsers(); }作业解答:全局FIilters&如何获取请求IP
src\filters\all-exception.filter.ts
定义全局异常过滤器
import { Catch, ExceptionFilter, HttpException, HttpStatus, } from '@nestjs/common'; import type { ArgumentsHost, LoggerService } from '@nestjs/common'; import { HttpAdapterHost } from '@nestjs/core'; import * as requestIp from 'request-ip'; import { time } from 'console'; import { query } from 'winston'; // 全局异常捕获过滤器 @Catch() export class AllExceptionFilter implements ExceptionFilter { constructor( private logger: LoggerService, private readonly httpAdapterHost: HttpAdapterHost, ) {} catch(exception: any, host: ArgumentsHost) { const { httpAdapter } = this.httpAdapterHost; // 获取请求上下文 const ctx = host.switchToHttp(); // 获取请求和响应对象 const response = ctx.getResponse(); // 获取请求对象 const request = ctx.getRequest(); // 获取异常状态码 const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const responseBody = { headers: request.headers, query: request.query, body: request.body, params: request.params, time: new Date().toISOString(), ip: requestIp.getClientIp(request), exception: exception['name'], error: exception['response'] || 'Internal server error', }; this.logger.error( `HTTP Status: ${status} Error Message: ${exception.message}`, ); httpAdapter.reply(response, responseBody, status); } }src\main.ts
应用入口注册全局异常过滤器
通用业务系统:日志模块代码重构(作业)
src\main.ts
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import 'winston-daily-rotate-file'; async function bootstrap() { const app = await NestFactory.create(AppModule, {}); // 设置日志 app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER)); // 设置全局前缀 app.setGlobalPrefix('api/v1'); await app.listen(process.env.PORT ?? 3000); } bootstrap();src\user\user.controller.ts
@common.Controller('user') export class UserController { constructor( private readonly userService: UserService, private readonly configService: ConfigService, @common.Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: common.LoggerService, ) { this.logger.log('UserController initialized'); }src\logs\logs.module.ts
import { Module } from '@nestjs/common'; import winston from 'winston'; import { WinstonModule, WinstonModuleOptions } from 'nest-winston'; import { ConfigService } from '@nestjs/config'; import { LogEnum } from 'src/enum/config.enum'; import { LogsController } from './logs.controller'; import { LogsService } from './logs.service'; @Module({ imports: [ WinstonModule.forRootAsync({ inject: [ConfigService], useFactory: (configService: ConfigService) => { const consoleTransports = new winston.transports.Console({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.simple(), ), }); const dailyTransports = new winston.transports.DailyRotateFile({ level: 'warn', dirname: 'logs', filename: 'application-%DATE%.log', datePattern: 'YYYY-MM-DD-HH', zippedArchive: true, maxSize: '20m', maxFiles: '14d', format: winston.format.combine( winston.format.timestamp(), winston.format.simple(), ), }); const dailyInfoTransports = new winston.transports.DailyRotateFile({ level: configService.get(LogEnum.LOG_LEVEL), dirname: 'logs', filename: 'info-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', format: winston.format.combine( winston.format.timestamp(), winston.format.simple(), ), }); return { transports: [ consoleTransports, ...(configService.get(LogEnum.LOG_ON) ? [dailyInfoTransports, dailyTransports] : []), ], } as WinstonModuleOptions; }, }), ], controllers: [LogsController], providers: [LogsService], }) export class LogsModule {}数据库代码重构:TypeORM Cli与Nestjs集成
ormconfig.ts
import { Profile } from './src/entities/entities/Profile'; import { User } from './src/entities/entities/User'; import { Logs } from './src/entities/entities/Logs'; import { Roles } from './src/entities/entities/Roles'; import { TypeOrmModuleAsyncOptions } from '@nestjs/typeorm'; import { DataSource, DataSourceOptions } from 'typeorm'; export const connectionParams = { type: 'mysql', host: '127.0.0.1', port: 3306, username: 'root', password: 'example', database: 'testdb', entities: [User, Profile, Logs, Roles], synchronize: true, logging: true, } as TypeOrmModuleAsyncOptions; export default new DataSource({ ...connectionParams, migrations: ['src/migrations/**'], subscribers: [], } as DataSourceOptions);src\app.module.ts
生产代码重构:TypeORM数据库及生产配置
ormconfig.ts
根据不同环境变量获取不同配置信息
import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { DataSource, DataSourceOptions } from 'typeorm'; import * as fs from 'fs'; import * as dotenv from 'dotenv'; import { ConfigEnum } from 'src/enum/ConfigEnum'; // 通过环境变量读取不同的 .env 文件 function getEnv(env: string): Record<string, unknown> { if (fs.existsSync(env)) { return dotenv.parse(fs.readFileSync(env)); } return {}; } // 通过 dotEnv 来解析不同的配置 function buildConnectionOptions() { const defaultConfig = getEnv('.env'); const envConfig = getEnv(`.env.${process.env.NODE_ENV || 'development'}`); const config = { ...defaultConfig, ...envConfig }; const entitiesDir = process.env.NODE_ENV === 'test' ? [__dirname + '/**/*.entity.ts'] : [__dirname + '/**/*.entity{.ts,.js']; return { type: config[ConfigEnum.DB_TYPE], host: config[ConfigEnum.DB_HOST], port: config[ConfigEnum.DB_PORT], username: config[ConfigEnum.DB_USERNAME], password: config[ConfigEnum.DB_PASSWORD], database: config[ConfigEnum.DB_DATABASE], entities: entitiesDir, synchronize: config[ConfigEnum.DB_SYNC], logging: false, } as TypeOrmModuleOptions; } export const connectionParams = buildConnectionOptions(); export default new DataSource({ ...connectionParams, migrations: ['src/migrations/**'], subscribers: [], } as DataSourceOptions);tsconfig.json
添加 ts 环境配置
{ "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "es2017", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": false, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, "noFallthroughCasesInSwitch": false } }src\app.module.ts
import { Global, Logger, Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import * as joi from 'joi'; import { TypeOrmModule } from '@nestjs/typeorm'; import * as dotenv from 'dotenv'; import { UserModule } from './user/user.module'; import { LogsModule } from './logs/logs.module'; import { connectionParams } from '../ormconfig'; const envFilePath = `.env.${process.env.NODE_ENV || 'development'}`; // 全局模块 @Global() @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath, load: [() => dotenv.config({ path: '.env' })], validationSchema: joi.object({ NODE_ENV: joi .string() .valid('development', 'production', 'test', 'provision') .default('development'), DB_PORT: joi.number().default(3000), DB_HOST: joi .alternatives() .try(joi.string().ip(), joi.string().domain()), DB_TYPE: joi.string().valid('mysql', 'postgres'), DB_DATABASE: joi.string().required(), DB_USERNAME: joi.string().required(), DB_PASSWORD: joi.string().required(), DB_SYNC: joi.boolean().default(false), LOG_ON: joi.boolean().default(false), LOG_LEVEL: joi.string().valid('info', 'warn', 'error').default('info'), }), }), TypeOrmModule.forRoot(connectionParams), UserModule, LogsModule, ], controllers: [], providers: [Logger], exports: [Logger], }) export class AppModule {}src\logs\logs.module.ts
日志环境配置信息
import { Module } from '@nestjs/common'; import * as winston from 'winston'; import { utilities, WinstonModule, WinstonModuleOptions } from 'nest-winston'; import { ConfigService } from '@nestjs/config'; import { LogEnum } from 'src/enum/config.enum'; import { LogsController } from './logs.controller'; import { LogsService } from './logs.service'; import DailyRotateFile from 'winston-daily-rotate-file'; import { Console } from 'winston/lib/winston/transports'; function createDailyRotateTrasnport(level: string, filename: string) { return new DailyRotateFile({ level, dirname: 'logs', filename: `${filename}-%DATE%.log`, datePattern: 'YYYY-MM-DD-HH', zippedArchive: true, maxSize: '20m', maxFiles: '14d', format: winston.format.combine( winston.format.timestamp(), winston.format.simple(), ), }); } @Module({ imports: [ WinstonModule.forRootAsync({ inject: [ConfigService], useFactory: (configService: ConfigService) => { const consoleTransports = new Console({ level: 'info', format: winston.format.combine( winston.format.timestamp(), utilities.format.nestLike(), ), }); return { transports: [ consoleTransports, ...(configService.get(LogEnum.LOG_ON) ? [ createDailyRotateTrasnport('info', 'application'), createDailyRotateTrasnport('warn', 'error'), ] : []), ], } as WinstonModuleOptions; }, }), ], controllers: [LogsController], providers: [LogsService], }) export class LogsModule {}