对于一个服务,若我们没有进行日志记录,那么这台服务器可以说成是瞎子也不为过了,为服务写日志的目的是为了了解到问我们服务运行期间的一些情况,被多少次访问了,期间什么时候那些接口出现了错误等等我们都需要日志来进行记录
-
常见的日志我们都是记录到文件中的,为什么不用redis数据库或者是mysql数据库来存储我们这里也先说一下:
- 关于redis缓存数据库我们之前进行登录验证的时候也说到了,它作为内存数据库的存储空间的是极其昂贵的,而我们的日志也不是什么需要平凡操作的东西,所以完全没有必要进行redis存储
- 而对于mysql而言,它的存储是以数据表的形式,我们的日志只需要一条一条的日志信息写如即可,所以也没有这种必要,减少工作何乐而不为呢?
-
那既然我们说日志需要使用文件的形式进行存储,所以我们就有必要先说说node原生的fs文件处理模块
- fs模块是node原生提供的一个文件处理模块,我们这里主要说一下的是它的三个常用方法:
1. readFile()文件读取方法
2. writeFile()文件写入方法
3. exist()判断文件存在与否
// 引入模块:
const fs = require('fs')
const path = require('path')
// 定义一个文件读取的路径
const fileName = path.resolve(__dirname, 'data.txt')
/**
* 文件读取函数: readFile()函数
* 参数1: 文件读取的路径
* 参数2: 文件读取完成的回调函数: 参数1为错误信息, 参数2为文件读取到的信息
*/
fs.readFile(fileName, (err, data) => {
if(err){
throw err
}
// 这里读取到的data为一个二进制的Buffer,所以我们需要使用toString()方法转化为字符串
console.log(data.toString())
})
/**
* 文件写入函数: writeFile()
* 参数1: 文件写入的路径
* 参数2: 文件写入的内容
* 参数3: 文件写入的一些配置: 常设置写入是进行覆盖还是追加
* 参数4: 写入完成的回调函数: 参数为错误信息
*/
const content = '这是将要被写入到data.txt文件的文本'
const opt = {
flags: 'a' // 'a'表示将写入的文本追加到文件最后,若是想覆盖原文本,可以使用 'w'
}
fs.writeFile(fileName, content, opt, (err) => {
if(err){ throw err}
console.log("complete")
})
/**
* 判断文件是否存在: exists()函数
* 参数1: 文件路径
* 参数2: 回调函数: 参数为布尔值,表示文件是否存在
*/
fs.exists(fileName, (exist) => {
console.log(exist) //打印为 true
})
- 由于文件的读取的这种方式是有一定的问题的,当我们的文件内容特别大的时候,比如我们看的一部电影,如果这样直接的读取,效率会非常的低下,所以我们这里我们引入流的概念:
- 流好比就是我们的水流,当一个容器需要时,他就从载有他的容器中通过管道的形式流到这个容器,其实我们在线看视频也是类似如此的,当我们点开一部电影观看时,服务器不是直接就把所以的资源都给你了的,意识我们的网络带宽的原因,其次还有就是我们的硬件设备的有可能是承受不住的,所以服务器都是一点一点的将视频资源流给我们,我们得到一点就播放一点,到最后数据发送完毕了,我们的视频也差不多看完了,这样的数据传输方法能很有效的提高我们数据传输的效率,那我们就来简单的看两个node中我们之前加到的流的案例:
例:
const http = require('http')
const server = http.createServer((req, res) => {
if(req.method === "POST"){
let data = ''
req.on('data', chunk => {
// 这里我们使用监听req的data事件来接收客户端发送POST请求传递数据,其实就是以流的形式从客户端传递过来的,我们通过监听这个data事件一点一点的接收这些流过来的数据
data += chunk
}
req.on('end', () => {
console.log("数据接收完毕")
}
}
})
- 那我们如何使用流的形式传输数据呢? 在node中其实是很容易的,我们只需要在两个传输的容器间架设一个管道即可(pipe()) 及 A.pipe(B) 表示的就是将A容器中的内容传递到B.这里我为了节省篇幅,就直接将A, B这两个容器使用上stream了,在fs模块中,分别有一个读取流readStream 和一个写入流writeStream,我们只需要在读取流和写入流之间架设一条管道将读取流的数据传输到写入流里去:
const fs = require('fs')
const path = require('path')
// 定义两个文件路径,将文件data.txt的内容拷贝到文件copy.txt中:
const file1 = path.resolve(__dirname, 'data.txt')
const file2 = path.resolve(__dirname, 'copy.txt')
// 创建连个文件传输流
// 文件读取流: 参数为文件的地址
const readStream = fs.createReadeStream(file1)
// 文件写入流: 参数为文件地址
const writeStream = fs.createWriteStream(file2)
// 在两个容器间架设管道:
readStream.pipe(writeStream)
// 监听readStream的end事件表示什么时候文件传输完毕:
readStream.on('end', () => {
console.log('copy done')
})
// 而如果我们需要单纯的以流的形式读取出文件中的内容,我们只需要监听readStream的data事件以及end事件即可:
readStream.on('data', (chunk) => {
console.log(chunk.toString())
})
前面说了这么多,还是省略了很多的内容后,所以到现在,我们就进入今天的正题: 为我们的服务写访问日志:
- 其实了解了上面的思路后我们实现起来时很简单的,当客户端的请求到达时,我们只需要将请求的一些信息写入到我们的一个日志文间中即可:
- 这里我们就简单的长创建一个服务为例进行代码的编写:
- 其次我们在当前的目录下加一个logs日志文件夹,建一个access.log文件用于记录我们的访问日志:
- 我们现在先来创建一个工具函数用于进行日志的写入操作:
// 引入模块:
const fs = require('fs')
const path = require('path')
/**
* @description 日志写入函数
* @param writeStream 写入文件的流容器
* @param log 写入文件的信息
*/
function writeLog(writeStream, log){
writeStream.write(log + '\n')
}
/**
* @description 定义生成writeStream
*/
function createWriteStream(fileName){
const fileFullName = path.resolve(__dirname, 'logs', fileName)
const writeStream = fs.createWriteStream(fileFullName, {
flags: 'a' //指定写入是追加的方式
})
return writeStream
}
/**
* @description 定义日志写入函数
*
*/
function access(log){
// 生成日志记录流实例
const accessWriteStream = createWriteStream('access.log')
writeLog(accessWriteStream, log)
}
module.exports = {
access
}
- 对服务进行文件写入:
const http = require('http')
const { access } = require('./logs/log.js')
const server = createServer((req, res) => {
// 当请求到达时就将该请求的一些信息写入到我们的access.log文件
// 这里我们写入的信息包括: 请求的方式, 请求的路径, 请求的客户端信息(req.headers[user-agent]), 请求的时间
access(`${req.method} -- ${req.url} -- ${req.headers[user-agent]} -- ${Date.now()}`)
if(req.method === "GET" && req.url === "/api"){
res.end("请求成功")
}
})
server.listen(3000)
如此我们使用浏览器访问我们的localhost:3000/api 路径时,我们的access.log中就会记录我们的请求的写入信息: 这里我展示一下我实验的情况图:
到此我们的日志写入是基本可以了,但是还有一个问题是当我们的请求不段的请求时我们的日志信息会越来越多,所以我们需要对我们的日志文件进行拆分,若是在线上环境下,我们可以按照每天拆分日志的形式,让我们的服务定时将我们每天的日志开拷贝一份到一个以当天日期命名的文件中去,然后清空这个日志文件access.log就可以的,我们举一个在linux服务器下的情况: 在linux下可以使用crontab进行操作,然crontab对我们日志文件进行定时的拆分,具体怎么操作就抱歉让大家自行百度了,毕竟我也还没用到linux,并且使用这样的方式会涉及到写一段shell脚本的让系统进行操作的问题,我们就不在这里说了,之后有机会我在补充说吧.
- 最后还有一部就是,对我们的日志文件进行分析,实际就是对日志中的信息进行一下读取,对一些信息进行统计等等,这里我们就统计一下我们的访问中chrome浏览器访问的此时为例说一下:
- 使用node提供的原生模块: readline对我们的日志文件进行逐行的读取,在对每行的日志信息进行分析: 主要是我们的日志也是逐行进行记录的
const fs = require('fs')
const path = require('path')
const readline = requrie('readline')
// 定义我们读取日志文件的路径
const fileName = path.resolve(__dirname, 'logs', 'access.log')
//因为readline逐行读取也是基于stream流的,所以我们这里也就得按照流读取文件的形式读取
// 创建读取流
const readStream = fs.createReadStream(fileName)
// 创建readline对象
const rl = readline.createInterface({
input: readStream
})
let chromeNum = 0
let sum = 0
// readline逐行读取文件
rl.on('line', (lineDate) => {
if(!lineData){
return
}
sum++
// 拆分每行日志读取客户端信息,根据上面我们日志存储的信息可以知道我们的日志信息是在三位上的
cosnt arr = lineData.split(' -- ')
if(arr[2] && arr[2].indexOf('Chrome') > 0){
chromeNum++
}
})
// 逐行读取结束: 监听'close' 事件
rl.on('close', () => {
console.log("chrome访问次数占比是: " + chromeNum / sum)
})
如此我们便基本完成了日志操作,对于日志的分析,大家可以根据自己的需求来,甚至是日志的信息大家都可以依据实际需要进行写入.到这里我们今天的node便结束了,下次我们说在express和koa中如何使用工具进行写日志操作