# 功能-日志

# winston

winston (opens new window) 是一个通用的 node 日志库,支持灵活的配置和多种传输方式(如控制台输出、文件存储、HTTP 传输等)。它适用于记录应用程序的各种日志,包括信息日志、错误日志、调试日志等。

# 概念

  • Logger: 在 winston 中,日志记录器(Logger)是核心组件,负责创建和管理日志记录。每个日志记录器可以配置多个传输方式,并可以设置不同的日志级别。

  • Levels: 定义了日志的级别,例如 errorwarninfodebug 等。不同级别的日志可以输出到不同的 Transport。

  • Transports: 传输方式,定义了日志的输出目标,例如控制台、文件、数据库等。winston 内置了多种 Transport,也支持自定义。

  • Format: 格式化器, 定义了日志的格式,例如 JSON文本颜色等。winston 提供了多种内置格式,也支持自定义。

示例:

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

# 日志级别

winston 默认支持的日志级别(从高到低)如下:

级别 说明
error 错误信息,表示严重的故障或问题。
warn 警告信息,表示潜在的问题或异常。
info 一般信息,用于记录应用程序的运行状态。
verbose 详细信息,提供比 info 更详细的日志。
debug 调试信息,用于开发和调试阶段。
silly 最详细的日志信息,通常用于极端调试场景。

日志Levels 可以在 winstonLogger 中配置,也可以在 Transport 中配置,如:

// 在 Logger 中配置
const logger = winston.createLogger({
  level: 'info',
  transports: [new winston.transports.Console()],
});

// 在 Transport 中配置
const logger = winston.createLogger({
  level: 'info',
  transports: [new winston.transports.Console()],
});

# 自定义日志级别

日志Levels 可以自定义,如我们自定义下面的日志级别并设置颜色:

const customLevels = {
  levels: {
    critical: 0, // 自定义 严重错误
    error: 1,
    warning: 2,
    notice: 3,
    info: 4,
    debug: 5
  },
  colors: {
    critical: 'red',
    error: 'orange',
    warning: 'yellow',
    notice: 'blue',
    info: 'green',
    debug: 'gray'
  }
};

const logger = createLogger({
  levels: customLevels.levels,
  transports: [new transports.Console()]
});

logger.critical('This is a critical message');
logger.notice('This is a notice message');

在logger中设置级别颜色

const { format, transports } = require('winston');
// 引入combine、colorize、printf格式
const { combine, colorize, printf } = format;

// 自定义格式
const customFormat = printf(({ level, message }) => {
  return `${level}: ${message}`;
});

const logger = createLogger({
  levels: customLevels.levels,
  // 设置日志格式
  format: combine(colorize(), customFormat),
  // 设置日志输出位置
  transports: [new transports.Console()]
});

// 执行
logger.critical('This is a critical message');
logger.notice('This is a notice message');

# Format

Format 是 winston 中用于定义日志格式的核心概念。每个 Format 负责将日志转换为特定的格式,例如 JSON文本颜色等。以下是 Format 的详细介绍和使用方法。

Format 说明
format.json() 将日志格式化为 JSON。
format.simple() 将日志格式化为简单文本。
format.combine() 组合多个格式。
format.timestamp() 添加时间戳。
format.colorize() 为日志添加颜色。
format.printf() 自定义日志格式。
format.prettyPrint() 将日志格式化为易读的 JSON。
format.label() 为日志添加标签。
format.metadata() 添加元数据。

除此之外我们还可以自定义格式,如:

const customFormat = format.printf(({ level, message, timestamp }) => {
  return `${timestamp} [${level}]: ${message}`;
});

const logger = createLogger({
  format: format.combine(
    format.timestamp(),
    customFormat
  ),
  transports: [new transports.Console()]
});

logger.info('This is an info message');

# Transports

Transports 是 winston 中用于定义日志输出目标的核心概念。每个 Transport 负责将日志发送到特定的目标,例如控制台、文件、数据库等。以下是 Transports 的详细介绍和使用方法。

Transport 说明
transports.Console 将日志输出到控制台。
transports.File 将日志输出到文件。
transports.Http 将日志发送到 HTTP 服务器。
transports.Stream 将日志输出到流。

例如:使用transports.Console 在开发环境下将日志输出到控制台

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

在生产环境下使用transports.File 将日志输出到文件

// 自定义日志格式
const logFormat = winston.format.printf(({ level, message, timestamp }) => {
  return `${timestamp} [${level}]: ${JSON.stringify(message)}`;
});
// 设置北京时间的格式化函数
const beijingTime = winston.format((info) => {
  info.timestamp = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
  return info;
});

new winston.transports.File({
  filename: path.join(logDir, `${date}-${type}.log`), // 日志文件名,使用日期和类型拼接
  level: type, // 日志级别,使用类型作为级别
  maxAge: 1000 * 60 * 60 * 24 * 30, // 日志保留时间30天
  maxFiles: 10, // 最大文件数,最多保留10个日志文件
  maxsize: 10000000, // 10MB,单个日志文件最大大小为10MB
  tailable: true, // 是否跟随日志文件,如果为true,则可以实时读取日志文件的新内容
  zippedArchive: true, // 是否压缩日志文件,如果为true,则将日志文件压缩存储
  format: winston.format.combine( // 日志格式
    beijingTime(), // 使用北京时间,默认使用UTC时间
    winston.format.timestamp(), // 添加时间戳
    winston.format.colorize(), // 添加颜色
    logFormat // 自定义日志格式
  )
})

# 详细代码

在logs目录下创建一个index.js文件,并添加以下代码:


const winston = require('winston');
const fs = require('fs');
const path = require('path');

// 创建日志目录
const logDir = path.join(__dirname, './logs');
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir);
}

// 自定义日志格式
const logFormat = winston.format.printf(({ level, message, timestamp }) => {
  return `${timestamp} [${level}]: ${JSON.stringify(message)}`;
});

// 设置北京时间的格式化函数
const beijingTime = winston.format((info) => {
  info.timestamp = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
  return info;
});

// 根据环境配置日志传输器
const transports = [];

if (process.env.NODE_ENV === 'production') {
  const types = ['error', 'info'];
  const date = new Date().toLocaleString().split(' ')[0];
  types.forEach(type => {
    transports.push(
      new winston.transports.File({
        filename: path.join(logDir, `${date}-${type}.log`), // 日志文件名,使用日期和类型拼接
        level: type, // 日志级别
        maxFiles: 10, // 最大文件数
        maxsize: 10000000, // 10MB,最大文件大小
        tailable: true, // 是否跟随日志文件
        zippedArchive: true, // 是否压缩归档
        format: winston.format.combine( // 日志格式
          beijingTime(), // 北京时间
          winston.format.timestamp(), // 时间戳
          logFormat // 自定义日志格式
        )
      })
    )
  })
}else {
  // 开发环境下,输出到控制台
  transports.push(
    new winston.transports.Console({
      format: winston.format.combine(
        // 获取北京时间
        beijingTime(),
        // 格式化日志输出
        winston.format.colorize(),
        // 添加时间戳
        winston.format.timestamp(),
        // 自定义日志格式
        logFormat
      )
    })
  )
}


// 创建日志记录器
const logger = winston.createLogger({
  level: 'info',
  transports,
});

module.exports = logger;

在app.js中使用

const logger = require('./logs');

// 中间件:记录所有请求的日志
app.use((req, res, next) => {
  logger.info({
    method: req.method,
    ip: req.ip,
    url: req.originalUrl,
    data: req.body
  });
  next();
});

// 全局错误处理中间件
app.use((err, req, res, next) => {
  logger.error({
    method: req.method,
    ip: req.ip,
    url: req.originalUrl,
    error: err.message,
    stack: err.stack
  });
  res.status(500).json({ message: 'Internal Server Error' });
});

# 其它日志

  • Morgan (opens new window) 作为一款专为 Node.js 设计的 HTTP 请求记录中间件,它的出现极大地简化了开发者在调试和性能优化过程中对于 HTTP 请求日志的处理工作。
  • logs4js (opens new window) 是一个基于 log4jsNode.js 日志库,它提供了灵活的日志记录功能,支持多种日志格式和传输方式。