分布式系统的日志监控
服务端日志你有多重视我们没有日志有日志但基本不去控制需要输出的内容经常微调日志只输出我们想看和有用的经常监控日志一方面帮助日志微调一方面及早发现程序的问题只做到第1点的你可以洗洗去睡了。很多公司都有做到第2点和第3点这些公司的服务端程序基本已经跑了很长时间了已比较稳定确实无需花太多时间去关注。如果一个新产品在上线初期我觉得就有必要做到第4点。日志怎么看都说了我们没有日志线上日志逐个tailgrep编写脚本下载某个时间范围内的全部日志到本地再搜索tailgrep或者把日志下载下来再搜索可以应付不多的主机和应用不多的部署场景。但对于多机多应用部署就不合适了。这里的多机多应用指的是同一种应用被部署到几台服务器上每台服务器上又部署着不同的多个应用。可以想象这种场景下为了监控或者搜索某段日志需要登陆多台服务器执行多个tail -F和grep命令。一方面这很被动。另一方面效率非常低数次操作下来程序员的心情也会变糟我还要去维护宇宙和平的好嘛。这篇文章讲的就是如何解决分布式系统的日志管理问题。先给大家看看最终的效果单个屏幕上所有服务器的日志实时滚动着显示。每条日志开头还标明日志的来源下图。实现这种效果的原理是后台跑着一个程序这个程序负责汇总所有日志到一个本地文件中。只要执行tail -f这个文件就可以做到监控日志了。因为所有日志都汇总在一个文件里了所以做日志搜索的时候只要针对这一个文件搜索就可以了。能够汇总日志文件的工具名字叫Logstash即本文的介绍重点。它使用JRuby编写开源主流免费使用简单宇宙和平使者必备单品。2. Logstash部署架构Logstash的理念很简单它只做3件事情Collect数据输入Enrich数据加工如过滤改写等Transport数据输出别看它只做3件事但通过组合输入和输出可以变幻出多种架构实现多种需求。这里只抛出用以解决日志汇总需求的部署架构图解释术语Shipper日志收集者。负责监控本地日志文件的变化及时把日志文件的最新内容收集起来输出到Redis暂存。Indexer日志存储者。负责从Redis接收日志写入到本地文件。Broker日志Hub用来连接多个Shipper和多个Indexer。无论是Shipper还是IndexerLogstash始终只做前面提到的3件事Shipper从日志文件读取最新的行文本经过处理这里我们会改写部分元数据输出到RedisIndexer从Redis读取文本经过处理这里我们会format文本输出到文件。一个Logstash进程可以有多个输入源所以一个Logstash进程可以同时读取一台服务器上的多个日志文件。Redis是Logstash官方推荐的Broker角色“人选”支持订阅发布和队列两种数据传输模式推荐使用。输入输出支持过滤改写。Logstash支持多种输出源可以配置多个输出实现数据的多份复制也可以输出到EmailFileTcp或者作为其它程序的输入又或者安装插件实现和其他系统的对接比如搜索引擎Elasticsearch。总结Logstash概念简单通过组合可以满足多种需求。3. Logstash的安装搭建和配置3.1. 安装Java下载JDK压缩包。一般解压到/user/local/下形成/usr/local/jdk1.7.0_79/bin这种目录结构。配置JAVA_HOME环境变量echo export JAVA_HOME/usr/local/jdk1.7.0_79 ~/.bashrc。3.2 安装Logstash去官网下载Logstash的压缩包。一般也解压到/usr/local/下形成/usr/local/logstash-1.4.3/bin这种目录结构。Logstash的运行方式为主程序配置文件。CollectEnrich和Transport的行为在配置文件中定义。配置文件的格式有点像json又有点像php。3.3. 编写Shipper角色的配置文件shipper.confinput { file { path [ # 这里填写需要监控的文件 /data/log/php/php_fetal.log, /data/log/service1/access.log ] } }如上input描述的就是数据如何输入。这里填写你需要收集的本机日志文件路径。output { # 输出到控制台 # stdout { } # 输出到redis redis { host 10.140.45.190 # redis主机地址 port 6379 # redis端口号 db 8 # redis数据库编号 data_type channel # 使用发布/订阅模式 key logstash_list_0 # 发布通道名称 } }如上output描述的就是数据如何输出。这里描述的是输出到Redis。data_type的可选值有channel和list两种。用过Redis的人知道channel是Redis的发布/订阅通信模式而list是Redis的队列数据结构。两者都可以用来实现系统间有序的消息异步通信。channel相比list的好处是解除了发布者和订阅者之间的耦合。举个例子一个Indexer在持续读取Redis中的记录现在想加入第二个Indexer如果使用list就会出现上一条记录被第一个Indexer取走而下一条记录被第二个Indexer取走的情况两个Indexer之间产生了竞争导致任何一方都没有读到完整的日志。channel就可以避免这种情况。这里Shipper角色的配置文件和下面将要提到的Indexer角色的配置文件中都使用了channel。filter { mutate { # 替换元数据host的值 replace [host, 10.140.46.134 B[1]] } }如上filter描述的是如何过滤数据。mutate是一个自带的过滤插件它支持replace操作可以改写数据。这里改写了元数据中的host字段替换成了我们自己定义的文本。Logstash传递的每条数据都带有元数据如versiontimestamphost等等。有些可以修改有些不允许修改。host记录的是当前主机的信息。Logstash可能不会去获取主机的信息或者获取的不准确这里建议替换成自己定义的主机标示以保证最终的日志输出可以有完美的格式和良好的可读性。3.4 编写Indexer角色的配置文件indexer.confinput { redis { host 10.140.45.190 # redis主机地址 port 6379 # redis端口号 db 8 # redis数据库编号 data_type channel # 使用发布/订阅模式 key logstash_list_0 # 发布通道名称 } }如上input部分设置为从redis接收数据。output { file { path /data/log/logstash/all.log # 指定写入文件路径 message_format %{host} %{message} # 指定写入格式 flush_interval 0 # 指定刷新间隔0代表实时写入 } }如上output部分设置为写入本地文件。官方文档里flush_interval为缓冲时间单位秒。我实践下来不是秒而是数量Logstash会等待缓冲区写满一定数量后才输出。这对线上调试是不能接受的建议上线初期设为0。程序稳定后随着日志量的增大可以增大flush_interval的值以提高文件写入性能。Indexer的配置文件中我明确指定了message_format的格式其中%{host}对应的就是之前手动设置的host元数据。3.5. 启动Logstash# 先在Indexer主机上启动 nohup /usr/local/logstash-1.4.3/bin/logstash agent -f indexer.conf /dev/null # 然后在Shipper主机上启动 nohup /usr/local/logstash-1.4.3/bin/logstash agent -f shipper.conf /dev/null # 最后在Indexer上观察日志 tail -f /data/log/logstash/all.log我们来测试一下切到Shipper主机上模拟日志产生echo Hello World /data/log/php/php_fetal.log再切换到Indexer主机上如果出现10.140.46.134 B[1] Hello World说明Logstash部署成功。3.6. 日志着色脚本在tail -f的时候如果使用awk配合echo可以匹配你想要高亮的文本改变他们的前景色和背景色。就像效果图里的那样这是宇宙和平使者必备单品的重要属性好嘛。这里附上我写的脚本把脚本中的关键信息替换成你想要匹配的文本即可tail -f /data/log/logstash/all.log | awk { if (match($0, /.*(PHP Deprecated|PHP Notice|PHP Fatal error|PHP Warning|ERROR|WARN).*/)) { print \033[41;37;1m$0\033[0m } else if (match($0, /.*关键信息1.*/)) { print \033[32;1m$0\033[0m } else if (match($0, /.*关键信息2.*/)) { print \033[36;1m$0\033[0m } else { print $0 } }