1. 选择合适的日志库
根据应用场景(如性能需求、功能复杂度)选择日志库是基础。常用库包括:
2. 配置环境适配的日志级别
根据环境调整日志详细程度,避免不必要的性能消耗:
debug或verbose级别,记录函数入口/出口、变量值等详细信息,便于调试;info或warn级别,记录关键业务流程和潜在问题;warn或error级别,仅记录错误和重要警告,减少日志体积和IO压力。process.env.NODE_ENV动态设置,例如:const level = process.env.NODE_ENV === 'production' ? 'warn' : 'debug';
3. 使用结构化日志(JSON格式)
结构化日志(如JSON)便于后续解析、分析和可视化,是现代日志管理的核心要求。相比纯文本,JSON日志可通过工具(如ELK、Grafana)快速提取字段(如event、userId、timestamp),支持聚合和过滤。示例如下:
logger.info({
event: 'user_login',
userId: '12345',
username: 'john_doe',
ip: '192.168.1.1',
userAgent: 'Chrome/120.0.0.0'
});
4. 实现日志轮转(避免文件过大)
当日志文件过大时,需通过轮转分割文件,防止磁盘空间耗尽。可使用winston-daily-rotate-file等工具,配置如下:
const DailyRotateFile = require('winston-daily-rotate-file');
const transport = new DailyRotateFile({
filename: 'application-%DATE%.log', // 按日期命名(如application-2025-10-12.log)
datePattern: 'YYYY-MM-DD', // 日期格式
zippedArchive: true, // 压缩旧日志
maxSize: '20m', // 单个文件最大20MB
maxFiles: '14d' // 保留14天内的日志
});
const logger = winston.createLogger({
transports: [transport]
});
5. 敏感信息脱敏
日志中避免记录用户密码、信用卡号、身份证号等敏感数据,防止泄露。可通过以下方式处理:
sanitize-html、lodash.omit等工具过滤请求体中的敏感字段;*****代替密码)。示例如下:const sanitize = require('sanitize-html');
app.post('/login', (req, res) => {
const sanitizedBody = sanitize(req.body, {
allowedTags: [],
allowedAttributes: {}
});
logger.info('Login attempt:', { userId: req.body.userId, password: '*****' });
});
6. 集成监控与报警
将日志与监控系统(如Prometheus、Grafana、Sentry)集成,实现实时监控和异常报警:
const Sentry = require('@sentry/node');
Sentry.init({ dsn: 'your-sentry-dsn' });
app.use((err, req, res, next) => {
logger.error('Unhandled error:', { error: err.message, stack: err.stack });
Sentry.captureException(err); // 上报到Sentry
res.status(500).send('Internal Server Error');
});
7. 全链路日志追踪(Request ID)
在微服务或分布式系统中,使用唯一requestId标记每个请求,跟踪其在整个链路中的流动(如从API网关到数据库)。通过requestId可快速关联不同服务的日志,定位跨服务问题。示例如下:
const { v4: uuidv4 } = require('uuid');
app.use((req, res, next) => {
const requestId = req.headers['x-request-id'] || uuidv4(); // 获取或生成requestId
req.requestId = requestId;
logger.info(`Request started: ${requestId}`, { method: req.method, url: req.url });
res.on('finish', () => {
logger.info(`Request completed: ${requestId}`, { status: res.statusCode });
});
next();
});
8. 异步日志记录(避免阻塞主线程)
日志写入是IO密集型操作,应使用异步方式避免阻塞应用主线程。多数日志库(如Winston、Pino)默认支持异步,例如Winston的transports.File会自动使用异步写入,确保日志记录不影响请求处理性能。