Go语言进阶(十五)日志

日志

日志级别

https://github.com/golang/glog, 是 google 提供的一个不维护的日志库,glog 有其他语言的一些版本,对我当时使用 log 库有很大的影响。它包含如下日志级别:

  • info
  • warning
  • error
  • fatal (会中断程序执行)

还有类似 log4go,loggo,zap 等其他第三方日志库,他们还提供了设置日志级别的可见行,一般提供日志级别:

  • debug
  • trace
  • info
  • warning
  • error
  • critical

Warning

没人会看警告,因为从定义上讲,没有什么出错。也许将来会出问题,但这听起来像是别人的问题。我们尽可能的消除警告级别,它要么是一条信息性消息,要么是一个错误。我们参考 Go 语言设计额哲学,所有警告都是错误,其他语言的 warning 都可以忽略,除非 IDE 或者在 CICD 流程中强制他们为 error,然后逼着程序员们尽可能去消除。同样的,如果想要最终消除 warning 可以记录为 error,让代码作者重视起来。

Fatal

记录消息后,直接调用 os.Exit(1),这意味着:

  • 在其他 goroutine defer 语句不会被执行;
  • 各种 buffers 不会被 flush,包括日志的;
  • 临时文件或者目录不会被移除;

不要使用 fatal 记录日志,而是向调用者返回错误。如果错误一直持续到 main.main。main.main 那就是在退出之前做处理任何清理操作的正确位置。

Error

也有很多人,在错误发生的地方要立马记录日志,尤其要使用 error 级别记录。

  • 处理 error;
  • 把 error 抛给调用者,在顶部打印日志;

如果您选择通过日志记录来处理错误,那么根据定义,它不再是一个错误 — 您已经处理了它。记录错误的行为会处理错误,因此不再适合将其记录为错误。

Debug

相信只有两件事你应该记录:

  • 开发人员在开发或调试软件时关心的事情。
  • 用户在使用软件时关心的事情。

显然,它们分别是调试和信息级别。

log.Info 只需将该行写入日志输出。不应该有关闭它的选项,因为用户只应该被告知对他们有用的事情。如果发生了一个无法处理的错误,它就会抛出到 main.main。main.main 程序终止的地方。在最后的日志消息前面插入 fatal 前缀,或者直接写入 os.Stderr。

log.Debug,是完全不同的事情。它由开发人员或支持工程师控制。在开发过程中,调试语句应该是丰富的,而不必求助于 trace 或 debug2(您知道自己是谁)级别。日志包应该支持细粒度控制,以启用或禁用调试,并且只在包或更精细的范围内启用或禁用调试语句。

Logger

案例,在 package 使用的时候,

上面代码有2个问题:

  • foo 耦合了 mylogger
  • 所有使用 foo 的其他库,被透明依赖了 mylogger

我们应该怎么做?使用interface, 让外部传入,依赖倒置

日志选型

一个完整的集中式日志系统,需要包含以下几个主要特点:

  • 收集-能够采集多种来源的日志数据;
  • 传输-能够稳定的把日志数据传输到中央系统;
  • 存储-如何存储日志数据;
  • 分析-可以支持 UI 分析;
  • 警告-能够提供错误报告,监控机制;

开源界鼎鼎大名 ELK stack,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个 FileBeat,它是一个轻量级的日志收集处理工具(Agent),Filebeat 占用资源少,适合于在各个服务器上搜集日志后传输给 Logstash,官方也推荐此工具。

image-20211224151000486

此架构由 Logstash 分布于各个节点上搜集相关日志、数据,并经过分析、过滤后发送给远端服务器上的 Elasticsearch 进行存储。

Elasticsearch 将数据以分片的形式压缩存储并提供多种 API 供用户查询,操作。用户亦可以更直观的通过配置 Kibana Web方便的对日志查询,并根据数据生成报表。

因为 logstash 属于 server 角色,必然出现流量集中式的热点问题,因此我们不建议使用这种部署方式,同时因为 还需要做大量 match 操作(格式化日志),消耗的 CPU 也很多,不利于 scale out。

这种架构引入了消息队列机制,位于各个节点上的 Logstash Agent 先将数据/日志传递给 Kafka,并将队列中消息或数据间接传递给 Logstash,Logstash 过滤、分析后将数据传递给Elasticsearch 存储。最后由 Kibana 将日志和数据呈现给用户。因为引入了 Kafka,所以即使远端 Logstash server 因故障停止运行,数据将会先被存储下来,从而避免数据丢失。

image-20211224151741817

更进一步的:

将收集端 logstash 替换为 beats,更灵活,消耗资源更少,扩展性更强。

设计目标

  • 接入方式收敛;
  • 日志格式规范;
  • 日志解析对日志系统透明;
  • 系统高吞吐、低延迟;
  • 系统高可用、容量可扩展、高可运维性;

格式规范

JSON作为日志的输出格式:

  • time: 日志产生时间,ISO8601格式;
  • level: 日志等级,ERROR、WARN、 INFO、DEBUG;
  • app_id: 应用id,用于标示日志来源;
  • instance_id: 实例 id,用于区分同一应用不同实例,即 hostname;

image-20211224152007227

otel规范:https://github.com/open-telemetry/opentelemetry-specification

设计与实现

日志从产生到可检索,经历几个阶段:

  • 生产 & 采集
  • 传输 & 切分
  • 存储 & 检索

采集

logstash:

  • 监听 tcp/udp
  • 适用于通过网络上报日志的方式

filebeat:

  • 直接采集本地生成的日志文件
  • 适用于日志无法定制化输出的应用

logagent:

  • 物理机部署,监听 unixsocket
  • 日志系统提供各种语言 SDK
  • 直接读取本地日志文件

image-20211224152248268

logagent设计

image-20211224152337024

传输

基于 flume + Kafka 统一传输平台

基于 LogID 做日志分流:

  • 一般级别
  • 低级别
  • 高级别(ERROR)

image-20211224152508908

切分

从kafka消费日志,解析日志,写入elasticsearch,

bili-index: B站自研,golang 开发,逻辑简单,性能 高, 可定制化方便。

  • 日志规范产生的日志(log agent 收集)

logstash: es 官方组件,基于 jruby 开发,功能强大, 资源消耗高,性能低。

  • 处理未按照日志规范产生的日志(filebeat、logstash 收集),需配置各种日志解析规则。

image-20211224152659664

存储和检索

elasticsearch多集群架构:

  • 日志分级、高可用

单数据集群内:

  • master node + data node(hot/stale) + client node
  • 每日固定时间进行热->冷迁移
  • Index 提前一天创建,基于 template 进行mapping 管理
  • 检索基于 kibana

image-20211224153128004

文件

使用自定义协议,对 SDK 质量、版本升级都有比较高的要求,因此我们长期会使用“本地文件”的方案实现:

  • 采集本地日志文件:位置不限,容器内 or 物理机
  • 配置自描述:不做中心化配置,配置由 app/paas 自身提供,agent 读取配置并生效
  • 日志不重不丢:多级队列,能够稳定地处理日志收集过程中各种异常
    可监控:实时监控运行状态
  • 完善的自我保护机制:限制自身对于宿主机资源的消耗,限制发送速度

容器日志采集

容器内应用日志采集:基于 overlay2,直接从物理机上查找对应日志文件

image-20211224153426342