告警表达式语法
夜莺 v9 告警表达式语法:用 `$A`、`$B` 引用查询结果做算术 / 比较 / 逻辑运算,构造任意业务/SQL/Prometheus 数据源的复合告警条件。
概述
告警表达式是夜莺通用的告警判定语言,可以引用一个或多个数据源查询的结果(用 $A、$B…),用算术/关系/逻辑运算符合成布尔表达式,作为告警是否触发的判定。
侧栏路径:告警规则编辑页 → 查询条件区域 → 选用"表达式模式"。
适用场景:
- 跨查询联合判定:
$A.qps > 1000 && $B.error_rate > 0.05(QPS 高 + 错误率高); - 比率类指标:
$B.failed_orders / $A.total_orders > 0.1(失败率 > 10%); - SQL / 业务库告警:MySQL 数据源查 SQL,按业务字段判定(详见 MySQL 告警举例);
- PromQL 不易表达的逻辑:复合关系判定、字符串匹配、值范围等。
核心概念:值字段是数值,标签字段是字符串
这是理解整套表达式语法的前提,先讲清楚再看运算符会顺很多。
表达式的本质是时序数据之间的运算。查询结果(无论来自 Prometheus、MySQL 还是其它数据源)都会先映射成统一的时序格式:
<metric name>{<label name>=<label value>, …} value
映射靠"查询条件"里的两个字段完成,它们直接决定了每一列在表达式里是什么类型:
| 配置字段 | 在表达式里的类型 | 引用方式 | 能做什么 |
|---|---|---|---|
| 值字段 | 数值(float64) | $A |
算术 + - * /、数值比较、between、时间戳运算 |
| 标签字段 | 字符串 | $A.<标签名> |
字符串相等/字典序比较、contains、matches、in |
一句话记住:值字段会被当成数值,只有标签字段才是字符串。
所以"我想对某列做什么运算"反过来决定了"该把这列配在哪":
- 要做数值比较 / 算术(如
> 1000、求比率)→ 放值字段;- 要做字符串判定(如等于
"FAILED"、匹配正则、比时间字符串)→ 放标签字段。如果把字符串列(如
FAILED、23:55:00)错放进值字段,引擎按数值解析,要么失败要么变成 0,字符串比较不会生效。
举例:一列既能当值也能当标签
MySQL 查询返回:
| product_name | price |
|---|---|
| apple | 100 |
| banana | 50 |
设 值字段 = price、标签字段 = product_name,被识别成两条时序:
$A.price{product_name=apple} 100
$A.price{product_name=banana} 50
此时:
$A(= price)是数值,可写$A > 80;$A.product_name是字符串,可写$A.product_name == "apple"。
基于标签的对齐
运算只在相同 Label 集合的数据之间进行,不同 Label 集合的数据无法直接运算。
如果再有 $B 查询返回 cost 字段,只有 product_name 标签也对齐的 series 才会和 $A.price 做运算($A.price - $B.cost)。标签不一致时用"字段重命名"统一两侧的标签 key/value。
支持的数据类型
承接上一节,表达式引擎支持以下几类值,关系运算两侧类型必须一致:
| 类型 | 来源举例 | 可用运算 |
|---|---|---|
| 数值(int / float) | 值字段 $A、数字字面量 10、now().Unix() |
算术 + - * /、关系 > < >= <= == !=、between |
| 字符串 | 标签值 $A.host、带引号字面量 "FAILED" |
== != > <(字典序)、contains、matches、in |
| 布尔 | 关系/逻辑运算的结果 | && || ! |
| 数组 | 字面量 [1, 2, 3]、["a", "b"] |
in、not in |
| 日期 / 时间 | now()、date("2025-01-01")、duration("1h") |
比较大小、相减得时长、取 .Hour() 等(见下文) |
没有"自动类型转换":数值和字符串不能直接比较。例如
$A > "23:55:00"中$A是数值、右侧是字符串,会因类型不匹配判定失败。需要时用int()/float()/string()/date()显式转换。
运算符全集
算术运算符(数值)
| 符号 | 说明 | 示例 |
|---|---|---|
+ |
相加 | $A + $B |
- |
相减 | $A - $B |
* |
相乘 | $A * $B |
/ |
相除 | $A / $B |
( ) |
优先级 | ($A - $B) * $C |
关系运算符(产出布尔,告警判定的核心)
| 符号 | 说明 | 示例 |
|---|---|---|
== |
等于 | $A == 0 |
!= |
不等于 | $A.count != 0 |
> |
大于 | $A > $B |
< |
小于 | $A < 0 |
>= |
大于等于 | $A >= 100 |
<= |
小于等于 | $A <= $B |
两侧类型必须一致:值(如
$A)是数值,只能和数值比较;标签(如$A.host)是字符串,只能和带引号的字符串字面量比较。
字符串(标签值)的比较
前提:要比较的列必须配成标签字段(参见 核心概念)。放在值字段的列是数值,无法做字符串比较。
标签值是字符串,可以和带双引号的字符串字面量做比较。常用于「按状态/时间字符串判定」——比如标签里存的是状态码,或 HH:MM:SS 形式的时间:
| 表达式 | 标签值 | 结果 | 说明 |
|---|---|---|---|
$A.status == "FAILED" |
FAILED |
true | 字符串相等 |
$A.ts == "23:55:00" |
23:55:00 |
true | 字符串相等 |
$A.ts != "23:55:00" |
10:00:00 |
true | 字符串不等 |
$A.ts > "23:55:00" |
23:56:00 |
true | 字典序比较 |
$A.ts < "23:55:00" |
10:00:00 |
true | 字典序比较 |
注意事项:
- 字面量必须加双引号:
$A.ts > 23:55:00(不加引号)会被解析为非法语法(冒号被当成运算符)而报错,整条表达式判定失败。 >/<比较的是字典序,不是时间大小。只有当时间是定宽零填充的HH:MM:SS格式时,字典序才与时间先后一致。如果格式不规整(如9:05:00未补零),"9:05:00" > "23:55:00"字典序为 true、时间上却为 false——务必保证格式统一补零;或用date($A.ts)解析后再比较(见 日期 / 时间运算)。
逻辑运算符
| 符号 | 说明 | 示例 |
|---|---|---|
&& |
与 — 两边都 true 才 true | $A > 0 && $B < 10 |
|| |
或 — 任一边 true 即 true | $A + $B > 20 || $A * $B < 50 |
集合运算
| 符号 | 说明 | 示例 |
|---|---|---|
in |
元素是否在数组里 | $A.status in ["admin", "moderator"] |
not in |
元素不在数组里 | $A not in [1, 2, 3] |
字符串包含 / 正则(标签值)
| 符号 | 说明 | 示例 |
|---|---|---|
contains |
字符串包含 | $A.msg contains "error" → true |
not contains |
字符串不包含 | 同上反逻辑 |
matches |
字符串是否匹配正则 | $A.code matches "^[0-9]+$",$A.code="123" → true |
值范围(数值)
| 符号 | 说明 | 示例 |
|---|---|---|
between |
在区间内(闭区间) | between($A, [100, 200]),$A=155 → true |
not between |
不在区间内 | not between($A, [100, 200]) |
注意:
between适用数值类型(int / float),不支持字符串。
日期 / 时间运算
引擎内置日期/时间类型,可用以下函数构造,再做比较或相减:
| 函数 / 方法 | 说明 | 示例 |
|---|---|---|
now() |
当前时间(时间对象) | now().Unix()、now().Hour() |
now().Unix() |
当前 Unix 秒数(数值) | now().Unix() - $A.timestamp > 60(超过 60s 未更新) |
date("...") |
解析时间字符串为时间对象,支持 2025-01-01、2025-01-01T12:00:00Z 等 |
date($A.d) > date("2025-01-01") |
duration("...") |
解析时长,支持 s/m/h 等 |
now() - $A.start_ts > duration("1h") |
.Hour() / .Minute() / .Unix() / .Sub() |
时间对象的方法 | now().Hour() >= 22(22 点后) |
常见用法:
| 场景 | 表达式 |
|---|---|
| 数据超过 60s 未更新 | now().Unix() - $A.last_update_ts > 60 |
| 标签里的日期早于某天 | date($A.d) < date("2025-01-01") |
| 两个时间相差超过 24 小时 | now() - date($A.created_at) > duration("24h") |
| 当前时刻在某小时之后 | now().Hour() >= 22 |
注意:
- 时间对象之间可以比较大小、相减(得到时长),但不能乘除。
- 标签里的时间/日期是字符串,要先用
date(...)解析成时间对象再比较;直接用字符串>走的是字典序(见上文「字符串(标签值)的比较」),仅在定宽零填充格式下才与时间一致。 - 标签里的 Unix 时间戳字符串,用
int($A.ts)转成数值再和数字比较。
实操:常用告警表达式
| 场景 | 表达式 | 值/标签配置要点 |
|---|---|---|
| QPS 高 + 错误率高 | $A.qps > 1000 && $B.error_rate > 0.05 |
qps、error_rate 放值字段 |
| 失败率超过阈值 | $B.failed / ($A.total + $B.failed) * 100 > 5 |
failed、total 放值字段 |
| 数据 60s 未更新(异常停止) | now().Unix() - $A.last_update_ts > 60 |
时间戳放值字段(数值) |
| 状态属于异常集合 | $A.status in ["FAILED", "TIMEOUT", "ERROR"] |
status 放标签字段(字符串) |
| 订单金额超出区间 | between($A.amount, [10000, 1000000]) == false |
amount 放值字段 |
| 已取消且金额大 | $A.status == "Order Canceled" && $A.total_amount > 100 |
status 放标签、total_amount 放值 |
更详细的 SQL 类业务告警场景见 MySQL 告警举例。
常见问题
Q1:表达式可以引用几个查询?
A:理论上没硬上限,但建议 ≤ 3 个($A / $B / $C)。查询数越多对齐越复杂、表达式越难维护、性能开销也越大。
Q2:为什么字符串比较(如 == "FAILED")不生效?
A:最常见原因是把该列配在了"值字段"——值字段会被当成数值解析,字符串会失败或变成 0。要做字符串判定,必须把该列配成标签字段,再用 $A.<标签名> 引用。参见 核心概念。
Q3:两个查询的标签不一致,表达式不生效,怎么办?
A:常见对齐技巧:
- 在查询本身的"标签字段"里对齐字段名(重命名字段统一);
- 用聚合把不需要的标签去掉(如 PromQL 用
sum without (instance)); - 检查返回数据的实际标签集合(数据预览功能可看)。
Q4:== 比浮点数为什么经常不准?
A:浮点比较的常见陷阱。例如 0.1 + 0.2 == 0.3 在大多数语言里是 false。建议用:
- 范围判定:
between($A, [0.299, 0.301]); - 差值阈值:
$A - 0.3 < 0.001 && $A - 0.3 > -0.001。
Q5:能不能写 $A > 23:55:00 这样按时间判定?
A:不能直接这么写。23:55:00 不加引号会被当成非法语法(: 被识别为运算符)导致编译失败;即便加引号成 "23:55:00",指标的值 $A 是数值,和字符串比较会类型不匹配。两种写法都会让表达式判定失败(且界面无明显报错,仅后端日志记录)。
正确做法有几种:
- 和标签里的日期/时间比:把该列配成标签字段,先用
date(...)解析再比较,如date($A.ts) > date("2025-01-01");若标签是定宽零填充的HH:MM:SS,也可直接字符串比较$A.ts > "23:55:00"(字典序,见上文「字符串(标签值)的比较」)。 - 和当前时刻比:用
now(),如now().Unix() - $A.ts > 60(值字段是 Unix 时间戳)、now().Hour() >= 22(22 点后)、now() - date($A.created_at) > duration("24h")。 - 限定告警只在某时间段生效:用告警规则的「生效时间」配置,而不是写进表达式。
Q6:表达式里能调用自定义函数吗?
A:目前不支持用户定义函数。内置的运算符 + 函数(如 between、now()、contains、matches)覆盖绝大多数场景。需要复杂逻辑建议在数据源那边(SQL/PromQL)就把结果算好,表达式里只做布尔判定。