mtail

志不强者智不达,言不信者行不果。这篇文章主要讲述mtail相关的知识,希望能为你提供帮助。
mtail官网地址:??https://github.com/google/mtail??
一、简介mtail日志处理器是由Google的SRE人员编写的,其采用Apache 2.0许可证,并且使用Go语言。mtail日志处理器专门用于从应用程序日志中提取要导出到时间序列数据库中的指标。


mtail日志处理器通过运行“程序”(program)来工作,它定义了日志匹配模式,并且指定了匹配后要创建和操作的指标。它与Prometheus配合得很好,可以暴露任何要抓取的指标,也可以配置为将指标发送到collectd、StatsD或Graphite等工具。
mtail 是用于从应用程序日志中提取指标以导出到时间序列数据库或时间序列计算器以进行警报和仪表板显示的工具。简单来说,就是实时读取应用程序的日志,并且通过自己编写的脚本实时分析,最终生成时间序列指标的工具。
目前网上关于mtail的文档,除了github官网之外,几乎没有其他的文档,增加了学习mtail的难度。
二、参数说明

参数 描述
address: 绑定HTTP监听器的主机或IP地址
alsologtostderr: 记录标准错误和文件-block_profile_rate 报告goroutine阻塞事件之前的纳秒时间
collectd_prefix: 发送给collectd的指标的metrics前缀
collectd_socketpath: collectd unixsock路径,用于向其写入metrics
compile_only: 仅尝试编译mtail脚本程序,不执行,用于测试脚本
disable_fsnotify: 是否禁用文件动态发现机制。为true时,不会监听动态加载发现的新文件,只会监听程序启动时的文件。
dump_ast: 解析后dump程序的AST(默认到/tmp/mtail.INFO)
dump_ast_types: 在类型检查之后dump带有类型注释的程序的AST(默认到/tmp/mtail.INFO)
dump_bytecode: dump程序字节码
emit_metric_timestamp: 发出metric的记录时间戳。如果禁用(默认设置),则不会向收集器发送显式时间戳。
emit_prog_label: 在导出的变量里面展示’prog’对应的标签。默认为true
expired_metrics_gc_interval: metric的垃圾收集器运行间隔(默认为1h0m0s)
graphite_host_port graphite: carbon服务器地址,格式Host:port。用于向graphite carbon服务器写入metrics
graphite_prefix: 发送给graphite指标的metrics前缀
ignore_filename_regex_pattern: 需要忽略的日志文件名字,支持正则表达式。使用场景:当-logs参数指定的为一个目录时,可以使用ignore_filename_regex_pattern 参数来忽略一部分文件
jaeger_endpoint: 如果设为true,可以将跟踪导出到Jaeger跟踪收集器。使用–jaeger_endpoint标志指定Jaeger端点URL
log_backtrace_at: 当日志记录命中设置的行N时,发出堆栈跟踪
log_dir: mtail程序的日志文件的目录,与logtostderr作用类似,如果同时配置了logtostderr参数,则log_dir参数无效
logs: 监控的日志文件列表,可以使用,分隔多个文件,也可以多次使用-logs参数,也可以指定一个文件目录,支持通配符*,指定文件目录时需要对目录使用单引号。如:
-logs a.log,b.log,c.log
-logs a.log -logs b.log -logs c.log
-logs ‘/export/logs/*.log’
logtostderr: 直接输出标准错误信息,编译问题也直接输出
metric_push_interval_seconds: metric推送时间间隔,单位:秒,默认60秒
metric_push_write_deadline: 在出现错误退出之前等待推送成功的时间。(默认10s)
mtailDebug: 设置解析器debug级别
mutex_profile_fraction: 报告的互斥锁争用事件的分数。0关闭。(此参数为直译,不太理解啥意思)
one_shot: 此参数将编译并运行mtail程序,然后从指定的文件开头开始读取日志(从头开始读取日志,不是实时tail),然后将收集的所有metrics打印到日志中。此参数用于验证mtail程序是否有预期输出,不用于生产环境。
override_timezone: 设置时区,如果使用此参数,将在时间戳转换中使用指定的时区来替代UTC
poll_interval: 设置轮询所有日志文件以获取数据的间隔;必须为正,如果为零将禁用轮询。使用轮询模式,将仅轮询在mtail启动时找到的文件
port: 监听的http端口,默认3903
progs: mtail脚本程序所在路径
stale_log_gc_interval: stale的垃圾收集器运行间隔(默认为1h0m0s)
statsd_hostport: statsd地址,格式Host:port。用于向statsd写入metrics
statsd_prefix: 发送给statsd指标的metrics前缀
stderrthreshold: 严重性级别达到阈值以上的日志信息除了写入日志文件以外,还要输出到stderr。各严重性级别对应的数值:INFO—0,WARNING—1,ERROR—2,FATAL—3,默认值为2.
syslog_use_current_year: 如果时间戳没有年份,则用当前年替代。(默认为true)
trace_sample_period: 用于设置跟踪的采样频率和发送到收集器的频率。将其设置为100,则100条收集一条追踪。
v: v日志的日志级别,该设置可能被 vmodule标志给覆盖.默认为0.
version: 打印mtail版本
vmodule: 按文件或模块来设置日志级别,如:-vmodule=mapreduce=2,file=1,gfs*=3



三、mtail脚本语法官方文档地址:??https://github.com/google/mtail/blob/main/docs/Programming-Guide.md??
1.mtail脚本标准格式标准格式为:
COND {
ACTION
}

其中??COND??是一个条件表达式。它可以是正则表达式,也可以boolean类型的条件语句。如下:
/foo/ {
ACTION1
}


variable > 0 {
ACTION2
}


/foo/ & & variable > 0 {
ACTION3
}

??COND??表达式可用的运算符如下:
  • 关系运算符
< , < = , > , > = , == , != , =~ , !~ , || , & & , !



  • 算术运算符
| , & , ^ , + , - , * , /, < < , > > , **

导出的指标变量可用的运算符如下:
= , += , ++ , –



??mtail??的目的是从日志中提取信息并将其传递到监控系统。因此,必须导出指标变量并命名,命名可以使用counter、、gauge等指标类型,并且命名的变量必须在??COND??脚本之前。
如,导出一个counter类型的指标lines_total:统计日志行数,脚本内容如下:
# simple line counter
counter lines_total
/$/ {
lines_total++
}



2.mtail支持的类型mtail中的counter、gauge、histogram三种类型与prometheus类型中描述的作用一致。


counter
【mtail】counter 类型的数据是单调递增的指标,即只增不减。如,你可以使用 counter 类型的指标来表示服务的请求数、成功任务数、失败的任务数等。


gauge
gauge类型的数据是指可以任意变化的指标,可增可减。如,可以提取正则匹配到的数据,直接赋值给指标变量返回,或者计算后返回。


histogram
在许多监控方法、博客、论坛和文章中,直方图比平均值更受欢迎,以便让操作员更好地了解系统的行为。
histogram(直方图)将数据分段统计,引用prometheus中对histogram的描述:
在大多数情况下人们都倾向于使用某些量化指标的平均值,例如 CPU 的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统 API 调用的平均响应时间为例:如果大多数 API 请求都维持在 100ms 的响应时间范围内,而个别请求的响应时间需要 5s,那么就会导致某些 WEB 页面的响应时间落到中位数的情况,而这种现象被称为长尾问题。 为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在 010ms 之间的请求数有多少而 1020ms 之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram 和 Summary 都是为了能够解决这样问题的存在,通过 Histogram 和 Summary 类型的监控指标,我们可以快速了解监控样本的分布情况。 Histogram 在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。


??mtail?? 支持将直方图作为第一类度量标准,使用桶(bucket)范围列表创建histogram,如下:
histogram foo buckets 1, 2, 4, 8



以上脚本创建了一个新的直方图foo,其桶的范围为[0-1),[1-2),[2-4),[4-8),[8, +∞)。
??注意:0-1和8-无穷大是自动创建的。??
如下一个例子,展示使用Histogram 统计服务器延迟情况:
日志格式如下:
GET /foo/bar.html latency=1s httpcode=200
GET /foo/bar.html latency=2s httpcode=200
GET /foo/bar.html latency=1s httpcode=200
GET /foo/baz.html latency=0s httpcode=200



脚本如下:
histogram webserver_latency buckets 0, 1, 2, 4, 8
/latency=(?P< latency> \\d+)/ {
webserver_latency = $latency
}



统计到的指标如下:
webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="1"} 1
webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="2"} 1
webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="4"} 1
webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="8"} 1
webserver_latency_by_code_bucket{httpcode="200",prog="software_errors.mtail",le="+Inf"} 1
webserver_latency_by_code_sum{httpcode="200",prog="software_errors.mtail"} 1
webserver_latency_by_code_count{httpcode="200",prog="software_errors.mtail"} 2



3.更改导出metrics的变量名称像sql中给字段起别名一样,此处放在定义变量的地方,如下:
counter lines_total as "lines-total"



4.可复用常量如匹配ip等,可能会有多个地方调用的,可以配置成常量,如下:
const IP /\\d+(\\.\\d+){3}/
const MATCH_IP /(?P< ip> / + IP + /)/

...

# Duplicate lease
/uid lease / + MATCH_IP + / for client .* is duplicate on / {
duplicate_lease++
}



与标准正则一样,小括号()内的内容可以使用$1,$2等方式获取到,mtail脚本中此处MATCH_IP 中使用的?P< ip> 则用来代替$1,$2等,可以直接使用$ip来获取匹配到的内容.


5.解析日志行时间戳如果解析日志行中的时间戳,是为了导出变量使用,那么建议直接使用正则匹配到相应格式的时间戳,通过别名返回。
如果解析日志行中的时间戳是为了记录当前时间戳并且后续使用,那么就可以通过strptime(x,y)函数来解析日志中的时间戳,但是这个函数对日志时间戳格式有要求,可参考本文“内置函数”章节中的strptime()函数。
如,统计日志中时间格式包含“2006-01-02 15:04:05”格式类型的请求总数,脚本如下:
counter request_total
/(?P< date> \\d{4}\\/\\d{2}\\/\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}).*/ {
strptime($date, "2006-01-02 15:04:05")
request_total++
}



6.通用时间戳解析可复用常量解决了表达式中重复使用子表达式的问题,而通用时间戳解析则是为了解决,多个变量之中重复解析设置时间戳的问题。
定义的格式如下:
def syslog {
/(?P< date> \\w+\\s+\\d+\\s+\\d+:\\d+:\\d+)/ {
strptime($date, "Jan2 15:04:05")
next
}
}



使用??def???关键字定义名称,使用大括号块定义内容,使用??next??关键字,跳转到导出变量的代码块执行,使用的格式如下:
@syslog {
/some event/ {
variable++
}
}



@syslog表示该块首先由syslog装饰器执行,先提取日志的时间戳,然后使用??next??跳转到“/some event/”块内执行,例:
def syslog {
/(?P< date> \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3}).*/ {
strptime($date, "2006-01-02 15:04:05")
next
}
}
gauge sumVal

@syslog {

/-------(?P< val> \\d)+--------/ {
sumVal+=$val
}
}



7.条件判断通常情况下,当条件表达式不匹配时,会直接跳过。而通过??else???关键字可实现表达式不匹配时执行其他的动作。而通过??otherwise??关键字则可以实现像java中的switch一样的多条件判断的功能,如下:
#如果foo不匹配,则执行ACTION2
/foo/ {
ACTION1
} else {
ACTION2
}

/foo/ {
#当匹配foo时,才会进行foo1、foo2、otherwise的比配
#当匹配foo1时,执行ACTION1,匹配foo2时执行ACTION2,否则执行ACTION3
/foo1/ {
ACTION1
}
/foo2/ {
ACTION2
}
otherwise {
ACTION3
}
}



8.临时变量临时变量就是只在mtail脚本程序中使用,不导出为metrics指标的变量。通过在??gauge???定义时前面加上??hidden???关键字来定义。临时变量使用完后,需要使用??del??关键字来删除临时变量控制内存。
hidden gauge connection_time by pid
gauge connection_time_total by pid
...

# 记录连接开始时间
/connect from \\S+ \\(\\d+\\.\\d+\\.\\d+\\.\\d+\\)/ {
# 此处记录开始时间,需要先使用strptime或者settime函数设置时间戳,timestamp才能正常执行
connection_time[$pid] = timestamp()
}

...

# 记录连接关闭时间
/sent (?P< sent> \\d+) bytesreceived (?P< received> \\d+) bytestotal size \\d+/ {
# 记录总时间
connection_time_total[$pid] += timestamp() - connection_time[$pid]

# 删除中间变量,控制内存
del connection_time[$pid]
}

此示例中,连接时间戳记录在临时变量??connection_time??中,并且??connection_time??通过pid来区分不同的链接.在链接结束时,通过当前时间戳-开始时间戳来计算增量,并且删除临时变量??connection_time??。
9.内置函数
函数 描述
timestamp(): 获取时间戳。
注:需要使用strptime或者settime函数设置时间戳之后才能使用timestamp函数
strptime($date, “2006-01-02 15:04:05”) 第一个参数为日志中解析的时间格式,第二个参数为格式模板,此函数作用为使用第二个参数的格式来解析日志中的时间戳,并设置当前时间戳,将变量导出至上游收集器时使用此时间戳。
注:如果第二个参数格式不对,则编译抛出异常;如果日志解析出的时间格式不匹配,则抛出运行时异常。第二个参数必须使用这个特定的时间:2006-01-02 15:04:05。若需要其他格式,可参考Go的时间.parse()格式字符串中的const 部分下的ANSIC等格式。
len(str): 获取字符串长度
getfilename(): 获取文件名
tolower(x): 字符串转小写
int(x): 将x转换为整数,如果x的类型支持转为整型,则转为整型;如果x的类型不支持转为整型,则触发编译错误;如果x的值不支持转为整型,则触发运行时错误。
float(x): 将x转换为浮点数,规则同int(x)
string(x): 将x转为字符串
strtol(x, y): 使用base将字符串x转为整数y。用于转换日志消息中的八进制、十六进制值
settime(x): x为整型,用于设置当前时间戳,将变量导出至上游收集器时使用此时间戳





五、示例mtail读取多个文件时,按照文件名输出metrics,此场景针对监听目录,或者多个文件的情况,需要使用到内置函数getfilename(),脚本如下:
counter request_total by filename

/-------(?P< val> \\d)+--------/ {
request_total[getfilename()]+=$val
}



东西很多,后面会继续补充完善。。。。


总结:
这个日志监控,如果是项目日志还是用ELK的好啊!
中间件的日志:比如nginx,是可以使用这个的,因为这类的日志格式一般是标准的。
一定要做的话,需要在mtail收集的地方,就要配置各种的指标了,对运维还是有点要求和工作量的。
对比于ELK的全量文件导入,进行全文检索,进行后续分析。这个的动态可配置性就差了很多了。







    推荐阅读