当前位置:网站首页>04. 项目博客之日志

04. 项目博客之日志

2022-07-06 05:17:00 John是橘红

项目博客之日志

  • 系统没有日志,就等于人没有眼睛

  • 第一,访问日志 access log(server 端最重要的日志)

  • 第二,自定义日志(包括自定义事件、错误记录等)

1. nodejs 文件操作

文件读取

基本的文件读取方法:

const fs = require("fs");
const path = require("path");

// 获取当前文件夹的 data.txt 所在路径
const filePath = path.resolve(__dirname, "data.txt");

// 读取文件内容
fs.readFile(filePath, (err, data) => {
    
  if (err) {
    
    console.error(err);
    return;
  }
  // data 是二进制类型,需要转换为字符串
  console.log(data.toString());
});

一口气读取了文件里的内容然后进行输出。这就带来了风险,如果要读取的这个文件特别大,内存吃不消。

文件写入

基本的文件写入方法:

// 写入文件
const content = "这是新写入的内容\n";
const opt = {
    
  flag: "a",
};
// writeFile 四个参数:
// filePath: 目标文件路径 content: 要写入的内容
// opt: 写入方式 'a' 代表追加写入,覆盖用 'w'
// 最后一个参数是回调函数
fs.writeFile(filePath, content, opt, (err) => {
    
  if (err) {
    
    console.error(err);
  }
});

每次写入的时候都得获取文件路径然后打开进行写入,这样就十分耗内存。另外,如果要写入一个特别大的内容,整个内容要存入内存里,内存吃不消。

判断文件是否存在

// 同步检查是否存在
console.log(fs.existsSync(filePath)); // true
// 异步检查是否存在
fs.exists(filePath, (exist) => {
    
  console.log(exist);
});

2. stream

2.1 IO 操作的性能瓶颈

  • IO 包括”网络 IO“和”文件 IO“
  • 相比于 CPU 计算和内存读写,IO 的突出特点就是慢
  • 如何在有限的硬件资源下提高 IO 操作效率

2.2 stream 通俗介绍

stream 就是流动,之前一口气读取文件内容出来就好比搬水桶,直接把整个水桶抬走了。但是大多数人并没有那力气。更好的方式是,接个水管,将水稳定地通过管道转移到其他地方,降低了成本(小孩子都能够完成)。stream 就类似于此,极大降低了硬件资源要求。

2.1 stream 演示

标准输入输出

process.stdin.pipe(process.stdout);

终端输入的内容将通过管道持续输出到终端中。

请求内容输出到响应内容

const http = require('http')
const server = http.createServer((req, res) => {
    
    if (req.method === 'POST') {
    
        req.pipe(res)  // 最主要
    }
})
server.listen(8000)

现在,req 和 res 通过 pipe 连接,一旦 req 接收到了东西,就会立刻稳定地输出到 res 中。

stream 拷贝文件

通过 fs.createReadStream 创建一个读取文件的 stream 对象,fs.createWriteStream 创建一个写入文件的 stream 对象。通过 pipe 连接使得数据流动。

// 两个文件路径
const fileName1 = path.resolve(__dirname, 'data.txt')
const fileName2 = path.resolve(__dirname, 'data-bak.txt')

// 读取文件的 stream 对象
const readStream = fs.createReadStream(fileName1)
// 写入文件的 stream 对象
const writeStream = fs.createWriteStream(fileName2)

// 通过 pipe 执行拷贝
readStream.pipe(writeStream)

// 可以监听数据传输时的内容
readStream.on('data', chunk => {
    
    console.log(chunk.toString())
})

// 监听到读取完成后,执行回调
readStream.on('end', () => {
    
    console.log('copy done')
})

stream 读取文件内容

发起一个 get 请求来读取文件,只要读取文件流通过管道输出到 response 里即可。

const http = require('http')
const fs = require('fs')
const path = require('path')
const fileName1 = path.resolve(__dirname, 'data.txt')
const server = http.createServer((req, res) => {
    
    if (req.method === 'GET') {
    
        const readStream = fs.createReadStream(fileName1)
        readStream.pipe(res)
    }
})
server.listen(8000)

3. 写日志

3.1 创建写日志的相关方法

在项目里创建 logs 文件夹,里边存储日志文件。同时在 src 目录创建 utils 用于放常用的工具方法,里边创建 log.js,里面写了写日志相关方法。

const fs = require("fs");
const path = require("path");

// 写日志
const writeLog = (writeStream, log) => {
    
  // 写入信息并换行
  writeStream.write(log + "\n");
};

// 生成 writeStream
const createWriteStream = (fileName) => {
    
  const fullFileName = path.resolve(__dirname, "../", "../", "logs", fileName);
  const writeStream = fs.createWriteStream(fullFileName, {
    
    // 追加写入
    flags: "a",
  });
  return writeStream;
};

// 写访问日志,error log 和 event log 同理
const accessWriteStream = createWriteStream("access.log");
const access = (log) => {
    
  writeLog(accessWriteStream, log);
};

module.exports = {
    
  access,
};

3.2 每次接收到请求后写入日志

app.js 里的 serverHandle 每次调用的时候便是接收到请求的时候,此时写入访问日志即可。其他的日志,例如 error 和 event 在合适的时候写入即可。

const serverHandle = async (req, res) => {
    
  // 记录访问信息
  access(
    `${
      req.method} -- ${
      req.url} -- ${
       req.headers["user-agent"] } -- ${
      Date.now()}`
  );
	......其他主要代码
}

实现效果,访问的时候成功写入了 log 文件:

4. 日志拆分

  • 日志内容会慢慢积累,放在一个文件中不好处理
  • 按时间划分日志文件,方便定位
  • 实现方式:Linux 的 crontab 命令,即定时任务

crontab

  • 设置定时任务格式: (分钟) (小时) (日期) (月份) (星期) command,不需要填的用星号 * 代替
  • 当时间到了,将 access.log 拷贝并重命名为当前时间的 log,如
  • 清空 access.log 文件,继续积累日志

4.1 实现方法

以下流程适用于 Linux 和 macOS。

写 sh 脚本

写一个脚本 copy.sh 用于产生具有日期标注的日志,命令行执行 sh coppy.sh 查看效果:

#!/bin/sh
cd logs文件夹的绝对路径
# 复制文件
cp access.log $(date +%Y-%m-%d).access.log
# 清空 access.log
echo "" > access.log

理想情况是,生成新的带有日期的日志文件,并且 access.log 里的内容被清空。

使用 crontab

在命令行中执行 crontab -e 进入编辑模式,写入以下内容,意思为每天的第 0 小时执行 sh 命令。

* 0 * * * sh copy.sh的绝对路径

查看 crontab 任务

crontab -l 可以查看当前系统的定时任务。

5. 日志分析

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

比如,如下的日志内容,前四个是 Safari 浏览器跑的,后两个是 Chrome 浏览器。

GET -- /api/blog/detail?id=1 -- Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15 -- 1655271842104
GET -- /favicon.ico -- Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15 -- 1655271843024
GET -- /api/blog/detail?id=1 -- Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15 -- 1655271853955
GET -- /api/blog/detail?id=2 -- Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15 -- 1655271854350
GET -- /api/blog/detail?id=1 -- Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36 -- 1655271857708
GET -- /api/blog/detail?id=1 -- Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36 -- 1655271859548

可以看出,Safari 浏览器发送请求时,里面没有 Chrome 标记。就可以依据这个来判别 Chrome 的占比(当然比较不严谨,演示用)

计算 Chrome 占比的脚本如下:

// utils/readline.js
const fs = require("fs");
const path = require("path");
const readline = require("readline");

// 文件路径
const fullFileName = path.resolve(
  __dirname,
  "../",
  "../",
  "logs",
  "access.log"
);
// 创建 readStream
const readStream = fs.createReadStream(fullFileName);

// 创建 readline 对象
const rl = readline.createInterface({
    
  input: readStream,
});

let chromeNum = 0;
let sum = 0;

// 逐行读取
rl.on("line", (lineData) => {
    
  if (!lineData) {
    
    return;
  }

  // 记录总行数
  sum++;

  const [method, url, userAgent] = lineData.split(" -- ");
  if (userAgent?.includes("Chrome")) {
    
    // 累加 Chrome 数量
    chromeNum++;
  }
});

// 监听读取完成
rl.on("close", () => {
    
  console.log(`Chrome 占比:${
      chromeNum / sum}`);
});

运行结果:

Chrome 占比:0.3333333333333333

6. 总结

  • 日志对 server 端的重要性,相当于人的眼睛
  • IO 性能瓶颈,使用 stream 提高性能
  • 使用 crontab 拆分日志文件,使用 readline 分析日志内容
原网站

版权声明
本文为[John是橘红]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_45196863/article/details/125591924