Node === Winston 日志工具详解

118 阅读4分钟

Node === Winston 日志工具详解

目录

[TOC]

简介

Winston 是强大、灵活的  Node.js 开源日志库之一,理论上, Winston 是一个可以记录所有信息的记录器。这是一个高度直观的工具,易于定制。可以通过更改几行代码来调整其背后的逻辑。它使对数据库或文件等持久存储位置的日志记录变得简单容易。

Winston 提供以下功能:

  • 集中控制日志记录的方式和时间 :在一个地方更改代码即可
  • 控制日志发送的位置 :将日志同步保存到多个目的地(如Elasticsearch、MongoDB、Postgres等)。
  • 自定义日志格式 :带有时间戳、颜色日志级别、JSON格式等前缀。

winston实践

实践代码将在项目中增加日志功能,安装依赖:

npm install winston --save

1. 基本配置说明

const { createLogger, format, transports } = require("winston");
 
const logger = createLogger({
  level: "info",  // 设置日志级别
  format: format.json(), // 设置日志格式为JSON
  defaultMeta: { service: "user-service" }, // 添加默认元数据
  transports: [
    // 配置日志输出目标
  ]
});

这里使用 createLogger() 创建了一个日志实例,主要配置包括:

  • level: 设置日志级别,低于此级别的日志不会记录

  • format: 设置日志格式,这里使用JSON格式

  • defaultMeta: 为所有日志添加默认的元数据

  • transports: 配置日志输出的目标

2. 日志传输配置

  // 错误日志文件配置
  new transports.File({ 
    filename: "error.log", 
    level: "error" 
  }),
 
  // 信息日志文件配置
  new transports.File({
    filename: "info.log",
    level: "info",
    format: format.combine(
      format((info) => (info.level === "info" ? info : false))()
    ),
  }),
]

这里配置了两个文件传输:

  • error.log - 只记录 error 级别的日志

  • info.log - 只记录 info 级别的日志,使用format.combine()组合格式化器

3. 开发环境控制台输出

if (process.env.NODE_ENV !== "production") {
  logger.add(
    new transports.Console({
      format: format.combine(
        format.colorize(), // 添加颜色
        format.simple()    // 简单格式
      ),
    })
  );
}

在非生产环境下添加控制台输出,并:

  • 使用 format.colorize() 为日志添加颜色

  • 使用 format.simple() 采用简单的输出格式

4. 实用日志方法封装

const logInfo = (message, meta = {}) => {
  logger.info(message, { 
    timestamp: new Date().toISOString(), 
    ...meta 
  });
};
 
const logError = (message, error, meta = {}) => {
  const errorObj = error instanceof Error ? error : null;
  logger.error(message, {
    timestamp: new Date().toISOString(),
    message: errorObj ? errorObj.message : error.message,
    stack: errorObj ? errorObj.stack : error.message,
    ...meta,
  });
};
 
const logWarn = (message, meta = {}) => {
  logger.warn(message, { 
    timestamp: new Date().toISOString(), 
    ...meta 
  });
};

封装了三个常用的日志方法:

1. logInfo() - 记录信息日志

  • logError() - 记录错误日志,包含错误堆栈

  • logWarn() - 记录警告日志

每个方法都:

  • 自动添加时间戳

  • 支持传入额外的元数据

  • 错误日志特别处理了Error对象

5. 使用示例

// 记录普通信息
logInfo("用户登录成功", { userId: "123" });
 
// 记录错误
try {
  throw new Error("数据库连接失败");
} catch (err) {
  logError("操作失败", err, { operation: "db_connect" });
}
 
// 记录警告
logWarn("磁盘空间不足", { available: "20GB" })

6. 日志级别说明

Winston 支持以下日志级别(优先级从高到低):

{
  error: 0,   // 错误
  warn: 1,    // 警告
  info: 2,    // 信息
  http: 3,    // HTTP请求
  verbose: 4, // 详细信息
  debug: 5,   // 调试信息
  silly: 6    // 追踪信息
}

设置某个级别后,只有优先级相同或更高的日志会被记录。

这个日志工具的实现提供了:

  • 分级别的日志记录

  • 多目标输出

  • 灵活的格式化

  • 开发/生产环境区分

  • 错误堆栈跟踪

  • 时间戳和元数据支持

是一个功能完整的生产级日志解决方案。

完整代码

封装方法

const { createLogger, format, transports } = require("winston");
 
const logger = createLogger({
  // level 表示日志级别  例如 info warn error 等
  // 默认info级别 低于info级别的日志不会显示
  level: "info",
  //  format 日志格式 例如 json格式
  format: format.json(),
 
  // defaultMeta 表示日志的元数据 例如 服务名称
  defaultMeta: { service: "user-service" },
 
  // transports 表示日志的输出方式 例如 输出到文件 输出到控制台
  transports: [
    // 输出到error.log文件 级别为error(error fatal)
    new transports.File({ filename: "error.log", level: "error" }),
 
    new transports.File({
      filename: "info.log",
      level: "info",
      format: format.combine(
        format((info) => (info.level === "info" ? info : false))()
      ),
    }),
  ],
});
 
// 如果当前不是生产环境 则将日志输出到控制台
// 格式为 (level: message) json.stringify({ ...rest })
if (process.env.NODE_ENV !== "production") {
  logger.add(
    new transports.Console({
      // 使用组合格式
      format: format.combine(
        format.colorize(), // 添加颜色
        format.simple() // simple 为 简洁格式
      ),
    })
  );
}
 
// 添加一些实用的日志方法
const logInfo = (message, meta = {}) => {
  logger.info(message, { timestamp: new Date().toISOString(), ...meta });
};
 
const logError = (message, error, meta = {}) => {
  const errorObj = error instanceof Error ? error : null;
  logger.error(message, {
    timestamp: new Date().toISOString(),
    message: errorObj ? errorObj.message : error.message,
    stack: errorObj ? errorObj.stack : error.message,
    ...meta,
  });
};
 
const logWarn = (message, meta = {}) => {
  logger.warn(message, { timestamp: new Date().toISOString(), ...meta });
};
 
module.exports = {
  logger,
  logInfo,
  logError,
  logWarn,
};

koa 日志中间件

const { logInfo, logError } = require("../utils/logger");
 
const loggerMiddleware = async (ctx, next) => {
  const start = Date.now();
  const requestId =
    ctx.request.headers["x-request-id"] || Date.now().toString(36);
 
  try {
    logInfo("请求开始", {
      requestId,
      method: ctx.method,
      url: ctx.url,
      query: ctx.query,
      body: ctx.request.body,
      ip: ctx.ip,
      userAgent: ctx.headers["user-agent"],
    });
 
    await next();
 
    const ms = Date.now() - start;
    logInfo("请求完成", {
      requestId,
      method: ctx.method,
      url: ctx.url,
      status: ctx.status,
      duration: `${ms}ms`,
      responseBody: ctx.body,
    });
  } catch (err) {
    const ms = Date.now() - start;
    logError("请求异常", err, {
      requestId,
      method: ctx.method,
      url: ctx.url,
      status: ctx.status || 500,
      duration: `${ms}ms`,
      body: ctx.request.body,
    });
    throw err; // 继续抛出错误,让错误处理中间件处理
  }
};
 
module.exports = loggerMiddleware;

在app使用

const loggerMiddleware = require("../middleware/logger.middleware");
 
const app = new Koa();
 
// 注册日志中间件
app.use(loggerMiddleware);