winston
一个几乎可以记录一切的NodeJs日志库
说明
大部分为 Ai + 机翻,已人工核对。版本:winston v3.12.0, npm发布最新版,
于2024年3月17日星期日
约定
transports
翻译为 传输器
logger
翻译为 日志器
winston@3
有关详细信息,请参阅升级指南。欢迎Bug报告和PR!
Looking for winston@2.x
documentation?
请注意,以下文档用于winston@3.x阅读winston@2.x文档
Motivation
Winston 是一个简单而通用的日志库,支持多种传输方式。传输本质上是日志的存储设备。每个 Winston 日志记录器可以有多个 transports
(参见:transports
)配置在不同的级别(参见: 日志级别)。例如,您可能希望将错误日志存储在一个持久的远程位置(如数据库) ,但是所有日志都输出到控制台或本地文件。
Winston 的目标是解耦部分日志记录过程,使其更加灵活和可扩展。注意支持日志格式(参见: 格式)和级别(参见: 使用自定义日志级别)的灵活性,并确保这些 API 与传输日志的实现(即如何存储/索引日志,参见: 添加自定义 transports
)解耦到他们暴露给程序员的 API。
快速上手
TL;DR? 在 ./examples/ 中查看快速上手的示例。./examples/*.js
中还有许多其他的例子。没找到你认为应该有的例子吗?提交一个拉请求添加它!
使用
使用 Winston 的推荐方法是创建自己的日志记录器。最简单的方法是使用 winston.createLogger:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
//
// - 将所有重要级别为‘error’或更低的日志写入‘ error.log’
// - 将所有重要级别为“ info”或更低的日志写入“ combined.log”
//
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
//
// 如果我们不是在生产环境使用,则使用以下格式打印“控制台”
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
您也可以直接通过由 **require(‘winston’)**公开的默认日志记录器进行日志记录,但这仅仅是为了成为一个方便的共享日志记录器,如果您愿意,可以在整个应用程序中使用。注意,默认的日志记录器在默认情况下没有任何 transports
。您需要自己添加 transports
,如果默认的日志记录器没有任何 transports
,可能会产生高内存使用问题。
目录
文章目录
- winston
- winston@3
- Looking for `winston@2.x` documentation?
- Motivation
- [快速上手 ]()
- 使用
- 目录
- Logging(日志)
- Formats(格式化)
- Logging Levels(日志级别)
- Transports(传输器)
- Multiple transports of the same type(同一类型的多个传输器)
- Adding Custom Transports(添加自定义传输器)
- Common Transport options(常用传输器选项)
- Exceptions(异常)
- Rejections(拒绝)
- Profiling(性能分析)
- Querying Logs(查询日志)
- Streaming Logs(流日志)
- Further Reading(阅读延申)
- Installation(安装)
- Run Tests(运行测试)
Logging(日志)
Winston 中的日志级别符合 RFC5424 指定的严重性顺序: 假定所有级别的严重性在数值上从最重要到最不重要。
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
};
Creating your own Logger(创建自己的日志记录器)
首先,您可以使用 winston.createLogger 创建一个 logger实例:
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});
logger实例 可以接收以下参数来配置或执行其功能
Name(名称) | Default(默认值) | Description(说明) |
---|---|---|
level | 'info' | 只有当 info.level 小于或等于此级别时才记录 |
levels | winston.config.npm.levels | 表示日志优先级的级别(和颜色) |
format | winston.format.json | info 消息的格式(参见: Formats) |
transports | [] *(No transports) | info messages的日志记录目标集 |
exitOnError | true | 如果为 false,已处理的异常将不会导致进程关闭 |
silent | false | 如果为 true,则禁用所有日志 |
提供给 createLogger
函数的级别将会作为便捷方法定义在返回的 logger 对象上
//
// 日志
//
logger.log({
level: 'info',
message: 'Hello distributed log files!'
});
logger.info('Hello again distributed logs');
在 Winston 库通过 winston.createLogger
创建并返回给你一个 logger 实例之后,你可以向这个 logger 添加或移除 transports
//将日志消息写入名为 'combined.log' 的文件
const files = new winston.transports.File({ filename: 'combined.log' });
// 创建了一个新的 Console transport,用于将日志消息输出到控制台
const console = new winston.transports.Console();
logger
.clear() // 清除 logger 当前配置的所有 transports。
.add(console) // 将刚刚创建的 Console transport 添加到 logger
.add(files) // 将先前创建的 files 添加到 logger,
// 同时将日志消息输出到控制台和写入 'combined.log' 文件。
.remove(console); // 移除 console transport,不再把日志消息输出到控制台,
// 但仍将继续将日志消息写入 'combined.log' 文件
你也可以通过使用 configure
方法整体重新配置一个 Winston.Logger 实例
const logger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});
// 完全替换原有的 transports 配置,新的配置中只有一个 transports
// Winston 的第三方扩展,它提供了按天自动轮转日志文件的功能
const DailyRotateFile = require('winston-daily-rotate-file');
logger.configure({
// 设置新的日志级别为 'verbose'
level: 'verbose',
transports: [
new DailyRotateFile(opts)
]
});
Creating child loggers(创建子日志器)
你可以基于现有的日志器创建子日志器(child loggers),以便传递元数据覆盖重写
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
]
});
const childLogger = logger.child({ requestId: '451' });
如果同时自定义了 Logger 类,可能会导致 .child()
方法出现问题。这是因为某些实现细节可能会使得 JavaScript 中的 this
关键字指向非预期的对象。因此,在扩展 Logger 类的同时使用 .child()
方法需要格外小心,确保类扩展不会破坏方法的内部机制。在实际开发中,要留意此类兼容性问题并适当地测试和调试。
Streams, objectMode
, and info
objects
在 winston 中,无论是 Logger 实例还是 Transport 实例都被视为支持对象模式(objectMode)的流,这类流能够接受一个 info 对象。
info 参数是传递给特定格式化器的一个单一日志消息表示。info 对象本身是可变的。每个 info 对象至少应包含 level 和 message 两个属性:
const info = {
level: 'info', // 日志消息的级别,如 'info'、'warn'、'error' 等
message: 'Hey! Log something?' // 被记录的描述性信息
}
Winston 日志库中,除了 level
和 message
属性之外的其他属性被视为“元数据”(meta data)
const { level, message, ...meta } = info;
Winston 中集成的 logform 格式化器库自身也添加了一些额外的属性:
Property | Format added by | Description |
---|---|---|
splat | splat() | String interpolation splat for %d %s -style messages. |
timestamp | timestamp() | timestamp the message was received. |
label | label() | Custom label associated with each message. |
ms | ms() | Number of milliseconds since the previous log message. |
Property | Format added by | Description |
---|---|---|
splat | splat() | 由 splat() 格式处理器添加。这个属性用于字符串插值,特别适用于含有 %d (数字)和 %s (字符串)样式的消息。它收集并处理额外的字符串插值参数,将它们合并到最终的日志消息中。 |
timestamp | timestamp() | 由 timestamp() 格式处理器添加。这个属性记录了接收到消息的时间戳,即每条日志消息产生的时间。 |
label | label() | 由 label() 格式处理器添加。这是一个与每条消息关联的自定义标签,可用于区分不同来源或类型的日志消息。 |
ms | ms() | 由 ms() 格式处理器添加。这个属性记录了自上次日志消息以来的毫秒数,可以帮助计算消息之间的间隔时间,对于监控应用性能或理解消息间的时序关系非常有用。 |
作为一个使用者,您可以根据需要向 info 对象添加任何自定义属性。但是,内部状态由 Symbol 类型的属性维护,以确保关键属性的唯一性和不可变性:
Symbol.for('level')
(只读):等于 info 对象的 level 属性,所有代码都认为它是不可变的。这是日志消息的级别标识符,如 ‘info’、‘warn’、‘error’ 等。Symbol.for('message')
:完整的字符串消息,由“finalizing formats
(最终格式化器)”设置,例如 json、logstash、printf、prettyPrint 和 simple 格式化器。这些格式化器会在处理 info 对象时生成一个完整的、格式化的消息字符串。Symbol.for('splat')
:额外的字符串插值参数。专门用于 splat() 格式化器,用于处理额外的格式化占位符参数。
const { LEVEL, MESSAGE, SPLAT } = require('triple-beam');
console.log(LEVEL === Symbol.for('level'));
// true
console.log(MESSAGE === Symbol.for('message'));
// true
console.log(SPLAT === Symbol.for('splat'));
// true
请注意,在向 logger 提供的元数据对象(meta object)中,如果有 { message }
属性,它会自动与已经提供的 msg(消息主体)进行拼接。例如,下面的代码将会把 ‘world’ 与 ‘hello’ 连接在一起:
// 第一个参数是日志级别(这里是 'error' 和 'info'),
// 第二个参数是原始的消息字符串 'hello'。
// 第三个参数是一个包含 { message: 'world' } 的元数据对象。
logger.log('error', 'hello', { message: 'world' });
logger.info('hello', { message: 'world' });
// { message: 'world' } 中的 message 属性会被自动追加到原始的 'hello' 消息后,
// 最终输出的日志消息将是 'helloworld'
Formats(格式化)
在 Winston 中,格式化器可以通过 winston.format
访问,它们实际上是通过独立于 Winston 的模块 logform 实现的。这样的设计增强了灵活性,让你在编写自定义 transports 时,可以根据需要轻松地为其包含默认的格式化器。
在现代版本的 Node.js 中,模板字符串(template literals)的性能表现非常好,因此推荐在大部分情况下使用模板字符串来自定义格式化日志。如果你想对日志进行定制化的格式化处理,Winston 提供了 winston.format.printf
工具来实现这一需求:
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf } = format;
// 自定义格式化函数,输出形如:[时间戳] [标签] 级别: 消息
const myFormat = printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
// 创建一个日志器实例,配置组合的格式化器和传输器
const logger = createLogger({
// 组合使用 label、timestamp 和自定义的 myFormat 格式化器
format: combine(
label({ label: 'right meow!' }), // 添加标签信息,默认为 'right meow!'
timestamp(), // 添加时间戳信息
myFormat // 使用自定义的格式化函数
),
transports: [new transports.Console()] // 输出日志到控制台
});
若想查看有哪些内置的日志格式可供使用,并了解更多关于如何创建自定义日志格式的信息,请参考 logform
模块
Combining formats(结合格式)
format.combine
方法允许我们将多个格式器串联起来形成一个复合格式器,由于format.combine不接受opts,为了方便起见,无需额外的选项参数,直接返回一个已创建好的组合格式实例。
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, prettyPrint } = format;
const logger = createLogger({
format: combine(
label({ label: 'right meow!' }),
timestamp(),
prettyPrint()
),
transports: [new transports.Console()]
})
logger.log({
level: 'info',
message: 'What time is the testing at?'
});
// 输出:
// { level: 'info',
// message: 'What time is the testing at?',
// label: 'right meow!',
// timestamp: '2017-09-30T03:57:26.875Z' }
String interpolation(字符串插值)
log方法使用util.format
提供字符串插值。必须使用format.splat()
启用它。
首先,通过 format.splat()
格式器开启字符串插值功能,然后结合 format.simple()
格式器将整个日志信息序列化成简单易读的形式。
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
format: format.combine(
format.splat(),
format.simple()
),
transports: [new transports.Console()]
});
// info: test message my string {}
logger.log('info', 'test message %s', 'my string');
// info: test message 123 {}
logger.log('info', 'test message %d', 123);
// info: test message first second {number: 123}
logger.log('info', 'test message %s, %s', 'first', 'second', { number: 123 });
Filtering info
Objects(过滤info对象)
如果希望在记录日志时完全过滤掉某个 info 对象,只需返回一个假值即可。
const { createLogger, format, transports } = require('winston');
// 定义了一个格式化器 ignorePrivate,
// 它会检查传入的日志信息(info)中是否存在 private 属性且为真。
// 若有,则通过返回 false 禁止该条日志信息被进一步处理和输出
const ignorePrivate = format((info, opts) => {
if (info.private) { return false; }
return info;
});
const logger = createLogger({
format: format.combine(
ignorePrivate(), // 使用 ignorePrivate() 格式化器过滤私密日志
format.json() // 使用 format.json() 将日志信息转换为 JSON 格式
),
transports: [new transports.Console()]
});
// 输出: {"level":"error","message":"Public error to share"}
logger.log({
level: 'error',
message: 'Public error to share'
});
// 该条日志将不会被记录或输出到任何地方,因为它在经过 ignorePrivate 格式化器时被过滤掉了
logger.log({
private: true,
level: 'error',
message: 'This is super secret - hide it.'
});
在 winston 中使用 format.combine
时,如果其中一个格式化器返回了假值(falsey value),那么组合中的后续格式化器将不再被执行,示例:
const { format } = require('winston');
const { combine, timestamp, label } = format;
const willNeverThrow = format.combine(
format(info => { return false })(), // 忽略所有日志信息
format(info => { throw new Error('Never reached') })() // 此处的错误永远不会抛出
);
Creating custom formats(创建自定义格式)
在winston中,格式(Formats)是原型对象(prototypal objects),它们定义了一个transform(info, opts)
方法。
info
:表示日志消息的对象。opts
: 特定于当前格式实例的设置。
它们并期望返回以下两种结果之一:
- 一个代表修改后日志信息的对象。如果偏好不可变性,不需要保持对象引用。目前Winston的所有内置格式都考虑info是可变的,但未来可能会考虑采用[immutablejs]来实现不可变性。
- 一个假值,表示调用者应当忽略info参数。(参见:Filtering info Objects)
要定义一个新的格式,只需要传递一个transform(info, opts)
函数给winston.format
,就能得到一个新的Format
。
创建一个名为volume
的格式,它可以根据传入的opts
参数调整日志消息的大小写:
const { format } = require('winston');
// volume函数根据传入的选项创建了两个不同的格式实例scream和whisper。
// 当调用.transform方法时,它们分别将日志消息转换为大写或小写
const volume = format((info, opts) => {
if (opts.yell) {
info.message = info.message.toUpperCase();
} else if (opts.whisper) {
info.message = info.message.toLowerCase();
}
return info;
});
// `volume`现在变成了一个返回格式实例的函数。
const scream = volume({ yell: true });
console.dir(scream.transform({
level: 'info',
message: `sorry for making you YELL in your head!`
}, scream.options));
// 输出:{ level: 'info', message: 'SORRY FOR MAKING YOU YELL IN YOUR HEAD!' }
// `volume`可以多次使用以创建不同的格式。
const whisper = volume({ whisper: true });
console.dir(whisper.transform({
level: 'info',
message: `WHY ARE THEY MAKING US YELL SO MUCH!`
}, whisper.options));
// 输出:{ level: 'info', message: 'why are they making us yell so much!' }
Logging Levels(日志级别)
在 winston 中,日志级别遵循 RFC5424 中规定的严重性排序原则:所有级别的严重性假设是按照从最重要到最不重要的顺序递增的数值排列。
每个级别都有一个特定的整数优先级。优先级越高,表明消息越重要,对应的整数优先级就越低。例如,RFC5424 明确规定了 syslog 级别的优先级是从 0 到 7(从高到低):
{
emerg: 0,
alert: 1,
crit: 2,
error: 3,
warning: 4,
notice: 5,
info: 6,
debug: 7
}
同样的,npm 日志级别也遵循从 0 到 6(从高到低)的优先级排序:
{
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
}
如果没有明确指定 winston 应该使用哪些日志级别,那么将默认使用上面列出的 npm 级别
Using Logging Levels(使用日志级别)
可以通过以下两种方式之一来设置日志消息的级别。可以将表示日志记录级别的字符串传递给 log ()
方法,也可以使用在每个 winston Logger
上定义的级别指定方法。
// 任意一个 logger 实例
logger.log('silly', "127.0.0.1 - there's no place like home"); // 最详细的日志级别
logger.log('debug', "127.0.0.1 - there's no place like home");
logger.log('verbose', "127.0.0.1 - there's no place like home");
logger.log('info', "127.0.0.1 - there's no place like home");
logger.log('warn', "127.0.0.1 - there's no place like home");
logger.log('error', "127.0.0.1 - there's no place like home");
// 也可以直接使用指定级别的方法
logger.info("127.0.0.1 - there's no place like home"); // info 级别日志
logger.warn("127.0.0.1 - there's no place like home"); // warn 级别日志
logger.error("127.0.0.1 - there's no place like home"); // error 级别日志
// 默认 logger
winston.log('info', "127.0.0.1 - there's no place like home"); // 使用默认 logger 时同样适用
winston.info("127.0.0.1 - there's no place like home"); // 也可以直接使用默认 logger 的指定级别方法
winston 许你在每个 transport 上定义一个 level
属性,该属性指定了该 transport 应该记录的最大级别消息。例如,如果我们使用 syslog 级别,可以将错误级别的消息仅输出到控制台,而将 info 级别及更低级别的所有消息(包括错误消息)输出到文件:
// 创建一个 logger 实例,配置 transport 的级别
const logger = winston.createLogger({
levels: winston.config.syslog.levels, // 使用 syslog 级别
transports: [
new winston.transports.Console({ level: 'error' }), // 控制台仅记录错误级别及以上的日志
new winston.transports.File({
filename: 'combined.log', // 所有 info 级别及以下的日志(包括错误)将被记录到文件 'combined.log' 中
level: 'info'
})
]
});
还可以动态改变一个 transport 的日志级别:
const transports = {
console: new winston.transports.Console({ level: 'warn' }), // 控制台仅记录警告及以上级别的日志
file: new winston.transports.File({ filename: 'combined.log', level: 'error' }) // 文件仅记录错误级别的日志
};
const logger = winston.createLogger({
transports: [
transports.console,
transports.file
]
});
logger.info('Will not be logged in either transport!'); // 此信息不会被任何 transport 记录,因为当前级别高于它们的配置级别
// 动态改变 transport 的日志级别
transports.console.level = 'info'; // 控制台现在记录 info 级别及以上的日志
transports.file.level = 'info'; // 文件现在记录 info 级别及以上的日志
logger.info('Will be logged in both transports!'); // 此信息将在控制台和文件中都被记录,因为它们现在的级别都允许记录 info 级别的日志
winston 支持自定义日志级别,并且默认使用 npm 风格的日志级别。但在创建 logger 时必须指定日志级别。
Using Custom Logging Levels(使用自定义日志级别)
除了 winston 内置支持的 npm、syslog 和 cli 等日志级别外,还可以选择定义自己的自定义日志级别:
const myCustomLevels = {
levels: {
foo: 0,
bar: 1,
baz: 2,
foobar: 3
},
colors: {
foo: 'blue',
bar: 'green',
baz: 'yellow',
foobar: 'red'
}
};
// 使用自定义日志级别创建一个 logger
const customLevelLogger = winston.createLogger({
levels: myCustomLevels.levels
});
// 使用自定义级别记录日志
customLevelLogger.foobar('some foobar level-ed message');
在这个数据结构中,虽然存在一些重复(每个级别既出现在 levels 对象中又出现在 colors 对象中),但它提供了一种简单的封装方式,如果不想使用颜色标记日志级别,这种方式很便捷。但如果确实希望在控制台上显示彩色日志,除了将自定义级别传递给 Logger 外,还需要让 winston 知道这些颜色映射:
// 告诉 Winston 使用自定义的颜色方案
winston.addColors(myCustomLevels.colors);
在控制台输出日志时,对应自定义级别的日志就会按照指定的颜色显示。例如,使用 customLevelLogger.foobar(...)
记录的日志将显示为红色。
此外,还可以更改背景颜色和字体样式:
baz: 'italic yellow',
foobar: 'bold red cyanBG'
以下是 Winston 中可用的选项:
字体样式:bold(粗体)、dim(暗淡)、italic(斜体)、underline(下划线)、inverse(反转)、hidden(隐藏)、strikethrough(删除线)。
字体前景颜色:black(黑色)、red(红色)、green(绿色)、yellow(黄色)、blue(蓝色)、magenta(品红)、cyan(青色)、white(白色)、gray 或 grey(灰色)。
背景颜色:blackBG(黑底)、redBG(红底)、greenBG(绿底)、yellowBG(黄底)、blueBG(蓝底)、magentaBG(品红底)、cyanBG(青底)、whiteBG(白底)。
Colorizing Standard logging levels(对标准日志级别进行颜色编码)
要为标准日志级别添加颜色编码,可以这样操作:
winston.format.combine(
winston.format.colorize(), // 颜色编码格式器
winston.format.simple() // 其他你想要使用的简单格式器
);
这里要注意,winston.format.colorize()
格式器必须放在任何你想对其内容进行颜色编码的其他格式器之前。
Colorizing full log line when json formatting logs(对 JSON 格式化日志的完整行进行颜色编码)
若要在使用 JSON 格式化日志时为整行日志添加颜色编码,可以使用如下代码:
winston.format.combine(
winston.format.json(), // JSON 格式化器
winston.format.colorize({ all: true }) // 颜色编码格式器,这里的 { all: true } 表示对整行日志进行颜色编码
);
Transports(传输器)
winston 包含了几种核心的传输器(Transports),它们充分利用了 Node.js 核心提供的内置网络和文件 I/O 功能。另外,社区成员还编写了许多额外的传输器。
Multiple transports of the same type(同一类型的多个传输器)
在构建传输器时,可以使用同一类型(如 wiston.transports.File
)创建多个传输器实例。
const logger = winston.createLogger({
transports: [
// 创建第一个 File 类型传输器,用于记录所有 info 级别及以上的日志到 'combined.log' 文件
new winston.transports.File({
filename: 'combined.log',
level: 'info'
}),
// 创建第二个 File 类型传输器,仅记录 error 级别及以上的日志到 'errors.log' 文件
new winston.transports.File({
filename: 'errors.log',
level: 'error'
})
]
});
如果想要移除其中一个已添加的传输器,可以通过查找并使用该传输器实例本身来实现:
// 寻找名为 'combined.log' 的传输器实例
const combinedLogs = logger.transports.find(transport => {
return transport.filename === 'combined.log';
});
// 移除找到的 'combined.log' 传输器
logger.remove(combinedLogs);
Adding Custom Transports(添加自定义传输器)
在 winston 中添加自定义传输器非常简单,您只需接受所需的任何选项,实现一个 log()
方法,并将其与 winston 结合使用。
// 引入 winston-transport 库,它是 Winston 中用于创建自定义传输器的基础类
const Transport = require('winston-transport');
const util = require('util');
// 定义一个继承自 winston-transport 的新类,以便能够利用基础功能和异常处理功能
module.exports = class YourCustomTransport extends Transport {
constructor(opts) {
super(opts);
//
// 在这里消费任何自定义选项。
// 在构造函数中初始化传输器,接收并处理自定义选项。
// 例如,您可以在这里处理数据库连接信息、API 身份验证信息
// (如 Loggly、Papertrail、Logentries 等)
//
}
// 接收日志信息对象(info)和回调函数(callback)
log(info, callback) {
// 为了确保异步操作完成后再触发 'logged' 事件,我们可以使用 setImmediate 函数
setImmediate(() => {
this.emit('logged', info);
});
// 在这里实现将日志信息写入远程服务的操作
// 当写入操作完成后,调用回调函数通知 Winston
callback();
}
};
Common Transport options(常用传输器选项)
由于所有传输器都是从 winston-transport
派生的,因此每个传输器都可以单独配置格式和级别。
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'error.log',
level: 'error',
format: winston.format.json()
}),
// 只发送警告级别及以上的日志到 HTTP 服务;
new winston.transports.Http({
level: 'warn',
format: winston.format.json()
}),
// 使用组合格式 (winston.format.combine()),
// 其中包括颜色编码 (winston.format.colorize())
// 和简单格式 (winston.format.simple())
new winston.transports.Console({
level: 'info',
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
Exceptions(异常)
Handling Uncaught Exceptions with winston(使用winston处理未捕获异常)
在使用 winston 时,可以捕获并记录进程中的未捕获异常(uncaughtException)事件。无论是创建 logger
实例时还是应用程序生命周期的后期,都可以启用这种行为:
const { createLogger, transports } = require('winston');
// 在创建 logger 实例时启用异常处理
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' }) // 记录常规日志
],
exceptionHandlers: [
new transports.File({ filename: 'exceptions.log' }) // 记录未捕获异常
]
});
// 在稍后阶段动态启用异常处理
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' })
]
});
// 通过调用 .exceptions.handle 并传入一个传输器来处理异常
logger.exceptions.handle(
new transports.File({ filename: 'exceptions.log' })
);
在 winston 的默认 logger
上启用未捕获异常的处理功能,有两种方法:
// 为默认日志器添加独立的异常处理器:
// 可以直接调用 .exceptions.handle() 方法,并传入一个传输器实例,专门用来处理未捕获异常。
// 添加一个独立的异常日志传输器,记录到 'path/to/exceptions.log' 文件
winston.exceptions.handle(
new winston.transports.File({ filename: 'path/to/exceptions.log' })
);
// 添加传输器时设置 handleExceptions 为 true: 你也可以在向 Winston 添加传输器时,
// 设置 handleExceptions 选项为 true,这样该传输器不仅可以记录普通日志,
// 还会自动处理未捕获的异常
// 添加一个传输器,同时设置 handleExceptions 为 true,使其既能记录常规日志也能处理异常
winston.add(new winston.transports.File({
filename: 'path/to/combined.log',
handleExceptions: true
}));
第一种方式可以让你更细致地控制异常日志的存储位置,而第二种方式则简化了配置,使异常处理与常规日志记录集成在一起。
To Exit or Not to Exit(退出还是不退出)
winston 在处理未捕获异常时,默认情况下会退出程序,但如果这不是我们期望的行为,可以通过设置 exitOnError
为 false
来改变这一行为。
// 创建一个 logger 实例,设置 exitOnError 为 false,遇到未捕获异常时不退出程序
const logger = winston.createLogger({ exitOnError: false });
// 或者,创建后也可以通过修改属性值达到同样的效果
logger.exitOnError = false;
对于自定义的日志器实例,可以为 exceptionHandlers
属性传入单独的传输器来处理异常,或者在任一传输器上设置 handleExceptions
为 true
示例1
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'path/to/combined.log' }) // 用于记录常规日志
],
exceptionHandlers: [
new winston.transports.File({ filename: 'path/to/exceptions.log' }) // 专门处理异常并记录到单独的文件
]
});
示例2
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
handleExceptions: true // 控制台传输器将同时处理异常并打印出来,同时设置 exitOnError: false 防止程序退出
})
],
exitOnError: false
});
此外,exitOnError
选项也可以是一个函数,根据错误类型决定是否退出程序。下面的示例展示了如何忽略 EPIPE 错误类型的退出:
function ignoreEpipe(err) {
return err.code !== 'EPIPE';
}
// 创建一个 logger 实例,仅在非 EPIPE 错误时退出程序
const logger = winston.createLogger({ exitOnError: ignoreEpipe });
// 或者,创建后也可以这样设置
logger.exitOnError = ignoreEpipe;
这样,只有当错误不是 EPIPE 类型时,程序才会在发生未捕获异常时退出。
Rejections(拒绝)
使用 winston 来捕获并记录 Node.js 进程中的未处理Promise拒绝(unhandledRejection)事件。类似于未捕获异常的处理,您可以在创建日志器实例时或应用程序生命周期的后期启用此功能:
const { createLogger, transports } = require('winston');
// 创建日志器实例时启用拒绝处理:
// 创建一个日志器实例,同时配置拒绝处理器(rejectionHandlers),
// 当发生未处理的Promise拒绝时,相关信息将被记录到指定的文件中
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' }) // 记录常规日志
],
rejectionHandlers: [
new transports.File({ filename: 'rejections.log' }) // 记录未处理的Promise拒绝
]
});
// 稍后启用拒绝处理:
// 如果在创建日志器实例时未配置拒绝处理器,
// 可以在任何时候通过添加传输器或调用 .rejections.handle() 方法来启用。
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' })
]
});
// 通过调用 .rejections.handle() 并传入一个传输器来处理未处理的Promise拒绝
logger.rejections.handle(
new transports.File({ filename: 'rejections.log' })
);
对于默认日志器,同样可以轻松启用此功能:
// 添加独立的拒绝日志传输器
// 为默认日志器添加一个专门处理未处理Promise拒绝的传输器
winston.rejections.handle(
new winston.transports.File({ filename: 'path/to/rejections.log' })
);
// 在添加传输器时设置 handleRejections 为 true
// 添加一个传输器,并设置 handleRejections 为 true,使其能处理未处理的Promise拒绝
winston.add(new winston.transports.File({
filename: 'path/to/combined.log',
handleRejections: true
}));
Profiling(性能分析)
除了记录消息和元数据之外,winston 还为任何日志器提供了一个简单的性能分析机制:
// 开始名为 'test' 的性能分析计时
logger.profile('test');
setTimeout(function () {
// 停止名为 'test' 的性能分析计时。此时将执行日志记录:
// '17 Jan 21:00:00 - info: test duration=1000ms'
//
logger.profile('test');
}, 1000);
此外,还可以启动一个定时器,并保留一个引用,然后在其上调用 .done()
方法
// 返回一个对应于特定计时的对象。当调用 done 方法时,
// 计时器将结束并记录持续时间。例如:
const profiler = logger.startTimer();
setTimeout(function () {
profiler.done({ message: 'Logging message' });
}, 1000);
默认情况下,所有的性能分析日志消息都被设置为 ‘info’ 级别,而且消息和元数据都是可选的。对于单个性能分析消息,可以通过提供一个包含 level 属性的元数据对象来覆盖默认的日志级别:
logger.profile('test', { level: 'debug' });
这意味着当调用 logger.profile('test', { level: 'debug' })
时,性能分析结束时的消息将以 ‘debug’ 级别记录,而不是默认的 ‘info’ 级别。这样,我们可以根据需要选择不同的日志级别来记录性能分析结果
Querying Logs(查询日志)
目前 winston 中支持查询日志的运输器(transports)包括 File、Couchdb、Redis、Loggly、Nssocket 和 Http
options
对象包含了用于筛选日志的各种条件,例如:
from
: 查询起始时间,此处表示从昨天到现在的时间范围。until
: 查询终止时间,此处表示当前时间。limit
: 返回结果的数量限制,此处限制为返回最近的10条日志。start
: 分页参数,此处设为0,表示从第一条日志开始。order
: 结果排序方式,此处为’desc’,表示按时间降序排列。fields
: 需要返回的字段列表,此处只返回 ‘message’ 字段。
// 查询在过去24小时内(即昨天到今天之间)的日志
const options = {
from: new Date() - (24 * 60 * 60 * 1000), // 昨天的时间戳
until: new Date(), // 当前时间戳
limit: 10,
start: 0,
order: 'desc',
fields: ['message']
};
// 使用上述选项查询日志
logger.query(options, function (err, results) {
if (err) {
// 处理错误
throw err;
}
// 输出查询结果
console.log(results);
});
Streaming Logs(流日志)
stream
方法可以从所选的传输器(transport)中流式读取日志。
// 从末尾开始读取日志
winston.stream({ start: -1 }).on('log', function(log) {
console.log(log);
});
// start: -1 参数指示从日志的末尾开始读取,
// 即最新的一条日志开始。stream() 方法会返回一个可读流,
// 当监听这个流的 'log' 事件时,每当从日志源读取到一条新的日志记录时,就会触发该事件,
// 回调函数将接收到这条日志记录作为参数
Further Reading(阅读延申)
Using the Default Logger(使用 Winston 的默认日志器)
在 winston 中,可以直接通过 winston
模块访问默认日志器。任何可以应用于日志器实例的方法也都可以直接应用于默认日志器
const winston = require('winston');
// 使用默认日志器记录日志
winston.log('info', 'Hello distributed log files!');
winston.info('Hello again distributed logs');
// 设置默认日志器的日志级别
winston.level = 'debug';
winston.log('debug', 'Now my debug messages are written to console!');
默认情况下,winston 的默认日志器并未设置任何传输器(transports)。如果要添加或移除传输器,需要通过 add()
和 remove()
方法:
// 创建两个传输器实例
const files = new winston.transports.File({ filename: 'combined.log' });
const consoleTransport = new winston.transports.Console();
// 将传输器添加到默认日志器
winston.add(consoleTransport);
winston.add(files);
// 移除某个传输器
winston.remove(consoleTransport);
也可以通过一次性调用 configure()
方法来配置默认日志器:
winston.configure({
transports: [
new winston.transports.File({ filename: 'somefile.log' })
]
});
若想了解更多关于 winston 支持的各个传输器的详细文档,请参阅 winston 的 Transports 文档。
Awaiting logs to be written in winston
(在winston等待日志被完全写入)
在 winston 中,有时我们需要在进程退出前等待日志完全写入。每个 winston.Logger
实例也是一个 [Node.js 流],在流结束后,当所有日志已经刷新至所有传输器后,将会触发一个 'finish'
事件。
const transport = new winston.transports.Console();
const logger = winston.createLogger({
transports: [transport]
});
// 监听 'finish' 事件,当所有日志都已写入后触发
logger.on('finish', function (info) {
// 所有 `info` 级别的日志信息现已记录完毕
});
// 记录一条日志信息
logger.info('CHILL WINSTON!', { seriously: true });
// 结束日志流,等待所有日志写入后触发 'finish' 事件
logger.end();
此外,值得注意的是,如果在日志器本身发生错误,日志器还会触发一个 ‘error’ 事件,你需要对此进行处理,或者如果不希望出现未捕获异常,则需进行处理或抑制:
// 处理源自日志器本身的错误
logger.on('error', function (err) {
// 对错误进行处理
});
Working with multiple Loggers in winston(在winston中使用多个记录器)
在大型复杂的应用程序中,通常需要拥有多个具有不同设置的日志器实例,每个日志器负责不同的功能区域(或类别)。Winston 通过两种方式提供了这一功能:通过 winston.loggers
和 winston.Container
实例。实际上,winston.loggers
就是一个预定义的 winston.Container
实例。
const winston = require('winston');
const { format } = winston;
const { combine, label, json } = format;
// 为名为 'category1' 的功能区域配置日志器,
// 设定其格式为标签(label)加上JSON格式,
// 同时指定了两个传输器:一个将所有级别(level: 'silly')的日志信息输出到控制台,
// 另一个将日志信息写入到 somefile.log 文件
winston.loggers.add('category1', {
format: combine(label({ label: 'category one' }), json()),
transports: [
new winston.transports.Console({ level: 'silly' }),
new winston.transports.File({ filename: 'somefile.log' })
]
});
// 接着为名为 'category2' 的另一个功能区域配置日志器,
// 同样设置了标签和JSON格式,但其传输器只有一个,
// 即通过HTTP协议将日志信息发送到 localhost 的8080端口
winston.loggers.add('category2', {
format: combine(label({ label: 'category two' }), json()),
transports: [
new winston.transports.Http({ host: 'localhost', port: 8080 })
]
});
现在你的日志器已经设置好了,在你的应用程序中的任何文件里,你都可以通过 require(‘winston’) 来访问这些预先配置好的日志器:
const winston = require('winston');
// 获取预先配置好的日志器
const category1 = winston.loggers.get('category1');
const category2 = winston.loggers.get('category2');
// 使用预先配置的日志器记录日志
category1.info('logging to file and console transports');
category2.info('logging to http transport');
如果你更倾向于自己管理 Container,那么可以直接实例化一个 winston.Container
对象:
const winston = require('winston');
const { format } = winston;
const { combine, label, json } = format;
const container = new winston.Container();
// 添加名为 'category1' 的日志器配置
container.add('category1', {
format: combine(label({ label: 'category one' }), json()),
transports: [
new winston.transports.Console({ level: 'silly' }),
new winston.transports.File({ filename: 'somefile.log' })
]
});
// 通过容器获取并使用预先配置的日志器
const category1 = container.get('category1');
category1.info('logging to file and console transports');
Installation(安装)
npm install winston
yarn add winston
Run Tests(运行测试)
所有的 winston 测试都是用 mocha、 nyc 编写的,并且可以用 npm 运行。
npm test