十二要素应用方法中说到,应该把日志当作事件流。因此日志文件不是日志本身,而只是
一种输出格式。日志是流,意味着它表示按时间排序的事件。它的原始格式通常为文本格式,每
行代表一个事件,但在某些情况下可能跨越多行。这是与运行错误相关的任何回溯的典型情况。
根据十二要素应用方法论,应用不应该知道日志的存储格式。也就是说,不应该由应
用代码来维护对文件的写入或日志的转储(rotation)和保留。这些是应用运行环境的责任。
这可能会造成一些困扰,因为许多框架都提供了用于管理日志文件的函数和类以及转储、
压缩和保留的实用程序。这些工具是很吸引人的,因为一切都可以包含在应用代码库中,
但实际上这是一种应该避免的反模式。
处理日志的最佳约定可以总结为几条规则:
● 应用应该总是将未缓冲的日志写入标准输出(stdout)。
● 执行环境应该负责收集日志并将其发送给最终目标。
对于上面提到的执行环境,其主要部分通常是某种进程管理工具。常见的 Python 解决
方案(例如 Supervisor 或 Circus)是第一批负责处理日志收集和发送的。如果日志要保存
在本地文件系统中,那么只将日志写入实际的日志文件中。
Supervisor 和 Circus 都能够处理被管理进程的日志转储和保留,但是你应该考虑这是
否是你想要的技术路线。成功的运营主要在于简单性和一致性。你自己的应用日志可能不
是你唯一想要处理和归档的日志。如果你使用 Apache 或 Nginx 作为反向代理,那么你可能
想要收集其访问日志。你可能还希望存储并处理缓存和数据库的日志。如果你正运行某个
流行的 Linux 发行版,那么很可能每个服务自己的日志文件都被一个叫作 logrotate 的
流行实用程序处理(转储、压缩等)。我强烈建议忘记 Supervisor 和 Circus 的日志转储功能,
以便与其他系统服务保持一致。logrotate 的可配置性更高,也支持压缩。
日志处理工具
如果你没有处理大量日志的经验,那么在使用负载很高的产品时,你最终会获得这方
面的经验。你很快会注意到,基于将日志保存在文件中并在某个永久存储中备份以用于稍
后检索的简单方法是不够的。如果没有合适的工具,这将会变得简陋且代价高昂。简单的
实用程序(如 logrotate)只能帮你确保硬盘不会由于不断增加的新事件数量而溢出,
但是分割和压缩日志文件仅有助于数据归档过程,但不会使数据检索或分析变得更简单。
在使用跨越多个节点的分布式系统时,最好有一个中心点,从中可以检索和分析所有
日志。这需要一个不止于简单的压缩和备份的日志处理流程。幸运的是,这是一个众所周
知的问题,因此有许多可用的工具可以解决它。
许多开发人员最常见的选择之一是 Logstash。它是日志收集守护进程,可以观察活跃
的日志文件、解析日志条目并以结构化形式将其发送到后端服务。后端服务的选择几乎总
是相同的——Elasticsearch。Elasticsearch 是在基于 Lucene 构建的搜索引擎。除了文本搜索
能力,它还有一个独特的数据聚合框架,非常适合日志分析的目的。
除了这两种工具,还有一个是 Kibana。它对于 Elasticsearch 来说是一个非常通用的监
控、分析和可视化平台。由于这 3 种工具互相补充的方式,所以它们几乎总是同时用于日
志处理。
将现有服务与 Logstash 集成非常简单,因为它可以监听现有日志文件对新事件的更改,
并只需对日志配置进行最少的修改。它以文本形式解析日志,并预配置了对一些常用日志
格式的支持,例如 Apache/Nginx 的访问日志。Logstash 的唯一问题是它不能很好地处理日
志转储,这有点令人惊讶。通过发送一个已定义的 Unix 信号(通常是 SIGHUP 或 SIGUSR1)
来强制进程重新打开其文件描述符是一个相当完善的模式。似乎每个处理日志的应用(唯
一地)都应该知道并且能够处理各种日志文件转储场景。遗憾的是,Logstash 并不是其中
之一,因此如果你想使用 logrotate 实用程序来管理日志保留,记得要大量依赖其
copytruncate 选项。Logstash 进程无法处理原始日志文件被移动或删除的情况,因此,
如果没有 copytruncate 选项,它在日志转储之后将无法接收新事件。Logstash 当然可以
处理不同的日志流输入,例如 UDP 包、TCP 连接或 HTTP 请求。
另一个弥补 Logstash 某些缺点的解决方案是 Fluentd。它是一个备用的日志收集守护程
序,可与上述日志监控堆栈中的 Logstash 交换使用。它还有一个选项,可以在日志文件中
直接监听并解析日志事件,因此只需要少许工作就可以做到最小集成。与 Logstash 相反,
它对重新加载的处理非常好,甚至不需要被通知日志文件是否转储。无论如何,最大的优
势来自于使用其中一个替代日志收集选项,这需要对应用中的日志配置进行一些实质性的
修改。
Fluentd 的确把日志当作事件流(正如十二要素应用的建议)。基于文件的集成仍
是可能的,但只是对于将日志主要作为文件的遗留应用的向后兼容。每个日志条目都
是一个事件,应该都是结构化的。Fluentd 可以解析文本日志,并有多个插件选项可用
于处理。
● 常见格式(Apache、Nginx 和 syslog)。
● 使用正则表达式指定的任意格式或自定义解析插件处理的任意格式。
● JSON 等结构化消息的通用格式。
Fluentd 的最佳事件格式是 JSON,因为它增加的开销最小。对于 JSON 中的消息,几
乎不需任何更改就可以传递给后端服务(例如 Elasticsearch 或数据库)。
Fluentd 另一个非常有用的功能是利用传输而不是写入磁盘的日志文件来传递事件流
的能力。最有名的内置输入插件包括。
● in_udp:利用这个插件,每个日志事件都作为 UDP 包发送。
● in_tcp:利用这个插件,事件通过 TCP 连接发送。
● in_unix:利用这个插件,事件通过 Unix 域套接字(名称套接字)发送。
● in_http:利用这个插件,事件作为 HTTP POST 请求发送。
● in_exec:利用这个插件,Fluentd 进程定期执行外部命令,以 JSON 或 MessagePack
格式拉取事件。
● in_tail:利用这个插件,Fluentd 进程监听文本文件中的事件。
日志事件的备用传输在需要处理机器存储的较低 I/O 性能的情况下可能特别有用。在
云计算服务上,默认磁盘存储通常具有非常低的 IOPS(Input Output Operations Per Second,
每秒输入输出操作)数,你需要花很多钱才能获得更好的磁盘性能。如果你的应用输出了
大量日志消息,那么即使数据不是很大,I/O 能力也很容易饱和。有了备用传输,你可以更
有效地使用硬件,因为你将数据缓冲的责任只留给了单个进程——日志收集器。如果配置
在内存中而不是磁盘中缓冲消息,你甚至可以完全不需要日志的磁盘写入,尽管这可能会
大大降低已收集日志的一致性保证。
使用不同的传输似乎有些违反十二要素应用方法的第 11 条规则。对“把日志作为事件
流”这一条详细解释的话,应用应始终只通过单一标准输出流(stdout)进行日志记录。
在不破坏此规则的情况下,仍然可以使用备用传输。写入 stdout 并不一定意味着这个流
必须写入文件。你可以保留应用的日志记录方式,并用一个外部进程包装它,这个外部进
程将捕获这个流并将其直接传递给 Logstash 或 Fluentd,同时不占用文件系统。这是一种高
级模式,可能不适合于每个项目。它有一个明显的缺点就是复杂度更高,所以你需要自己
考虑是否真的值得这么做。
小结
代码部署并不是一个简单的主题,读完本章之后你应该已经认识到这一点。对这个问
题的广泛讨论很可能需要几本书。即使将范围限定于 Web 应用,我们也只是介绍了一些皮
毛而已。本章以十二要素应用方法为基础。我们仅详细讨论了其中几个要素:日志处理、
管理依赖关系和分离构建/运行阶段。
读完本章后,你应该知道如何根据最佳实践正确地将部署过程自动化,并能够为在远
程主机上运行的代码添加适当的检测与监控。
Python 高手编程系列二百五十九:基本的低级日志实践
最新推荐文章于 2024-09-07 20:08:36 发布