JimQing's Blog


View JimQing's projecton GitHub

winston构建koa日志收集

20 Oct 2019

文章简概

​ 学习和熟悉winston.js,并通过集成为中间件,在Koa中构建为日志上报模块。

winston

winston是一个 logger js,主要用于打印日志,它提供了丰富的API,足以满足我们的需求。通过对官方文档的一些研究,总结了一下具体的使用指南,并尝试集成为中间件用于Koa.js框架中进行使用。

Installation

npm install winston

Usage

winston可以通过createLogger简单的实现一个日志API。

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    defaultMeta: {service: 'user-service'},
    transports: [
    //
    // - Write to all logs with level `info` and below to `combined.log`
    // - Write all logs error (and below) to `error.log`.
    //
        new winston.transports.File({ filename: 'error.log', level: 'error' }),
        new winston.transports.File({ filename: 'combined.log' })
    ]
});
//
// If we're not in production then log to the `console` with the format:
// `${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’)生成logger(如上述代码)进行日志记录,通过判断运行环境,在非生产mode下使用不同的格式。

Logging

winston有不同的类型的日志,分为error,warn,info,verbose,debug,silly,通过一下方法赋予这些类型各自的权重。

const levels = {
    error: 0,
    warn: 1,
    info: 2,
    verbose: 3,
    debug: 4,
    silly: 5
};

levels 用于对当前的日志进行排序,可以的以上5种类型,levels 越低则越严重。

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’ Log only if info.level less than or equal to this level
levels winston.config.npm.levels Levels (and colors) representing log priorities
format winston.format.json Formatting for info messages (see: Formats)
transports [] (No transports) Set of logging targets for info messages
exitOnError true If false, handled exceptions will not cause process.exit
silent false If true, all logs are suppressed
//// Logging//
// 打印的两种方式,当然第二种更加简洁
logger.log({
    level: 'info',
    message: 'Hello distributed log files!'
});
logger.info('Hello again distributed logs');

在一个logger中你可以通过add or remove 去添加或者删除 transports:

const files = new winston.transports.File({ filename: 'combined.log' });
const console = new winston.transports.Console();
logger
.clear() // Remove all transports
.add(console) // Add console transport
.add(files) // Add file transport
.remove(console); // Remove console transport

通过如下方式可重新配置 logger , 包括日志等等

const logger = winston.createLogger({
    level: 'info',
    transports: [
        new winston.transports.Console(),
        new winston.transports.File({ filename: 'combined.log' })
    ]
});

//// Replaces the previous transports with those in the// new configuration wholesale.//
const DailyRotateFile = require('winston-daily-rotate-file');
logger.configure({
    level: 'verbose',
    transports: [
        new DailyRotateFile(opts)
    ]
});

String interpolation

log 支持以下方式输出日志,当然可以简化为 logger.info 而简化掉 第一个参数 ‘info’

logger.log('info', 'test message %s', 'my string');
// info: test message my string
logger.log('info', 'test message %d', 123);
// info: test message 123
logger.log('info', 'test message %j', {number: 123}, {});
// info: test message {"number":123}// meta = {}
logger.log('info', 'test message %s, %s', 'first', 'second', {number: 123});
// info: test message first, second// meta = {number: 123}
logger.log('info', 'test message', 'first', 'second', {number: 123});
// info: test message first second// meta = {number: 123}
logger.log('info', 'test message %s, %s', 'first', 'second', {number: 123}, function(){});
// info: test message first, second// meta = {number: 123}// callback = function(){}
logger.log('info', 'test message', 'first', 'second', {number: 123}, function(){});
// info: test message first second// meta = {number: 123}// callback = function(){}

demo举例

// logger.js
const {
    createLogger,
    format,
    transports
} = require('winston');
const {
    combine,
    timestamp,
    label,
    printf
} = format;
// 对logger进行基础配置
const loggerConfig = {
    levels: {
        error: 0,
        warn: 1,
        info: 2,
        verbose: 3,
        debug: 4,
        silly: 5
    },
    color: {
        error: 'red',
        debug: 'blue',
        warn: 'yellow',
        data: 'grey',
        log: 'green',
        verbose: 'cyan',
        silly: 'magenta'
    },
     // 自定义输出的格式
     // 参数 info 指的是在 createLogger 函数的 变量中的 format 变量下的 combine所包含的变量
     // 如: info.label 事实上拿到的是下方的 labelName
    logFormat: printf(info => {
        return `[${info.date}] [${info.label}] [${info.level}] - ${info.message}`;
    }),
     // 日志文件绝对路径
    logFileName: logdir + '/' + date + '.log'
};
const logger = function (labelName) {
    const _labelName = labelName && /^[\S]+$/.test(labelName) ? labelName : '默认';
    return createLogger({
        levels: loggerConfig.levels,
        format: combine(
            label({
                label: _labelName
            }),
             // 时间戳
            timestamp(),
             // 设置颜色
            format.colorize({
                all: true
            }),
             // 开启插值功能 
            format.splat(),
             // 输出格式的配置
            loggerConfig.logFormat
        ), 
        // 日志的相关配置
        transports: [
             // 控制台输出日志
            new transports.Console(),
             // 绑定日志文件
            new transports.File({
                level: 'error',
                filename: loggerConfig.logFileName,
                colorize: false
            })
        ]
    });
};

module.exports = logger; // 暴露logger对象
// app.js
// 如何使用
const logger = require('./logger')('错误收集');

logger.error('错误: %s', err.stack);
//   扩展,制作为Koa中间件,配合使用app.use, 需异步!
//   利用try-catch 抓错误,并使用 logger 处理
//   由于Koa的洋葱模型,在走完 ' start ' 之后,next走向下个中间件,在下方的报错会向上回溯
//   从而被try-catch 抓到,如果说放置在业务代码之后,事实上是抓不到错误的
app.use(
     // 中间件部分,务必写在页面业务代码之前
    async (ctx, next) => {
        try {
            console.log('start');
            await next();
        } catch (err) {
            logger.error('错误: %s', err.stack);
        }
    }
);

//   Koa中的请求会依次走过被 app.use 的中间件,此处可以对相应的请求进行上报日志
//   获取行为 如 duration '请求耗时' 等等数据

app.use(
    async (ctx, next) => {
        const start = Date.now();
        await next();
        const duration = Date.now() - start;
        logger.info('[%s] %s %s (%s ms)', ctx.method, ctx.url, ctx.ip, duration);
    }
);

参考文章

Winston Github https://github.com/winstonjs/winston

Winston docs 文档地址: https://github.com/winstonjs/winston/tree/2.x

至此,希望此博客能对你有所帮助。