将非结构化的log日志信息结构化为JSON格式,以方便在Grafana界面侧的浏览和查询。
0. 目录
- 1. 需求背景和描述
- 2. 实现(Promtail侧)
- 3. 注意事项
- 4. 参考
1. 需求背景和描述
最近几个月,部门内部开始尝试统一日志收集、查询统计相关的技术栈,并结合自身的业务特点最终放弃传统的ELK方案,转向Grafana开源的轻量级解决方案Loki + Promtail。
最终制定出来的标准是要求上报到Loki的日志必须是结构化的JSON格式,但我们很多历史项目的日志输出采用的是传统的非结构化文本形式,如下面这条样例:
14:22:23.002][TID:9a77717bb9a34c6eae403df629f3eeb8.203.16800709430010901][pid:28764][tid: XNIO-1 task-6][m.XXXX.apigateway.filter.PreHeaderFilter:?][ INFO] uri: /XX/XXXXX/thumb/ht0313.png
最终需要达到的效果如下:
这个需求刚出现时,团队里急性子的同事马上提议 —— 这还不简单,咱们直接把系统里日志输出格式改了不就完事了。
听得我是一脑门汗 —— 大哥,你这当做毕设呢?咱这没有“一言不合就要把锅砸了,另立一口”的。咱们先不说基本的"开闭原则",你知道现有的日志格式被多少功能所依赖着啊,你上来使这么大的身段?
最后苦劝暂缓了他的操作,然后加上凭借过去一点浅薄的ELK经验,花了点时间算是把这个需求给满足了。
注:本实现只涉及promtail配置的修改,下游的业务以及上游的Grafana无感知。
2. 实现(Promtail侧)
闲话扯完,本小节步入正文。直接摆出解决方案。
Promtail配置文件:
positions:
filename: ./positions.yaml
#sync_period: 10s
clients:
- url: http://{lokiIp}:3100/loki/api/v1/push
scrape_configs:
- job_name: buInfoLog17
pipeline_stages:
# 底部有官方说明文档的链接
# 1. 使用regex从非结构化的log提取出关键信息. 例如这里的time, tid, pid等. (注意提取出来的信息会以键值对的形式存放在`extracted map`中, 让之后的stage使用, 比如我们下面马上要看到的 template stage)
- regex:
# Flag (?s:.*) needs to be set for regex stage to capture full traceback log in the extracted map.
# 这里可以对比上面给出的日志文本样例, 来快速理解该正则表达式的含义, 并且最快速地微调出满足自己需求的正则表达式
expression: '^\[(?P<time>(\d{2}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}))\]\[TID:(?P<tid>(.*?))\]\[pid:(?P<pid>(.*?))\]\[tid:(?P<thread>(.*?))\]\[(?P<cls>(.*?))\]\[(?P<level>(.*?))\](?P<message>(?s:.*))$'
# 2. 将提取出来的数据(存放在`extracted map`中的键值对), 重新组织拼接为JSON格式.
- template:
- # 这里的意思是: 将拼接出来的JSON字符串作为value, 'jsonFormatD'为key, 以键值对的形式存放到`extracted map`中.
# 注意这里的 message部分我们使用了GO template function: TrimSpace
source: jsonFormatD
template: '{"time":"{{.time}}","tid":"{{.tid}}","pid":"{{.pid}}","thread":"{{.thread}}","cls":"{{.cls}}","level":"{{.level}}","message":"{{ TrimSpace .message }}"}'
#- labels: #测试成功, 但是按照https://grafana.com/blog/2020/08/27/the-concise-guide-to-labels-in-loki/中的不推荐这么弄, 会大幅增加label所占用的存储空间
# tid:
# time:
# level:
# 3. 将上面一步的键值对 jsonFormatD 对应的value推送到Loki中.
- output:
source: jsonFormatD
static_configs:
- targets:
- localhost
labels:
job: buInfoLog17
__path__: /var/log/*info.log
3. 注意事项
实际实现的过程中还是走了一些弯路,但当完成之后回头看的时候发现:***文档里这不是写得清清楚楚的吗?
- 这里用到了三个stage,分别是:
1.1 Parsing stages - regex
1.2 Transform stages - template
1.3 Action stages - output - 上面的regex Stage中的正则表达式,在message信息的提取中使用了
TrimSpace
,其目的是去除所捕获到的 \n 换行符,让最终的字符串满足JSON格式。(一开始我们是尝试在正则匹配阶段就丢弃掉这个换行符,但实际测试过程中却始终无法生效,而GPT给出的答案也是互相打架)
4. 参考
- Promtail - Pipeline Stages
- Promtail - 如何快速调试Promtail? 。这里面包括:如何快速验证配置文件满足语法格式?如何查看各Stage阶段的输出,以定位出是哪个Stage导致的结果不及预期?等等。