winston是一个简单、通用的日志库,支持多种传输。传输是一个存储设备。每个winston日志可以有多个不同日志级别的传输.
winston目的是解耦日志处理,使得更自由及易扩展。关注点在日志格式、日志级别上更灵活,保证了这些api与传输日志解耦。
1、日志
日志级别有
const levels = {
error:0,
warn:1,
info:2,
http:3,
verbose:4,
debug:5,
silly:6
};
1.1 创建日志
const logger = winston.createLogger({
transports:[
new winston.transports.Console(),
new winston.transports.File({filename:'combined.log'})
]
});
创建日志时支持下面的参数
名称 | 默认值 | 说明 |
level | 'info' | 当info.level小于或者等于此level时才输出日志 |
levels | winston.config.npm.levels | 表示日志优先级的级别 |
format | winston.format.json | 格式化info消息 |
transports | [] | 设置info消息的日志输出位置 |
exitOnError | true | 如果为false,处理异常不会导致process.exit |
silent | false | 如果为true,所有日志被抑制 |
提供给createLogger的日志级别会定义为返回的logger的方法。
logger.log({
level:'info',
message:'Hello distributed log files!'
});
logger.info('Hello distributed log files!');
一旦通过winston.createLogger创建logger后,可以添加或者删除transports
const files = new winston.transports.File({filename:'combined.log'});
const console = new winston.transports.Console();
logger
.clear()
.add(console)
.add(files)
.remove(console);
也可以通过configure方法重新配置winston.Logger实例
const logger = winston.createLogger({
level:'info',
transports:[
new winston.transports.Console(),
new winston.transports.File({filename:'combined.log'})
]
});
const DailyRotateFile = require('winston-daily-rotate-file');
logger.configure({
level:'verbose',
transports:[
new DailyRotateFile(opts)
]
});
通过已有的日志传递元数据覆盖创建子日志
const logger = winston.createLogger({
transports:[
new winston.transports.Console()
]
});
const childLogger = logger.child({requestId:'451'});
1.2 Streams, objectMode和Info对象
在winston中,Logger和Transport实例被当作objectMode流,接受info对象。
info参数表示一个日志消息,对象本身是可变的,每个info至少要包含level和message属性。
const info = {
level:'info',
message:'Hey! Log something?'
};
除了level和message之外的属性认为是meta元数据
const {level, message, ...meta} = info;
logform中的几种格式添加了另外的属性
属性 | 由哪个格式添加 | 说明 |
splat | splat() | %d%s样式消息的字符串插值splat |
timestamp | timestamp() | 接受到消息的时间戳 |
label | label() | 自定义关联消息的标签 |
ms | ms() | 自上一个日志消息以来的毫秒数 |
你可以任意添加属性,内部状态通过Symbol属性来获取。
- Symbol.for('level'):相当于level属性,不可修改
- Symbole.for('message'):完整的由最终格式的消息
- json
- logstash
- printf
- prettyPrint
- simple
- Symbol.for('splat'):另外的字符串插值参数。
Symbols存储在triple-beam库中。
2、格式
winston中的格式可以通过winston.format访问。在logform中实现,一个单独的源于winston的模块。这允许在编写自己的传输时具有灵活性,以防您希望在传输中包含默认格式。在node当前版本中,模板字符串是性能比较好的,在作用户端格式化时是推荐的方式。如果你想定制你的日志格式,使用winston.format.printf
const {createLogger, format, transports} = require('winston');
const {combine, timestamp, label, printf} = format;
const myFormat = printf(({level, mesage, label, timestamp}) => {
return `${timestamp}[${label}]${level}:${message}`;
});
const logger = createLogger({
format:combine(
lable({label:'right meow!'}),
timestamp(),
myFormat
),
transports:[new transports.Console()]
});
2.1 合并格式
多个格式通过format.combine合并成一个格式。由于format.combine不接受选项,因此为了方便起见,它会返回预先创建的组合格式实例。
const {createLogger, format, transports} = require('winston');
const {combine, timestamp, label, prettyPrint} = format;
const logger = createLogger({
format:combine(
lable({label:'right meow!'}),
timestamp(),
prettyPrint()
),
transports:[new transports.Console()]
});
logger.log({
level:'info',
message:'What time is the testing at?'
});
2.2 字符串插值
log方法使用util.formt提供字符串插值。必须使用format.splat()来开启
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 });
2.3 过滤info对象
如果希望过滤掉info对象,只需要返回false值
const { createLogger, format, transports } = require('winston');
// Ignore log messages if they have { private: true }
const ignorePrivate = format((info, opts) => {
if (info.private) { return false; }
return info;
});
const logger = createLogger({
format: format.combine(
ignorePrivate(),
format.json()
),
transports: [new transports.Console()]
});
// Outputs: {"level":"error","message":"Public error to share"}
logger.log({
level: 'error',
message: 'Public error to share'
});
// Messages with { private: true } will not be written when logged.
logger.log({
private: true,
level: 'error',
message: 'This is super secret - hide it.'
});
2.4 自定义格式
格式是定义单个方法的原型对象,方法为transform(info,opts),返回可变的info
- info:表示日志消息的对象
- opts:指定当前格式实例的设置。
返回值有两种情况:
- info 对象,表示可修改的info参数。
- false值,表示info参数会被当前调用者忽略
winston.format设计的足够简单,定义一个新的格式,只需要传递一个transform(info,opts)函数给format得到一个新的格式。
const { format } = require('winston');
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` is now a function that returns instances of the format.
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` can be used multiple times to create different formats.
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!'
// }
3、日志级别
winston中的日志级别遵循rfc5424。每个级别给定一个整数权值。权值越高,消息越重要。
如果没有显示指定 日志级别,使用的是npm级别。
3.1 使用日志级别
设置日志消息级别可以使用两种方式,一种是传递表示日志级别的字符串给log方式,或者使用指明级别的方法
//
// Any logger instance
//
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");
logger.warn("127.0.0.1 - there's no place like home");
logger.error("127.0.0.1 - there's no place like home");
//
// Default logger
//
winston.log('info', "127.0.0.1 - there's no place like home");
winston.info("127.0.0.1 - there's no place like home");
winston允许你为每个传输定义level属性来指明传输日志的最高消息级别。例如,使用syslog级别日志,仅仅将error级别消息输出到控制台,info级别及以下的输出到文件。
const logger = winston.createLogger({
levels: winston.config.syslog.levels,
transports: [
new winston.transports.Console({ level: 'error' }),
new winston.transports.File({
filename: 'combined.log',
level: 'info'
})
]
});
也可以动态修改传输的日志级别
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!');
transports.console.level = 'info';
transports.file.level = 'info';
logger.info('Will be logged in both transports!');
3.2 自定义日志级别
除了预先定义的npm,syslgo,cli级别外,也可以自定义。
const myCustomLevels = {
levels: {
foo: 0,
bar: 1,
baz: 2,
foobar: 3
},
colors: {
foo: 'blue',
bar: 'green',
baz: 'yellow',
foobar: 'red'
}
};
const customLevelLogger = winston.createLogger({
levels: myCustomLevels.levels
});
customLevelLogger.foobar('some foobar level-ed message');
如果希望有颜色,除了传递levels到日志外,还需要添加颜色
winston.addColors(myCustomLevels.colors);
通过上面方法使得日志可以使用 colorize格式。
另外,也可以修改背景颜色、字体样式。
baz: 'italic yellow',
foobar: 'bold red cyanBG'
可用的选项如下
- 字体样式: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
给标准的日志级别添加颜色
winston.format.combine(
winston.format.colorize(),
winston.format.json()
);
4、传输
4.1 同一类型的多个传输
有可能对同一个类型使用多个传输
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'combined.log',
level: 'info'
}),
new winston.transports.File({
filename: 'errors.log',
level: 'error'
})
]
});
如果后面想删除其中的一个传输,可以使用下面方法
const combinedLogs = logger.transports.find(transport => {
return transport.filename === 'combined.log'
});
logger.remove(combinedLogs);
4.2 添加自定义传输
需要做的是接受你需要的选项,实现log方法。
const Transport = require('winston-transport');
const util = require('util');
//
// Inherit from `winston-transport` so you can take advantage
// of the base functionality and `.exceptions.handle()`.
//
module.exports = class YourCustomTransport extends Transport {
constructor(opts) {
super(opts);
//
// Consume any custom options here. e.g.:
// - Connection information for databases
// - Authentication information for APIs (e.g. loggly, papertrail,
// logentries, etc.).
//
}
log(info, callback) {
setImmediate(() => {
this.emit('logged', info);
});
// Perform the writing to the remote service
callback();
}
};
4.3 传输选项
每个传输继承自winston-transport,可以在每个传输设置自定义格式 及日志级别
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'error.log',
level: 'error',
format: winston.format.json()
}),
new transports.Http({
level: 'warn',
format: winston.format.json()
}),
new transports.Console({
level: 'info',
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
})
]
});
5、异常
5.1 处理未捕获异常
winston可以捕获和记录异常。通过日志实例,可以在创建时或者以后开启这个行为
const { createLogger, transports } = require('winston');
// Enable exception handling when you create your logger.
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' })
],
exceptionHandlers: [
new transports.File({ filename: 'exceptions.log' })
]
});
// Or enable it later on by adding a transport or using `.exceptions.handle`
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' })
]
});
// Call exceptions.handle with a transport to handle exceptions
logger.exceptions.handle(
new transports.File({ filename: 'exceptions.log' })
);
如果想在默认日志上使用这个特征,简单调用.exceptions.handle()推荐传输实例
//
// You can add a separate exception logger by passing it to `.exceptions.handle`
//
winston.exceptions.handle(
new winston.transports.File({ filename: 'path/to/exceptions.log' })
);
//
// Alternatively you can set `handleExceptions` to true when adding transports
// to winston.
//
winston.add(new winston.transports.File({
filename: 'path/to/combined.log',
handleExceptions: true
}));
5.2 退出还是不退出
默认情况下,winston在记录一个异常时会退出。可以通过设置exitOnError=false来不退出。
const logger = winston.createLogger({ exitOnError: false });
//
// or, like this:
//
logger.exitOnError = false;
当使用自定义日志实例时,你可以传递exceptionHandlers属性给单独的传输或者在任意传输上设置handleExceptions
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'path/to/combined.log' })
],
exceptionHandlers: [
new winston.transports.File({ filename: 'path/to/exceptions.log' })
]
});
或者
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
handleExceptions: true
})
],
exitOnError: false
});
exitOnError选项也有可能是一个函数来防止在特定的错误类型上退出
function ignoreEpipe(err) {
return err.code !== 'EPIPE';
}
const logger = winston.createLogger({ exitOnError: ignoreEpipe });
//
// or, like this:
//
logger.exitOnError = ignoreEpipe;
6、拒绝
6.1 处理未经批准的拒绝承诺
可能捕获uncaughtRejection事件。使用日志实例可以在创建时或者以后开启此行为
const { createLogger, transports } = require('winston');
// Enable rejection handling when you create your logger.
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' })
],
rejectionHandlers: [
new transports.File({ filename: 'rejections.log' })
]
});
// Or enable it later on by adding a transport or using `.rejections.handle`
const logger = createLogger({
transports: [
new transports.File({ filename: 'combined.log' })
]
});
// Call rejections.handle with a transport to handle rejections
logger.rejections.handle(
new transports.File({ filename: 'rejections.log' })
);
如果想使用默认日志的特征,调用.rejections.handle()携带传输实例
//
// You can add a separate rejection logger by passing it to `.rejections.handle`
//
winston.rejections.handle(
new winston.transports.File({ filename: 'path/to/rejections.log' })
);
//
// Alternatively you can set `handleRejections` to true when adding transports
// to winston.
//
winston.add(new winston.transports.File({
filename: 'path/to/combined.log',
handleRejections: true
}));