express默认日志组件morgan的方法


Posted in Javascript onApril 05, 2018

章节概览

morgan是express默认的日志中间件,也可以脱离express,作为node.js的日志组件单独使用。本文由浅入深,内容主要包括:

  1. morgan使用入门例子
  2. 如何将日志保存到本地文件
  3. 核心API使用说明及例子
  4. 进阶使用:1、日志分割 2、将日志写入数据库
  5. 源码剖析:morgan的日志格式以及预编译

入门例子

首先,初始化项目。

npm install express morgan

然后,在basic.js中添加如下代码。

var express = require('express');
var app = express();
var morgan = require('morgan');

app.use(morgan('short'));
app.use(function(req, res, next){
 res.send('ok');
});

app.listen(3000);

node basic.js运行程序,并在浏览器里访问 http://127.0.0.1:3000 ,打印日志如下

➜  2016.12.11-advanced-morgan git:(master) ✗ node basic.js
::ffff:127.0.0.1 - GET / HTTP/1.1 304 - - 3.019 ms
::ffff:127.0.0.1 - GET /favicon.ico HTTP/1.1 200 2 - 0.984 ms

将日志打印到本地文件

morgan支持stream配置项,可以通过它来实现将日志落地的效果,代码如下:

var express = require('express');
var app = express();
var morgan = require('morgan');
var fs = require('fs');
var path = require('path');
var accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), {flags: 'a'});
app.use(morgan('short', {stream: accessLogStream}));
app.use(function(req, res, next){
 res.send('ok');
});
app.listen(3000);

使用讲解

核心API

morgan的API非常少,使用频率最高的就是morgan(),作用是返回一个express日志中间件。

morgan(format, options)

参数说明如下:

  1. format:可选,morgan与定义了几种日志格式,每种格式都有对应的名称,比如combined、short等,默认是default。不同格式的差别可参考这里。下文会讲解下,如果自定义日志格式。
  2. options:可选,配置项,包含stream(常用)、skip、immediate。
  3. stream:日志的输出流配置,默认是process.stdout。
  4. skip:是否跳过日志记录,使用方式可以参考这里。
  5. immediate:布尔值,默认是false。当为true时,一收到请求,就记录日志;如果为false,则在请求返回后,再记录日志。

自定义日志格式

首先搞清楚morgan中的两个概念:format 跟 token。非常简单:

  1. format:日志格式,本质是代表日志格式的字符串,比如 :method :url :status :res[content-length] - :response-time ms。
  2. token:format的组成部分,比如上面的:method、:url即使所谓的token。

搞清楚format、token的区别后,就可以看下morgan中,关于自定义日志格式的关键API。

morgan.format(name, format); // 自定义日志格式
morgan.token(name, fn); // 自定义token

自定义format

非常简单,首先通过morgan.format()定义名为joke的日志格式,然后通过morgan('joke')调用即可。

var express = require('express');
var app = express();
var morgan = require('morgan');
morgan.format('joke', '[joke] :method :url :status');
app.use(morgan('joke'));
app.use(function(req, res, next){
 res.send('ok');
});
app.listen(3000);

我们来看下运行结果

➜  2016.12.11-advanced-morgan git:(master) ✗ node morgan.format.js
[joke] GET / 304
[joke] GET /favicon.ico 200

自定义token

代码如下,通过morgan.token()自定义token,然后将自定义的token,加入自定义的format中即可。

var express = require('express');
var app = express();
var morgan = require('morgan');
// 自定义token
morgan.token('from', function(req, res){
 return req.query.from || '-';
});
// 自定义format,其中包含自定义的token
morgan.format('joke', '[joke] :method :url :status :from');

// 使用自定义的format
app.use(morgan('joke'));
app.use(function(req, res, next){
 res.send('ok');
});
app.listen(3000);

运行程序,并在浏览器里先后访问 http://127.0.0.1:3000/hello?from=app 和 http://127.0.0.1:3000/hello?from=pc

➜  2016.12.11-advanced-morgan git:(master) ✗ node morgan.token.js
[joke] GET /hello?from=app 200 app
[joke] GET /favicon.ico 304 -
[joke] GET /hello?from=pc 200 pc
[joke] GET /favicon.ico 304 -

高级使用

日志切割

一个线上应用,如果所有的日志都落地到同一个本地文件,时间久了,文件会变得非常大,既影响性能,又不便于查看。这时候,就需要用到日志分割了。

借助file-stream-rotator插件,可以轻松完成日志分割的工作。除了file-stream-rotator相关的配置代码,其余跟之前的例子差不多,这里不赘述。

var FileStreamRotator = require('file-stream-rotator')
var express = require('express')
var fs = require('fs')
var morgan = require('morgan')
var path = require('path')

var app = express()
var logDirectory = path.join(__dirname, 'log')

// ensure log directory exists
fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory)

// create a rotating write stream
var accessLogStream = FileStreamRotator.getStream({
 date_format: 'YYYYMMDD',
 filename: path.join(logDirectory, 'access-%DATE%.log'),
 frequency: 'daily',
 verbose: false
})

// setup the logger
app.use(morgan('combined', {stream: accessLogStream}))

app.get('/', function (req, res) {
 res.send('hello, world!')
})

日志写入数据库

有的时候,我们会有这样的需求,将访问日志写入数据库。这种需求常见于需要实时查询统计的日志系统。

在morgan里该如何实现呢?从文档上,并没有看到适合的扩展接口。于是查阅了下morgan的源码,发现实现起来非常简单。

回顾下之前日志写入本地文件的例子,最关键的两行代码如下。通过stream指定日志的输出流。

var accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), {flags: 'a'});
app.use(morgan('short', {stream: accessLogStream}));

在morgan内部,大致实现是这样的(简化后)。

// opt为配置文件
var stream = opts.stream || process.stdout;
var logString = createLogString(); // 伪代码,根据format、token的定义,生成日志
stream.write(logString);

于是,可以用比较取巧的方式来实现目的:声明一个带write方法的对象,并作为stream配置传入。

var express = require('express');
var app = express();
var morgan = require('morgan');

// 带write方法的对象
var dbStream = {
 write: function(line){
 saveToDatabase(line); // 伪代码,保存到数据库
 }
};

// 将 dbStream 作为 stream 配置项的值
app.use(morgan('short', {stream: dbStream}));
app.use(function(req, res, next){
 res.send('ok');
});

app.listen(3000);

深入剖析

morgan的代码非常简洁,从设计上来说,morgan的生命周期包含:

token定义 --> 日志格式定义 -> 日志格式预编译 --> 请求达到/返回 --> 写日志

其中,token定义、日志格式定义前面已经讲到,这里就只讲下 日志格式预编译 的细节。

跟模板引擎预编译一样,日志格式预编译,也是为了提升性能。源码如下,最关键的代码就是compile(fmt)。

function getFormatFunction (name) {
 // lookup format
 var fmt = morgan[name] || name || morgan.default

 // return compiled format
 return typeof fmt !== 'function'
 ? compile(fmt)
 : fmt
}

compile()方法的实现细节这里不赘述,着重看下compile(fmt)返回的内容:

var morgan = require('morgan');
var format = morgan['tiny'];
var fn = morgan.compile(format);

console.log(fn.toString());

运行上面程序,输出内容如下,其中tokens其实就是morgan。 

function anonymous(tokens, req, res
/**/) {
 return ""
 + (tokens["method"](req, res, undefined) || "-") + " "
 + (tokens["url"](req, res, undefined) || "-") + " "
 + (tokens["status"](req, res, undefined) || "-") + " "
 + (tokens["res"](req, res, "content-length") || "-") + " - "
 + (tokens["response-time"](req, res, undefined) || "-") + " ms";
}

看下morgan.token()的定义,就很清晰了

function token (name, fn) {
 morgan[name] = fn
 return this
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Extjs gridpanel 出现横向滚动条问题的解决方法
Jul 04 Javascript
基于jquery插件实现常见的幻灯片效果
Nov 01 Javascript
js替代copy(示例代码)
Nov 27 Javascript
做好七件事帮你提升jQuery的性能
Feb 06 Javascript
js 动态修改css文件的方法
Aug 05 Javascript
JavaScript bold方法入门实例(把指定文字显示为粗体)
Oct 17 Javascript
jquery中$each()方法的使用指南
Apr 30 Javascript
Vue.js第一天学习笔记(数据的双向绑定、常用指令)
Dec 01 Javascript
利用n工具轻松管理Node.js的版本
Apr 21 Javascript
webstorm和.vue中es6语法报错的解决方法
May 08 Javascript
微信小程序实现富文本图片宽度自适应的方法
Jan 20 Javascript
微信小程序使用自定义组件导航实现当前页面高亮
Jan 02 Javascript
React Native悬浮按钮组件的示例代码
Apr 05 #Javascript
关于Google发布的JavaScript代码规范你要知道哪些
Apr 04 #Javascript
Angular HMR(热模块替换)功能实现方法
Apr 04 #Javascript
解决vue中使用swiper插件问题及swiper在vue中的用法
Apr 04 #Javascript
vue基于mint-ui实现城市选择三级联动
Jun 30 #Javascript
vue中mint-ui的使用方法
Apr 04 #Javascript
JS实现访问DOM对象指定节点的方法示例
Apr 04 #Javascript
You might like
PHP个人网站架设连环讲(二)
2006/10/09 PHP
centos下file_put_contents()无法写入文件的原因及解决方法
2017/04/01 PHP
PHP删除数组中指定下标的元素方法
2018/02/03 PHP
Laravel相关的一些故障解决
2020/08/19 PHP
javaScript - 如何引入js代码
2021/03/09 Javascript
面向对象继承实例(a如何继承b问题)(自写)
2013/07/01 Javascript
深入理解javascript构造函数和原型对象
2014/09/23 Javascript
同一个网页中实现多个JavaScript特效的方法
2015/02/02 Javascript
js实现鼠标移到链接文字弹出一个提示层的方法
2015/05/11 Javascript
详解Bootstrap按钮
2016/01/04 Javascript
基于Javascript实现返回顶部按钮
2016/02/29 Javascript
AngularJS页面访问时出现页面闪烁问题的解决
2016/03/06 Javascript
Bootstrap图片轮播组件Carousel使用方法详解
2016/10/20 Javascript
vue如何集成raphael.js中国地图的方法示例
2017/08/15 Javascript
vue登录以及权限验证相关的实现
2019/10/25 Javascript
Vue实现手机号、验证码登录(60s禁用倒计时)
2020/12/19 Vue.js
[02:17]快乐加倍!DOTA2食人魔魔法师至宝+迎霜节活动上线
2019/12/22 DOTA
[01:33:30]DOTA2-DPC中国联赛 正赛 RNG vs Phoenix BO3 第二场 2月5日
2021/03/11 DOTA
Python字符串拼接、截取及替换方法总结分析
2016/04/13 Python
python实时分析日志的一个小脚本分享
2017/05/07 Python
浅谈numpy数组的几种排序方式
2017/12/15 Python
用Python一键搭建Http服务器的方法
2018/06/01 Python
python实现机器学习之多元线性回归
2018/09/06 Python
Python读写文件基础知识点
2019/06/10 Python
python的re模块使用方法详解
2019/07/26 Python
Python基于BeautifulSoup和requests实现的爬虫功能示例
2019/08/02 Python
python django生成迁移文件的实例
2019/08/31 Python
基于Python-Pycharm实现的猴子摘桃小游戏(源代码)
2021/02/20 Python
前端隐藏出边界内容的实现方法
2016/04/14 HTML / CSS
本科毕业生求职自荐信
2014/04/09 职场文书
感恩老师的演讲稿
2014/05/06 职场文书
幼儿园教师自荐书
2015/03/06 职场文书
优秀党员主要事迹材料
2015/11/04 职场文书
文艺有韵味的诗句(生命类、亲情类...)
2019/07/11 职场文书
Python下opencv使用hough变换检测直线与圆
2021/06/18 Python
Android Studio 计算器开发
2022/05/20 Java/Android