第 184 章:日志系统(Logger)
学习目标
- 掌握内置 Logger
- 学会自定义 Logger
- 接入第三方日志库(Pino/Winston)
- 实现日志分级与归档
一、内置 Logger
1.1 基本使用
typescript
import { Logger } from '@nestjs/common';
@Injectable()
export class UserService {
private readonly logger = new Logger(UserService.name);
create(dto: CreateUserDto) {
this.logger.log('Creating user'); // 普通
this.logger.error('Something failed', 'stack trace');
this.logger.warn('Warning message');
this.logger.debug('Debug info');
this.logger.verbose('Verbose info');
}
}输出:
[Nest] 12345 - 12/15/2024, 10:30:00 AM [UserService] Creating user
[Nest] 12345 - 12/15/2024, 10:30:00 AM [UserService] Warning message1.2 日志级别
| 级别 | 方法 | 优先级 |
|---|---|---|
| log | log() | 默认 |
| error | error() | 最高 |
| warn | warn() | 中 |
| debug | debug() | 低 |
| verbose | verbose() | 最低 |
二、全局 Logger 配置
2.1 启动时配置
typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn', 'log'], // 启用的级别
// logger: false, // 关闭所有
});
await app.listen(3000);
}2.2 进程级日志
typescript
const app = await NestFactory.create(AppModule);
app.useLogger(app.get(Logger));
// 之后所有框架日志走自定义 Logger三、自定义 Logger
3.1 实现 LoggerService
typescript
import { LoggerService } from '@nestjs/common';
export class MyLogger implements LoggerService {
log(message: any, ...optionalParams: any[]) {
console.log(`[LOG] ${this.format(message)}`, ...optionalParams);
}
error(message: any, ...optionalParams: any[]) {
console.error(`[ERROR] ${this.format(message)}`, ...optionalParams);
}
warn(message: any, ...optionalParams: any[]) {
console.warn(`[WARN] ${this.format(message)}`, ...optionalParams);
}
debug?(message: any, ...optionalParams: any[]) {
console.debug(`[DEBUG] ${this.format(message)}`, ...optionalParams);
}
verbose?(message: any, ...optionalParams: any[]) {
console.log(`[VERBOSE] ${this.format(message)}`, ...optionalParams);
}
private format(message: any): string {
const ts = new Date().toISOString();
return `${ts} ${typeof message === 'object' ? JSON.stringify(message) : message}`;
}
}3.2 替换全局 Logger
typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(new MyLogger());
await app.listen(3000);
}四、Pino(高性能日志库)
4.1 安装
bash
pnpm add nestjs-pino pino-http4.2 注册
typescript
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport: {
target: 'pino-pretty', // 开发用 pretty
options: { singleLine: true },
},
// production JSON
// transport: undefined,
},
}),
],
})
export class AppModule {}4.3 注入使用
typescript
import { PinoLogger } from 'nestjs-pino';
@Injectable()
export class UserService {
constructor(private logger: PinoLogger) {
this.logger.setContext(UserService.name);
}
create(dto: CreateUserDto) {
this.logger.info('Creating user');
this.logger.warn({ dto }, 'Email already exists');
this.logger.error({ userId: 1 }, 'Create failed');
}
}4.4 请求日志
Pino 自动记录所有 HTTP 请求,无需手写:
json
{
"level": 30,
"time": 1700000000000,
"req": {
"method": "POST",
"url": "/api/users",
"remoteAddress": "::1",
"headers": {}
},
"res": {
"statusCode": 201
},
"responseTime": 12
}五、Winston(功能丰富)
5.1 安装
bash
pnpm add nest-winston winston5.2 配置
typescript
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import 'winston-daily-rotate-file'; // 按天归档
@Module({
imports: [
WinstonModule.forRoot({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json(),
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
),
}),
new winston.transports.DailyRotateFile({
filename: 'logs/app-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
}),
new winston.transports.DailyRotateFile({
filename: 'logs/error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
level: 'error',
}),
],
}),
],
})
export class AppModule {}5.3 使用
typescript
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
@Injectable()
export class UserService {
constructor(
@Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: Logger,
) {}
create(dto: CreateUserDto) {
this.logger.log('Creating user');
this.logger.error('Failed', { trace: '...' });
this.logger.warn('warning', { context: 'UserService' });
}
}六、日志中间件
typescript
// logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP');
intercept(context: ExecutionContext, next: CallHandler) {
const req = context.switchToHttp().getRequest();
const { method, url, body, query, params, headers } = req;
const userAgent = headers['user-agent'] || '';
const ip = req.ip;
const start = Date.now();
return next.handle().pipe(
tap(() => {
const res = context.switchToHttp().getResponse();
this.logger.log(
`${method} ${url} ${res.statusCode} ${Date.now() - start}ms ${ip} ${userAgent}`,
);
}),
);
}
}七、日志规范
7.1 结构化日志
typescript
// ❌ 不推荐
this.logger.log('User with id 1 not found');
// ✅ 推荐:结构化
this.logger.warn({
message: 'User not found',
userId: 1,
action: 'findOne',
duration: 12,
});7.2 包含上下文
typescript
// ❌ 不推荐
this.logger.error('failed');
// ✅ 推荐:带 context
this.logger.error('failed', 'Stack trace here', 'UserService');7.3 区分环境
typescript
const isDev = process.env.NODE_ENV === 'development';
this.logger.debug('Debug info', isDev ? JSON.stringify(data) : '');
// 生产不打 debug八、日志归档与清理
8.1 按天归档
typescript
new winston.transports.DailyRotateFile({
filename: 'logs/app-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true, // 自动压缩
maxSize: '20m', // 单文件 20MB
maxFiles: '30d', // 保留 30 天
})8.2 集中式日志(ELK)
typescript
// pino + elasticsearch
pinoHttp: {
level: 'info',
transport: {
target: 'pino-elasticsearch',
options: {
node: process.env.ES_NODE,
index: 'myapp-logs',
},
},
},九、本章小结
| 库 | 特点 |
|---|---|
| 内置 Logger | 简单够用 |
| Pino | 高性能,JSON 友好 |
| Winston | 功能丰富,支持归档 |
| Bunyan | 老牌日志库 |
| 级别 | 用途 |
|---|---|
| error | 错误 |
| warn | 警告 |
| log | 一般 |
| debug | 调试 |
| verbose | 详细 |
动手练习
- 用内置 Logger 记录 Service 操作
- 集成 nestjs-pino,替换默认日志
- 配置 winston 按天归档,保留 14 天
- 写一个 LoggingInterceptor 记录所有 HTTP 请求
推荐阅读
下一章:第 185 章:数据验证与 DTO →