2021-10-28

7-1 开始

日志

  • 系统没有日志等于人没有眼睛 - 抓瞎
    • 比如看使用的情况,通过日志才能看到问题,看qps
  • 第一,访问日志 access log (server 端最重要的日志)
    • 类似于通过 http-server 访问的时候出现的那些内容
    • 这里就可以看出每次请求它的请求方式是什么,浏览器的UA,计算机的系统等等信息,后续还能扩展,比如此次访问的时间,chrome浏览器的占比

image.png

  • 第二:自定义日志(包括自定义事件,错误记录等)——业务上的日志

课程重点:
1.如何记录日志 2.记录好的日志如何拆分和分析日志

目录

  • nodejs 文件操作,nodejs stream
    • 因为上线之后日志要放到文件中来保存,放在控制台中不方便查看
    • 为了节约 CPU 和内存,要把日志记录放到 stream 中来做
  • 日志功能开发和使用
  • 日志文件拆分和日志内容分析
    • 按照每天一个文件或者每小时一个文件来拆分

nodejs 文件操作

  • 日志要存储到文件中
  • 为何不存储到 mysql 中?
    • mysql 是表结构,不太方便。对性能要求不高
  • 为何不存储到 redis 中?
    • 文件太大,不需要那么急

7-2 nodejs 文件操作

读文件

新建文件夹 file-test,然后新建文件 test1.js
写入代码:

  1. const fs = require('fs')
  2. const path = require('path')
  3. // 分别为文件处理和路径处理的方法
  4. // 处理路径为当前目录(__dirname) 再加 data.txt
  5. const fileName = path.resolve(__dirname, 'data.txt')
  6. // 读取文件内容
  7. fs.readFile(fileName, (err, data) => {
  8. if (err) {
  9. console.error(err)
  10. return
  11. }
  12. // data 原生是二进制类型,toString 将其转换为对象格式
  13. console.log(data.toString());
  14. })

然后新建 data.txt 用来处理日志文件
随便写入一些内容,比如 test log file
然后运行一下
image.png
成功了!

但是这样是不合理的,因为如果 data 内容过大的话,运行时会消耗大量的 CPU 和内存,并且也占不了太大,因为 nodejs 占比有限

写文件

  1. // 写入文件
  2. const content = '这是新写入的内容\n'
  3. const opt = {
  4. flag: 'a' // 追加写入,覆盖用 'w'
  5. }
  6. // writeFile 传入三个参数:文件路径,内容和方式,最后有回调函数
  7. fs.writeFile(fileName, content, opt, (err) => {
  8. if (err) {
  9. console.error(err)
  10. }
  11. console.log('success')
  12. })

试一下,成功了
image.png
image.png

写入也有问题,执行 writeFile 会很耗费性能,比如写入的内容非常的大的话,也很困难

判断文件是否存在

  1. // 判断文件是否存在
  2. fs.exists(fileName, (exist) => {
  3. console.log('exist', exist);
  4. })

exists 已被废弃
image.png

2021-11-1

7-3 stream 介绍

IO 操作的性能瓶颈

  • IO 包括 网络IO 和 文件IO
    • 上节课只是介绍了文件IO的问题
    • 因为在线看电影等的话,不可能下载文件的时候把文件所有内容都下载,需要网络IO来缓存下载
  • 相比于 CPU 计算和内存读写,IO的突出特点就是:慢
    • 本身的特点没办法避免,不可能让它和 CPU 一样快
  • 如何在有限的硬件资源下提高 IO 的操作效率

解决:使用 stream

Stream

image.png
如果把读取内容比作从一个水桶倒水到另一个水桶中的话,那么之前操作的时候,就相当于将水桶的水一股脑全部倒入另一个水桶中,并且倒完才使用。
但是这样意味着性能强劲,但是事实不能这样
因此应该像鱼缸换水一样,拿一个管子慢慢换水。
这样可以节约硬件资源

代码中是这样操作
image.png
之前的网络IO 客户端向用户端传递数据的时候,就是用到了这种 stream 的方式
image.png
一个网络io的请求
目的:将输入的内容立马返回,使用 pipe 方法
image.png

2021-11-4

7-4 stream演示(1)

新建一个文件夹来演示 stream 流: stream-test
image.png
然后新建一个 test1.js

测试标准输入,输出

  1. // 标准输入输出
  2. process.stdin.pipe(process.stdout)

输入什么就输出什么
image.png
也就是说,通过标准输入去输入,一旦输入结束他就会流动到标准输出里去。
相当于两个水桶接到一起去了

测试网络 IO 请求的 stream 流

继续在 test1.js 中写:

  1. const http = require('http')
  2. const server = http.createServer((req, res) => {
  3. if (req.method === 'POST') {
  4. req.pipe(res) // 最主要
  5. }
  6. })
  7. server.listen(8000)

node 运行完之后,打开 postman 测试一下
image.png
简单测试之后发现,数据就传输过来了,但是我们这里内容很少,如果数据很多,就会像水流一样传过来
就像之前学习中的那样,传递 postData
image.png
这样可以优化 CPU 内存和网络带宽

stream 操作文件

这里采用拷贝文件的例子
image.png
读取文件内容的例子
image.png

7-5 stream演示(2)

代码演示操作文件的 stream 流

复制文件

首先新建两个文件,data.txt(用来复制的) 和 data-bak.txt(用来粘贴的)
image.png
然后随便在data.txt 中写入一些内容
然后写代码

  1. // 复制文件
  2. const fs = require('fs')
  3. const path = require('path')
  4. const tobeCopyedFile = path.resolve(__dirname, 'data.txt')
  5. const CopyedFile = path.resolve(__dirname, 'data-bak.txt')
  6. // 分别新建两个输入输出流
  7. const readStream = fs.createReadStream(tobeCopyedFile)
  8. const writeStream = fs.createWriteStream(CopyedFile)
  9. // 然后倒水
  10. readStream.pipe(writeStream)
  11. // 处理拷贝完成
  12. readStream.on('end', () => {
  13. console.log('copy success');
  14. })

image.png
打开文件看下,发现拷贝成功
image.png

监听每一次读取的数据

readStream 除了像上边一样监视 end 外,也可以监视 data
每次读取的内容就是 chunk
chunk.toString 后就是每次一点点的数据内容

  1. // 监听每一次读取的数据
  2. readStream.on('data', (chunk)=> {
  3. console.log(chunk.toString());
  4. })

结合文件操作和 io 操作

  1. // 结合文件 IO 操作 http 操作
  2. const fs = require('fs')
  3. const path = require('path')
  4. const http = require('http')
  5. const tobeCopyedFile = path.resolve(__dirname, 'data.txt')
  6. const server = http.createServer((req, res) => {
  7. if (req.method === 'GET') {
  8. const readStream = fs.createReadStream(tobeCopyedFile)
  9. readStream.pipe(res)
  10. }
  11. })
  12. server.listen(8000)

然后运行一下
image.png

2021-11-6

7-6 写日志

回到 blog-1 的文件中
创建 logs 文件夹,目录为 blog-1/logs

在logs文件夹下创建三个文件,分别为 access.log、event.log、error.log
这三个文件分别存储运行成功的log,自定义事件的log以及错误事件的log

然后新建 utils 文件夹,创建 log.js 用来操纵写日志的 js
image.png

log.js

写入操作正确的和错误的代码

  1. const fs = require('fs')
  2. const path = require('path')
  3. // 写所有的日志
  4. function writeLog(writeSteam, log) {
  5. writeSteam.write(log + '\n') // 关键代码
  6. }
  7. // 生成 write stream(第二个水桶)
  8. function createWriteStream(fileName, flags) {
  9. // 找到真正的日志文件的地址
  10. const fullFileName = path.join(__dirname, '../', '../', 'logs', fileName)
  11. const writeSteam = fs.createWriteStream(fullFileName, {
  12. // 给它添加一个标识符
  13. flags
  14. })
  15. return writeSteam
  16. }
  17. // 写访问成功的日志
  18. const accessWriteStream = createWriteStream('access.log', 'a')
  19. function access(log) {
  20. writeLog(accessWriteStream,log)
  21. }
  22. module.exports = {
  23. access
  24. }

然后去使用起来
来到 app.js 中,将 access 和 error 方法引入进来
然后在 serverHandle 方法中引入 access 并且写入一些基本的内容

  1. const serverHandle = (req, res) => {
  2. // 记录 access log
  3. const basicContent = `${req.method} -- ${req.url} -- ${req.headers['user-agent']} -- ${Date.now()}`
  4. access(basicContent)
  5. ...
  6. }

然后刷新页面,发现写日志成功了
image.png

error.log 和 event.log 在后面会讲

7-7 拆分日志

  • 日志内容会慢慢累积,放在一个文件中不好处理
  • 按时间划分日志文件,如2021-11-6.access.log
  • 实现方式:Linux 的 crontab 命令,即定时任务
    • 运维去做,只需简单了解

crontab

  • 设置定时任务的格式:*command
    • (分别是分钟、小时、日、月、星期几)command 为运行脚本 .sh 文件
  • 将 access.log 拷贝并重命名为 2021-11-06.access.log(当前日期的命名)
  • 清空 access.log 文件,继续累积日志

用 shell 脚本 操作比 nodejs 更加方便

操作

首先进入 logs 然后把它的绝对路径拷贝一下
/Users/luobin/Downloads/nodejs-learning/blog-1/logs
image.png
然后在 utils 中书写一个 copy.sh 的 shell 脚本,用来书写 crontab 内容
首先进入 logs 中去
然后重命名 access.log 为当前日期的 access.log
最后使用 echo 指令将 access.log 清空

  1. #!/bin/sh
  2. cd /Users/luobin/Downloads/nodejs-learning/blog-1/logs
  3. cp access.log $(dat +%Y-%m-%d).access.log
  4. echo "" > access.log

然后运行一下
image.png
发现 access.log 清空了,并且生成了正确的按时间来的日志名字
image.png

7-8 分析日志介绍

记录一下脚本的 pwd
/Users/luobin/Downloads/nodejs-learning/blog-1/src/utils

使用 crontab

在 blog-1 下 使用 crontab -e 写入下面这个脚本
image.png
使用 crontab -l 就可以看到当前本机添加了哪些脚本

日志分析

  • 如针对 access.log 日志,分析 chrome 的占比
  • 日志是按行存储的,一行就是一条日志
  • 使用 nodejs 的 readline (基于 stream ,效率高)

7-9 readline演示

启动服务后,换几个服务器运行一下
在 utils 中新建一个 readline.js
然后写占比代码

  1. const fs = require('fs')
  2. const path = require('path')
  3. const readline = require('readline')
  4. // 文件名
  5. const fileName = path.join(__dirname, '../', '../', 'logs', 'access.log')
  6. // 创建 readStream
  7. const readStream = fs.createReadStream(fileName)
  8. // 创建 readline
  9. const readlineObject = readline.createInterface({
  10. input: readStream
  11. })
  12. let chromeNum = 0
  13. let totalNum = 0
  14. // 逐行读取
  15. // 每一行数据读完之后会触发
  16. readlineObject.on('line', (lineData) => {
  17. if (!lineData) {
  18. return
  19. }
  20. // 记录总行数
  21. totalNum++
  22. const arr = lineData.split(' -- ')
  23. if (arr[2] && arr[2].includes('Chrome')) {
  24. // 累加 chrome 的数量
  25. chromeNum++
  26. }
  27. })
  28. // 监听读取完成
  29. readlineObject.on('close', () => {
  30. console.log('Chrome 读取占比', chromeNum / totalNum)
  31. })

image.png
成功!

7-10 总结

  • 日志对于 server 端的重要性,相当于人的眼睛
  • IO 性能瓶颈,使用 stream 操作
  • 使用 crontab 来拆分日志,使用 readline 来分析日志内容