专栏首页云计算教程系列如何使用Winston记录Node.js应用程序
原创

如何使用Winston记录Node.js应用程序

介绍

在本指南中,我们将重点介绍Winston的日志包,这是一个极其通用的日志库,是基于NPM下载统计信息,可用于Node.js应用程序的日志记录解决方案。Winston的功能包括支持多个存储选项和日志级别,日志查询,甚至是内置的分析器。本教程将展示如何使用Winston记录我们创建的Node/Express应用程序。还将研究如何将Winston与另一个名为Morgan的Node.js的HTTP请求中间件记录器结合起来,以便将HTTP请求数据日志与其他信息进行整合。

完成本教程后,您将拥有一个小型Node /Express应用程序的Ubuntu服务器。您还将用Winston将错误和消息输出到文件和控制台。

准备

在开始本指南之前,您需要以下内容:

  • 一个Ubuntu 16.04服务器,包括一个sudo权限的非root用户和防火墙权限。
  • 使用官方PPA安装Node.js。

不会搭建Nodejs的用户可以查看腾讯云实验室提供了搭建Nodejs环境的相关教程,有了这些准备,我们就可以构建我们的应用程序并安装Winston。

第一步,创建Node/ Express应用程序

Winston的一个常见用途是从使用Node.js构建的Web应用程序中记录事件。为了充分展示如何使用Winston,我们将使用Express框架创建一个简单的Node.js Web应用程序。我们使用express-generator一个命令行工具来快速运行Node/ Express Web应用程序。因为我们安装了Node Package Manager,所以可以使用npm命令进行安装express-generator。我们使用-g标志来全局安装软件包。使用以下命令安装软件包:

$ sudo npm install express-generator -g

我们可以使用express命令创建我们的应用程序,然后我们想用我们的项目目录的名称。这将创建我们的应用程序,包含了我们开始所需的一切:

$ express myApp

接下来,安装Nodemon,它会在我们进行任何更改时自动重新加载应用程序。每当对源代码进行更改时,都需要重新启动Node.js应用程序。Nodemon将自动监视更改并为我们重新启动应用程序。由于我们希望用nodemon作命令行工具,我们将使用-g标志全局安装:

$ sudo npm install nodemon -g

要完成应用程序的设置,请切换到应用程序目录并安装依赖项,如下所示:

$ cd myApp
$ npm install

默认情况下,使用express-generator在端口3000上运行创建的应用程序,因此我们需要确保防火墙不阻止该端口。要打开端口3000,请运行以下命令:

$ sudo ufw allow 3000

我们现在拥有启动Web应用程序所需的一切。为此,请运行以下命令:

$ nodemon bin/www

这将启动在端口3000上运行的应用程序。我们可以通过访问Web浏览器来测试它是否正常工作。你应该看到这样的东西:http://your_server_ip:3000

localhost:3000

目前为止我们一直使用的SSH会话,并且当前正在运行应用程序作为会话A.我们将使用新的SSH会话来运行命令和编辑文件,我们将此会话称为会话B.除非另有说明,否则所有剩余命令应在会话B中运行。

第二步,自定义Node.js应用程序

创建的默认应用程序在express-generator启动方面做得很好,甚至包括我们用于记录有关HTTP请求的数据的Morgan HTTP日志记录中间件。由于Morgan支持输出流,因此它与Winston内置的流支持很好地配对,使我们能够将HTTP请求数据日志与Winston记录的任何其他内容进行整合。

默认情况下,express-generator样板文件在引用包时使用变量记录器morgan。由于我们将使用morgan和winston,这两个都是记录包,调用其中一个记录器都会变得很困惑。因此,让我们通过编辑app.js项目根目录中的文件并进行一些更改来改变它。

要打开app.js进行编辑,请使用以下nano命令:

$ nano \~/myApp/app.js

在文件顶部附近找到以下行:

~/myApp/app.js

...
var logger = require('morgan');
...

将其更改为以下内容:

~/myApp/app.js

...
var morgan = require('morgan');
...

我们还需要找到文件中引用变量记录器的位置并将其更改为morgan。在我们使用它时,让我们将morgan包使用的日志格式更改combined为标准的Apache日志格式,并在日志中包含有用信息,例如远程IP地址和用户代理HTTP请求标头。

为此,请找到以下行:

~/myApp/app.js

...
app.use(logger('dev'));
...

将其更改为以下内容:

~/myApp/app.js

...
app.use(morgan('combined'));
...

在我们集成Winston配置后,这些更改将有助于我们更好地了解日志包。通过输入CTRL-X,然后Y,然后ENTER来退出并保存文件。

现在我们的应用程序已经建立,我们已准备好开始Winston配置。

第三步, 安装和配置Winston

我们现在准备安装和配置Winston。在这一步,我们将探讨作为winston软件包的一些配置选项,并创建一个将信息记录到文件和控制台的记录器。

要安装,请winston运行以下命令:

$ cd ~/myApp
$ npm install winston

创建一个包含winston配置的config文件夹:

$ mkdir ~/myApp/config

现在让我们创建包含我们winston配置的文件,我们将调用它winston.js:

$ touch ~/myApp/config/winston.js

接下来,创建一个包含日志文件的文件夹:

$ mkdir ~/myApp/logs

最后,让我们安装app-root-path。此程序包与Winston没有直接关系,但在Node.js代码中指定文件路径时会有很大帮助。我们将使用它来指定项目根目录中的Winston日志文件的位置,并避免丑陋的相对路径语法:

$ npm install app-root-path --save

我们需要配置我们想要如何处理日志记录的所有内容,因此我们可以继续定义配置设置。首先打开~/myApp/config/winston.js编辑:

$ nano ~/myApp/config/winston.js

接下来,需要app-root-pathwinston包:

~/myApp/config/winston.js

var appRoot = require('app-root-path');
var winston = require('winston');

有了这些变量,我们可以为传输定义配置设置。传输是Winston引入的一个概念,它指的是用于日志的存储/输出机制。Winston带有三个核心传输元素-控制台文件HTTP。本教程专注于控制台和文件传输:控制台传输将信息记录传输到控制台,文件传输将信息记录传输到指定的文件。每个传输定义都可以包含自己的配置设置,例如文件大小,日志级别和日志格式。以下是我们将使用的每个传输设置的快速摘要:

  • level - 要记录的消息级别。
  • filename - 用于将日志数据写入的文件。
  • handleExceptions - 捕获并记录未处理的异常。
  • json - 以JSON格式记录日志数据。
  • maxsize - 在创建新文件之前,日志文件的最大大小(以字节为单位)。
  • maxFiles - 限制超出日志文件大小时创建的文件数。
  • colorize - 着色输出。这在查看控制台日志时很有用。

记录级别表示消息优先级,并由整数表示。Winston使用npm优先级从0到5(从最高到最低)的日志记录级别:

  • 0:error
  • 1:warn
  • 2:info
  • 3:verbose
  • 4:debug
  • 5:silly

指定特定传输的日志记录级别时,将记录该级别或更高级别的任何内容。例如,通过指定信息级别,将记录级别为错误,警告或信息的任何内容。调用记录器时指定了日志级别,这意味着我们可以执行以下操作来记录错误:logger.error('test error message').

我们可以在配置中定义file和console传输的配置设置,winston如下所示:

~/myApp/config/winston.js

...
var options = {
  file: {
    level: 'info',
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    json: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    colorize: false,
  },
  console: {
    level: 'debug',
    handleExceptions: true,
    json: false,
    colorize: true,
  },
};

接下来,使用options变量中定义的属性实例化具有文件和控制台传输的新winston记录器:

~/myApp/config/winston.js

...
var logger = new winston.Logger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console)
  ],
  exitOnError: false, // do not exit on handled exceptions
});

默认情况下,只有morgan输出到控制台,所以让我们定义一个流函数,它可以将morgan生成的输出生成到winston日志文件中。我们将使用该info级别,因此输出将由两个传输(文件和控制台)拾取:

~/myApp/config/winston.js

...
logger.stream = {
  write: function(message, encoding) {
    logger.info(message);
  },
};

最后,导出记录器,以便它可以在应用程序的其他部分中使用:

~/myApp/config/winston.js

...
module.exports = logger;

完成的winston配置文件应如下所示:

~/myApp/config/winston.js

var appRoot = require('app-root-path');
var winston = require('winston');

// define the custom settings for each transport (file, console)
var options = {
  file: {
    level: 'info',
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    json: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    colorize: false,
  },
  console: {
    level: 'debug',
    handleExceptions: true,
    json: false,
    colorize: true,
  },
};

// instantiate a new Winston Logger with the settings defined above
var logger = new winston.Logger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console)
  ],
  exitOnError: false, // do not exit on handled exceptions
});

// create a stream object with a 'write' function that will be used by `morgan`
logger.stream = {
  write: function(message, encoding) {
    // use the 'info' log level so the output will be picked up by both transports (file and console)
    logger.info(message);
  },
};

module.exports = logger;

退出并保存文件。我们现在配置了记录器,但我们的应用程序仍然没有意识到它或如何使用它。我们现在将记录器与应用程序集成在一起。

第四步, 将Winston与应用程序集成

我们已经在步骤2中看到我们app.js中的快速配置,所以让我们将记录器导入到该文件中。运行以下命令打开文件进行编辑:

$ nano ~/myApp/app.js

winston使用其他命令语句在文件顶部附近导入:

~/myApp/app.js

...
var winston = require('./config/winston');
...

我们实际使用winston的第一个地方是morgan。我们将使用stream选项,并将其设置为我们在winston配置中创建的流接口。为此,请找到以下行:

~/myApp/app.js

...
app.use(morgan('combined'));
...

把它改成这个:

~/myApp/app.js

...
app.use(morgan('combined', { stream: winston.stream }));
...

退出并保存文件。

我们将看到一些日志数据了!如果您在Web浏览器中重新加载页面,您应该在SSH会话A的控制台中看到类似于以下内容的内容:

[nodemon] restarting due to changes...
[nodemon] starting `node bin/www`
info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:29:36 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:29:37 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://167.99.4.120:3000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

这里有两个日志条目 -第一个用于HTML页面的请求,第二个用于附带的样式表。由于每个传输都配置为处理info级别日志数据,我们还应该在位于\~/myApp/logs/app.log的文件传输中看到类似的信息。但是,文件传输中的输出应该写为JSON对象,因为我们json:true在文件传输配置中指定了它。您可以在我们的JSON教程简介中了解有关JSON的更多信息。要查看日志文件的内容,请运行以下命令:

$ tail ~/myApp/logs/app.log

您应该看到类似于以下内容的内容:

{"level":"info","message":"::ffff:72.80.124.207 - - [07/Mar/2018:17:29:36 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\"\n","timestamp":"2018-03-07T17:29:36.962Z"}
{"level":"info","message":"::ffff:72.80.124.207 - - [07/Mar/2018:17:29:37 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://167.99.4.120:3000/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\"\n","timestamp":"2018-03-07T17:29:37.067Z"}

到目前为止,我们的记录器只记录HTTP请求和相关数据。这是我们日志中非常重要的信息,有时候我们需要记录自定义日志消息来记录错误或分析数据库查询性能。为了说明我们如何做到记录自定义日志消息,让我们从错误处理程序路由调用记录器。

该express-generator软件包默认包含404和500错误处理程序路由,因此我们将使用它。打开~/myApp/app.js文件:

$ nano ~/myApp/app.js

找到文件底部的代码块,如下所示:

~/myApp/app.js

...
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
...

这是最终的错误处理路由,最终将错误响应发送回客户端。由于所有服务器端错误都将通过此路由运行,因此这是包含winston记录器的好地方。

因为我们现在正在处理错误,所以我们希望使用error日志级别。同样,两个传输都配置为记录error级别消息,因此我们应该在控制台和文件日志中看到输出。我们可以在日志中包含我们想要的任何内容,因此请务必包含一些有用的信息,例如:

  • err.status - HTTP错误状态代码。如果尚未存在,则默认为500。
  • err.message - 错误的详细信息。
  • req.originalUrl - 请求的URL。
  • req.path - 请求URL的路径部分。
  • req.method - 请求的HTTP方法(GET,POST,PUT等)。
  • req.ip - 请求的远程IP地址。

更新错误处理程序路由以匹配以下内容:

~/myApp/app.js

...
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // add this line to include winston logging
  winston.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
...

退出并保存文件。为了测试这个,让我们尝试访问我们项目中不存在的页面,这将引发404错误。返回Web浏览器,尝试加载以下URL:http://your_server_ip:3000/foo,由于快速发生器创建的样板,应用程序已经设置为响应这样的错误。您的浏览器应显示如下所示的错误消息(您的错误消息可能比显示的更详细)

localhost:3000

现在再看看SSH会话A中的控制台。应该有一个错误的日志条目,并且由于colorize设置,它应该很容易找到。

[nodemon] starting `node bin/www`
error: 404 - Not Found - /foo - GET - ::ffff:72.80.124.207
info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:40:11 +0000] "GET /foo HTTP/1.1" 404 985 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:40:11 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://167.99.4.120:3000/foo" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

至于文件记录器,tail再次运行该命令时,应该向我们显示新的日志记录:

$ tail ~/myApp/logs/app.log

您将看到如下消息:

{"level":"error","message":"404 - Not Found - /foo - GET - ::ffff:72.80.124.207","timestamp":"2018-03-07T17:40:10.622Z"}

错误消息包括我们专门指示winston作为错误处理程序的一部分记录的所有数据,包括错误状态(404 -未找到),请求的URL(localhost / foo),请求方法(GET),IP地址发出请求,以及发出请求的时间戳。

结论

在本教程中,您构建了一个简单的Node.js Web应用程序并集成了Winston日志记录解决方案。您可以为应用程序构建强大的日志记录解决方案,尤其是在您的需求变得更加复杂时。我们建议您花点时间查看其他一些文档:

如果您对NodeJs感兴趣,可以访问腾讯云实验查看Nodejs实验相关的更多内容。


参考文献:《How To Use Winston to Log Node.js Applications》

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何在CentOS 7上安装Bacula Server

    Bacula是一种开源网络备份解决方案,允许您创建备份并执行计算机系统的数据恢复。它非常灵活和强大,这使得它在配置时稍微麻烦,适合在许多情况下进行备份。备份系统...

    姚啊姚
  • 如何使用Bacula备份Ubuntu 14.04服务器

    本教程将向您展示如何通过网络连接设置Bacula来创建远程Ubuntu 14.04主机的备份。这涉及在远程主机上安装和配置Bacula Client软件,并对现...

    姚啊姚
  • 如何在Ubuntu 18.04上安装R [快速入门]

    R是一种开源编程语言,专门用于统计计算和图形。在本教程中,我们将在Ubuntu 18.04服务器上安装R.

    姚啊姚
  • 2015智能可穿戴市场白皮书 - 腾讯ISUX

    腾讯ISUX
  • coding感想(二)

    songleo
  • 云计算到底离我们有多远?

    现在好象不谈一谈云计算,就不象IT 圈的人。CIO自媒体联盟要求大家统一以此为题写一写对于云计算的感受,这是一篇命题作文。好吧,那就谈一谈这片云到底在哪儿?离我...

    静一
  • 分布式锁系列--03关于分布式锁的选型分析

    本文分析,在分布式系统中,使用redis实现分布式锁,会遇到什么问题。关于分布式锁概念和redis分布式锁的具体实现,可参考前面的2篇文章。本文重点在于,对分布...

    IT云清
  • ArrayList、LinkedList 你真的了解吗?

    经常在面试时,被问到集合的概念,集合 List、Map、Set 等底层设计以及其使用场景与注意细节。但大部分人的回答都是千篇一律,跟网上的答案一模一样,这是致命...

    程序猿Damon
  • 注意了!ArrayList 增删千万不要乱用…

    编程过程中常常需要使用到集合,而ArrayList是我们常常使用的,但是最近在一次删除和增加中出现了一些问题,分享记录下。

    Java技术栈
  • 五年 Web 开发者 star 的 github 整理说明

    前端从业几年,积累了不少 github 开源库。其实老早就有整理 github 上资源的想法,现在才付诸行动。正在写的时候收到稀土圈公众号开源库功能上线的通知,...

    深蓝zz

扫码关注云+社区

领取腾讯云代金券