从应用日志中提取监控metrics

本文讲解如何使用 Categraf 的 mtail 插件从应用日志中提取 metrics 指标,适用于无法改代码埋点、需要从系统日志关键字生成监控指标、或希望用一个 Categraf 进程统一管理多个日志解析规则的场景。

作者 孔飞@快猫星云

mtail 是谷歌开源的一款从应用日志中提取 metrics 的工具。categraf 将 mtail 作为一个插件集成了进来,并且兼容 mtail 的语法,简化了部署。原本 mtail 和日志文件之间是一对一的关系,即一般都是一个 mtail 进程处理一类日志,但是对于很高配的物理机,上面会部署很多不同的服务,每个服务进程都对应一个 mtail,此时机器上会出现特别多的 mtail 进程,较难维护。把 mtail 集成到 categraf 之后,复用 categraf 的多实例插件机制,可以做到一台机器上只有一个 categraf 进程即可读取解析多个服务的日志。示例图如下:

Mtail

下面来看如何使用这个插件从日志中提取metrics。

核心要点

  • mtail 的价值是把日志中的文本模式提取成 counter、gauge、histogram 等 metrics,让无法埋点的应用也能进入指标监控体系。
  • Categraf 集成 mtail 后,可以用一个 Categraf 进程管理多个日志源和多套规则,减少一台机器上运行多个 mtail 进程的维护成本。
  • mtail 规则的核心是“正则匹配 + 表达式计算”,适合提取请求数、错误数、延迟桶、关键字出现次数等指标。
  • 生产环境要特别注意指标命名、label 维度和日志时间的时区处理,避免产生高基数或时间错位问题。

适用场景

场景 为什么适合 mtail
老应用无法改代码埋点 直接从现有日志中提取指标
系统日志关键字监控 用正则匹配错误关键字并生成计数指标
多服务日志在同一台机器 借助 Categraf 多实例插件机制统一管理
希望日志告警进入指标系统 将日志事件转换为 Prometheus 风格 metrics

插件简介

插件启动

编辑mtail.toml文件, 注意每个instance 需要指定不同的progs参数(不同的progs文件或者目录)

输入

logs参数指定要处理的日志源, 支持模糊匹配, 支持多个log文件。

处理规则

progs指定具体的规则文件目录(或文件)

处理规则与语法

处理流程

for line in lines:
  for regex in regexes:
    if match:
      do something

语法

exported variable 

pattern { 
  action statements
} 

def decorator { 
  pattern and action statements
}

定义指标名称

前面也提过,指标仅支持 counter gauge histogram 三种类型。举一个🌰

counter lines
/INFO.*/ {
    lines++
}

注意,定义的名称只支持 C类型的命名方式(字母/数字/下划线),如果想使用"-" 要使用"as"导出别名。例如,

counter lines_total as "line-count"

这样获取到的就是line-count这个指标名称了

匹配与计算(pattern/action)

PATTERN {
ACTION
}

// examples:

/foo/ {
  ACTION1
}

variable > 0 {
  ACTION2
}

/foo/ && variable > 0 {
  ACTION3
}

支持RE2正则匹配

const PREFIX /^\w+\W+\d+ /

PREFIX {
  ACTION1
}

PREFIX + /foo/ {
  ACTION2
}

这样,ACTION1 是匹配以小写字符+大写字符+数字+空格的行,ACTION2 是匹配小写字符+大写字符+数字+空格+foo开头的行。

关系运算符

  • < 小于 <= 小于等于
  • > 大于 >= 大于等于
  • == 相等 != 不等
  • =~ 匹配(模糊) !~ 不匹配(模糊)
  • || 逻辑或 && 逻辑与 ! 逻辑非

数学运算符

  • | 按位或
  • & 按位与
  • ^ 按位异或
  • + - * / 四则运算
  • << 按位左移
  • >> 按位右移
  • ** 指数运算
  • = 赋值
  • ++ 自增运算
  • -- 自减运算
  • += 加且赋值

支持else与otherwise

/foo/ {
ACTION1
} else {
ACTION2
}

支持嵌套

/foo/ {
  /foo1/ {
     ACTION1
  }
  /foo2/ {
     ACTION2
  }
  otherwise {
     ACTION3
  }
}

支持命名与非命名提取

/(?P<operation>\S+) (\S+) \[\S+\] (\S+) \(\S*\) \S+ (?P<bytes>\d+)/ {
  bytes_total[$operation][$3] += $bytes
}

增加常量label

# test.mtail
# 定义常量label env
hidden text env
# 给label 赋值 这样定义是global范围;
# 局部添加,则在对应的condition中添加
env="production"
counter line_total by logfile,env
/^(?P<date>\w+\s+\d+\s+\d+:\d+:\d+)/ {
    line_total[getfilename()][env]++
}

获取到的metrics中会添加上env=production的label 如下:

# metrics
line_total{env="production",logfile="/path/to/xxxx.log",prog="test.mtail"} 4 1661165941788

如果要给metrics增加变量label,必须要使用命名提取。例如

# 日志内容
192.168.0.1 GET /foo
192.168.0.2 GET /bar
192.168.0.1 POST /bar
# test.mtail
counter my_http_requests_total by log_file, verb 
/^/ +
/(?P<host>[0-9A-Za-z\.:-]+) / +
/(?P<verb>[A-Z]+) / +
/(?P<URI>\S+).*/ +
/$/ {
    my_http_requests_total[getfilename()][$verb]++
}
# metrics
my_http_requests_total{logfile="xxx.log",verb="GET",prog="test.mtail"} 4242
my_http_requests_total{logfile="xxx.log",verb="POST",prog="test.mtail"} 42

命名提取的变量可以在条件中使用

/(?P<x>\d+)/ && $x > 1 {
nonzero_positives++
}

时间处理

不显示处理,则默认使用系统时间

默认emit_metric_timestamp=“false”(注意是字符串)

http_latency_bucket{prog="histo.mtail",le="1"} 0
http_latency_bucket{prog="histo.mtail",le="2"} 0
http_latency_bucket{prog="histo.mtail",le="4"} 0
http_latency_bucket{prog="histo.mtail",le="8"} 0
http_latency_bucket{prog="histo.mtail",le="+Inf"} 0
http_latency_sum{prog="histo.mtail"} 0
http_latency_count{prog="histo.mtail"} 0

参数 emit_metric_timestamp=“true”(注意是字符串)

http_latency_bucket{prog="histo.mtail",le="1"} 1 1661152917471
http_latency_bucket{prog="histo.mtail",le="2"} 2 1661152917471
http_latency_bucket{prog="histo.mtail",le="4"} 2 1661152917471
http_latency_bucket{prog="histo.mtail",le="8"} 2 1661152917471
http_latency_bucket{prog="histo.mtail",le="+Inf"} 2 1661152917471
http_latency_sum{prog="histo.mtail"} 3 1661152917471
http_latency_count{prog="histo.mtail"} 4 1661152917471

使用日志的时间

Aug 22 15:28:32 GET /api/v1/pods latency=2s code=200
Aug 22 15:28:32 GET /api/v1/pods latency=1s code=200
Aug 22 15:28:32 GET /api/v1/pods latency=0s code=200
histogram http_latency buckets 1, 2, 4, 8
/^(?P<date>\w+\s+\d+\s+\d+:\d+:\d+)/ {
        strptime($date, "Jan 02 15:04:05")
	/latency=(?P<latency>\d+)/ {
		http_latency=$latency
	}
}

日志提取的时间,一定要注意时区问题,有一个参数 override_timezone 可以控制时区选择,否则默认使用UTC转换。 比如我启动时指定 override_timezone=Asia/Shanghai, 这个时候日志提取的时间会当做东八区时间 转换为timestamp, 然后再从timestamp转换为各时区时间时 就没有问题了,如图:

mtail timestamp

如果不带 override_timezone=Asia/Shanghai, 则默认将Aug 22 15:34:32 当做UTC时间,转换为timestamp。 这样再转换为本地时间时,会多了8个小时, 如图:

mtail timestamp

配置和规则编写建议

  1. 先控制日志范围logs 支持模糊匹配,但生产环境不要一次匹配过多无关文件,避免重复采集或误解析。
  2. 指标名保持稳定:指标名称建议使用字母、数字和下划线,需要导出带 - 的名称时再使用 as
  3. 谨慎增加 label:变量 label 必须来自命名提取,但不要把用户 ID、请求 ID、完整 URL 这类高基数字段直接作为 label。
  4. 区分日志时间和采集时间:如果使用日志里的时间戳,要配置好 override_timezone,否则跨时区展示时容易出现偏移。
  5. 从简单 counter 开始:先提取行数、错误数、接口请求数,再逐步增加 histogram 和复杂条件。

其他日志处理方案

本文介绍的日志处理方案相对简单,就像一把瑞士军刀,小巧锋利,如果你已经把日志都收集到 ElasticSearch 中了,想对 ElasticSearch 中的日志做告警,这就需要另一个方案了,请参考《ElasticSearch日志告警

FAQ

Q1:mtail 适合替代应用埋点吗?

A:不适合完全替代。mtail 更适合无法改代码、短期补齐监控或从系统日志中提取关键指标。新应用如果可以改代码,仍建议优先使用标准 metrics SDK 做结构化埋点。

Q2:Categraf 集成 mtail 的主要好处是什么?

A:主要好处是减少进程数量和部署复杂度。原本一类日志可能对应一个 mtail 进程,集成到 Categraf 后,可以复用 Categraf 的多实例插件机制,由一个进程读取并解析多个服务日志。

Q3:从日志提取 metrics 最容易踩什么坑?

A:最常见的是正则过宽导致误匹配、label 维度过高导致指标膨胀、日志时间时区处理错误,以及同一日志文件被重复匹配造成指标重复。

关于作者

本文作者孔飞,快猫星云工程师,文章内容是快猫技术团队共同沉淀的结晶。我们会持续输出监控、稳定性保障相关的技术文章,文章可转载,转载请注明出处,尊重技术人员的成果。

联系我们交流

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云