【云原生•监控】mtail轻量日志监控系统
前言
「笔者已经在公有云上搭建了一套临时环境,可以先登录体验下:」
http://124.222.45.207:17000/login
账号:root/root.2020
简介
「可观测性平台三大支柱:日志监控、调用链监控和度量指标监控,其中最为大家熟知的是日志监控,因为我们开发系统基本都离不开日志,也是解决问题最为常见的一种方式。日志的特点就是它是一个个离散的事件,因为一个事件的产生所以导致了一条日志的产生,用于问题分析判断时提供更为详尽的线索。」
举个例子:如程序突然无法连接MySQL数据库
,通过异常日志(如下)很容易发现是由于Too many connections
导致数据库连接失败:
[ERROR] [2023-05-20 21:14:43] com.alibaba.druid.pool.DruidDataSource.init(629) | init datasource error
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Too many connections
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:408)
at com.mysql.jdbc.Util.getInstance(Util.java:383)
Too many connections
这种异常通常是由于超过MySQL
服务器允许的最大连接数而引起的,可以通过增加数据库连接数或关闭未使用的连接来解决该问题。从这个示例可以看出:「日志监控对异常情况下问题分析更加直接,比如请求参数、异常事件等,为问题分析判断提供更为详尽的线索;调用链监控偏向请求调用的链路分析,往往用于发现链路间性能问题;而度量指标监控特点是数值类型,往往用于统计聚合或提供性能指标的运行趋势情况,需要丰富经验才能发现潜在问题,一般为问题分析定位过程中可能的猜测提供有效的数据支撑依据,如上述Too many connections导致数据库连接异常,就可以通过度量指标查看MySQL的连接数进行佐证,并通过连接数趋势线查看什么时间点开始出现连接数异常上升进行进一步的分析排查。」
「说到日志监控,大家第一反应的可能是ELK的方案,或者Loki的方案,这两个方案都是把日志采集了发到中心,在中心存储、查看、分析。这种日志方案问题是使用elasticsearch,存储量非常大,巨大的全文索引开销,如一套ELK监控系统一天新增的日志存储量可能会上TB;另一个问题就是这些日志是离散的,而且是海量的,不太方便进行聚合统计。」
然后,在实际生产中往往还有如下需求:
-
如根据日志统计某个业务接口一天的调用量,从而获取业务订单量、支付金额等;
-
日志中触发Error的次数,以及想知道每天都触发了哪些Error,每种Error触发次数统计等;
-
基于日志关键字告警:
- 如上述
MySQL
连接异常:Too many connections
- 如
jvm
内存溢出:java.lang.OutOfMemoryError: Java heap space
- 如连接拒绝:
java.net.ConnectException: Connection refused
- 如上述
如上述需求并不需要全量的日志,所以,可以通过日志转度量指标解决:「将日志流消息转换计算后生成度量指标,然后监控系统抓取使用PromQL语法进行聚合统计分析」。
这里给大家介绍一个Google
出品的工具mtail
,mtail
就是流式读取日志,通过正则表达式匹配的方式从日志中提取metrics
指标,这种方式可以利用目标机器的算力,另外一个好处是无侵入性,不需要业务埋点,如果业务程序是第三方供应商提供的,我们改不了其代码,mtail
此时就非常合适了。
mtail安装使用
1、mtail安装:
[root@VM-4-14-centos tools]# mkdir /disk/tools/mtail
[root@VM-4-14-centos tools]# cd /disk/tools/mtail
[root@VM-4-14-centos mtail]# tar -zxvf mtail_3.0.0-rc51_Linux_x86_64.tar.gz
2、mtail启动:
[root@VM-4-14-centos mtail]# ./mtail --progs /disk/tools/mtail/conf --logs '/disk/tools/mtail/logs/*.log' --logs /var/log/messages --log_dir /disk/tools/mtail/logdir --poll_interval 250ms
❝
「核心参数如下:」
1、
--progs
:指定一个目录,这个目录里放置一堆的*.mtail
文件,每个mtail
文件就是描述的正则提取规则2、
--logs
: 监控的日志文件列表,可以使用,分隔多个文件,也可以多次使用--logs
参数,也可以指定一个文件目录,支持通配符,指定文件目录时需要对目录使用单引号。如:--logs a.log,b.log,c.log
--logs a.log
--logs b.log
--logs c.log
--logs '/export/logs/*.log'
3、
--log_dir
:mtail
组件自身日志存放目录4、
--port
:mtail
组件http
监听端口,默认3903❞
mtail
启动之后会自动监听一个端口3903,在3903的/metrics
接口暴露符合Prometheus
协议的监控数据,Prometheus
或者 Categraf
或者 Telegraf
等就可以从 /metrics
接口提取监控数据。
这样看起来,原理就很清晰了,mtail
启动之后,根据 --logs
找到相关日志文件,seek
到文件末尾,开始流式读取,每读到一行,就根据 --progs
指定的那些规则文件做匹配,看是否符合某些正则,从中提取时序数据,然后通过3903的/metrics
暴露采集到的监控指标。
规则语法
mtail
的目的是从日志中提取信息并将其传递到监控系统。因此,必须导出指标变量并命名,指标支持counter
、gauge
和histogram
三种类型,并且命名的变量必须在COND
脚本之前。
标准格式为:
COND {
ACTION
}
其中COND
是一个条件表达式。它可以是正则表达式,也可以boolean
类型的条件语句。如下:
/foo/ {
ACTION1
}
variable > 0 {
ACTION2
}
/foo/ && variable > 0 {
ACTION3
}
COND
表达式可用的运算符如下:
- 关系运算符:
❝
= , == , != , =~ , !~ , || , && , !
❞
- 算术运算符:
❝
| , & , ^ , + , - , * , /, > , **
❞
**「导出的指标变量」**可用的运算符如下:
❝
= , += , ++ , –
❞
规则示例
「1、导出一个counter类型的指标lines_total:统计日志行数」
# simple line counter
counter lines_total
/$/ {
lines_total++
}
「2、导出一个counter类型的指标error_count:统计出现ERROR、error、Failed、faild这四个关键字的日志行数」
counter error_count
/ERROR|error|Failed|faild/ {
error_count++
}
「3、导出一个counter类型的指标out_of_memory_count:统计内存溢出出现次数」
counter out_of_memory_count
/java.lang.OutOfMemoryError/ {
out_of_memory_count++
}
转成度量指标后,结合PromQL
语法很容易进行预警规则配置:
groups:
- name: memory.rules
rules:
- alert: OutOfMemoryError
expr: increase(out_of_memory_count[1m]) > 0
labels:
severity: series
annotations:
summary: "java.lang.OutOfMemoryError"
description: "{{ $labels.instance }} 出现JVM内存溢出错误"
「4、这里我用mtail监控一下n9e-server的日志,从中提取一下各个告警规则触发的 notify 的数量,这个日志举例:」
2021-12-27 10:00:30.537582 INFO engine/logger.go:19 event(cbb8d4be5efd07983c296aaa4dec5737 triggered) notify: rule_id=9 [__name__=net_response_result_code author=qin ident=10-255-0-34 port=4567 protocol=tcp server=localhost]2@1640570430
很明显,日志中有这么个关键字:notify: rule_id=9
,可以用正则来匹配,统计出现的行数,ruleid 也可以从中提取到,这样,我们可以把 ruleid 作为标签上报,于是乎,我们就可以写出这样的 mtail 规则了:
counter mtail_alert_rule_notify_total by ruleid
/notify: rule_id=(?P\d+)/ {
mtail_alert_rule_notify_total[$ruleid]++
}
「5、java异常类型统计」
counter exception_count by exception, log
/(?P[A-Z]*(.[A-Za-z]*)*(Exception|Error)):(?P.*)/ {
exception_count[$exception][$log]++
}
然后向日志文件中输入空指针异常和jvm内存溢出异常:
java.lang.NullPointerException: Some error message here.
at com.example.myapp.MyClass.someMethod(MyClass.java:123)
at com.example.myapp.OtherClass.doSomething(OtherClass.java:45)
java.lang.OutOfMemoryError: Java heap space
Dumping heap to d://\java_pid10000.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at cn.intsmaze.dump.OOMDump$OOMIntsmaze.(OOMDump.java:27)
at cn.intsmaze.dump.OOMDump.fillHeap(OOMDump.java:34)
at cn.intsmaze.dump.OOMDump.main(OOMDump.java:47)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
Heap dump file created [10195071 bytes in 0.017 secs]
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
访问/metrics
端点就可以看到指标数据:
# HELP exception_count defined at gj.mtail:7:9-23
# TYPE exception_count counter
exception_count{exception="java.lang.NullPointerException",log=" Some error message here.",prog="gj.mtail"} 3
exception_count{exception="java.lang.OutOfMemoryError",log=" Java heap space",prog="gj.mtail"} 2
❝
exception标签标识java异常类型,log标签标识简单异常描述。
注意:实际生产log标签是不太合适的,因为log标签基数太多会导致指标膨胀。
❞
将异常指标转成度量指标,使用PromQL
语法就很容易统计出每种异常类型每天触发的次数,或者结合Grafana
实时展示统计数据、趋势线等。