详解日志

相比起监控,日志好理解的多:在某个时间点向指定的地方输出一条信息,里面记录着重要性、时间、地点和发生的事件,这就是日志。

注意,本文和 Rust 无关,我们争取从一个中立的角度去介绍何为日志

日志级别和输出位置

日志级别

日志级别是对基本的“滚动文本”式日志记录的一个重要补充。每条日志消息都会基于其重要性或严重程度分配到一个日志级别。例如,对于某个程序,“你的电脑着火了”是一个非常重要的消息,而“无法找到配置文件”的重要等级可能就低一些;但对于另外一些程序,"无法找到配置文件" 可能才是最严重的错误,会直接导致程序无法正常启动,而“电脑着火”? 我们可能会记录为一条 Debug 日志(参见下文) :D。

至于到底该如何定义日志级别,这是仁者见仁的事情,并没有一个约定俗成的方式,就连很多大公司,都无法保证自己的开发者严格按照它所制定的规则来输出日志。而下面是我认为的日志级别以及相关定义:

  • Fatal: 程序发生致命错误,祝你好运。这种错误往往来自于程序逻辑的严重异常,例如之前提到的“无法找到配置文件”,再比如无法分配足够的硬盘空间、内存不够用等。遇到这种错误,建议立即退出或者重启程序,然后记录下相应的错误信息

  • Error: 错误,一般指的是程序级别的错误或者严重的业务错误,但这种错误并不会影响程序的运行。一般的用户错误,例如用户名、密码错误等,不使用 Error 级别

  • Warn: 警告,说明这条记录信息需要注意,但是不确定是否发生了错误,因此需要相关的开发来辨别下。或者这条信息既不是错误,但是级别又没有低到 info 级别,就可以用 Warn 来给出警示。例如某条用户连接异常关闭、无法找到相关的配置只能使用默认配置、XX秒后重试等

  • Info: 信息,这种类型的日志往往用于记录程序的运行信息,例如用户操作或者状态的变化,再比如之前的用户名、密码错误,用户请求的开始和结束都可以记录为这个级别

  • Debug: 调试信息,顾名思义是给开发者用的,用于了解程序当前的详细运行状况, 例如用户请求详细信息跟踪、读取到的配置信息、连接握手发包(连接的建立和结束往往是 Info 级别),就可以记录为 Debug 信息

可以看出,日志级别很多,特别是 Debug 日志,如果在生产环境中开启,简直就是一场灾难,每秒几百上千条都很正常。因此我们需要控制日志的最低级别:将最低级别设置为 Info 时,意味着低于 Info 的日志都不会输出,对于上面的分级来说,Debug 日志将不会被输出。

有些开发为了让特定的日志在控制上显示更明显,还会为不同的级别使用不同颜色的文字。

输出位置

通常来说,日志可以输出两个地方:终端控制台和文件。对于前者,我们还有一个称呼标准输出,例如使用 println! 打印到终端的信息就是输出到标准输出中。

如果没有日志持久化的需求,你只是为了调试程序,建议输出到控制台即可。悄悄的说一句,我们还可以为不同的级别设定不同的输出位置,例如 Debug 日志输出到控制台,既方便开发查看,但又不会占用硬盘,而 InfoWarning 日志可以输出到文件 info.log 中,至于 ErrorFatal 则可以输出到 error.log 中。

但是如果大家以为只有输出到文件才能持久化日志,那你就错了,在后面的日志采集我们会详细介绍,先来看看日志查看。

日志查看

关于如何查看日志,相信大家都非常熟悉了,常用的方式有三种(事实上,可能也只有这三种):

  • 在控制台查看,即可以直接查看输出到标准输出的日志,还可以使用 tail、cat、grep 等命令从日志文件中搜索查询或者以实时滚动的方式查看最新的日志
  • 最简单的,进入到日志文件中,进行字符串搜索,或者从头到尾、从尾到头进行逐行查看
  • 在可视化界面上查看,但是这个往往要配合日志采集工具,将日志采集到 ElasticSearch 或者其它搜索平台、数据中,然后再通过 kibana、grafana 等图形化服务进行搜索、查看,最重要的是可以进行日志的聚合统计,例如可以很方便的在 kibana 中查询满足指定条件的日志在某段时间内出现了多少次。

大家现在知道了,可视化,首先需要将日志集中采集起来,那么该如何采集日志呢?

日志采集

之前我们提到,不是只有输出到文件才能持久化日志,事实上,输出到控制台也能持久化日志。

其中的秘诀就在于使用一个日志采集工具去从控制台的标准输出读取日志数据,然后将读取到的数据发送到日志存储平台,例如 ElasticSearch,进行集中存储。当然,在存储前,还需要进行日志格式、数据的处理,以便只保留我们需要的格式和日志数据。

最典型的就是容器或容器云环境的日志采集,基本都是通过上面的方式进行的:容器中的进程将日志输出到标准输出,然后一个单独的日志采集服务直接读取标准输出中的日志,再通过网络发送到日志处理、存储的平台。大家发现了吗?这个流程完全不会在应用运行的本地或宿主机上存储任何日志,所以特别适合容器环境!

目前常用的日志采集工具有 filebeat、vector( Rust 开发,功能强大,性能非常高 ) 等,它们都是以 agent 的形式运行在你的应用程序旁边( 在同一个 pod 或虚拟机上 ),提供贴心的服务。

中心化日志存储

最后,我们再来简单介绍下日志存储。提到存储,首先不得不提的就是日志使用方式。

其实,除了 Debug 的时候,我们使用日志基本都是基于某个关键字进行搜索的,将日志存储在各台主机上的硬盘文件中,然后逐个去查询显然是非常非常低效的,最好的方式就是将日志集中收集上来后,存储在一个搜索平台中,例如 ElasticSearch。

当然,存储的时候肯定也不是简单的一行一行存储,而是需要将一条日志的多个关键词切取出来,然后以关键词索引的方式进行存储( 简化模型 ),这样我们就可以在后续使用时,通过关键词来搜索日志了。