Skip to content

Node系列 · ORM:log4js 日志记录

生产环境不能用 console.log——它没法分级别、没法分类、没法按大小切分文件、没法自动清理。本章讲清楚 log4js 的核心概念和按环境配置的实战。

一、为什么不用 console.log

维度console.loglog4js
级别控制✅ 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.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远程日志服务器
gelfGraylog 扩展日志格式

每个 appender 还可指定 layout(输出格式):

layout格式
pattern自定义模板字符串
colored带 ANSI 颜色(开发用)
jsonJSON 格式(机器解析)

三、安装与基础使用

bash
npm install log4js
javascript
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
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
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
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
// 应用退出前优雅关闭
process.on('SIGTERM', () => {
  log4js.shutdown(() => {
    console.log('日志系统已关闭');
    process.exit(0);
  });
});

shutdown() 等待所有缓冲日志写完再回调——避免最后几条日志丢失。

七、配置模板

javascript
// 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
logger.info('登录失败', { email: user.email, ip: req.ip });
// ❌ logger.info('登录失败', { password });  // 永远不要

九、与其他日志库对比

风格性能适合
log4js配置式中(同步阻塞风险用 stream 解决)传统项目 / 文件输出
pinoJSON 输出极快(异步)性能敏感 / 日志聚合
winston配置式老牌项目
bunyanJSON 输出微服务 / 日志聚合

TIP

新项目推荐 pino——JSON 输出 + 异步 = 不阻塞事件循环 + 易聚合到 ELK / Loki。但 log4js 在国内中小项目里依然主流,本文按 log4js 展开。

十、小结

  • 生产环境永远用日志库,不用 console.log
  • 三大核心:level(级别)/ category(分类)/ appender(输出源)
  • dateFile appender 按日期切分日志;file + maxLogSize 按大小
  • 按环境配置:开发期控制台 + debug;生产期文件 + info
  • Express 用 connectLogger 自动记录请求日志
  • 敏感字段永远不写日志(密码、token 等)
  • 应用退出前 log4js.shutdown() 刷写缓冲