日志采集(logging)是观测云 DataKit 重要的一项,它将主动采集或被动接收的日志数据加以处理,最终上传到观测云中心。
日志采集的执行过程可大致分为三段,分别是“定位日志”、“数据处理” 和 “状态同步” 。本文将介绍第一段 “定位日志”。
日志采集按照数据来源可以分为 “网络流数据” 和 “本地磁盘文件” 两种。
基本都是以订阅网络接口的方式,被动接收日志产生端发送过来的数据。
最常见的例子就是查看 Docker 日志,当执行 docker logs -f CONTAIENR_NAME
命令时,Docker 会启动一个单独的进程并连接到主进程,接收主进程发送过来的数据输出到终端。虽然 Docker 日志进程和主进程在同一台主机,但是它们的交互是通过本地环回网络。
更加复杂的网络日志场景比如 Kubenetes 集群,它们的日志分布在不同 Node 上面,需要以 api-server 进行中转,比 Docker 的单一访问链路复杂一倍。
但是大部分通过网络获取日志都存在一个问题 —— 无法指定日志位置。日志接收端只能选择从首部开始接收日志,有多少收多少,可能一次收到几十万条;或从尾部开始,类似 tail -f
只接收当前产生的最新的数据,如果日志接收端的进程重启,那么这期间的日志就丢失了。
DataKit 的容器日志采集最初是使用网络接收的方式,被上述问题困扰许久,后通过逐步调整改为下文提到的 “本地磁盘文件” 采集方式。
采集本地日志文件是最常见和最高效的方式,省去了中间繁杂的传输步骤,直接对磁盘文件进行访问,可操控性更高,但是实现更复杂,会遇到一系列的细节问题,比如:
这些问题等同是将 Docker 日志服务给铺展开,各种细节和执行都交由自己来处理,只省去最后的网络传输部分,实现的复杂度比单纯用网络接收要麻烦很多。
本文将主要针对 “本地磁盘文件”,自底向上,分为 “发现文件”、“采集数据并处理”、“发送和同步” 三个方面,依次介绍 DataKit 日志采集系统的设计和实现细节。
补充,DataKit 日志采集执行流如下,涵盖和细分了上述的 “三个方面” :
glob 发现文件 Docker API 发现文件 Containerd(CRI)发现文件
| | |
------------------------------------------------------
|
添加到日志调度器,分配到指定 lines
|
---------------------------------------------------
| | | |
line1 line2 line3 line4
|
| |- 采集数据,分行
| |
| |- 数据转码
|-----> | |
| | |- 特殊字符处理
| |- 文件 A |
| | 一个采集周期 |- 多行处理
| | |
| | |- Pipeline 处理
| | |
| | |- 发送
| | |
| | |- 同步文件采集位置
| | |
| 流水线 | |- 文件状态检测
| 循环 |
| |
| |- 文件 B |-
| |
| |
| |- 文件 C |-
| |
|----------|
既然要读取和采集日志文件,那么首先要在磁盘上定位文件位置。在 DataKit 中主要有三种文件日志,其中两种容器日志,一种普通日志,它们的采集方式大同小异,本文也主要介绍这种三种,它们分别是:
json-file
驱动)Containerd(CRI)
普通日志文件是最常见的一种,它们是进程直接将可读的记录数据写到磁盘文件,像著名的 “log4j” 框架或者执行 echo "this is log" >> /tmp/log
命令都会产生日志文件。
这种日志的文件路径大部分情况都是固定的,像 MySQL 在 Linux 平台的日志路径是 /var/log/mysql/mysql.log
,如果运行 DataKit MySQL 采集器,默认会去找个路径找寻日志文件。但是日志存储路径是可配的,DataKit 无法兼顾所有情况,所以必须支持手动指定文件路径。
在 DataKit 中使用 glob 模式配置文件路径,它使用通配符来定位文件名(当然也可以不使用通配符)。
举个例子,现在有以下的文件:
$ tree /tmp
/tmp
├── datakit
│ ├── datakit-01.log
│ ├── datakit-02.log
│ └── datakit-03.log
└── mysql.d
└── mysql
└── mysql.log
3 directories, 4 files
在 DataKit logging 采集器中可以通过配置 logfiles
参数项,指定要采集的日志文件,比如:
采集 DataKit
目录下所有文件,glob 为/tmp/DataKit/*
采集所有带有 DataKit
名字的文件,对应的 glob 为/tmp/DataKit/DataKit-*log
采集 mysql.log
,但是中间有 mysql.d
和 mysql
两层目录,有好几种方法定位到 mysql.log
文件:
/tmp/mysql.d/mysql/mysql.log
/tmp/*/*/mysql.log
,这种方法基本用不到double star
):/tmp/**/mysql.log
,使用双星号 **
代替中间的多层目录结构,是较为简洁、常用的一种方式在配置文件中使用 glob 指定文件路径后,DataKit 会定期在磁盘中搜寻符合规则的文件,如果发现没有在采集列表中,便将其添加并进行采集。
在容器中输出日志有两种方式:
DataKit 通过连接 Docker 或 Containerd 的 sock 文件,访问它们的 API 获取指定容器的 LogPath
,类似在命令行执行 docker inspect --format='{{
{{.LogPath}}}}' $INSTANCE_ID
:
$ docker inspect --format='{{`{{.LogPath}}`}}' cf681e
/var/lib/docker/containers/cf681eXXXX/cf681eXXXX-json.log
获取到容器 LogPath
后,会使用这个路径和相应配置创建日志采集。
“定位日志” 不仅是找到这个磁盘文件,还包括管理文件的偏移位置,这也是为什么放弃使用网络流而直接访问磁盘,正是为了更细致地操控 “偏移量(offset)”。
正常情况下,查看日志都使用类似 tail -f
命令,从文件尾部开始输出日志。这也是 DataKit 最早的做法,即每次打开日志文件都将 offset 调整到文件尾部,在 linux 系统中表现为 SEEK_END
。
如果 DataKit 重启,将重新从文件末尾开始采集,在此期间产生的日志数据会全部丢失,因为 DataKit 跳过了这段数据。为了避免这种情况,DataKit 有三种设置 offset 的策略。
首先,最重要和优先级最高的策略,当然是 “记录偏移量位置”。
DataKit 会定期在数据采集、处理和发送等一系列操作完成后,将此段数据在文件的 offset 记录下来,然后写入到 cache 文件。即使 DataKit 重启,在创建新的日志文件采集时,也可以根据文件特征(例如文件名、inode),从 cache 文件中找到自己的 offset,然后从此处开始采集。
记录和使用 offset 是一个很简单的方案,没有很高深的细节管理,却可以最大限度的减少数据丢失、数据采集重复。
如果一个文件是新创建的、没有被采集过,或者 cache 文件被删除了找不到对应的 offset,那么只能最原始的方法——首部,或尾部。
对此,DataKit 有自己的区分方式。
create_time
距离当前时间不超过 2 分钟,那么会设置为从文件首部读取,可以涵盖这个文件的所有数据。tail -f
命令模式。“定位日志” 是日志采集系统的第一步,都说 “万事开头难”,只要在这一步处理妥当,就可以最大程度避免文件没采集、数据重复等问题。
下一步将开始读取文件数据,并做处理,会放在本系列的第二章节。