NestJS Pino 日志集成完整指南
NestJS 项目中集成 Pino 日志系统的最佳实践指南,包含从基础到企业级的完整配置方案。
核心知识点
1️⃣ 为什么选择 Pino?
- 性能卓越: Pino 是 Node.js 中最快的日志库之一,基于异步写入,几乎不阻塞主线程
- 结构化日志: 输出 JSON 格式,便于日志收集和分析(如 ELK、Loki 等)
- 生态丰富: 支持多种传输方式(文件、控制台、远程服务等)
- 低开销: 即使在高并发场景下也能保持稳定的性能表现
2️⃣ nestjs-pino 的作用
nestjs-pino 是 NestJS 生态中成熟的日志集成方案,提供了:
- 自动请求日志记录(包含请求 ID、响应时间等)
- 与 NestJS 依赖注入系统的无缝集成
- 模块级别的日志配置灵活性
3️⃣ 核心配置概念
Pino Http 配置结构
typescript
{
level: 'info', // 日志级别
transport: { // 日志传输方式
targets: [] // 支持多目标输出
},
serializers: {}, // 自定义序列化器
base: {}, // 全局基础字段
autoLogging: {} // 自动日志配置
}日志级别优先级
trace < debug < info < warn < error < fatal设置某个级别后,只会记录该级别及以上的日志。
三种配置方案
方案一:基础配置
适用场景: 小型项目、快速原型开发
特点:
- 配置简单直接
- 开发/生产环境分离
- 基础的日志滚动功能
核心代码:
typescript
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';
import { join } from 'path';
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
transport: {
targets: [
process.env.NODE_ENV === 'development'
? {
level: 'info',
target: 'pino-pretty', // 开发环境美化输出
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
singleLine: true,
}
}
: {
level: 'info',
target: 'pino-roll', // 生产环境文件滚动
options: {
file: join('logs', 'app.log'),
frequency: 'daily',
size: '10m',
mkdir: true,
}
}
]
}
}
})
],
})
export class AppModule {}方案二:配置抽离
适用场景: 中型项目、需要维护多个环境配置
特点:
- 配置文件独立,便于维护
- 支持更复杂的环境判断逻辑
- 代码结构更清晰
logger.config.ts:
typescript
import { join } from 'path';
import pino from 'pino';
import type { LoggerOptions } from 'pino';
interface CustomLoggerConfig extends LoggerOptions {
level: string;
development: {
transport: {
targets: Array<{
level: string;
target: string;
options?: Record<string, unknown>;
}>;
};
};
production: {
transport: {
targets: Array<{
level: string;
target: string;
options?: Record<string, unknown>;
}>;
};
};
}
export const loggerConfig: CustomLoggerConfig = {
level: process.env.LOG_LEVEL || 'info',
development: {
transport: {
targets: [
{
level: 'debug',
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
singleLine: true,
},
},
],
},
},
production: {
transport: {
targets: [
{
level: 'info',
target: 'pino-roll',
options: {
file: join('logs', 'app-logs', 'app.log'),
frequency: 'daily',
size: '20m',
mkdir: true,
maxSize: '100m',
maxFiles: 30,
compression: 'gzip',
},
},
],
},
},
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
requestId: req.id || req.headers?.['x-request-id'],
}),
res: (res) => ({
statusCode: res.statusCode,
}),
},
base: {
environment: process.env.NODE_ENV || 'development',
app: 'nestjs-app',
},
timestamp: pino.stdTimeFunctions.isoTime as unknown as () => string,
autoLogging: {
ignore: (req) => {
const ignoredPaths = ['/health', '/ping'];
return ignoredPaths.some((path) => req.url?.startsWith(path));
},
},
};
export default loggerConfig;app.module.ts:
typescript
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';
import loggerConfig from './config/logger.config';
const pinoHttp = {
...loggerConfig,
transport:
process.env.NODE_ENV === 'production'
? loggerConfig.production.transport
: loggerConfig.development.transport,
};
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp,
}),
],
})
export class AppModule {}方案三:企业级配置
适用场景: 生产环境、大型项目、微服务架构
特点:
- 完整的序列化器配置
- 多目标日志输出(普通日志 + 错误日志分离)
- 请求上下文自动记录
- 敏感信息过滤
- 健康检查路径忽略
核心配置:
typescript
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';
import { join } from 'path';
import pino from 'pino';
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: {
level: process.env.LOG_LEVEL || 'info',
// 多目标输出
transport:
process.env.NODE_ENV === 'development'
? {
targets: [
{
level: 'info',
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
singleLine: true,
},
},
],
}
: {
targets: [
// 普通日志
{
level: 'info',
target: 'pino-roll',
options: {
file: join('logs', 'app-logs', 'app.log'),
frequency: 'daily',
size: '20m',
mkdir: true,
maxSize: '100m',
maxFiles: 30,
compression: 'gzip',
},
},
// 错误日志单独存储
{
level: 'error',
target: 'pino/file',
options: {
destination: join('logs', 'app-logs', 'error.log'),
mkdir: true,
},
},
],
},
// 请求序列化
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
requestId: req.id || req.headers?.['x-request-id'],
userId: req.user?.id,
ip: req.ip || req.headers?.['x-forwarded-for'],
userAgent: req.headers?.['user-agent'],
contentType: req.headers?.['content-type'],
}),
res: (res) => ({
statusCode: res.statusCode,
}),
err: pino.stdSerializers.err as unknown as (err: unknown) => unknown,
},
// 自定义全局字段
base: {
environment: process.env.NODE_ENV,
app: 'nestjs-app',
},
// 自动日志过滤
autoLogging: {
ignore: (req) => {
const ignoredPaths = ['/health', '/ping', '/metrics'];
return ignoredPaths.some((path) => req.url?.startsWith(path));
},
},
// 时间格式
timestamp: pino.stdTimeFunctions.isoTime as unknown as () => string,
},
}),
],
})
export class AppModule {}关键依赖说明
1. nestjs-pino
作用: NestJS 与 Pino 的桥接模块
关键功能:
- 提供
Logger服务,可通过依赖注入使用 - 自动记录 HTTP 请求/响应日志
- 支持请求 ID 追踪
使用示例:
typescript
import { Controller, Get } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
@Controller('users')
export class UsersController {
constructor(private readonly logger: Logger) {}
@Get()
findAll() {
this.logger.info('获取用户列表', { userId: 123 });
this.logger.error('操作失败', { error: '详细错误信息' });
return [];
}
}2. pino
作用: 核心日志库
重要类型:
LoggerOptions: 定义完整的配置类型stdSerializers: 标准序列化器(如err用于错误对象)
3. pino-pretty
作用: 开发环境日志美化
常用配置:
typescript
{
colorize: true, // 彩色输出
translateTime: 'SYS:standard', // 人类可读时间格式
singleLine: true, // 单行显示
ignore: 'pid,hostname', // 忽略的字段
messageFormat: '[{level}] {msg}' // 自定义消息格式
}4. pino-roll
作用: 生产环境日志文件滚动
关键参数:
typescript
{
file: 'logs/app.log', // 日志文件路径
frequency: 'daily', // 滚动频率: daily | hourly
size: '20m', // 单文件大小限制
maxSize: '100m', // 总大小限制
maxFiles: 30, // 保留文件数量
compression: 'gzip', // 压缩算法
mkdir: true // 自动创建目录
}最佳实践总结
✅ 推荐做法
1. 环境分离配置
- 开发环境: 使用
pino-pretty提升可读性 - 生产环境: 使用
pino-roll实现文件滚动和压缩
2. 日志级别设置
- 开发环境:
debug级别,便于调试 - 生产环境:
info级别,平衡详细度和性能
3. 敏感信息保护
通过 serializers 只记录必要字段:
typescript
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
// ❌ 不要记录: req.headers.authorization
// ❌ 不要记录: req.headers.cookie
// ✅ 可以记录: req.headers['user-agent']
})
}4. 请求追踪
typescript
serializers: {
req: (req) => ({
requestId: req.id || req.headers?.['x-request-id'],
userId: req.user?.id,
ip: req.ip || req.headers?.['x-forwarded-for'],
})
}5. 日志分类存储
- 普通日志: 所有级别,用于日常分析
- 错误日志: 仅 error 级别,用于快速排查问题
6. 健康检查忽略
typescript
autoLogging: {
ignore: (req) => {
return ['/health', '/ping', '/metrics'].some(path =>
req.url?.startsWith(path)
);
}
}❌ 避免做法
1. 不要在生产环境使用 pino-pretty
- 美化输出会降低性能
- JSON 格式更利于日志收集系统处理
2. 不要记录完整的请求头
可能包含敏感信息:
authorization/cookie/set-cookiex-api-key/x-auth-token- 其他自定义认证头
3. 不要设置过低的日志级别
trace级别会产生大量日志- 影响性能和存储成本
4. 不要忽略错误堆栈
typescript
// ✅ 正确
err: pino.stdSerializers.err
// ❌ 错误
err: (err) => ({ message: err.message })环境变量配置
.env 文件配置
bash
# 应用环境
NODE_ENV=production
# 日志级别: trace | debug | info | warn | error | fatal
LOG_LEVEL=info
# 应用版本(用于日志追踪)
APP_VERSION=1.0.0
# 日志存储路径
LOG_DIR=./logs
# 是否启用请求日志
ENABLE_REQUEST_LOGGING=true
# 是否启用错误堆栈跟踪
ENABLE_STACK_TRACE=truepackage.json 脚本
json
{
"scripts": {
"start": "cross-env NODE_ENV=production nest start",
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:prod": "cross-env NODE_ENV=production node dist/main"
}
}说明:
cross-env确保跨平台环境变量设置兼容性- 不同环境通过
NODE_ENV自动切换配置
常见问题排查
Q1: 日志文件没有生成?
检查清单:
- ✅ 确认
NODE_ENV环境变量正确设置 - ✅ 检查日志目录是否有写入权限
- ✅ 生产环境才会写入文件,开发环境输出到控制台
- ✅ 确认
pino-roll已正确安装
Q2: 日志格式不符合预期?
解决方案:
- 开发环境应该看到美化输出,如果看到 JSON 说明配置错误
- 检查
pino-pretty是否正确安装 - 确认
transport配置的target路径正确
Q3: 如何实现日志远程收集?
推荐方案:
- 使用 pino-multistream:
typescript
import pino from 'pino';
import multistream from 'pino-multistream';
const streams = multistream([
{ level: 'info', stream: process.stdout },
{ level: 'error', stream: fs.createWriteStream('error.log') }
]);- 或使用 Filebeat + Logstash + Elasticsearch 组合
- Pino 的 JSON 输出天然兼容主流日志系统
Q4: 如何在单元测试中验证日志?
方案:
typescript
import pino from 'pino';
const transport = pino.transport({
target: 'pino/file',
options: { destination: Buffer.toString() }
});
const logger = pino(transport);
// 测试中可以读取 buffer 内容验证Q5: 生产环境日志量太大怎么办?
优化方案:
- 提高日志级别到
info或warn - 增加健康检查路径的忽略规则
- 调整日志滚动配置,减少保留天数
- 考虑使用日志采样策略
使用示例
在 Controller 中使用
typescript
import { Controller, Get, Param } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
@Controller('users')
export class UsersController {
constructor(private readonly logger: Logger) {}
@Get()
findAll() {
this.logger.info('获取所有用户');
return [];
}
@Get(':id')
findOne(@Param('id') id: string) {
this.logger.info('获取用户详情', { userId: id });
return {};
}
}在 Service 中使用
typescript
import { Injectable } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
@Injectable()
export class UsersService {
constructor(private readonly logger: Logger) {}
async create(data: any) {
this.logger.debug('创建用户', { userData: data });
try {
// 业务逻辑
this.logger.info('用户创建成功', { userId: data.id });
} catch (error) {
this.logger.error('用户创建失败', {
error: error.message,
stack: error.stack,
});
throw error;
}
}
}异步日志记录
typescript
import { Injectable } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
@Injectable()
export class OrderService {
constructor(private readonly logger: Logger) {}
async processOrder(orderId: string) {
const startTime = Date.now();
try {
// 处理订单逻辑
const duration = Date.now() - startTime;
this.logger.info('订单处理完成', {
orderId,
duration: `${duration}ms`,
});
} catch (error) {
this.logger.error('订单处理失败', {
orderId,
error: error.message,
duration: `${Date.now() - startTime}ms`,
});
throw error;
}
}
}进阶技巧
1. 自定义日志格式
typescript
serializers: {
req: (req) => ({
method: req.method,
url: req.url,
// 添加自定义字段
correlationId: req.headers?.['x-correlation-id'],
sessionId: req.session?.id,
})
}2. 日志分级存储策略
typescript
targets: [
// DEBUG 级别 - 仅开发
{
level: 'debug',
target: 'pino/file',
options: { destination: join('logs', 'debug.log') }
},
// INFO 级别 - 一般信息
{
level: 'info',
target: 'pino-roll',
options: { file: join('logs', 'app.log'), maxFiles: 7 }
},
// ERROR 级别 - 错误单独存储
{
level: 'error',
target: 'pino-roll',
options: {
file: join('logs', 'error.log'),
maxFiles: 90 // 保留 90 天
}
}
]3. 性能监控日志
typescript
// 添加性能监控中间件
app.use((req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
if (duration > 1000) {
req.log.warn('慢请求', {
url: req.url,
method: req.method,
duration: `${duration}ms`,
});
}
});
next();
});总结
NestJS 集成 Pino 的核心价值:
- 性能优先: 通过异步日志记录,最小化对业务逻辑的影响
- 结构化输出: JSON 格式便于日志分析和监控系统集成
- 环境适配: 开发环境美化,生产环境高效
- 生产就绪: 日志滚动、压缩、分类等企业级特性
- 可观测性: 请求追踪、错误完整记录、性能监控
核心价值: 通过结构化日志提升系统可观测性,为问题排查和性能优化提供强有力的数据支撑。