从 0 到 1,看我玩弄千万日志于股掌

大千世界 170 0

  程序员的工作离不开日志。

  日志就像一个笔记本,可以记录程序运行时的一些信息。

  

从 0 到 1,看我玩弄千万日志于股掌-第1张图片-大千世界


  日志文件

  通过日志,我们可以做很多事情。

  日志的作用

  1. 记录系统和接口的使用情况,比如请求日志

  2. 记录和分析用户的行为,比如网站访问日志

  3. 调试程序,和控制台的作用类似,但是控制台中的内容并不会保存到文件中,而日志可以长期保存。

  4. 帮助我们排查和定位错误。比如在系统抛出异常时,将异常信息记录到日志,可以事后复盘。

  5. 通过分析日志还能够优化代码逻辑、提升系统性能、稳定性等。

  

从 0 到 1,看我玩弄千万日志于股掌-第2张图片-大千世界


  日志虽然有那么多的作用,但如果数量过多,也会让开发人员感到头疼。对于大型的系统,程序员们经常要看几千、几万行日志,常常看日志看到头晕眼花。

  

从 0 到 1,看我玩弄千万日志于股掌-第3张图片-大千世界


  但是,其实处理日志是有很多技巧的,下面鱼皮分享自己和日志的故事。

  故事分为 7 个阶段,从看日志看到怀疑人生,再到玩弄千万日志于股掌,鱼皮都做了哪些事情?

  鱼皮和日志的爱恨情仇

  第一阶段 无日志

  刚开始搭建新的系统时,为了图个方便,鱼皮没有给系统接入任何的日志框架,也没有记录任何日志,整个项目非常的干净丝滑。需要调试时就直接用输出函数将信息打印在控制台,出 了异常就直接打印堆栈。

  

从 0 到 1,看我玩弄千万日志于股掌-第4张图片-大千世界


  真是无事一身轻,爽 的不得了!

  可惜,好景不长。在项目上线之后,突然有一天,系统出问题了,数据查不出来了,同事找上门来了。

  鱼皮笑着说:“问题不大!”

  

从 0 到 1,看我玩弄千万日志于股掌-第5张图片-大千世界


  然后登录服务器,进入项目目录,瞬间傻眼。

  项目目录依旧干净丝滑,原来我特么根本没记日志啊!

  这下好了,什么问题都查不出来。乖乖地去给项目加上日志功能吧。

  第二阶段 引入日志类库

  Java 语言有很多优秀的日志类库,比如 Logback、Log4j2 等,提供了很多记录和打印日志的方法,非常方便。可以直接使用其中一个类库,而无需自己实现。此处因为鱼皮的项目使用 Spring Boot 框架进行开发,直接使用其默认日志库 Logback 即可。

  使用方式很简单,先添加 logback.xml 配置文件,主要配置了日志文件的存储路径和格式。Logback 框架还会自动将日志按天进行压缩,并且在一定天数后进行删除,以节约磁盘空间。最大存储天数也可以在配置文件中指定。

  配置文件大概长这样:

  

从 0 到 1,看我玩弄千万日志于股掌-第6张图片-大千世界


  在要打印日志的类上创建一个日志对象:

  Logger logger = LoggerFactory.getLogger(MyApp.class);

  然后就可以使用该对象去记录日志啦:

  catch(Exception e) { logger.error("app error", e);}

  加上配置文件后,启动项目,就可以看见生成的日志文件了。欧耶,下次系统再出问题,就不怕缺乏信息来排错啦!

  

从 0 到 1,看我玩弄千万日志于股掌-第7张图片-大千世界


  系统运行了一个小时之后,同事又找上门来了,这次鱼皮很有底气,笑着说:“问题不大!”

  

从 0 到 1,看我玩弄千万日志于股掌-第5张图片-大千世界


  然后打开日志文件一看,傻眼了,有几千行日志,我怎么知道哪行日志是报错信息呢?

  

从 0 到 1,看我玩弄千万日志于股掌-第9张图片-大千世界


  就这你能秒了我?直接用 Linux 命令过滤出带 “ERROR” 字段的日志行就行了~

  cat application.log | grep 'ERROR'

  虽然解决了问题,但是后面每次报错,都要输一遍这个筛选命令,而且随着文件越来越大,命令执行的速度越来越慢了。

  能不能把所有错误日志和正常日志区分开,放在不同的文件中呢?

  第三阶段 日志分级

  幸运的是,一般的日志框架都提供了日志分级存储功能,可以通过修改配置文件来实现。

  修改 logback.xml 配置文件,将 ERROR(错误)级别的日志单独输出到 error.log 文件中,实现日志分级:

  

从 0 到 1,看我玩弄千万日志于股掌-第10张图片-大千世界


  启动项目,日志按预期分级写到了 application.log 和 error.log 两个文件。系统再出现异常时,鱼皮只需打开 error.log 文件,错误信息一览无遗!

  

从 0 到 1,看我玩弄千万日志于股掌-第11张图片-大千世界


  系统运行一段时间后,鱼皮上线了一个很重要的服务,记录了相当多的业务日志。虽然目前错误日志可以单独查看,但是核心服务的日志和其他服务的正常日志都堆积在 application.log 中,想要仅查看核心服务的日志依旧要采用命令过滤的方式,比较麻烦。

  

从 0 到 1,看我玩弄千万日志于股掌-第12张图片-大千世界


  有没有什么办法,把核心业务的日志单独记录到一个文件中呢?

  第四阶段 按类隔离

  幸运的是,Logback 日志框架支持将不同的类产生的日志记录到不同的文件中,修改配置文件即可。比如将所有 RequestAOP 类产生的请求日志记录到 request.log 中:

  

从 0 到 1,看我玩弄千万日志于股掌-第13张图片-大千世界


  启动项目,自动生成了 request.log 文件,打开这个文件,就可以查看所有的请求日志,可以用于流控分析等等,真爽死了!

  

从 0 到 1,看我玩弄千万日志于股掌-第14张图片-大千世界


  后来,随着系统的访问量越来越大,单台服务器已经不能满足对并发的需求,因此鱼皮又加了三台机器,共同提供服务。

  

从 0 到 1,看我玩弄千万日志于股掌-第15张图片-大千世界


  有一天,系统又出问题了,同事找上门来,鱼皮心想:信不信分分钟给你解决 bug!

  

从 0 到 1,看我玩弄千万日志于股掌-第5张图片-大千世界


  一顿操作猛如虎,登录一台服务器查看日志,结果错误日志空空如也,比鱼皮的兜儿都干净。

  奇怪了,怎么找不到错误信息?

  对啊,现在有四台机器,请求可能落在了其他机器上,因此错误日志也可能在别的机器上!

  

从 0 到 1,看我玩弄千万日志于股掌-第17张图片-大千世界


  哎,没办法,一台一台登录服务器去找错误信息吧。

  其实四台机器还能忍,但是后来随着并发量的增大,鱼皮负责的系统已经有十台机器了,每次查看日志要依次登录十台机器去找!而且单个日志数据的量已经达到几十万行,无论怎么切分看起来都太累了。

  哦,乔治,这太难受了!有没有什么办法,能让我在一个地方集中看日志啊!

  

从 0 到 1,看我玩弄千万日志于股掌-第18张图片-大千世界


  要不直接把日志记录到数据库中?

  不行不行,日志数据量太大了,数据库肯定存不下。而且写入数据库的速度受到网络传输等限制,比较缓慢。

  怎么办啊?算了,先睡一觉。

  

从 0 到 1,看我玩弄千万日志于股掌-第19张图片-大千世界


  第五阶段 日志上报与集中式管理

  “嘿,少年,你想要力量么?”

  “废话,谁不想要!”

  “听说过 ELK 么?他会指引你前进的方向。”

  鱼皮从梦中惊醒,对啊,可以用 ELK 搭建一个分布式日志收集系统啊!

  

从 0 到 1,看我玩弄千万日志于股掌-第20张图片-大千世界


  ELK 是 Elasticsearch、Logstash 和 Kibana 的简称,不是单独的一个软件,而是一整套问题的解决方案。

  Elasticsearch(简称 ES)是全文搜索引擎,能够对海量数据进行存储和高效的搜索。

  

从 0 到 1,看我玩弄千万日志于股掌-第21张图片-大千世界


  Logstash 是一个数据管道,能够从各种数据源(比如 MySQL 数据库)收集数据,将数据从一处传输到另一处,并加以解析和转换。

  

从 0 到 1,看我玩弄千万日志于股掌-第22张图片-大千世界


  Kibana 是数据可视化平台,可以将 Elasticsearch 中存储的数据进行展示。在 Kibana 上,我们不仅可以看到所有原始的日志信息,还能够自定义各种精美直观的可视化图表。

  

从 0 到 1,看我玩弄千万日志于股掌-第23张图片-大千世界


  通常使用 Logstash 统一收集各个机器上的数据,并传输至 Elasticsearch 进行存储,最后通过 Kibana 进行数据展示,之后就可以利用 Kibana 轻松地查看和分析所有的数据了。

  

从 0 到 1,看我玩弄千万日志于股掌-第24张图片-大千世界


  既然可以解决问题,那就接入 ELK 吧~

  但是使用 ELK 相当于为系统引入了三个新组件,考虑到系统使用的组件越多,复杂度越高,就越难维护;而且 Logstash 比较重,对 CPU 和内存的占用较高。因此,鱼皮灵机一动,干脆舍弃掉 Logstash,直接将 Elasticsearch 当成数据库来使用。

  先在 Spring Boot 中整合 Elasticsearch,然后将日志数据通过依赖包提供的 API 接口存储到 Elasticsearch,最后接入 Kibana 进行展示。

  Maven 引入依赖:

  

从 0 到 1,看我玩弄千万日志于股掌-第25张图片-大千世界


  访问 ES 的接口:

  @Repositorypublic interface UserRepository extends ElasticsearchRepository{

  }

  想法是美好的,现实却是贼特么残酷的。

  虽然 Spring Boot 接入 Elasticsearch 的确很方便,但是要把整个项目中的记日志代码全部替换成写入 ES 的代码,对项目的改动和侵入性太大了。而且将日志存入 ES 的耗时远远大于原来异步写入文件的耗时,并发量很大时,偶尔出现日志写入失败的情况。因此改代码改到一半时,鱼皮就抓狂放弃了,直接把改了的代码全部还原。

  

从 0 到 1,看我玩弄千万日志于股掌-第26张图片-大千世界


  鱼皮再次陷入沉思,有没有一种方式,可以在不改动一行代码的情况下,将日志写入 ES 呢?

  第六阶段 日志代理

  如果不改动任何代码,每台机器产生的日志仍然是独立记录到当前机器的日志文件中的,想要通过一个界面集中查看各机器上的日志非常麻烦。

  那如果把日志文件中的数据自动同步到 ES 上,不就能通过 Kibana 方便地查看了么!

  谁来做同步这件事呢?难道要我自己写个定时任务程序把日志文件上传到 ES 上?如果是那样,我还不如继续改原来项目的代码。

  

从 0 到 1,看我玩弄千万日志于股掌-第27张图片-大千世界


  能不能找个代理来帮我做这件事呢?

  就像我们每天丢辣鸡一样,把辣鸡丢到小区门口的辣鸡桶就行了,然后辣鸡车会帮我们把辣鸡运送至辣鸡站集中处理。

  我们的日志文件相当于辣鸡,代理就相当于辣鸡车,而 ES 就相当于辣鸡站。

  通过百度,发现 ELK 早就升级为 ElasticStack 了,除了上面说的三大组件外,还多了一个 Beats。

  Beats 是轻量级数据采集器,针对不同的数据类型提供了不同的组件。

  

从 0 到 1,看我玩弄千万日志于股掌-第28张图片-大千世界


  要将日志文件数据上传到 ES 进行存储,可以使用 Filebeat。Filebeat 是轻量型日志采集器,其提供了一种轻量型方法,用于转发和汇总日志与文件,让我们轻松面对成百上千、甚至成千上万的服务器、虚拟机和容器生成的日志。

  Filebeat 就相当于一个代理(agent),可以帮助收集各个机器上的日志,然后传输给 Logstash 进行处理或者直接传输到 Elasticsearch 进行存储。这样就完全不用修改项目的代码!

  ElasticStack 整体架构如下:

  

从 0 到 1,看我玩弄千万日志于股掌-第29张图片-大千世界


  那怎么使用 Filebeat 呢?

  其实非常简单,直接将 Filebeat 安装到日志文件所在的服务器上,然后在其配置文件中定义输入(要采集的日志文件路径)和输出(要将采集到的数据发送到哪里)即可。比如在下面的配置中,会采集 system 日志并传输给 Logstash:

  filebeat.inputs:- type: log paths: - /var/log/system.log

  output.logstash: hosts: ["127.0.0.1:5044"]

  搞定,这下真是爽死了!曾经几千行日志就能将我淹没,不知所措。而现在,只需要打开 Kibana 控制台,动动手指,就能轻松地查看和分析几十万、几百万的日志。

  

从 0 到 1,看我玩弄千万日志于股掌-第30张图片-大千世界


  感觉自己就像一个大将军,这些日志是我统治的小兵,都得乖乖听我号令,好不痛快!

  

从 0 到 1,看我玩弄千万日志于股掌-第31张图片-大千世界


  第七阶段 完善日志架构

  利用 ElasticStack,已经能够轻松地集中管理海量的日志,而且 Elasticsearch 支持水平扩容,可以应对日志量级的持续增大,存个千万条数据完全没有问题。

  但是,当每秒产生的日志量过多时,ElasticStack 并不是无敌的,虽然 Filebeat、Elasticsearch、Kibana 都很强劲,但往往 Logstash 是那阿喀琉斯之踵(或者三娃的屁股)!

  

从 0 到 1,看我玩弄千万日志于股掌-第32张图片-大千世界


  因为 Logstash 要同时接受多个 Filebeat 采集的日志,机器越多,部署的 Filebeat 也就越多,Logstash 的压力就会越大。虽然也可以像扩容 ES 一样增加 Logstash 的节点数,但是并不能从根本上解决问题,当日志量级增大到一定程度时,不仅是 Logstash,连 ES 集群都有可能撑不住!

  因此,我们需要接入一些中间件来进行缓冲,首选的可靠且高性能的消息队列 Kafka(依赖分布式协调工具 Zookeeper)。

  最终,完善的分布式日志收集系统架构如下:

  

从 0 到 1,看我玩弄千万日志于股掌-第33张图片-大千世界


  至此,鱼皮终于将千万日志玩弄于股掌,这种感觉真的是太爽了。

  

从 0 到 1,看我玩弄千万日志于股掌-第34张图片-大千世界


  如果你也正在被日志折磨,一定要试着搭建一套完善的日志系统。

  最后分享自己记录日志的经验:

  1. 不要过度依赖日志,什么都记,日志应当简洁明晰,具有实际价值。

  2. 在保证可理解的同时适当减少日志的长度,比如把 this is an apple 简化为 apple。

  3. 将日志进行分级和分类,仅在开发和测试环境输出 DEBUG 级别日志,不要在生产环境中使用。

  4. 统一日志的格式,便于后续处理分析,通常在日志框架配置即可。

  5. 不要把日志当成存储数据的工具!注意日志信息中不能出现敏感信息,也不要对外公开!

  鱼皮从 0 到 1,经历了七个阶段,成功地玩弄千万日志于股掌。其实,无论是学习还是实际应用,我们都需要有这种持续实践、探索和优化的精神。

  会当凌绝顶,一览众山小。


标签: 日志

抱歉,评论功能暂时关闭!