日志
- 系统没有日志,就等于人没有眼睛————抓瞎
- 第一,访问日志access log(server端最重要的日志)
- 每一次请求都会保存一个访问日志,以前端项目为例:
- 第二,自定义日志(包括自定义事件、错误记录等)
- 自定义日志并不是每次请求都保存,只有我们设置好的情况下才保存
- 上线之前,日志放在控制台方便查看,但上线之后日志就要放到文件中来方便查看了。
- 日志为何不放在redis中:日志文件非常大,且日志对性能要求不是特别高(写文件是个异步操作),放在redis(内存数据库)中不合适
- 日志为何不放在mysql中:mysql中存储的数据应当是表结构的,日志只是一行一行的,不涉及复杂的表操作,放在mysql(硬盘数据库)中没必要
- 日志为何存储在文件中:日志可能会考虑到各个服务器上去计算/运行,所有服务器都能识别文件,这样就不需要另外的环境/基础,比较方便。如果是mysql,不仅要考虑环境,还要考虑版本的统一
- 虽然存取速度上redis(内存)>mysql(硬盘)>文件(硬盘),但成本上 redis>mysql>文件
nodejs文件操作(nodejs stream)
- 所有的文件操作都是异步的!
读取文件内容 fs.readFile()
- 新建file-test文件夹,不需要使用npm插件,所以不需要初始化,直接使用原生nodejs即可
- file-test下新建data.txt,在里面随便写点东西作为测试文件
- file-test下新建test1.js
- 引入
fs
库(文件操作) - 引入
path
库(路径操作):因为windows和linux、mac系统的文件路径不同,需要统一一下。 - 获取文件路径:
const fileName = path.resolve(__dirname, "data.txt");
path.resolve()
:拼接目录__dirname
:当前js文件的目录(nodejs自带的变量)
- **读取文件内容
readFile()
**:readFile()
:异步 读取文件- 参数1:需要读取的文件路径
- 参数2:回调函数,该函数接受的参数1为读取失败后的错误信息,参数2为读取成功后获取的数据(获取的数据原生是二进制类型的,需要使用**
toString()
转换为字符串类型**)
- 运行:
- 引入
写入文件内容 fs.writeFile()
- 在file-test下的test1.js中:
- 写入文件
writeFile()
:writeFile()
写入文件(异步)- 参数1:需要读取的文件路径
- 参数2:写入内容
- 参数3:写入方式
flag
:a追加写入(append)/w覆盖写入(write)
- 参数4:回调函数,由于是写入,不需要返回数据,所以该函数只接受一个参数,即读取失败后的错误信息
- 运行:执行什么也没输出就说明写入成功,可以到txt文件中看
- 写入文件
- 注意:content是js变量,如果一直写入内容则一直需要打开文件并修改js变量,频繁操作js变量会非常耗费进程
判断文件是否存在 exists()
- 在file-test下的test1.js中:
exists()
判断文件是否存在(异步)- 参数1:需要读取的文件路径
- 参数2:回调函数,该函数只接受一个参数(boolean),为true时说明存在。
IO操作的性能瓶颈
- IO包括”网络IO”和“文件IO”
- 相比于CPU计算和内存读写,IO的突出特点就是:慢!
- 如何在有限的硬件资源下提高IO的操作效率?使用stream
stream(stream对象.pipe()
)
- 使用上面的方法去读取文件会一次读取所有内容,写入文件则需要操作js变量并操作文件,非常损耗性能
- 提高IO操作的性能的方法就是使用stream,一边读取一边显示
- 比如之前用到过的:
- req和res都有stream的特性,所以他们**可以直接调用
pipe()
**来实现stream。(req.pipe()
/res.pipe()
) - 只需了解 标准的输入输出 即可,他其实是linux中的概念,node中可以通过API(
pipe()
)来实现。
stream演示
- 新建stream-test文件夹,一样不需要使用npm插件,所以不需要初始化,直接使用原生nodejs即可
- stream-test文件夹下新建test1.js:
- 只需了解 标准的输入输出 即可,他其实是linux中的概念,node中可以通过API(
pipe()
)来实现。
- 只需了解 标准的输入输出 即可,他其实是linux中的概念,node中可以通过API(
stream操作文件
createReadStream()
:创建读取文件的stream对象(左边的水桶)- 参数:文件的路径
createWriteStream()
:创建写入文件的stream对象(右边的水桶)- 参数:文件的路径
pipe()
: 两个**stream对象/req与res 之间使用pipe()
**进行数据传递a.pipe(b)
表示a通过stream的方式传数据给b
- 例子:
- 使用stream读取res内容:
- 使用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下文件路径分隔符使用的是
/
- windows下文件路径分隔符使用的是
- “path片段”:该方法接收的是多个路径的部分或全部,然后简单将其拼接。
- “规范化”:如果你给出的路径片段中任一路径片段不是一个字符串,则抛出TypeError。
- 例子:
1
2path.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官方文档
- 注意:如果没有传入path片段,则resolve会返回当前工作目录的绝对路径:
1
2path.resolve();
// 'E:\nodejsWorkspaces\blog-1\src\utils\log.js' - 如果处理完全部给定的 path 片段后还未生成一个绝对路径,则当前工作目录(绝对路径)会被用上:
1
2path.resolve('bar', 'baz', 'foo');
// 'E:\nodejsWorkspaces\blog-1\src\utils\log.js\bar\baz\foo' - 拼接的时候需要小心使用斜杠
/
:生成的路径是规范化后的,且末尾的斜杠/
会被删除,除非路径被解析为根目录:1
2
3
4
5
6path.resolve('/foo', 'bar/', 'baz/');
// 'E:\foo\bar\baz'
// 注意:foo前面的 '/' 代表根目录,即'E:'; 并且baz末尾的斜线会删除
path.resolve('/');
// 'E:\' 如果路径为根路径,末尾的斜线不会删除 - 给定的路径的序列是 “从右往左” 被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径:
1
2
3path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// 如果当前工作目录为 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
日志功能开发和使用
- 进入blog-1文件夹,运行
- blog-1下新建logs文件夹:
- 新建access.log文件:存储访问日志
- 新建error.log文件:存储错误日志
- 新建event.log文件:存储自定义事件
- src下新建utils文件-新建log.js文件,编写函数用于书写访问日志:
- 自定义的函数和fs自带的函数重名也无所谓
- app.js中引入并使用函数:
- 测试:进入3次页面,查看access.log可看到3条数据:
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是一行一行读取的,效率更高。
- utils下新建readline.js:
- 引入readline库
- 创建readStream对象
- 基于readStream对象创建readline对象
- 使用readline对象的on()逐行读取数据
on("data",func)
是一块数据来了会触发func,**on("line",func)
是一行数据来了会触发func**。on("close",func)
类似stream中的on("end",func)
,当数据读取完成后执行func。
- 使用别的浏览器运行网页,测试: