【夜鶯監控】從日志中提取指標的瑞士軍刀
mtail是谷歌開源的一款從應用日志提取 metrics 的工具,它會實時讀取應用程序的日志,然后通過自己編寫的腳本分析日志,最終生成時間序列的指標,項目地址是:https://github.com/google/mtail。
夜鶯的Categraf對日志指標的收集也是采用的 mtail,不過做了一些優化,具體優化了什么我們慢慢道來。
現在,我們先從谷歌的mtail開始聊起,再慢慢聊到夜鶯的 mtail 插件。
mtail 的安裝
前面已經對mtail做了簡短的介紹,其實那就是全部。
所以,我們直接從安裝開始。
從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后臺啟動
$ nohup mtail -port 3903 -logtostderr -progs test.mtail -logs test.log &
# 默認端口是3903
$ nohup ./mtail -progs test.mtail -logs test.log &
# 查看是否啟動成功
$ ps -ef | grep mtail
# 查看mtail的幫助文檔
$ mtail -h
mtail 參數詳解
安裝完mtail之后,如果對mtail的參數一無所知的話,也就不知道如何下手了,本小節就帶大家來了解一下 mtail 有哪些參數。
我們可以通過mtail -h來查看mtail支持的參數列表,下面我對這些參數加一些中文注釋,應該能夠幫助你了解它們的意思了。
$ 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監聽器的主機或者IP地址
-alsologtostderr # 記錄標準錯誤和文件
-block_profile_rate int # 報告goroutine阻塞事件之前的阻塞時間的納秒數。0表示關閉。
-collectd_prefix string # 發送給collectd的指標前綴
-collectd_socketpath string # collectd socket路徑,用于向其寫入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 duration # metric的垃圾收集器運行間隔(默認為1h0m0s)
-graphite_host_port string # graphite carbon服務器地址,格式Host:port。用于向graphite carbon服務器寫入metrics
-graphite_prefix string # 發送給graphite指標的metrics前綴
-http_debugging_endpoint # 是否開啟調式接口(/debug/*),默認開啟
-http_info_endpoint # 是否開始info接口(/progz,/varz),默認開啟
-ignore_filename_regex_pattern string # 需要忽略的日志文件名字,支持正則表達式。使用場景:當-logs參數指定的為一個目錄時,可以使用ignore_filename_regex_pattern 參數來忽略一部分文件
-jaeger_endpoint string # 如果設為true,可以將跟蹤導出到Jaeger跟蹤收集器。使用–jaeger_endpoint標志指定Jaeger端點URL
-log_backtrace_at value # 當日志記錄命中設置的行N時,發出堆棧跟蹤
-log_dir string # mtail程序的日志文件的目錄,與logtostderr作用類似,如果同時配置了logtostderr參數,則log_dir參數無效
-logs value # 監控的日志文件列表,可以使用,分隔多個文件,也可以多次使用-logs參數,也可以指定一個文件目錄,支持通配符*,指定文件目錄時需要對目錄使用單引號。
-logtostderr # 直接輸出標準錯誤信息,編譯問題也直接輸出
-max_recursion_depth int # 以解析的標記來衡量mtail語句的最大長度。過長的mtail表達式可能會導致編譯和運行時的性能問題。(默認為100)
-max_regexp_length int # 一個mtail regexp表達式的最大長度。過長的模式可能會導致編譯和運行時的性能問題。(默認為1024)
-metric_push_interval duration # metric推送時間間隔,單位:秒,默認60秒
-metric_push_interval_seconds int # 棄用,用--metric_push_interval代替
-metric_push_write_deadline duration # 在出現錯誤退出之前等待推送成功的時間。(默認10s)
-mtailDebug int # 設置解析器debug級別
-mutex_profile_fraction int # 報告的互斥爭奪事件的比例。 0將關閉
-one_shot # 此參數將編譯并運行mtail程序,然后從指定的文件開頭開始讀取日志(從頭開始讀取日志,不是實時tail),然后將收集的所有metrics打印到日志中。此參數用于驗證mtail程序是否有預期輸出,不用于生產環境。
-one_shot_format string # 與-one_shot一起使用的格式。這只是一個調試標志,不適合生產使用。支持的格式: json, prometheus. (默認為 "json")
-override_timezone string # 設置時區,如果使用此參數,將在時間戳轉換中使用指定的時區來替代UTC
-poll_interval duration # 設置輪詢所有日志文件以獲取數據的間隔;必須為正,如果為零將禁用輪詢。使用輪詢模式,將僅輪詢在mtail啟動時找到的文件
-poll_log_interval duration # 設置找到所有匹配的日志文件進行輪詢的時間間隔;必須是正數,或者是0來禁用輪詢。 在輪詢模式下,只有在mtail啟動時發現的文件會被輪詢。(默認250ms)
-port string # 監聽的http端口,默認3903
-progs string # mtail腳本程序所在路徑
-stale_log_gc_interval duration # stale的垃圾收集器運行間隔(默認為1h0m0s)
-statsd_hostport string # statsd地址,格式Host:port。用于向statsd寫入metrics
-statsd_prefix string # 發送給statsd指標的metrics前綴
-stderrthreshold value # 嚴重性級別達到閾值以上的日志信息除了寫入日志文件以外,還要輸出到stderr。各嚴重性級別對應的數值:INFO—0,WARNING—1,ERROR—2,FATAL—3,默認值為2.
-syslog_use_current_year # 如果時間戳沒有年份,則用當前年替代。(默認為true)
-trace_sample_period int # 用于設置跟蹤的采樣頻率和發送到收集器的頻率。將其設置為100,則100條收集一條追蹤。
-unix_socket string # socket監控地址
-v value # v日志的日志級別,該設置可能被 vmodule標志給覆蓋.默認為0.
-version # 打印mtail版本
-vm_logs_runtime_errors # 啟用運行時錯誤的記錄到標準日志。 如果設置為false,則只將錯誤打印到HTTP控制臺。(默認為true)
-vmodule value # 按文件或模塊來設置日志級別,如:-vmodule=mapreduce=2,file=1,gfs*=3
配置參數非常多,一般情況下我們使用的也就那幾個,如下:
nohup ./mtail -progs test.mtail -logs test.log &
指定 mtail 腳本以及日志目錄即可。
mtail 腳本語法
在https://github.com/google/mtail/blob/main/docs/Programming-Guide.md處對腳本語法有相應的介紹,這里做一個簡單的介紹。
腳本標準的格式如下:
COND {
ACTION
}
其中COND是一個條件表達式,可以是正則表達式,也可以是 boolean 類型的條件語句,如下:
/foo/ {
ACTION1
}
variable > 0{
ACTION2
}
/foo/ && variable > 0{
ACTION3
}
COND表達式可用的運算符如下:
- 關系運算符:< , <= , > , >= , == , != , =~ , !~ , || , && , !
- 算術運算符:| , & , ^ , + , - , * , /, << , >> , **
另外,ACTION是具體的操作,如下表示從日志中匹配到 foo 字段,就給相應的指標 foo_total 的值就加 1:
counter foo_total
/foo/ {
foo_total++
}
對于指標,可以用= , += , ++ , –等運算符進行操作。
mtail的目的是從日志中提取信息并將其傳遞到監控系統。因此,必須導出指標變量并命名,命名可以使用counter、histogram、gauge指標類型,并且命名的變量必須在COND腳本之前。
- Counter(計數器):用于記錄單調遞增的值,例如請求數、錯誤數等。
- Gauge(儀表):用于記錄可增可減的值,例如 CPU 使用率、內存使用量等。
- Histogram(直方圖):用于記錄數據的分布情況,例如請求延遲、響應大小等。
我們知道,拿 Prometheus 來說,除了上面的三種指標類型之外還有一個Summary的指標類型,為什么 mtail 沒有呢?
因為在 Prometheus 中,summary 指標類型用于記錄數據的分布情況,并計算出更多的統計信息,例如平均值、中位數、標準差等。但是,由于 mtail 是從日志文件中提取指標,而不是直接從應用程序中提取指標,因此沒有必要使用 summary 指標類型。
高階用法
變量定義
對于在一個腳本中需要重復使用的表達式,可以將其定義為一個變量,后續可以直接使用變量。
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++
}
這是開發中常用的手段。
解析時間戳
mtail 會為每一個日志事件都賦予一個時間戳,如果日志里沒有時間戳,mtail 會為本次日志事件賦予一個當前的日志時間。
除此之外,如果日志里的時間戳不是標準時間或者其他情況,可以使用 strptime 對其進行解析,如下:
/^/ +
/(?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 程序中正常的條件控制流結構。
如果模式匹配,那么該塊中的動作就會被執行。如果模式不匹配,則跳過該塊。
else關鍵字允許程序在模式不匹配的情況下執行動作。
/pattern/ {
action
} else {
alternative
}
除此之外,還可以使用 otherwise 來處理沒有匹配到的規則,如下:
{
/pattern1/ { _action1_ }
/pattern2/ { _action2_ }
otherwise { _action3_ }
}
這種語法類似于switch case default語法。
精準匹配
上面的/pattern/ { _action_ }形式隱含地匹配了當前的輸入日志行。
如果想與另一個字符串變量匹配,可以使用=~操作符,或者用!~來否定匹配,像這樣:
$1 =~ /GET/ {
...
}
解析非數字類型的數字字段
有時候遇到的日志里輸出的數字是字符串,而非數字,mtail 可以對其進行解析,如下:
counter total
/^[a-z]+ ((?P<response_size>\d+)|-)$/ {
$1 != "-" {
total = $response_size
}
}
解析帶有額外字符的數字
一些日志包含除了包含數字,還包含分隔符,我們可以用 subst 函數刪除它們:
/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))
}
過濾操作
如果你想過濾一些不必要的日志被mtail采集,你可以使用stop,如下:
getfilename() !~ /apache.access.?log/ {
stop
}
重寫操作
一些日志,如網絡服務器日志,描述了一些常見的元素,其中有獨特的標識符,如果不加處理,會導致大量的度量衡鍵,而沒有有用的計數。要重寫這些捕獲組,可以使用 subst(),將模式作為第一個參數:
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中/后面的任何數字部分替換為字面字符串/:num,所以我們最終只計算 URL 路由的靜態部分。
mtail 實操
說一千,道一萬,不如真正來一遍。
當然,我這里也不會把上面說的都來一次。
為了方便闡述,我把本次操作的腳本都放到~/Desktop/mtail目錄中。
單日志采集
# 創建prog1,里面用于保存日志處理的規則腳本
$ mkdir prog1
# 在prog1里創建prog1.mtail文件并寫入以下內容
$ cat prog1.mtail
counter foo_count
/foo/{
foo_count++
}
# 創建log1目錄
$ mkdir log1
# 在log1中創建a.log文件
¥ touch a.log
# 啟動mtail
$ mtail -progs ~/Desktop/mtail/prog1 -logs ~/Desktop/mtail/log1/a.log
# 向a.log中寫入foo
$ echo "foo" > ~/Desktop/mtail/log1/a.log
# 查看指標明細
$ 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指標數為1了
多日志采集
如果多日志在同一個文件夾里,這時候采集的指標就可能混淆。
# 在log1目錄中創建b.log
$ touch b.log
# 然后為b.log重新創建一個指標腳本
$ cat prog1/prog2.mtail
counter bar_count
/bar/{
bar_count++
}
# 啟動mtail
$ mtail -progs ~/Desktop/mtail/prog1 -logs ~/Desktop/mtail/log1/a.log -logs ~/Desktop/mtail/log1/b.log
# 向b.log寫入日志
$ echo "bar" >> ~/Desktop/mtail/log1/b.log
# 查看指標
$ 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
可以看到能正常收集指標,但是如果我們向 a.log 也寫入 bar 日志,指標會增加嗎?
# 向a.log寫入日志
$ echo "bar" >> ~/Desktop/mtail/log1/a.log
# 查看指標
$ 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
可以看到指標依然會增加。其實我們的期望是prog1.mtail只收集a.log的日志指標,prog2.mtail只收集b.log的指標,不要相互影響。
如果要解決這個問題,就需要啟動不同的mtail才行。換句話說有多少日志文件,如果想分開收集,則要啟動多少個mtail,可以想想這是一個非常恐怖的事情。
鑒于此,Categraf 對 mtail 插件做了一些優化,優化后的 mtail 插件可以做到一個 Categraf 進程同時解析多個服務的日志,改造后的示例圖如下:
Categraf 操作
在前面的夜鶯監控系列中,對 Categraf 基本都有一個印象。默認情況下它的配置都在 conf 目錄下,其中插件都在以 input 開頭的文件夾里。
我們進入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
然后添加需要的目錄以及腳本:
# 創建文件夾
$ mkdir {prog1,prog2,log1}
# 增加規則文件
$ 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}
啟動 categraf:
# 使用測試模式啟動
$ ./categraf -test -inputs mtail
然后往a.log寫入foo日志。
echo "foo" >> log1/a.log
然后看到指標增加了:
再往b.log寫入bar日志。
echo "bar" >> log1/b.log
bar_count的指標也相應增加了。
那如果我們向a.log增加bar的日志,bar_count會增加么?我們來測試一下:
echo "bar" >> log1/a.log
通過觀察bar_count指標不會增加。
Categraf 就完美解決了不同日志指標錯亂的問題。
除了正常的處理指標,如果想給不同的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 就可以看到指標多了一個 label。
其他的腳本語法和原生的 mtail 一致,這里不再追溯了。
總結
相比于谷歌的mtail,categraf對mtail做了一些優化,可以更好的處理多日志的問題。而且 categraf 本身集成了很多插件,都可以統一使用它實現。
另外,還是相同的問題,假設插件開啟比較多,categraf 的具體性能如何以及會不會影響主機的整體性能,這還有待研究。