vivo手机系统日志目录,vivo手机操作日志

首页 > 实用技巧 > 作者:YD1662024-01-09 16:39:51

作者:vivo 互联网存储技术团队- Qiu Sidi

在企业大数据体系建设过程中,数据采集是其中的首要环节。然而,当前行业内的相关开源数据采集组件,并无法满足企业大规模数据采集的需求与有效的数据采集治理,所以大部分企业都采用自研开发采集组件的方式。本文通过在vivo的日志采集服务的设计实践经验,为大家提供日志采集Agent在设计开发过程中的关键设计思路。

一、概述

在企业大数据体系的建设过程中,数据的处理一般包含4个步骤:采集、存储、计算和使用。其中,数据采集,是建设过程中的首要的环节,也是至关重要的环节,如果没有采集就没有数据,更谈不上后续的数据处理与使用。所以,我们看到的企业中的运营报表、决策报表、日志监控、审计日志等的数据来源都是基于数据采集。一般的,我们对数据采集的定义是,把各种分散的源头上的数据(可以包括企业产品的埋点的日志、服务器日志、数据库、IOT设备日志等)统一汇聚到大数据存储组件的过程(如下图所示)。其中,日志文件类型的采集场景,是各种数据采集类型中最常见的一种。接下来,将围绕该场景提出我们的设计实践方案。

vivo手机系统日志目录,vivo手机操作日志(1)

通常,日志采集服务可以分为几个部分(业界常见的架构如下图所示):日志采集Agent组件(常见的开源采集Agent组件有Flume、Logstash、Scribe等)、采集传输与存储组件(如kafka、HDFS)、采集管理平台。Bees采集服务是vivo自研的日志采集服务,本文章是通过在Bees采集服务中的关键组件bees-agent的开发实践后,总结出一个通用的日志采集Agent设计中的核心技术点和一些关键思考点,希望对大家有用。

vivo手机系统日志目录,vivo手机操作日志(2)

二、特性&能力

  1. 具备基本的日志文件的实时与离线采集能力
  2. 基于日志文件,无侵入式采集日志
  3. 具备自定义的过滤超大日志的能力
  4. 具备自定义的过滤采集、匹配采集、格式化的能力
  5. 具备自定义的限速采集的能力
  6. 具备秒级别的实时采集时效性
  7. 具备断点续传能力,升级和停止不丢数据
  8. 具备可视化的、中心化的采集任务管理平台
  9. 丰富的监控指标与告警(包括采集流量、时效性、完整性等)
  10. 低系统资源开销(包括磁盘、内存、CPU及网络等)

三、设计原则

  1. 简单优雅
  2. 健壮稳定

四、关键设计

目前业界流行的日志采集Agent组件,开源的有Flume、Logstash、Scribe、FileBeats、Fluentd等,自研的有阿里的Logtail。它们都有不错的性能与稳定性,如果想要快速上手,可以不妨使用它们。但是一般大企业会有个性化的采集需求,比如采集任务大规模管理、采集限速、采集过滤等,还有采集任务平台化、任务可视化的需求,为了满足上面这些需求我们自研了一个日志采集Agent。

在做一切的设计和开发之前,我们设定了采集Agent最基本的设计原则,即简单优雅、健壮稳定。

日志文件采集的一般流程会包括:文件的发现与监听、文件读取,日志内容的格式化、过滤、聚合与发送。当我们开始着手开始设计这样一个日志采集Agent时,会遇到不少关键的难点问题,比如:日志文件在哪里?如何发现日志文件新增?如何监听日志内容追加?如何识别一个文件?宕机重启怎么办?如何断点续传?等等问题,接下来,我们针对日志采集Agent设计过程中遇到的关键问题,为大家一一解答。(注:下文出现的文件路径与文件名都为演示样例非真实路径)

4.1 日志文件发现与监听

Agent要如何知道采集哪些日志文件呢?

最简单的设计,就是在Agent的本地配置文件中,把需要采集的日志文件路径都一一罗列进去,比如 /home/sample/logs/access1.log、/home/sample/logs/access2.log、/home/sample/logs/access3.log 等,这样Agent通过读取配置文件得到对应的日志文件列表,这样就能遍历文件列表读取日志信息。但是实际情况是,日志文件是动态生成的,像一般tomcat的业务日志,每个小时都会滚动生成一个新的的日志文件,日志名字通常会带上时间戳,命名类似 /data/sample/logs/

access.2021110820.log,所以采用直接配置固定的文件列表方式是行不通的。

所以,我们想到可以使用一个文件夹路径和日志文件名使用正则表达式或者通配符来表示(为了方便,下文统一使用通配符来表示)。机器上的日志一般固定存在某一个目录下,比如 /data/sample/logs/ 下,文件名由于某种规则是滚动产生的(比如时间戳),类似 access.2021110820.log、

access.2021110821.log、

access.2021110822.log,我们可以简单粗暴使用 access.*.log 的通配方法来匹配这一类的日志,当然实际情况可以根据你需要的匹配粒度去选择你的正则表达式。有了这个通配符方法,我们的Agent就能的匹配滚动产生的一批日志文件了。

如何持续发现和监听到新产生的日志文件呢?

由于新的日志文件会由其他应用程序(比如Nginx、Tomcat等)持续的按小时动态产生的,Agent如何使用通配符快速去发现这个新产生的文件呢?

最容易想到的,是使用轮询的设计方案,即是通过一个定时任务来检查对应目录下的日志文件是否有增加,但是这种简单的方案有个问题,就是如果轮询间隔时间太长,比如间隔设置为10s、5s,那么日志采集的时效性满足不了我们的需求;如果轮询间隔时间太短,比如500ms,大量的无效的轮询检查又会消耗许多CPU资源。幸好,Linux内核给我们提供一种高效的文件事件监听机制:Linux Inotify机制。该机制可监听任意文件的操作,比如文件创建、文件删除和文件内容变更,内核会给应用层一个对应的事件通知。Inotify这种的事件机制比轮询机制高效的多,也不存在CPU空跑浪费系统资源的情况。在java中,使用java.nio.file.WatchService,可以参考如下核心代码:

/** * 订阅文件或目录的变更事件 */ public synchronized BeesWatchKey watchDir(File dir, WatchEvent.Kind<?>... watchEvents) throws IOException { if (!dir.exists() && dir.isFile()) { throw new IllegalArgumentException("watchDir requires an exist directory, param: " dir); } Path path = dir.toPath().toAbsolutePath(); BeesWatchKey beesWatchKey = registeredDirs.get(path); if (beesWatchKey == null) { beesWatchKey = new BeesWatchKey(subscriber, dir, this, watchEvents); registeredDirs.put(path, beesWatchKey); logger.info("successfully watch dir: {}", dir); } return beesWatchKey; } public synchronized BeesWatchKey watchDir(File dir) throws IOException { WatchEvent.Kind<?>[] events = { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY }; return watchDir(dir, events); }

综合以上思考,日志文件的发现和日志内容变更的监听,我们使用的是"inotify机制为主 轮询机制兜底"、"通配符"的设计方案,如下图所示:

4.2 日志文件的唯一标识

要设计日志文件的唯一标识,如果直接使用日志文件的名称是行不通的,日志文件名可能被频繁重复使用,比如,一些应用程序使用的日志框架在输出日志时,对于当前应用正在输出的日志命名是不带任何时间戳信息的,比如固定是 access.log,只有等到当前小时写入文件完毕时,才把文件重命名为 access.2021110820.log,此时新生产的日志文件命名也是 access.log,该文件名对于采集Agent来说是重复的,所以文件名是无法作为文件唯一标识。

我们想到使用Linux操作系统上的文件inode号作为文件标识符。Unix/Linux文件系统使用inode号来识别不同文件,即使移动文件或重命名文件,inode号是保持不变的,创建一个新文件,会给这个新文件分配一个新的不重复的inode号,这样就能与现有磁盘上的其他文件很好区分。我们使用 ls -i access.log 可以快速查看该文件的inode号,如下代码块所示:

ls -i access.log 62651787 access.log

一般来说,使用系统的inode号作为标识,已经能满足大多数的情况了,但是为了更严谨的考虑,还可以进一步升级方案。因为Linux 的inode号存在复用的情况,这里的"复用"要和"重复"区别一下,在一台机器上的所有文件不会同一时刻出现重复的两个inode号,但是当文件删除后,另一个新文件创建时,这个文件的inode号是可能复用之前删除文件的inode号的,代码逻辑处理不好,很可能造成日志文件漏采集,这一点是要注意的。为了规避这个问题,我们把文件的唯一标识设计为" 文件inode与文件签名组合",这里的文件签名使用的是该文件内容前128字节的Hash值,代码参考如下:

public static String signFile(file file) throws IOException { String filepath = file.getAbsolutePath(); String sign = null; RandomAccessFile raf = new RandomAccessFile(filepath, "r"); if (raf.length() >= SIGN_SIZE) { byte[] tbyte = new byte[SIGN_SIZE]; raf.seek(0); raf.read(tbyte); sign = Hashing.sha256().hashBytes(tbyte).toString(); } return sign; }

关于inode再补充点小知识。Linux inode是会满的,inode的信息存储本身也会消耗一些硬盘空间,因为inode号只是inode内容中的一小部分,inode内容主要是包含文件的元数据信息:如文件的字节数、文件数据block的位置、文件的读写执行权限、文件的时间戳等,可以用stat命令,查看某个文件完整的inode信息(stat access.log)。因为这样的设计,操作系统是将硬盘分成两个区域的:一个是数据区,存放文件数据;另一个是inode区,存放inode所包含的信息。每个inode节点的大小,一般是128字节或256字节。查看每个硬盘分区的inode总数和已经使用的数量,可以使用df -i命令。由于每个文件都必须有一个inode,如果一个日志机器上,日志文件小而且数量太多,是有可能发生操作系统inode用完了即是inode区磁盘满了,但是我们使用的数据区硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。所以在日志打印规范上是要避免产生大量的小日志文件的。

vivo手机系统日志目录,vivo手机操作日志(3)

4.3 日志内容的读取

发现并且能有效监听日志文件后,我们应该如何去读取这个日志文件中实时追加的日志内容呢?日志内容的读取,我们期望从日志文件中把每一行的日志内容逐行读取出来,每一行以\n或者\r为分隔符。很显然,我们不能直接简单采用InputStreamReader去读取,因为Reader只能按照字符从头到尾读取整个日志文件,不适合读取实时追加日志内容的情况;最合适的选择应该是使用RandomAccessFile。RandomAccessFile它为代码开发者提供了一个可供设置的指针,通过指针开发者可以访问文件的随机位置,参考下图:

vivo手机系统日志目录,vivo手机操作日志(4)

首页 123下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.