从入门到生产级最佳实践
目录导读
- 日志配置的核心认知 – 为什么全栈框架需要统一日志策略?
- 主流全栈框架日志方案对比 – NestJS、Next.js、Spring Boot、Django、FastAPI
- 日志级别与输出格式设计 – 生产环境该用哪个级别?
- 结构化日志 vs 文本日志 – 哪个适合你?
- 日志链路追踪(Tracing)实战 – Request ID与分布式追踪
- 日志存储与轮转策略 – 别让硬盘爆炸
- 常见问题问答(FAQ)
日志配置的核心认知
在全栈开发中,日志不是“写几行console.log”那么简单,当你同时拥有前端(Next.js/Vue/Nuxt)、后端(NestJS/FastAPI/Spring Boot)和中间件(Redis/数据库)时,统一的日志策略决定了你能否在故障时快速定位问题。
关键原则:
- 前端只输出UI交互与API调用异常
- 后端记录业务逻辑、数据库查询、错误堆栈
- 中间件日志由框架自动捕获(如Nginx访问日志)
问答1:为什么不能用console.log直接记录生产日志?
答:console.log默认输出到stdout,不会自动轮转、无法按级别过滤、不包含时间戳和调用栈,生产环境必须使用成熟的日志库(如winston、pino、log4j)。
主流全栈框架日志方案对比
| 框架 | 推荐日志库 | 配置方式 | 特色 |
|---|---|---|---|
| NestJS | @nestjs/common/Logger + winston |
依赖注入自定义Logger | 支持自动注入请求上下文 |
| Next.js | pino 或 winston (配合next-logger) |
中间件模式 | 服务端日志和API路由日志分离 |
| Spring Boot | Logback (默认) + Lombok @Slf4j |
application.yml配置 | 天然支持MDC(映射诊断上下文) |
| Django | Python logging + loguru (推荐) |
settings.py的LOGGING字典 | 支持异步日志处理 |
| FastAPI | loguru 或 structlog |
依赖注入或中间件 | 配合Pydantic输出结构化JSON |
实战示例(NestJS + winston配置):
// main.ts
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
async function bootstrap() {
const logger = WinstonModule.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
}),
],
});
const app = await NestFactory.create(AppModule, { logger });
await app.listen(3000);
}
日志级别与输出格式设计
全栈日志应遵循 分级输出 规则(RFC 5424标准扩展):
- FATAL – 系统崩溃(应用退出前最后一条日志)
- ERROR – 数据库连接失败、第三方API超时、未捕获异常
- WARN – 请求参数异常、降级触发、阈值接近上限
- INFO – 用户登录、订单创建、核心流程变更
- DEBUG – 开发阶段SQL语句、变量中间值(生产环境必须关闭)
- TRACE – 函数调用链(绝不可用于生产)
生产环境推荐仅开启:ERROR + WARN + INFO(低流量系统可保留WARN)。
输出格式:统一使用JSON结构,便于ELK或Datadog解析。
{
"timestamp": "2025-04-04T10:30:00.123Z",
"level": "ERROR",
"requestId": "req-abc123",
"service": "order-service",
"message": "Failed to process payment",
"stack": "Error: timeout at ...",
"userId": "user-456"
}
问答2:日志中应该包含用户密码或信用卡号吗?
答:绝对不要!敏感信息必须脱敏,使用winston-mask-sensitive或自定义序列化器替换"password": "***",根据PCI DSS合规要求,信用卡前6后4以外均需隐藏。
结构化日志 vs 文本日志
文本日志(旧时代产物):
[2025-04-04 10:30:00] [ERROR] Failed to process order
问题:无法机器解析,难以按字段搜索。
结构化日志(现代企业标配):
{"time": "2025-04-04T10:30:00Z", "level": "ERROR", "orderId": "ORD-789"}
优势:可直接导入Grafana、Logstash、Splunk,支持聚合统计。
推荐工具:
- Node.js:pino(速度最快)、winston(生态最全)
- Python:structlog(支持异步)、loguru(开发者友好)
- Java:Logback的JSON编码器
日志链路追踪(Tracing)实战
在全栈架构中,一个请求经过:
Next.js前端 → API Gateway → 微服务A → 微服务B → PostgreSQL
如果每个服务独立记录日志,故障排查将如海底捞针,解决方案是注入唯一Request ID(Trace ID)。
实现方式:
- NestJS:利用
@nestjs/microservices的RpcException+cls-hooked(连续本地存储) - Next.js:在
middleware.ts中生成x-request-id并注入到API请求头 - Spring Cloud:推荐Sleuth或Micrometer Tracing(自动生成Trace ID)
Node.js示例(pino + http-context):
import { createNamespace } from 'cls-hooked';
const session = createNamespace('request');
export function traceMiddleware(req, res, next) {
session.run(() => {
const traceId = req.headers['x-trace-id'] || uuid();
session.set('traceId', traceId);
req.traceId = traceId;
res.setHeader('x-trace-id', traceId);
next();
});
}
// 在logger中自动附加
const logger = pino({
mixin() {
return { traceId: session.get('traceId') };
}
});
日志存储与轮转策略
单机部署(中小企业):
- 按天轮转:
logs/app-2025-04-04.log - 保留30天,超过自动压缩(
gzip) - 设置最大文件大小:
100MB每个文件
Kubernetes环境:
- 所有stdout/stderr被Docker收集
- 使用DaemonSet部署
Filebeat或Fluentd,将日志发往Elasticsearch - 云端则直接推送到
CloudWatch(AWS)、Stackdriver(GCP)或Log Analytics(Azure)
配置示例(winston DailyRotateFile):
import winstonDailyRotateFile from 'winston-daily-rotate-file';
const transport = new winstonDailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '100m',
maxFiles: '14d',
format: winston.format.json()
});
问答3:日志文件太多怎么办?
答:使用集中式日志系统(如ELK),本地只保留最近7天,超过7天的直接删除或上传到对象存储(S3/MinIO)并清理本地副本,对于极高频系统(如物联网),推荐只存储ERROR级别。
常见问题问答(FAQ)
Q1:前端(浏览器)日志应该怎么记录?
A:使用Sentry、LogRocket或Datadog RUM,而不是直接写控制台,后端API错误通过“错误边界”捕获并上报,UI交互作为性能指标发送。
Q2:日志中包含用户IP,是否违反GDPR?
A:IP属于个人数据,必须匿名化,记录/8网段或0.0.0(内部网络)即可,若需备案,请与法务确认数据保留策略。
Q3:异步日志是否会有性能问题?
A:绝大多数日志库(如winston、log4j2)使用异步I/O,在正常业务量下延迟<1ms,但若每秒钟记录数万条(如高并发秒杀),建议使用pino(C++写的JSON序列化器)或zerolog(Go)。
Q4:如何测试日志是否正常工作?
A:单元测试断言日志是否写入文件;集成测试断言错误日志被正确触发,使用testcontainers启动Elasticsearch容器验证日志是否能到达。
Q5:日志中发现了敏感数据泄露,怎么办?
A:立即执行以下步骤:
- 修改代码脱敏或临时降级日志级别
- 清理已存储的日志文件(如果存在明文密码)
- 通知安全团队进行密码强制重置
- 添加自动化扫描(如
git-secrets、trufflehog)到CI/CD流水线