Fluentd提取日志级别level作为新字段

事情起因

上次对超出docker 16k的大日志做了拼接后,虽然多行日志合并到了一行显示,但在查日志的时候依然不是特别方便;因为在很多情况下需要根据日志的level级别进行单独过滤,这次把日志中的level级别提取出来然后形成新的字段。

日志格式

通过统计日志发现,目前现网中存在多种日志格式,这里暂且划分为标准日志格式和非标准日志格式,它们的日志内容大致如下所示:

标准日志

服务打到docker容器中的标准格式日志如下:

1
2
{"log":"[2021-03-15 20:20:58.255] [] [DEFAULT.autoRefreshFlowGraphJob_Scheduler_Worker-1] INFO  [com.pintec.jingway.job.AutoRefreshFlowGraphJob] [38] - AutoRefreshFlowGraphJob 开始刷新, flowName:[Init_Test] \n","stream":"stdout","time":"2021-03-15T09:20:58.256399067Z"}
{"log":"[2021-03-15 20:20:58.259] [] [DEFAULT.autoRefreshFlowGraphJob_Scheduler_Worker-1] INFO [com.pintec.jingway.flow.FlowHolder] [235] - url:http://******/Init_Test/online,flowName:Init_Test,body:{\"status\":302,\"message\":\"[{\\\"params\\\":{},\\\"resourceKey\\\":\\\"mflow_tempInvalid\\\"}]\",\"data\":null,\"code\":302} \n","stream":"stdout","time":"2021-03-15T09:20:58.25939643Z"}

可以看到标准日志都有共同点 [] [] [] level []

非标准日志

当然,也有一些非标准格式日志类似如下:

1
{"log":"13:25:58.570 [http-nio-8080-exec-48] DEBUG org.javalite.activeweb.RequestDispatcher - Loaded routes from: app.config.RouteConfig\n","stream":"stdout","time":"2021-04-02T02:25:58.570630811Z"}

明确目标

在几经沟通后并确认后,标准格式日志占80%以上,最后确定下来只需要对标准日志提取level字段,而非标准日志则不提取;后续由开发同学将日志统一更换为标准格式日志。

过程中遇到的问题

fluentd占用资源

在进行配置前fluentd平均在线上占用的资源为20m左右cpu、120Mi左右内存;然后,在刚开始的调试过程中发现fluentd占用的资源有明显上升。

经过多次测试发现是因为fluentd在进行正则匹配时,如果遇到无法匹配的日志fluentd就会疯狂的输出日志,这时cpu和内存资源会飞快的上升,如下图所示:

图片4

解决方案:需要合理调整fluentd中插件的先后顺序,再一个可以区分开无法匹配的日志另行处理(这个在后面配置文件中# 区分标准格式和非标准格式日志)。

需要舍弃部分日志

一开始我是想把所有只要带有level日志级别的都提取出来作为关键字,关键配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<filter kubernetes.**>
@type grep
regexp1 message (DEBUG|INFO|WARNING|WARN|ERROR|FATAL|OFF|^[ java]+)
</filter>

<filter kubernetes.**>
@type parser
key_name message
reserve_data yes
<parse>
@type regexp
expression /(?<level>(DEBUG|INFO|WARNING|WARN|ERROR|FATAL|OFF))/
types level:string
</parse>
</filter>

用过滤的方法匹配日志级,很显然基本上所有的日志都可以提取出level;
但同时也会带来问题,如果日志中不带有level日志级别就会被舍弃掉,最终ES中就查不到该日志了。

解决方案:只提取标准日志格式的level字段(决不能丢掉任何服务日志)。

tag字段被替换

当把资源占用问题处理了、标准日志格式也提取出了level字段,最后因为在处理标准日志和非标准日志过程中标记了tag,就是因为这个tag替换了原本日志的tag(采集的日志文件名)

原日志tag类似:

图片1

被替换后的tag:

图片2

解决方案:将原tag字段的数据重新写入新的字段,如log_file (这个在后面配置文件中# 对解析出来的日志文件路径修改key,因为key与后面的关键字tag重名)。

解析出来的k8s相关字段过多

当一切准备就绪后,发现解析出来的k8s相关元数据非常多,大部分在查询过程中都使用不到,日志字段大致如下:

图片3

解决方案:删除日志字段(这个在后面配置文件中# 删除日志中不需要的key)。

最终的配置

最终的fluentd配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# 日志源配置,格式化成json
<source>
@id fluentd-containers.log
@type tail
path /var/log/containers/*.log
pos_file /var/log/es-containers.log.pos
tag raw.kubernetes.*
#read_from_head true
<parse>
@type multi_format
<pattern>
format json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</pattern>
<pattern>
format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
time_format %Y-%m-%dT%H:%M:%S.%N%:z
</pattern>
</parse>
</source>

# 检测java异常栈日志,并作为一条日志转发
<match raw.kubernetes.**>
@id raw.kubernetes
@type detect_exceptions
remove_tag_prefix raw
message log
stream stream
languages java
multiline_flush_interval 5
max_bytes 500000
max_lines 1000
</match>

# 将json日志log中的value转换成message字段内容,不使用则es中message会变成log字段
<filter kubernetes.**>
@id filter_parser
@type parser # multi-format-parser多格式解析器插件
key_name log # 在要解析的记录中指定字段名称。
reserve_data true # 在解析结果中保留原始键值对。
remove_key_name_field true # key_name 解析成功后删除字段。
<parse>
@type multi_format
<pattern>
format json
</pattern>
<pattern>
format none
</pattern>
</parse>
</filter>

# 多行日志拼接,主要针对pod日志超过docker 16k限制的大日志
<filter kubernetes.**>
@type concat
key message
separator ""
multiline_start_regexp /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}\]/
multiline_end_regexp /^\n$| \n$/
flush_interval 10
</filter>

# 添加k8s相关的元数据,pod namespace id 等
<filter kubernetes.**>
@id filter_kubernetes_metadata
@type kubernetes_metadata
</filter>

# 对解析出来的日志文件路径修改key,因为key与后面的关键字tag重名
<filter kubernetes.**>
#@type record_modifier #这两个插件都可以
@type record_transformer
<record>
log_file ${tag}
</record>
</filter>

# 区分标准格式和非标准格式日志
<match kubernetes.**>
@type rewrite_tag_filter
<rule>
key message
pattern /^\[20/
tag INFO.kubernetes.*
</rule>
<rule>
key message
pattern /^\[20/
tag NULL.kubernetes.*
invert true
</rule>
</match>

# 对标准格式的日志提取日志级别level
<filter INFO.kubernetes.**>
@type parser
key_name message
reserve_data true
<parse>
@type regexp
expression /^\[[^\]]*\] \[[^\]]*\] \[[^\]]*\] (?<level>\S+)/
types level:string
</parse>
</filter>

# 对标准格式的日志写日志标签 这一步可以忽略
<filter INFO.kubernetes.**>
@type record_transformer
#@type record_modifier #这个插件也可以
<record>
level ${record["level"]}
</record>
</filter>

# 非标准格式的日志level为null
<filter NULL.kubernetes.**>
@type record_transformer
<record>
level NULL
</record>
</filter>

# 排除特定标签的日志
<filter NULL.kubernetes.**>
@type grep
<exclude>
key $.kubernetes.labels.k8s-app
pattern /fluentd-es|kubernetes-dashboard/
</exclude>
</filter>

# 删除日志中不需要的key
<filter **>
@type record_transformer
remove_keys stream,$.kubernetes.container_image_id,$.kubernetes.labels.jenkins-fold,$.kubernetes.labels.pod-template-hash,$.kubernetes.labels.repo-config-group,$.kubernetes.labels.repo-config-project,$.kubernetes.labels.repo-group,$.kubernetes.labels.repo-project,$.kubernetes.master_url,$.kubernetes.namespace_id,$.kubernetes.namespace_labels.env,$.kubernetes.pod_id,$.kubernetes.container_name
</filter>

注意:由于引入插件ewrite_tag_filter在原官方docker镜像中没有安装,可以使用以下命令安装:

1
gem install fluent-plugin-rewrite-tag-filter

也可以使用我安装好的镜像:chenzz/fluentd:v3.0.2

output配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<match **>
@id elasticsearch
@type elasticsearch
@log_level info
type_name _doc
include_tag_key true
host vpc-es-log-vad4uirwivrwpfg5p3yy4gfasm.ap-southeast-2.es.amazonaws.com
port 443
scheme https
logstash_format true
#logstash_prefix fluentd-test
reload_connections false
reconnect_on_error true
reload_on_failure true
remove_keys tag #删除tag字段
<buffer>
@type file
path /var/log/fluentd-buffers/kubernetes.system.buffer
flush_mode interval
retry_type exponential_backoff
flush_thread_count 2
flush_interval 5s
retry_forever
retry_max_interval 30
chunk_limit_size 2M
total_limit_size 500M
overflow_action block
</buffer>
</match>
-------------本文结束感谢您的阅读-------------