日志的记录
对于关键进程,日志通常要记录:
- 收到每条内部消息的id(还可以包括关键字段,长度,hash等)
- 收到的每条外部消息的全文
- 发出的每条消息的全文,每条消息都有全局唯一的id
- 关键内部状态变更
时间戳
每条日志都有时间戳,这样就能完整追踪分布式系统中一个事件的来龙去脉.也只有这样才能差清楚发生故障时究竟发生了什么,比如业务流程卡到了那一步.
级别
日志的级别比较重要,日志的输出级别在运行时可调这样同一个可执行文件可以分别在QA测试环境的时候输出DEBUG级别的日志,在生产环境的时候输出INFO级别的之日,在必要的时候可以临时在线调整日志的输出级别.
调整日志的输出级别不需要重新编译,也不需要重启进程,只要调用muduo::Logger::setLogLevel()就能即时生效.
目的地
对于分布式系统中的服务进程而言,日志的目的地只有一个:本地文件.
不可以往网络中写日志,因为诊断日志的功能之一就有诊断网络故障,当网络发生故障, 例如接收网络日志消息的服务器发生故障或者出现进程死锁,通常会导致发送日志的服务器进程阻塞,或者内存暴涨,这无异于放大了单机故障.
另一个坏处是增加网络带宽消耗.
日志的滚动
若以本地文件为日志的目的地,那么日志的滚动就是必须的,这样可以简化日志归档的实现.
rolling 的条件通常有两个:文件大小
和 时间
文件名
一个典型的日志文件的文件名如下:
logfile_test.2012060-144022.hostname.3605.log
文件名有以下几个部分组成:
- 第一部分
logfile_test
进程名.
通常是main()函数参数中的basename(3)
这样容易区分究竟是哪个服务程序的日志 - 第二部分
2012060-144022
文件创建时间 - 第三部分
hostname
机器名称 . 这样即使是把日志文件拷贝到别的服务器上也能追溯其来源 - 第四部分
3605
进程id. 如果一个进程一秒之内反复重启,那么每次都会生成不同的日志文件. - 第五部分
.log
后缀,为了便于周边配套脚本的编写
日志文件压缩与归档 , 磁盘空间监控
这两者都是日志库可以没有的功能
往文件中写日志
常见问题,万一程序崩溃,那么最后若干条日志往往就丢失了,因为日志库不能每一条都flush硬盘,更不能每一条日志都open/close文件,那样开销太大
日志库用两个办法来对付这一点:
- 定期(默认3秒)将缓冲区的日志消息flush到硬盘
- 每条内存中的日志消息都带有cookie,其值为某个函数的地址
日志消息的格式
通常是固定的,不仅好看,用其他插件进行数据摘取分析也很方便.
日志消息格式通常由几个要点:
- 尽量每条日志占一行
- 时间戳精确到微秒,每条消息都通过gettimeofday(2)来获取时间
- 始终使用GMT时区
- 打印线程id
- 打印日志级别
- 打印源文件名和行号
多线程异步日志
在多线程程序中需要注意线程安全,即多个线程可以并发写日志,两个线程的日志消息不会重叠交织.
比较好的办法是一个多线程程序每个进程最好只写一个日志文件.并用一个背景线程收集日志消息,并写入日志文件,其他业务线程只负责往这个"日志线程"发送日志消息,这称为"异步日志".
在正常的业务处理流程中应该尽量避免磁盘IO
muduo日志库采用双缓冲技术,实际实现采用了四个缓冲区,这样可以进一步减少或避免日志前端的等待