Loki系统中使用Promtail处理解析SpringBoot的Log日志

近期需要使用Loki搭建日志系统,并匹配一批基于SpringBoot的模块的日志。经过学习和查找终于弄通了流程,这里记录一下相关知识点

# 什么是loki

Loki是Grafana开源的一个轻量级日志系统。相对于传统的ELK日志系统,Loki占用的系统资源更少、速度更快,非常适合不需要复杂功能的中小型系统

官方文档 https://grafana.com/docs/loki/latest/ 官方中文文档 https://grafana.org.cn/docs/loki/latest/

# 主要框架结构

参考官方文档(https://grafana.org.cn/docs/loki/latest/get-started/),Loki系统主要需要以下几个部分:

  1. Grafana:前端系统,用于显示查询界面
  2. Loki:主服务器,用于收集每个终端上传后的日志,对日志进行排序与聚合,并提供查询服务给grafana
  3. Promtail:日志上传代理,用于监控每个节点的日志并将日志发送到loki服务器。需要部署在每个产生日志的节点或Pod中,收集并将日志发送到Loki系统中
  4. 输出日志的服务:业务中需要被监控日志的服务,(建议将服务的日志输出路径规范化)
  • Grafana系统在各种监控中经常使用。因此在这个框架系统中,可以复用其他查询系统中已搭建的Grafana系统
  • Loki系统的搭建比较简单,对于小型系统,可以直接使用默认配置文件,基于Docker或二进制文件启动。这里不讨论
  • 本文主要讨论针对Promtail程序匹配各种格式的输出日志遇到的问题进行讨论

# 了解 Promtail

  • Promtail 是一个代理,它将本地日志的内容发送到私有的 Grafana Loki 实例或 Grafana Cloud。它通常部署在运行需要监控的应用程序的每台机器或Pod上。
  • 在 Promtail 可以将任何数据从日志文件发送到 Loki 之前,对日志信息进行处理。例如,对日志进行正则表达式匹配,时间戳转换,格式转换等

一个最简单的Promtail配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /var/lib/promtail/positions.yaml
clients:
  - url: https://127.0.0.1:3100/loki/api/v1/push
scrape_configs:
- job_name: system
  static_configs:
  - targets:
      - localhost
    labels:
      job: varlogs
      __path__: /var/log/**.log

其中:

  1. positions: promtail的位置配置文件路径
  2. clients:需要推送到的loki服务端api的url
  3. scrape_configs:日志抓取配置
    1. jobname:定义抓取任务
    2. static_configs:静态配置,指定要抓取的日志文件路径和标签信息。
  • 这种方式直接将本地的log文件收集,并传送到loki系统中。对于简单使用,这样已经足够
  • 但问题来了:常见的日志查询系统如ElasticSearch,Loki等,都希望传入的日志能被格式化为格式化的字段(如Json),从而更好的进行搜索。
  • 我们希望promtail在处理日志时,也能进行对应的转换

# Promtail中的pipeline

因此对于进一步使用,实际上的需求为:

  • 读取按行输出的日志时,将其转为json格式
  • 需要处理Java、Python等异常抛出时的多行堆栈信息,即多行为一个信息的情况
  • 对部分信息(如时间戳等)进行格式转换

针对这些需求,Promtail中提供了pipeline阶段进行各种处理

官方文档:

在 Promtail 中一个 pipeline 管道被用来转换一个单一的日志行、标签和它的时间戳。简单来说,一个 pipeline 管道是由一组 stages 阶段组成的,在 Promtail 配置中一共有 4 种类型的 stages。:

  • Parsing stages(解析阶段) 用于解析当前的日志行并从中提取数据,提取的数据可供其他阶段使用。
  • Transform stages(转换阶段) 用于对之前阶段提取的数据进行转换。
  • Action stages(处理阶段) 用于从以前阶段中提取数据并对其进行处理,包括:
    • 添加或修改现有日志行标签
    • 更改日志行的时间戳
    • 修改日志行内容
    • 在提取的数据基础上创建一个 metrics 指标
  • Filtering stages(过滤阶段) 可选择应用一个阶段的子集,或根据一些条件删除日志数据。

一个典型的 pipeline 将从解析阶段开始(如 regex 或 json 阶段)从日志行中提取数据。然后有一系列的处理阶段配置,对提取的数据进行处理。最后将数据输出到Loki

因此,针对我们的需求(处理多行异常日志),可以定义一个有如下阶段的pipeline

  1. multiline阶段:针对可能多行输出的异常信息,使用正则表达式匹配头行输出,并将余下行等待加入第一行
  2. regex阶段:对每一行日志使用正则表达式
  3. pack阶段:将regex识别到的各个部分重新打包为json
  4. timestamp阶段:修改时间戳格式

在得出这个最佳实践前,我绕了好多弯路,因此记录一些问题:

  1. 为什么使用pack而不使用template? 在网上查的一些配置文件中使用template定义一个json模板,并将regex阶段解析的正则结果填充到json模板中。这的确是个思路。但我在使用中碰到问题:msg中的一些字段带有"符号,会破坏json模板中的"引号配对从而导致最终输出的json格式错误
  2. 如何快速调试pipeline?
    • 可以参考官方文档:https://grafana.org.cn/docs/loki/latest/send-data/promtail/troubleshooting/,提供了详细的指示
    • 使用promtail -config.file=myConfigFile.yaml -check-syntax检测配置文件
    • 拉取一小份生产环境日志到本地,使用dry-run模式检查每个阶段的输出。检查Promtail的输出匹配,出现错误时再仔细debug
    • cat my.log | promtail --stdin --dry-run --inspect --client.url http://127.0.0.1:3100/loki/api/v1/push

# 处理SpringBoot框架日志例子

针对一个使用spring框架的java输出日志,log输出格式定义为:

1
%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p ${sys:PID} --- [%15.15t] %-40.40c{1.}: %m%n%xwEx

其中一个日志行的示例如下:

1
2024-11-18 15:59:57.575 INFO  39012 --- [           main] o.s.b.w.e.t.TomcatWebServer             : Tomcat initialized with port(s): 9008 (http)

针对这个配置我使用的pipeline配置文件如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    pipeline_stages:
      - multiline:
          firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\s*[A-Z]+\s*[0-9]+ --- \[\s*.*\] .*\s*:\s*.*'
          max_wait_time: 3s
      - regex:
          expression: '^(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s*(?P<level>[A-Z]+)\s*(?P<pid>[0-9]+) --- \[\s*(?P<thread>.*?)\] (?P<classMethod>.*?)\s*:\s*(?P<message>(.|\n)*)'
      - pack:
          labels:
            - time
            - level
            - pid
            - thread
            - classMethod
            - message

经过测试,无问题,正常匹配到loki后正常解析

# 其他

另外还有一个思路可行:使用SpringBoot中logback或log4j2的输出配置,在输出普通log日志时,再输出一份json格式的日志。这样promtail不再需要定义stage直接解析json就可以了。但这个方案对代码侵入性太高,需要改造大量模块的输出配置,对已上线的系统不建议使用。

# 总结

一开始在配置时照抄网上的一些配置文件,绕了很多弯路依然无法解析。最后还是重新看了官方文档一步一步慢慢调试得出了正确的结论。总结为平常还是太依赖别人的经验文档,应该更多看看官方文档,多思考怎么使用,减少抄别人配置的习惯

# 参考

参考&感谢文档

使用 Hugo 构建
主题 StackJimmy 设计