nestjs内置日志模块Logger关闭整个 logger 日志src\main.tsimport { 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.tsController(user) export class UserController { private logger new Logger(UserController.name); // 查询日志 Get(/logsByGroup) async getLogsByGroup(): Promiseany { 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.tswinston 日志配置/* 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如何获取请求IPsrc\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.tsimport { 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.tscommon.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.tsimport { 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.tsimport { 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): Recordstring, 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.tsimport { 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 {}
第9章 nestjs服务端开发:通用业务框架设计【日志收集】
发布时间:2026/6/4 9:49:01
nestjs内置日志模块Logger关闭整个 logger 日志src\main.tsimport { 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.tsController(user) export class UserController { private logger new Logger(UserController.name); // 查询日志 Get(/logsByGroup) async getLogsByGroup(): Promiseany { 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.tswinston 日志配置/* 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如何获取请求IPsrc\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.tsimport { 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.tscommon.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.tsimport { 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.tsimport { 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): Recordstring, 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.tsimport { 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 {}