博客项目日志

日志

  • 系统没有日志,就等于人没有眼睛————抓瞎
  • 第一,访问日志access log(server端最重要的日志)
    • 每一次请求都会保存一个访问日志,以前端项目为例:以前端项目为例
  • 第二,自定义日志(包括自定义事件、错误记录等)
    • 自定义日志并不是每次请求都保存,只有我们设置好的情况下才保存
  • 上线之前,日志放在控制台方便查看,但上线之后日志就要放到文件中来方便查看了。
    • 日志为何不放在redis中:日志文件非常大,且日志对性能要求不是特别高(写文件是个异步操作),放在redis(内存数据库)中不合适
    • 日志为何不放在mysql中:mysql中存储的数据应当是表结构的,日志只是一行一行的,不涉及复杂的表操作,放在mysql(硬盘数据库)中没必要
    • 日志为何存储在文件中:日志可能会考虑到各个服务器上去计算/运行,所有服务器都能识别文件,这样就不需要另外的环境/基础,比较方便。如果是mysql,不仅要考虑环境,还要考虑版本的统一
    • 虽然存取速度上redis(内存)>mysql(硬盘)>文件(硬盘),但成本上 redis>mysql>文件

nodejs文件操作(nodejs stream)

  • 所有的文件操作都是异步的!

读取文件内容 fs.readFile()

  1. 新建file-test文件夹,不需要使用npm插件,所以不需要初始化,直接使用原生nodejs即可
  2. file-test下新建data.txt,在里面随便写点东西作为测试文件data.txt
  3. file-test下新建test1.js
    1. 引入fs库(文件操作)
    2. 引入path库(路径操作):因为windows和linux、mac系统的文件路径不同,需要统一一下。
    3. 获取文件路径const fileName = path.resolve(__dirname, "data.txt");
      • path.resolve():拼接目录
      • __dirname:当前js文件的目录(nodejs自带的变量)
    4. **读取文件内容readFile()**:test1.js
      • readFile():异步 读取文件
        • 参数1:需要读取的文件路径
        • 参数2:回调函数,该函数接受的参数1为读取失败后的错误信息参数2为读取成功后获取的数据(获取的数据原生是二进制类型的,需要使用**toString()转换为字符串类型**)
    5. 运行运行结果

写入文件内容 fs.writeFile()

  1. 在file-test下的test1.js中:
    1. 写入文件writeFile()test1.js
      • writeFile()写入文件(异步)
        • 参数1:需要读取的文件路径
        • 参数2:写入内容
        • 参数3:写入方式
          • flag:a追加写入(append)/w覆盖写入(write)
        • 参数4:回调函数,由于是写入,不需要返回数据,所以该函数只接受一个参数,即读取失败后的错误信息
    2. 运行:执行什么也没输出就说明写入成功,可以到txt文件中看运行data.txt
  2. 注意:content是js变量,如果一直写入内容则一直需要打开文件并修改js变量,频繁操作js变量会非常耗费进程

判断文件是否存在 exists()

  1. 在file-test下的test1.js中:test1.js
    • exists()判断文件是否存在(异步)
      • 参数1:需要读取的文件路径
      • 参数2:回调函数,该函数只接受一个参数(boolean),为true时说明存在。

IO操作的性能瓶颈

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

stream(stream对象.pipe())

  • 使用上面的方法去读取文件会一次读取所有内容,写入文件则需要操作js变量并操作文件,非常损耗性能
  • 提高IO操作的性能的方法就是使用stream,一边读取一边显示stream图示
    • 比如之前用到过的:例子
  • req和res都有stream的特性,所以他们**可以直接调用pipe()**来实现stream。(req.pipe()/res.pipe())
  • 只需了解 标准的输入输出 即可,他其实是linux中的概念,node中可以通过API(pipe())来实现。

stream演示

  1. 新建stream-test文件夹,一样不需要使用npm插件,所以不需要初始化,直接使用原生nodejs即可
  2. stream-test文件夹下新建test1.jstest1.js
    • 只需了解 标准的输入输出 即可,他其实是linux中的概念,node中可以通过API(pipe())来实现。

stream操作文件

  • createReadStream()创建读取文件的stream对象(左边的水桶)
    • 参数:文件的路径
  • createWriteStream()创建写入文件的stream对象(右边的水桶)
    • 参数:文件的路径
  • pipe() 两个**stream对象/req与res 之间使用pipe()**进行数据传递
    • a.pipe(b)表示a通过stream的方式传数据给b
  • 例子
    1. 使用stream读取res内容:例子
    2. 使用stream复制文件,将文件1的内容拷贝到文件2中:使用stream将文件1的内容拷贝到文件2中

path.join()和path.resolve()的异同

  • 两者都能拼接路径,也都能规范化的去解析以后再进行拼接
  • path.resolve()和path.join()的参数都是字符串
  • 但join()只是简单的将路径片段进行拼接并规范化生成一个路径,而resolve()一定会生成一个绝对路径,相当于执行cd操作。

__dirname当前js文件的目录

  • __dirname:即当前js文件的目录(nodejs自带的变量)

path.join()拼接路径

path.join() 方法使用平台特定的分隔符把全部给定的 path 片段连接到一起,并规范化生成的路径。
长度为零的 path 片段会被忽略。 如果连接后的路径字符串是一个长度为零的字符串,则返回 ‘.’,表示当前工作目录。
————— nodejs官方文档

  • “平台特定的分隔符”:
    • windows下文件路径分隔符使用的是\
    • Linux下文件路径分隔符使用的是/
  • “path片段”:该方法接收的是多个路径的部分或全部,然后简单将其拼接。
  • “规范化”:如果你给出的路径片段中任一路径片段不是一个字符串,则抛出TypeError。
  • 例子:
    1
    2
    path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
    // 返回: '/foo/bar/baz/asdf'
  • 注意:如果路径中出现”..”即代表上一级目录。

path.resolve()拼接路径

path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径。
给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve(‘/foo’, ‘/bar’, ‘baz’) 会返回 /bar/baz。
如果处理完全部给定的 path 片段后还未生成一个绝对路径,则当前工作目录会被用上。
生成的路径是规范化后的,且末尾的斜杠会被删除,除非路径被解析为根目录。
长度为零的 path 片段会被忽略。
如果没有传入 path 片段,则 path.resolve() 会返回当前工作目录的绝对路径。
———— nodejs官方文档

  1. 注意:如果没有传入path片段,则resolve会返回当前工作目录的绝对路径:
    1
    2
    path.resolve();
    // 'E:\nodejsWorkspaces\blog-1\src\utils\log.js'
  2. 如果处理完全部给定的 path 片段后还未生成一个绝对路径,则当前工作目录(绝对路径)会被用上:
    1
    2
    path.resolve('bar', 'baz', 'foo');
    // 'E:\nodejsWorkspaces\blog-1\src\utils\log.js\bar\baz\foo'
  3. 拼接的时候需要小心使用斜杠/:生成的路径是规范化后的,且末尾的斜杠/会被删除,除非路径被解析为根目录:
    1
    2
    3
    4
    5
    6
    path.resolve('/foo', 'bar/', 'baz/');
    // 'E:\foo\bar\baz'
    // 注意:foo前面的 '/' 代表根目录,即'E:'; 并且baz末尾的斜线会删除

    path.resolve('/');
    // 'E:\' 如果路径为根路径,末尾的斜线不会删除
  4. 给定的路径的序列是 “从右往左” 被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径:
    1
    2
    3
    path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
    // 如果当前工作目录为 /home/myself/node,
    // 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'

日志功能开发和使用

  1. 进入blog-1文件夹,运行
  2. blog-1下新建logs文件夹
    • 新建access.log文件:存储访问日志
    • 新建error.log文件:存储错误日志
    • 新建event.log文件:存储自定义事件
  3. src下新建utils文件-新建log.js文件,编写函数用于书写访问日志log.js
    • 自定义的函数和fs自带的函数重名也无所谓
  4. app.js中引入并使用函数app.js
  5. 测试:进入3次页面,查看access.log可看到3条数据:查看access.log

stream对象.write()写入文件内容

  • 可以注意到这里并没使用pipe()进行数据传递,直接使用stream对象.write("内容")就可将内容写入stream对象(pipe适合于两个stream对象之间的数据传递)

拆分日志文件(了解即可)

  • 日志内容会慢慢积累,放在一个文件中不好处理
  • 可按时间划分日志文件,如2019-02-10.access.log
  • 实现方式:linux的crontab命令,即定时任务(了解即可,不需要掌握,windows上需要虚拟机才能练习,这个一般是专门的运维做的事情)
  • 了解即可
  • 了解即可

分析日志内容(readline库)

  • 如:针对access.log日志,分析chrome的占比
  • 日志是按行存储的,一行就是一条日志
  • 使用nodejs的readline库(基于stream,效率高)
    • stream是一点一点读取的,一点不一定是一行,readline是一行一行读取的,效率更高。
  1. utils下新建readline.js:readline.js
    1. 引入readline库
    2. 创建readStream对象
    3. 基于readStream对象创建readline对象
    4. 使用readline对象的on()逐行读取数据
    • on("data",func)是一块数据来了会触发func,**on("line",func)是一行数据来了会触发func**。
    • on("close",func)类似stream中的on("end",func)当数据读取完成后执行func。
  2. 使用别的浏览器运行网页,测试:测试结果