news 2026/6/4 9:48:39

第9章 nestjs服务端开发:通用业务框架设计【日志收集】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第9章 nestjs服务端开发:通用业务框架设计【日志收集】
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 {}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 9:45:06

如何将DeepSeek-R1-Distill-Qwen-1.5B-FP16部署到生产环境

如何将DeepSeek-R1-Distill-Qwen-1.5B-FP16部署到生产环境 【免费下载链接】DeepSeek-R1-Distill-Qwen-1.5B-FP16 项目地址: https://ai.gitcode.com/hf_mirrors/MindSpore-Lab/DeepSeek-R1-Distill-Qwen-1.5B-FP16 DeepSeek-R1-Distill-Qwen-1.5B-FP16是基于MindSpore…

作者头像 李华
网站建设 2026/6/4 9:45:05

PanGu Draw V3核心功能揭秘:从文本到惊艳图像的生成原理

PanGu Draw V3核心功能揭秘&#xff1a;从文本到惊艳图像的生成原理 【免费下载链接】pangu-draw-v3 项目地址: https://ai.gitcode.com/hf_mirrors/MindSpore-Lab/pangu-draw-v3 PanGu Draw V3是一款基于MindSpore框架开发的先进文本到图像生成模型&#xff0c;它能够…

作者头像 李华
网站建设 2026/6/4 9:40:18

Linux网络驱动之Fixed-Link(11)

接前一篇文章&#xff1a;Linux网络驱动之Fixed-Link&#xff08;10&#xff09; 本文内容参考&#xff1a; linux phy fixed-link-CSDN博客 fixed-link 网口驱动设备树-CSDN博客 GMAC网卡Fixed-Link模式 - StepForwards - 博客园 RTL8367RB的国产P2P替代方案用JL6107-PC的…

作者头像 李华
网站建设 2026/6/4 9:38:10

优刻得云部署智谱GLM-5实战:GPU推理服务化全链路指南

1. 项目概述&#xff1a;为什么要在优刻得云上跑智谱GLM-5&#xff1f; 最近两周&#xff0c;我连续接到三类客户咨询&#xff1a;一类是做智能客服中台的SaaS厂商&#xff0c;想把本地部署的GLM-4模型升级为GLM-5&#xff0c;但自建GPU集群运维成本太高&#xff1b;第二类是高…

作者头像 李华
网站建设 2026/6/4 9:38:09

API优先架构:为什么大模型服务不再需要讨好开源

1. 项目概述&#xff1a;一场被误读的开源姿态“MiniMax不需要讨好开源”——这句话在2024年中旬的技术圈传播时&#xff0c;像一块投入静水的石头&#xff0c;涟漪扩散得又快又远。很多人第一反应是&#xff1a;这是一家AI公司公开diss开源社区&#xff1f;是在挑衅GPL精神&am…

作者头像 李华
网站建设 2026/6/4 9:38:09

用STM32F103驱动HT1621段码屏,手把手教你做一个简易FM收音机频率显示器

基于STM32F103与HT1621的FM收音机频率显示器实战指南在嵌入式开发领域&#xff0c;将硬件驱动与实际应用结合是提升技能的关键一步。本文将以STM32F103微控制器为核心&#xff0c;搭配HT1621段码屏驱动芯片&#xff0c;构建一个完整的FM收音机频率显示系统。不同于简单的驱动演…

作者头像 李华