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);