为什么 Node 要用 Winston 打印日志?
  hINapgLEIiPz 2023年11月19日 33 0

Node 里怎么打印日志呢?

有同学说,不也是用 console.log 么。

不,服务端打印日志一般不会用 console.log。

因为 console.log 打印完就没了,而服务端的日志经常要用来排查问题,需要搜索、分析日志内容,所以需要写入文件或者数据库里。

而且打印的日志需要分级别,比如有的是错误的日志,有的只是普通日志,需要能够过滤不同级别的日志。

此外,打印的日志需要带上时间戳,所在的代码位置等信息。

这些都是 console.log 没有的功能。

所以我们一般都会用专门的日志框架来做,比如 winston。

它是 Node 最流行的日志框架,npm 官网上可以看到每周千万级的下载量:

为什么 Node 要用 Winston 打印日志?_Node.js

那 winston 都有什么功能?怎么用呢?

我们试试看:

mkdir winston-test
cd winston-test
npm init -y

为什么 Node 要用 Winston 打印日志?_json_02

先创建个项目。

安装 winston:

npm install --save winston

然后写下 index.js

import winston from 'winston';

const logger = winston.createLogger({
    level: 'debug',
    format: winston.format.simple(),
    transports: [
        new winston.transports.Console(),
        new winston.transports.File({ 
            dirname: 'log', filename: 'test.log' 
        }),
    ]
});

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

用 createLogger 创建了 logger 实例,指定 level、format、tranports。

level:打印的日志级别

format:日志格式

transports:日志的传输方式

我们指定了 Console 和 File 两种传输方式。

在 package.json 里指定 type 为 module,也就是所有代码都是 es module 的:

为什么 Node 要用 Winston 打印日志?_json_03

这样代码里就可以直接用 import、export 这些语法了。

用 node 跑一下:

node index.js

为什么 Node 要用 Winston 打印日志?_Node.js_04

可以看到控制台和文件里都有了打印的日志。

再跑一遍:

node index.js

会在后面追加:

为什么 Node 要用 Winston 打印日志?_javascript_05

那么问题来了,如果所有日志都写在一个文件里,那这个文件最终会不会特别大?

不用担心,winston 支持按照大小自动分割文件:

为什么 Node 要用 Winston 打印日志?_前端_06

我们指定 maxsize 为 1024 字节,也就是 1kb。

然后再跑几次:

为什么 Node 要用 Winston 打印日志?_前端_07

大概跑了 10 次左右,出现了第二个文件:

为什么 Node 要用 Winston 打印日志?_json_08

而这时第一个日志文件刚好是 1kb:

为什么 Node 要用 Winston 打印日志?_javascript_09

这就是根据大小自动分割日志文件的功能。

有同学说,一般日志都是按照日期自动分割的,比如 2023-10-28 的日志文件,2023-10-29 的日志文件,这样之后也好管理。

这个支持么?

当然支持,但是要换别的 Transport 了。

winston 文档里可以看到有很多 Transport:

为什么 Node 要用 Winston 打印日志?_javascript_10

Console、File、Http、Stream 这几个 Transport 是内置的。

下面还有很多社区的 Transport,比如 MongoDB 的 Transport,很明显就是把日志写入 mongodb 的。

这里的 DailyRotateFile 就是按照日期滚动存储到日志文件的 Transport。

我们试试看:

npm install --save winston-daily-rotate-file

安装这个 Transport。

然后改下代码:

import winston from 'winston';
import 'winston-daily-rotate-file';

const logger = winston.createLogger({
    level: 'debug',
    format: winston.format.simple(),
    transports: [
        new winston.transports.Console(),
        new winston.transports.DailyRotateFile({
            level: 'info',
            dirname: 'log2',
            filename: 'test-%DATE%.log',
            datePattern: 'YYYY-MM-DD-HH-mm',
            maxSize: '1k'
        })
    ]
});

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

这里使用了 DailyRotateFile 的 transport,然后指定了文件名和日期格式。

指定文件名里的日志格式包含分钟,所以不同的分钟打印的日志会写入不同文件里:

为什么 Node 要用 Winston 打印日志?_前端_11

这就达到了滚动日志的效果。

再来试试 http 的 transport:

先创建个 nest 服务:

nest new winston-log-server

为什么 Node 要用 Winston 打印日志?_JavaScript_12

添加一个路由:

为什么 Node 要用 Winston 打印日志?_json_13

@Post('log')
log(@Body() body) {
    console.log(body);
}

把它跑起来:

npm run start:dev

为什么 Node 要用 Winston 打印日志?_Node.js_14

然后改下 index.js

import winston from 'winston';
import 'winston-daily-rotate-file';

const logger = winston.createLogger({
    level: 'debug',
    format: winston.format.simple(),
    transports: [
        new winston.transports.Console(),
        new winston.transports.Http({
            host: 'localhost',
            port: '3000',
            path: '/log'
        })
    ]
});

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

使用 http 的 transport 来传输日志。

跑一下:

node ./index.js

为什么 Node 要用 Winston 打印日志?_前端_15

为什么 Node 要用 Winston 打印日志?_json_16

nest 服务收到了传过来的日志。

基本上,内置的和社区的 transport 就足够用了,不管是想把日志发送到别的服务,还是把日志存到数据库等,都可以用不同 Transport 实现。

这些 transport 可以用 add、remove 方法来动态增删:

import winston from 'winston';

const console = new winston.transports.Console();
const file = new winston.transports.File({ filename: 'test.log' });

const logger = winston.createLogger({
    level: 'debug',
    format: winston.format.simple()
});

logger.clear();
logger.add(console);
logger.remove(console);
logger.add(file);

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

比如我先 clear,然后动态添加又删除了 console,然后又添加了一个 file 的 transport。

效果就是只有一个 file 的 transport:

为什么 Node 要用 Winston 打印日志?_前端_17

再就是日志级别,winston 有 6 种级别的日志:

为什么 Node 要用 Winston 打印日志?_Node.js_18

从上往下,重要程度依次降低。

比如当你指定 level 是 info 时,那 info、warn、error 的日志会输出,而 http、debug 这些不会。

为什么 Node 要用 Winston 打印日志?_Node.js_19

为什么 Node 要用 Winston 打印日志?_javascript_20

日志级别的功能虽然简单,但却是很实用的功能。

日志可以通过 format 指定格式:

simple:

为什么 Node 要用 Winston 打印日志?_Node.js_21

json:

为什么 Node 要用 Winston 打印日志?_JavaScript_22

prettyPrint(比 json 的格式多了一些空格):

为什么 Node 要用 Winston 打印日志?_Node.js_23

用 combine 组合 timestamp 和 json:

为什么 Node 要用 Winston 打印日志?_json_24

或者再组合个 label:

为什么 Node 要用 Winston 打印日志?_Node.js_25

加上个标签,再搜索相关日志就方便多了。

彩色:

为什么 Node 要用 Winston 打印日志?_Node.js_26

通过这些,就可以指定各种日志格式。

但现在有个问题,如果我不同的 transport 要指定不同的格式呢?

可以这样:

import winston from 'winston';

const logger = winston.createLogger({
    level: 'debug',
    transports: [
        new winston.transports.Console({
            format: winston.format.combine(
                winston.format.colorize(),
                winston.format.simple()
            ),
        }),
        new winston.transports.File({ 
            dirname: 'log3',
            filename: 'test.log',
            format: winston.format.json()
        }),
    ]
});

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

每个 transport 单独指定 format 就好了。

为什么 Node 要用 Winston 打印日志?_JavaScript_27

为什么 Node 要用 Winston 打印日志?_javascript_28

那如果我有的日志只想 console,而有的日志希望写入文件,而且配置都不同呢?

我们可以创建多个 logger 实例,每个 logger 实例有不同的 format、transport、level 等配置:

import winston from 'winston';

winston.loggers.add('console', {
    format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
    ),
    transports: [
        new winston.transports.Console()
    ]
});

winston.loggers.add('file', {
    format:winston.format.combine(
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.File({
            dirname: 'log4',
            filename: 'test.log',
            format: winston.format.json()
        })
    ]
});


const logger1 = winston.loggers.get('console');

logger1.info('aaaaa');
logger1.error('bbbbb');

const logger2 = winston.loggers.get('file');

logger2.info('xxxx');
logger2.info('yyyy');

我们创建了 2 个 logger 实例,其中一个只写入 console,另一个只写入 file,并且 format 都不同。

然后分别用不同的 logger 来打印日志。

为什么 Node 要用 Winston 打印日志?_Node.js_29

为什么 Node 要用 Winston 打印日志?_JavaScript_30

这样,项目中有不同的日志需求的时候,就可以创建多个 logger 实例。

此外,winston 还支持指定如何处理未捕获的错误的日志:

import winston from 'winston';

const logger = winston.createLogger({
    level: 'debug',
    format: winston.format.simple(),
    transports: [
        new winston.transports.Console()
    ],
    exceptionHandlers: [
        new winston.transports.File({
            filename: 'error.log'
        })
    ]
});

throw new Error('xxx');

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

跑一下,可以看到错误日志被输出到了 error.log

为什么 Node 要用 Winston 打印日志?_JavaScript_31

除了 error 外,Promise 的未捕获异常也可以指定如何处理日志:

import winston from 'winston';

const logger = winston.createLogger({
    level: 'debug',
    format: winston.format.simple(),
    transports: [
        new winston.transports.Console()
    ],
    rejectionHandlers: [
        new winston.transports.File({
            filename: 'rejection.log'
        })
    ]
});

(async function(){
    throw Error('yyy');
})();

logger.info('光光光光光光光光光');
logger.error('东东东东东东东东');
logger.debug(66666666);

为什么 Node 要用 Winston 打印日志?_JavaScript_32

这些就是 winston 的主要功能了。

案例代码上传到了github

总结

Node 服务端我们不会用 console.log 打印日志,而是会用日志框架,比如 winston。

winston 支持 tranport 配置,可以把日志传输到 console、file、通过 http 发送到别的服务,写入 mongodb 数据库等。

社区有很多 transport 可用,我们尝试了滚动日志的 transport,可以根据日期来自动分割日志文件。

winston 还支持 level 配置,可以根据级别来过滤日志。

而且还支持 format 的设置,比如 json、simple、label、timstamp 等,一般我们输出到文件里的都是 json 格式,并且给他加上时间戳和 label,这样方便之后分析。

每个 transport 都可以单独指定 format,而且还可以创建多个 logger,每个 logger 用不同的配置。

此外,winston 还支持指定未捕获的 error 的日志怎么处理。

总之,相比直接 console.log,用 winston 这样的灵活强大的日志框架可太香了。

更多内容可以看我的小册《Nest 通关秘籍》


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月19日 0

暂无评论

推荐阅读
  f18CFixvrKz8   2024年05月20日   88   0   0 JavaScript
  fxrR9b8fJ5Wh   2024年05月17日   52   0   0 JavaScript
  2xk0JyO908yA   2024年04月28日   40   0   0 JavaScript
hINapgLEIiPz