【夜鶯監(jiān)控】從日志中提取指標(biāo)的瑞士軍刀
mtail是谷歌開(kāi)源的一款從應(yīng)用日志提取 metrics 的工具,它會(huì)實(shí)時(shí)讀取應(yīng)用程序的日志,然后通過(guò)自己編寫(xiě)的腳本分析日志,最終生成時(shí)間序列的指標(biāo),項(xiàng)目地址是:https://github.com/google/mtail。
夜鶯的Categraf對(duì)日志指標(biāo)的收集也是采用的 mtail,不過(guò)做了一些優(yōu)化,具體優(yōu)化了什么我們慢慢道來(lái)。
現(xiàn)在,我們先從谷歌的mtail開(kāi)始聊起,再慢慢聊到夜鶯的 mtail 插件。
mtail 的安裝
前面已經(jīng)對(duì)mtail做了簡(jiǎn)短的介紹,其實(shí)那就是全部。
所以,我們直接從安裝開(kāi)始。
從https://github.com/google/mtail/releases下載需要的版本,操作如下:
# 下載
$ wget https://github.com/google/mtail/releases/download/v3.0.0-rc51/mtail_3.0.0-rc51_Linux_x86_64.tar.gz
$ tar xf mtail_3.0.0-rc51_Linux_x86_64.tar.gz
$ cp mtail /usr/local/bin
# 查看mtail版本
$ mtail --version
mtail version 3.0.0-rc51 git revision 6fdbf8ec96a63c674c53148eeb9ec96043a2ec9c go version go1.19.4 go arch amd64 go os linux
# mtail后臺(tái)啟動(dòng)
$ nohup mtail -port 3903 -logtostderr -progs test.mtail -logs test.log &
# 默認(rèn)端口是3903
$ nohup ./mtail -progs test.mtail -logs test.log &
# 查看是否啟動(dòng)成功
$ ps -ef | grep mtail
# 查看mtail的幫助文檔
$ mtail -h
mtail 參數(shù)詳解
安裝完mtail之后,如果對(duì)mtail的參數(shù)一無(wú)所知的話,也就不知道如何下手了,本小節(jié)就帶大家來(lái)了解一下 mtail 有哪些參數(shù)。
我們可以通過(guò)mtail -h來(lái)查看mtail支持的參數(shù)列表,下面我對(duì)這些參數(shù)加一些中文注釋,應(yīng)該能夠幫助你了解它們的意思了。
$ mtail -h
mtail version 3.0.0-rc51 git revision 6fdbf8ec96a63c674c53148eeb9ec96043a2ec9c go version go1.19.4 go arch amd64 go os linux
Usage:
-address string # 綁定HTTP監(jiān)聽(tīng)器的主機(jī)或者IP地址
-alsologtostderr # 記錄標(biāo)準(zhǔn)錯(cuò)誤和文件
-block_profile_rate int # 報(bào)告goroutine阻塞事件之前的阻塞時(shí)間的納秒數(shù)。0表示關(guān)閉。
-collectd_prefix string # 發(fā)送給collectd的指標(biāo)前綴
-collectd_socketpath string # collectd socket路徑,用于向其寫(xiě)入metrics
-compile_only # 僅禪師編譯mtail腳本程序,不執(zhí)行
-disable_fsnotify # 是否禁用文件動(dòng)態(tài)發(fā)現(xiàn)機(jī)制。為true時(shí),不會(huì)監(jiān)聽(tīng)動(dòng)態(tài)加載發(fā)現(xiàn)的新文件,只會(huì)監(jiān)聽(tīng)程序啟動(dòng)時(shí)的文件。
-dump_ast # 解析后dump程序的AST(默認(rèn)到/tmp/mtail.INFO)
-dump_ast_types # 在類型檢查之后dump帶有類型注釋的程序的AST(默認(rèn)到/tmp/mtail.INFO)
-dump_bytecode # dump程序字節(jié)碼
-emit_metric_timestamp # 發(fā)出metric的記錄時(shí)間戳。如果禁用(默認(rèn)設(shè)置),則不會(huì)向收集器發(fā)送顯式時(shí)間戳。
-emit_prog_label # 在導(dǎo)出的變量里面展示prog對(duì)應(yīng)的標(biāo)簽。默認(rèn)為true
-expired_metrics_gc_interval duration # metric的垃圾收集器運(yùn)行間隔(默認(rèn)為1h0m0s)
-graphite_host_port string # graphite carbon服務(wù)器地址,格式Host:port。用于向graphite carbon服務(wù)器寫(xiě)入metrics
-graphite_prefix string # 發(fā)送給graphite指標(biāo)的metrics前綴
-http_debugging_endpoint # 是否開(kāi)啟調(diào)式接口(/debug/*),默認(rèn)開(kāi)啟
-http_info_endpoint # 是否開(kāi)始info接口(/progz,/varz),默認(rèn)開(kāi)啟
-ignore_filename_regex_pattern string # 需要忽略的日志文件名字,支持正則表達(dá)式。使用場(chǎng)景:當(dāng)-logs參數(shù)指定的為一個(gè)目錄時(shí),可以使用ignore_filename_regex_pattern 參數(shù)來(lái)忽略一部分文件
-jaeger_endpoint string # 如果設(shè)為true,可以將跟蹤導(dǎo)出到Jaeger跟蹤收集器。使用–jaeger_endpoint標(biāo)志指定Jaeger端點(diǎn)URL
-log_backtrace_at value # 當(dāng)日志記錄命中設(shè)置的行N時(shí),發(fā)出堆棧跟蹤
-log_dir string # mtail程序的日志文件的目錄,與logtostderr作用類似,如果同時(shí)配置了logtostderr參數(shù),則log_dir參數(shù)無(wú)效
-logs value # 監(jiān)控的日志文件列表,可以使用,分隔多個(gè)文件,也可以多次使用-logs參數(shù),也可以指定一個(gè)文件目錄,支持通配符*,指定文件目錄時(shí)需要對(duì)目錄使用單引號(hào)。
-logtostderr # 直接輸出標(biāo)準(zhǔn)錯(cuò)誤信息,編譯問(wèn)題也直接輸出
-max_recursion_depth int # 以解析的標(biāo)記來(lái)衡量mtail語(yǔ)句的最大長(zhǎng)度。過(guò)長(zhǎng)的mtail表達(dá)式可能會(huì)導(dǎo)致編譯和運(yùn)行時(shí)的性能問(wèn)題。(默認(rèn)為100)
-max_regexp_length int # 一個(gè)mtail regexp表達(dá)式的最大長(zhǎng)度。過(guò)長(zhǎng)的模式可能會(huì)導(dǎo)致編譯和運(yùn)行時(shí)的性能問(wèn)題。(默認(rèn)為1024)
-metric_push_interval duration # metric推送時(shí)間間隔,單位:秒,默認(rèn)60秒
-metric_push_interval_seconds int # 棄用,用--metric_push_interval代替
-metric_push_write_deadline duration # 在出現(xiàn)錯(cuò)誤退出之前等待推送成功的時(shí)間。(默認(rèn)10s)
-mtailDebug int # 設(shè)置解析器debug級(jí)別
-mutex_profile_fraction int # 報(bào)告的互斥爭(zhēng)奪事件的比例。 0將關(guān)閉
-one_shot # 此參數(shù)將編譯并運(yùn)行mtail程序,然后從指定的文件開(kāi)頭開(kāi)始讀取日志(從頭開(kāi)始讀取日志,不是實(shí)時(shí)tail),然后將收集的所有metrics打印到日志中。此參數(shù)用于驗(yàn)證mtail程序是否有預(yù)期輸出,不用于生產(chǎn)環(huán)境。
-one_shot_format string # 與-one_shot一起使用的格式。這只是一個(gè)調(diào)試標(biāo)志,不適合生產(chǎn)使用。支持的格式: json, prometheus. (默認(rèn)為 "json")
-override_timezone string # 設(shè)置時(shí)區(qū),如果使用此參數(shù),將在時(shí)間戳轉(zhuǎn)換中使用指定的時(shí)區(qū)來(lái)替代UTC
-poll_interval duration # 設(shè)置輪詢所有日志文件以獲取數(shù)據(jù)的間隔;必須為正,如果為零將禁用輪詢。使用輪詢模式,將僅輪詢?cè)趍tail啟動(dòng)時(shí)找到的文件
-poll_log_interval duration # 設(shè)置找到所有匹配的日志文件進(jìn)行輪詢的時(shí)間間隔;必須是正數(shù),或者是0來(lái)禁用輪詢。 在輪詢模式下,只有在mtail啟動(dòng)時(shí)發(fā)現(xiàn)的文件會(huì)被輪詢。(默認(rèn)250ms)
-port string # 監(jiān)聽(tīng)的http端口,默認(rèn)3903
-progs string # mtail腳本程序所在路徑
-stale_log_gc_interval duration # stale的垃圾收集器運(yùn)行間隔(默認(rèn)為1h0m0s)
-statsd_hostport string # statsd地址,格式Host:port。用于向statsd寫(xiě)入metrics
-statsd_prefix string # 發(fā)送給statsd指標(biāo)的metrics前綴
-stderrthreshold value # 嚴(yán)重性級(jí)別達(dá)到閾值以上的日志信息除了寫(xiě)入日志文件以外,還要輸出到stderr。各嚴(yán)重性級(jí)別對(duì)應(yīng)的數(shù)值:INFO—0,WARNING—1,ERROR—2,F(xiàn)ATAL—3,默認(rèn)值為2.
-syslog_use_current_year # 如果時(shí)間戳沒(méi)有年份,則用當(dāng)前年替代。(默認(rèn)為true)
-trace_sample_period int # 用于設(shè)置跟蹤的采樣頻率和發(fā)送到收集器的頻率。將其設(shè)置為100,則100條收集一條追蹤。
-unix_socket string # socket監(jiān)控地址
-v value # v日志的日志級(jí)別,該設(shè)置可能被 vmodule標(biāo)志給覆蓋.默認(rèn)為0.
-version # 打印mtail版本
-vm_logs_runtime_errors # 啟用運(yùn)行時(shí)錯(cuò)誤的記錄到標(biāo)準(zhǔn)日志。 如果設(shè)置為false,則只將錯(cuò)誤打印到HTTP控制臺(tái)。(默認(rèn)為true)
-vmodule value # 按文件或模塊來(lái)設(shè)置日志級(jí)別,如:-vmodule=mapreduce=2,file=1,gfs*=3
配置參數(shù)非常多,一般情況下我們使用的也就那幾個(gè),如下:
nohup ./mtail -progs test.mtail -logs test.log &
指定 mtail 腳本以及日志目錄即可。
mtail 腳本語(yǔ)法
在https://github.com/google/mtail/blob/main/docs/Programming-Guide.md處對(duì)腳本語(yǔ)法有相應(yīng)的介紹,這里做一個(gè)簡(jiǎn)單的介紹。
腳本標(biāo)準(zhǔn)的格式如下:
COND {
ACTION
}
其中COND是一個(gè)條件表達(dá)式,可以是正則表達(dá)式,也可以是 boolean 類型的條件語(yǔ)句,如下:
/foo/ {
ACTION1
}
variable > 0{
ACTION2
}
/foo/ && variable > 0{
ACTION3
}
COND表達(dá)式可用的運(yùn)算符如下:
- 關(guān)系運(yùn)算符:< , <= , > , >= , == , != , =~ , !~ , || , && , !
- 算術(shù)運(yùn)算符:| , & , ^ , + , - , * , /, << , >> , **
另外,ACTION是具體的操作,如下表示從日志中匹配到 foo 字段,就給相應(yīng)的指標(biāo) foo_total 的值就加 1:
counter foo_total
/foo/ {
foo_total++
}
對(duì)于指標(biāo),可以用= , += , ++ , –等運(yùn)算符進(jìn)行操作。
mtail的目的是從日志中提取信息并將其傳遞到監(jiān)控系統(tǒng)。因此,必須導(dǎo)出指標(biāo)變量并命名,命名可以使用counter、histogram、gauge指標(biāo)類型,并且命名的變量必須在COND腳本之前。
- Counter(計(jì)數(shù)器):用于記錄單調(diào)遞增的值,例如請(qǐng)求數(shù)、錯(cuò)誤數(shù)等。
- Gauge(儀表):用于記錄可增可減的值,例如 CPU 使用率、內(nèi)存使用量等。
- Histogram(直方圖):用于記錄數(shù)據(jù)的分布情況,例如請(qǐng)求延遲、響應(yīng)大小等。
我們知道,拿 Prometheus 來(lái)說(shuō),除了上面的三種指標(biāo)類型之外還有一個(gè)Summary的指標(biāo)類型,為什么 mtail 沒(méi)有呢?
因?yàn)樵?Prometheus 中,summary 指標(biāo)類型用于記錄數(shù)據(jù)的分布情況,并計(jì)算出更多的統(tǒng)計(jì)信息,例如平均值、中位數(shù)、標(biāo)準(zhǔn)差等。但是,由于 mtail 是從日志文件中提取指標(biāo),而不是直接從應(yīng)用程序中提取指標(biāo),因此沒(méi)有必要使用 summary 指標(biāo)類型。
高階用法
變量定義
對(duì)于在一個(gè)腳本中需要重復(fù)使用的表達(dá)式,可以將其定義為一個(gè)變量,后續(xù)可以直接使用變量。
counter duplicate_lease
const IP /\d+(\.\d+){3}/
const MATCH_IP /(?P<ip>/ + IP + /)/
/uid lease / + MATCH_IP + / for client .* is duplicate on / {
duplicate_lease++
}
這是開(kāi)發(fā)中常用的手段。
解析時(shí)間戳
mtail 會(huì)為每一個(gè)日志事件都賦予一個(gè)時(shí)間戳,如果日志里沒(méi)有時(shí)間戳,mtail 會(huì)為本次日志事件賦予一個(gè)當(dāng)前的日志時(shí)間。
除此之外,如果日志里的時(shí)間戳不是標(biāo)準(zhǔn)時(shí)間或者其他情況,可以使用 strptime 對(duì)其進(jìn)行解析,如下:
/^/ +
/(?P<date>\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}) / +
/.*/ +
/$/ {
strptime($date, "2006/01/02 15:04:05")
}
條件判斷
/pattern/ { action }是 mtail 程序中正常的條件控制流結(jié)構(gòu)。
如果模式匹配,那么該塊中的動(dòng)作就會(huì)被執(zhí)行。如果模式不匹配,則跳過(guò)該塊。
else關(guān)鍵字允許程序在模式不匹配的情況下執(zhí)行動(dòng)作。
/pattern/ {
action
} else {
alternative
}
除此之外,還可以使用 otherwise 來(lái)處理沒(méi)有匹配到的規(guī)則,如下:
{
/pattern1/ { _action1_ }
/pattern2/ { _action2_ }
otherwise { _action3_ }
}
這種語(yǔ)法類似于switch case default語(yǔ)法。
精準(zhǔn)匹配
上面的/pattern/ { _action_ }形式隱含地匹配了當(dāng)前的輸入日志行。
如果想與另一個(gè)字符串變量匹配,可以使用=~操作符,或者用!~來(lái)否定匹配,像這樣:
$1 =~ /GET/ {
...
}
解析非數(shù)字類型的數(shù)字字段
有時(shí)候遇到的日志里輸出的數(shù)字是字符串,而非數(shù)字,mtail 可以對(duì)其進(jìn)行解析,如下:
counter total
/^[a-z]+ ((?P<response_size>\d+)|-)$/ {
$1 != "-" {
total = $response_size
}
}
解析帶有額外字符的數(shù)字
一些日志包含除了包含數(shù)字,還包含分隔符,我們可以用 subst 函數(shù)刪除它們:
/sent (?P<sent>[\d,]+) bytes received (?P<received>[\d,]+) bytes/ {
# Sum total bytes across all sessions for this process
bytes_total["sent"] += int(subst(",", "", $sent))
bytes_total["received"] += int(subst(",", "", $received))
}
過(guò)濾操作
如果你想過(guò)濾一些不必要的日志被mtail采集,你可以使用stop,如下:
getfilename() !~ /apache.access.?log/ {
stop
}
重寫(xiě)操作
一些日志,如網(wǎng)絡(luò)服務(wù)器日志,描述了一些常見(jiàn)的元素,其中有獨(dú)特的標(biāo)識(shí)符,如果不加處理,會(huì)導(dǎo)致大量的度量衡鍵,而沒(méi)有有用的計(jì)數(shù)。要重寫(xiě)這些捕獲組,可以使用 subst(),將模式作為第一個(gè)參數(shù):
hidden text route
counter http_requests_total by method, route
/(?P<method\S+) (?P<url>\S+)/ {
route = subst(/\/d+/, "/:num", $url)
http_requests_total[method][route]++
}
這里我們把$url中/后面的任何數(shù)字部分替換為字面字符串/:num,所以我們最終只計(jì)算 URL 路由的靜態(tài)部分。
mtail 實(shí)操
說(shuō)一千,道一萬(wàn),不如真正來(lái)一遍。
當(dāng)然,我這里也不會(huì)把上面說(shuō)的都來(lái)一次。
為了方便闡述,我把本次操作的腳本都放到~/Desktop/mtail目錄中。
單日志采集
# 創(chuàng)建prog1,里面用于保存日志處理的規(guī)則腳本
$ mkdir prog1
# 在prog1里創(chuàng)建prog1.mtail文件并寫(xiě)入以下內(nèi)容
$ cat prog1.mtail
counter foo_count
/foo/{
foo_count++
}
# 創(chuàng)建log1目錄
$ mkdir log1
# 在log1中創(chuàng)建a.log文件
¥ touch a.log
# 啟動(dòng)mtail
$ mtail -progs ~/Desktop/mtail/prog1 -logs ~/Desktop/mtail/log1/a.log
# 向a.log中寫(xiě)入foo
$ echo "foo" > ~/Desktop/mtail/log1/a.log
# 查看指標(biāo)明細(xì)
$ curl 127.0.0.1:3903/metrics
# HELP foo_count defined at prog1.mtail:1:9-17
# TYPE foo_count counter
foo_count{prog="prog1.mtail"} 1 # 可以看到foo_count指標(biāo)數(shù)為1了
多日志采集
如果多日志在同一個(gè)文件夾里,這時(shí)候采集的指標(biāo)就可能混淆。
# 在log1目錄中創(chuàng)建b.log
$ touch b.log
# 然后為b.log重新創(chuàng)建一個(gè)指標(biāo)腳本
$ cat prog1/prog2.mtail
counter bar_count
/bar/{
bar_count++
}
# 啟動(dòng)mtail
$ mtail -progs ~/Desktop/mtail/prog1 -logs ~/Desktop/mtail/log1/a.log -logs ~/Desktop/mtail/log1/b.log
# 向b.log寫(xiě)入日志
$ echo "bar" >> ~/Desktop/mtail/log1/b.log
# 查看指標(biāo)
$ curl 127.0.0.1:3903/metrics
# HELP bar_count defined at prog2.mtail:1:9-17
# TYPE bar_count counter
bar_count{prog="prog2.mtail"} 2
# HELP foo_count defined at prog1.mtail:1:9-17
# TYPE foo_count counter
foo_count{prog="prog1.mtail"} 0
可以看到能正常收集指標(biāo),但是如果我們向 a.log 也寫(xiě)入 bar 日志,指標(biāo)會(huì)增加嗎?
# 向a.log寫(xiě)入日志
$ echo "bar" >> ~/Desktop/mtail/log1/a.log
# 查看指標(biāo)
$ curl 127.0.0.1:3903/metrics
# HELP bar_count defined at prog2.mtail:1:9-17
# TYPE bar_count counter
bar_count{prog="prog2.mtail"} 3
# HELP foo_count defined at prog1.mtail:1:9-17
# TYPE foo_count counter
foo_count{prog="prog1.mtail"} 0
可以看到指標(biāo)依然會(huì)增加。其實(shí)我們的期望是prog1.mtail只收集a.log的日志指標(biāo),prog2.mtail只收集b.log的指標(biāo),不要相互影響。
如果要解決這個(gè)問(wèn)題,就需要啟動(dòng)不同的mtail才行。換句話說(shuō)有多少日志文件,如果想分開(kāi)收集,則要啟動(dòng)多少個(gè)mtail,可以想想這是一個(gè)非??植赖氖虑?。
鑒于此,Categraf 對(duì) mtail 插件做了一些優(yōu)化,優(yōu)化后的 mtail 插件可以做到一個(gè) Categraf 進(jìn)程同時(shí)解析多個(gè)服務(wù)的日志,改造后的示例圖如下:
Categraf 操作
在前面的夜鶯監(jiān)控系列中,對(duì) Categraf 基本都有一個(gè)印象。默認(rèn)情況下它的配置都在 conf 目錄下,其中插件都在以 input 開(kāi)頭的文件夾里。
我們進(jìn)入input.mtail文件夾,編輯mtail.toml并增加如下配置:
[[instances]]
progs = "/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/prog1"
logs = ["/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/log1/a.log"]
# override_timezone = "Asia/Shanghai"
# emit_metric_timestamp = "true" #string type
[[instances]]
progs = "/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/prog2"
logs = ["/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/log1/b.log"]
# override_timezone = "Asia/Shanghai"
# emit_metric_timestamp = "true" # string type
然后添加需要的目錄以及腳本:
# 創(chuàng)建文件夾
$ mkdir {prog1,prog2,log1}
# 增加規(guī)則文件
$ cat prog1/a.mtail
counter foo_count
/foo/ {
foo_count++
}
$ cat prog2/b.mtail
counter bar_count
/bar/ {
bar_count++
}
# 增加日志文件
$ touch {log1/a.log,log1/b.log}
啟動(dòng) categraf:
# 使用測(cè)試模式啟動(dòng)
$ ./categraf -test -inputs mtail
然后往a.log寫(xiě)入foo日志。
echo "foo" >> log1/a.log
然后看到指標(biāo)增加了:
再往b.log寫(xiě)入bar日志。
echo "bar" >> log1/b.log
bar_count的指標(biāo)也相應(yīng)增加了。
那如果我們向a.log增加bar的日志,bar_count會(huì)增加么?我們來(lái)測(cè)試一下:
echo "bar" >> log1/a.log
通過(guò)觀察bar_count指標(biāo)不會(huì)增加。
Categraf 就完美解決了不同日志指標(biāo)錯(cuò)亂的問(wèn)題。
除了正常的處理指標(biāo),如果想給不同的instance指定label,也是可以的,如下:
[[instances]]
progs = "/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/prog1"
logs = ["/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/log1/a.log"]
labels = {"app"= "foo"}
# override_timezone = "Asia/Shanghai"
# emit_metric_timestamp = "true" #string type
[[instances]]
progs = "/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/prog2"
logs = ["/home/jokerbai/Desktop/categraf-v0.2.38-linux-amd64/conf/input.mtail/log1/b.log"]
labels = {"app"= "bar"}
# override_timezone = "Asia/Shanghai"
# emit_metric_timestamp = "true" # string type
重啟 Categraf 就可以看到指標(biāo)多了一個(gè) label。
其他的腳本語(yǔ)法和原生的 mtail 一致,這里不再追溯了。
總結(jié)
相比于谷歌的mtail,categraf對(duì)mtail做了一些優(yōu)化,可以更好的處理多日志的問(wèn)題。而且 categraf 本身集成了很多插件,都可以統(tǒng)一使用它實(shí)現(xiàn)。
另外,還是相同的問(wèn)題,假設(shè)插件開(kāi)啟比較多,categraf 的具體性能如何以及會(huì)不會(huì)影響主機(jī)的整體性能,這還有待研究。