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 相关文章推荐
JavaScript Timer实现代码
Feb 17 Javascript
Jquery 扩展方法
May 06 Javascript
关于onScroll事件在IE6下每次滚动触发三次bug说明
Sep 21 Javascript
JS判断字符串长度的5个方法(区分中文和英文)
Mar 18 Javascript
使用jQuery设置disabled属性与移除disabled属性
Aug 21 Javascript
JavaScript中使用Callback控制流程介绍
Mar 16 Javascript
使用jquery实现仿百度自动补全特效
Jul 23 Javascript
js实现简单的碰壁反弹效果
Aug 30 Javascript
利用JavaScript对中文(汉字)进行排序实例详解
Jun 18 Javascript
关于Vue.nextTick()的正确使用方法浅析
Aug 25 Javascript
JS禁止浏览器右键查看元素或按F12审查元素自动关闭页面示例代码
Sep 07 Javascript
原生JS实现小小的音乐播放器
Oct 16 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获取一年中每个星期的开始和结束日期的方法
2015/02/12 PHP
PHP如何实现订单的延时处理详解
2017/12/30 PHP
PHP实现的62进制转10进制,10进制转62进制函数示例
2019/06/06 PHP
基于JQuery的Pager分页器实现代码
2010/07/17 Javascript
JavaScript网页定位详解
2014/01/13 Javascript
在页面加载完成后通过jquery给多个span赋值
2014/05/21 Javascript
jQuery实现的原图对比窗帘效果
2014/06/15 Javascript
JavaScript遍历table表格中的某行某列并打印其值
2014/07/08 Javascript
JS实现先显示大图后自动收起显示小图的广告代码
2015/09/04 Javascript
如何抽象一个Vue公共组件
2017/10/17 Javascript
javaScript canvas实现(画笔大小 颜色 橡皮的实例)
2017/11/28 Javascript
JS实现的简单拖拽购物车功能示例【附源码下载】
2018/01/03 Javascript
浅谈VUE单页应用首屏加载速度优化方案
2018/08/28 Javascript
vue实现一拉到底的滑动验证
2019/07/25 Javascript
js获取 gif 的帧数的代码实例
2019/09/10 Javascript
vue 实现input表单元素的disabled示例
2019/10/28 Javascript
node创建Vue项目步骤详解
2020/03/06 Javascript
javascript实现评分功能
2020/06/24 Javascript
简单总结Python中序列与字典的相同和不同之处
2016/01/19 Python
详解Python验证码识别
2016/01/25 Python
对python中数组的del,remove,pop区别详解
2018/11/07 Python
对Python正则匹配IP、Url、Mail的方法详解
2018/12/25 Python
Python中调用其他程序的方式详解
2019/08/06 Python
python实现把二维列表变为一维列表的方法分析
2019/10/08 Python
Python栈的实现方法示例【列表、单链表】
2020/02/22 Python
Python 保存加载mat格式文件的示例代码
2020/08/04 Python
意大利奢侈品购物网站:Giglio
2018/01/05 全球购物
Pop In A Box英国:Funko POP搪胶公仔
2019/05/27 全球购物
安踏官方商城:anta.cn
2019/12/16 全球购物
旅游专业职业生涯规划范文
2014/01/13 职场文书
小学师德标兵先进事迹材料
2014/05/25 职场文书
领导班子党的群众路线教育实践活动对照检查材料
2014/09/25 职场文书
嘉宾邀请函
2015/01/31 职场文书
幼儿园开学家长寄语(2015秋季)
2015/05/27 职场文书
党员理论学习心得体会
2016/01/21 职场文书
idea搭建可运行Servlet的Web项目
2021/06/26 Java/Android