Node系列 · ORM:log4js 日志记录
生产环境不能用
console.log------它没法分级别、没法分类、没法按大小切分文件、没法自动清理。本章讲清楚log4js的核心概念和按环境配置的实战。
一、为什么不用 console.log
| 维度 | console.log |
log4js |
|---|---|---|
| 级别控制 | ❌ | ✅ trace / debug / info / warn / error / fatal |
| 分类 | ❌ | ✅ category(按模块拆分日志) |
| 输出目标 | 仅 stdout | ✅ 控制台 / 文件 / 网络 / 数据库 |
| 文件切分 | ❌ | ✅ 按大小 / 日期 |
| 自动清理 | ❌ | ✅ log4js 自动管理 |
| 性能 | 阻塞 stdout | 异步缓冲,不阻塞事件循环 |
::: tip
console.log 永远不是生产日志方案 。它没法分类、没法分级、还会在高并发下阻塞事件循环(stdout 同步写入)。
:::
二、log4js 三大核心概念
2.1 级别(level)
从低到高:
| 级别 | 用途 |
|---|---|
trace |
最详细的调试信息(一般关闭) |
debug |
调试信息 |
info |
正常运行日志 |
warn |
警告(潜在问题) |
error |
错误(功能受影响) |
fatal |
致命(进程退出) |
设置 level: 'warn' 后,只有 warn / error / fatal 会输出。
2.2 分类(category)
按模块 / 功能划分的日志命名空间:
javascript:log4js-basic.js
log4js.getLogger('http'); // HTTP 请求日志
log4js.getLogger('db'); // 数据库日志
log4js.getLogger('user'); // 用户模块日志
log4js.getLogger('default'); // 默认日志(不分类时用)
每个 category 可以单独配置级别和输出目标。
2.3 输出源(appender)
日志写到哪里:
| appender 类型 | 输出目标 |
|---|---|
console |
控制台 stdout / stderr |
file |
单个文件 |
dateFile |
按日期切分(如 app.2024-08-15.log) |
fileSync |
同步写文件(启动期用) |
tcp |
远程日志服务器 |
gelf |
Graylog 扩展日志格式 |
每个 appender 还可指定 layout(输出格式):
| layout | 格式 |
|---|---|
pattern |
自定义模板字符串 |
colored |
带 ANSI 颜色(开发用) |
json |
JSON 格式(机器解析) |
三、安装与基础使用
bash
npm install log4js
javascript:log4js-basic.js
const log4js = require('log4js');
log4js.configure({
appenders: { console: { type: 'console' } },
categories: { default: { appenders: ['console'], level: 'info' } },
});
const logger = log4js.getLogger();
logger.info('服务启动');
logger.warn('配置项缺失');
logger.error('请求失败', err);
bash
$ node app.js
[2024-08-15T14:30:00.000] [INFO] default - 服务启动
四、按环境配置
4.1 配置代码
javascript:log4js-config.js
const path = require('path');
const log4js = require('log4js');
const isDev = process.env.NODE_ENV !== 'production';
log4js.configure({
appenders: {
// 控制台:开发期彩色
consoleOut: {
type: 'console',
layout: { type: 'colored' },
},
// 按日期切分文件
appFile: {
type: 'dateFile',
filename: path.join('logs', 'app.log'),
pattern: 'yyyy-MM-dd',
// 文件名格式:app.2024-08-15.log
compress: true,
},
// 错误单独一个文件
errorFile: {
type: 'file',
filename: path.join('logs', 'error.log'),
// 旧文件超 10MB 自动备份
maxLogSize: 10 * 1024 * 1024,
backups: 5,
},
},
categories: {
default: {
appenders: isDev ? ['consoleOut'] : ['appFile', 'errorFile'],
level: isDev ? 'debug' : 'info',
},
http: {
appenders: ['appFile'],
level: 'info',
},
db: {
appenders: ['appFile'],
level: 'warn',
},
},
});
module.exports = log4js;
4.2 各级别模块用不同 logger
javascript:use-categories.js
const log4js = require('log4js');
const httpLogger = log4js.getLogger('http');
const dbLogger = log4js.getLogger('db');
httpLogger.info('GET /api/users 200');
dbLogger.warn('慢查询:耗时 1200ms');
五、Express 中间件
log4js 提供 Express 中间件自动记录请求:
javascript:express-middleware.js
const log4js = require('log4js');
const express = require('express');
const app = express();
// 自动记录每个 HTTP 请求
app.use(log4js.connectLogger(log4js.getLogger('http'), {
level: 'auto', // 5xx → error,4xx → warn,其他 → info
format: (req, res, format) => format(
':remote-addr - :method :url :status :res[content-length] - :response-time ms'
),
}));
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
输出示例:
[INFO] http - 127.0.0.1 - GET /api/users 200 42 - 12 ms
[ERROR] http - 127.0.0.1 - POST /api/users 500 89 - 234 ms
六、关闭与刷新
javascript:shutdown.js
// 应用退出前优雅关闭
process.on('SIGTERM', () => {
log4js.shutdown(() => {
console.log('日志系统已关闭');
process.exit(0);
});
});
shutdown() 等待所有缓冲日志写完再回调------避免最后几条日志丢失。
七、配置模板
javascript:log4js-template.js
// config/log4js.js
const path = require('path');
const log4js = require('log4js');
const LOG_DIR = path.resolve(__dirname, '../logs');
const isDev = process.env.NODE_ENV !== 'production';
log4js.configure({
appenders: {
// 开发期:控制台彩色输出
devConsole: {
type: 'console',
layout: { type: 'colored' },
},
// 生产期:分文件 + 错误单独
prodAll: {
type: 'dateFile',
filename: path.join(LOG_DIR, 'app.log'),
pattern: 'yyyy-MM-dd',
compress: true,
},
prodError: {
type: 'levelFilter',
// level: 'error' 之后的级别都写这个文件
appender: {
type: 'file',
filename: path.join(LOG_DIR, 'error.log'),
maxLogSize: 10 * 1024 * 1024,
backups: 5,
},
level: 'error',
},
},
categories: {
default: {
appenders: isDev ? ['devConsole'] : ['prodAll', 'prodError'],
level: isDev ? 'debug' : 'info',
},
http: {
appenders: isDev ? ['devConsole'] : ['prodAll'],
level: 'info',
},
db: {
appenders: isDev ? ['devConsole'] : ['prodAll'],
level: 'warn',
},
},
});
module.exports = log4js;
八、最佳实践
| 场景 | 推荐 |
|---|---|
| 开发期 | 控制台 + level: 'debug' |
| 生产期 | 文件 + 错误单独 + level: 'info' |
| HTTP 请求 | connectLogger 自动记录 |
| 慢查询 / 错误 | 单独 category + level: 'warn' |
| 敏感字段 | 日志前脱敏(密码、token、手机号中间四位) |
| 应用退出 | log4js.shutdown 刷写缓冲 |
| 文件清理 | dateFile + compress: true 自动管理 |
::: warning
永远不要把密码、token、完整手机号、身份证号写日志。即使是错误日志,也要先脱敏:
javascript:log4js-template.js
logger.info('登录失败', { email: user.email, ip: req.ip });
// ❌ logger.info('登录失败', { password }); // 永远不要
:::
九、与其他日志库对比
| 库 | 风格 | 性能 | 适合 |
|---|---|---|---|
| log4js | 配置式 | 中(同步阻塞风险用 stream 解决) | 传统项目 / 文件输出 |
| pino | JSON 输出 | 极快(异步) | 性能敏感 / 日志聚合 |
| winston | 配置式 | 中 | 老牌项目 |
| bunyan | JSON 输出 | 快 | 微服务 / 日志聚合 |
::: tip
新项目推荐 pino ------JSON 输出 + 异步 = 不阻塞事件循环 + 易聚合到 ELK / Loki。但 log4js 在国内中小项目里依然主流,本文按 log4js 展开。
:::
十、小结
- 生产环境永远用日志库,不用
console.log - 三大核心:level(级别)/ category(分类)/ appender(输出源)
dateFileappender 按日期切分日志;file+maxLogSize按大小- 按环境配置:开发期控制台 + debug;生产期文件 + info
- Express 用
connectLogger自动记录请求日志 - 敏感字段永远不写日志(密码、token 等)
- 应用退出前
log4js.shutdown()刷写缓冲