CMS通知富化组件是阿里云监控告警系统的核心组件,负责告警消息的富化、渲染和分发。系统采用双模板引擎架构,既保持了对传统Velocity模板的兼容,又提供了更强大的Go模板引擎支持。
支持的模板引擎
Go模板引擎 - 基于Go Template,提供更强大的模板功能,用户可以自定义。
Velocity模板引擎 - 基于Apache Velocity,用于传统告警模板,云监控内置。
数据流转架构
告警事件 → EnrichHandler → TemplateRenderHandler → MessageRenderHandler → 通知分发
↓ ↓ ↓ ↓ ↓
原始数据 变量富化 注解渲染 消息渲染 渠道适配自定义告警内容
使用Go Template语法在告警内容中自定义告警参数变量,用户收到的告警信息将包含这部分告警消息内容,需注意Go Template语法的正确性。
例如配置一个自定义告警内容,当选择指标分组的指标时,会有内置的PromQL以及已为各种通知方式预置的对应模板。当预置模板不能满足需求时,可以进行手动修改。例如:
节点 {{ $labels.instance }} CPU使用率{{$labels.metrics_params_opt}} {{$labels.metrics_params_value}}%,当前CPU使用率 {{ printf "%.2f" $value }}%Go Template在渲染模板时,需要依据上下文中的数据来填充模板,其中,命名空间{{$labels.namespace}}、Pod {{$labels.pod_name}}是取自于PromQL查询出的结果。
也可以增加自定义标签和注解,自定义的标签可以通过{{$labels.custom_label_key}}引入。
模版语法
告警模版基于 Go template 引擎,支持以下语法特性:
变量引用
{{.fieldName}} # 引用字段
{{.nested.field}} # 引用嵌套字段条件判断
{{if .condition}}
条件为真时显示
{{else}}
条件为假时显示
{{end}}循环遍历
{{range .items}}
- {{.name}}: {{.value}}
{{end}}比较操作
{{if eq .status "ok"}}正常{{end}} # 等于
{{if ne .status "error"}}非错误{{end}} # 不等于
{{if gt .value 100}}大于100{{end}} # 大于
{{if lt .value 50}}小于50{{end}} # 小于
{{if ge .value 80}}大于等于80{{end}} # 大于等于
{{if le .value 20}}小于等于20{{end}} # 小于等于逻辑操作
{{if and .cond1 .cond2}}两个条件都为真{{end}}
{{if or .cond1 .cond2}}至少一个条件为真{{end}}
{{if not .condition}}条件为假{{end}}管道操作
{{.value | functionName}} # 将值传递给函数处理
{{.timestamp | humanizeDate}} # 格式化时间
{{.ratio | humanizePercentage}} # 格式化百分比可用工具函数
算子 | 语法 | 输入类型 | 输出类型 | 功能 |
humanizeDate |
| float64 | string | 时间戳(毫秒)→ 可读时间 |
humanizePercentage |
| float64 | string | 小数 → 百分比 |
md5 |
| string | string | 字符串 → MD5 |
toJson |
| any | string | 任意值 → JSON 字符串 |
fromJson |
| string | any | JSON 字符串 → 任意值 |
quote |
| any | string | 字符串加引号(含转义) |
printf |
| string, ...any | string | 格式化字符串(同 fmt.Sprintf) |
len | any | int | 获取数组/切片/Map/字符串长度 | |
regexMatch |
| string, any | bool | 正则匹配,返回是否匹配 |
regexFind |
| string, any | string | 正则查找,返回第一个匹配子串 |
regexFindAll |
| string, int, any | []string | 正则查找所有匹配子串 |
regexReplaceAll |
| string, string, any | string | 正则替换所有匹配子串 |
humanizeDate
功能: 将毫秒时间戳转换为可读的日期时间格式
输入: 数值型毫秒时间戳 (float64)
输出: 格式化的日期字符串 2006-01-02 15:04:05
示例:
{"timestamp": 1634567890000}模版:
告警时间: {{.timestamp | humanizeDate}}输出:
告警时间: 2021-10-18 21:04:50humanizePercentage
功能: 将小数转换为百分比格式(保留两位小数)
输入: 小数值 (float64),范围 0-1
输出: 百分比字符串,格式 xx.xx%
示例:
{"cpu": 0.8567}模版:
CPU 使用率: {{.cpu | humanizePercentage}}输出:
CPU 使用率: 85.67%md5
功能: 将字符串转换为 MD5 哈希值(32位小写十六进制)
输入: 字符串 (string)
输出: 32位小写 MD5 字符串
示例:
{"alertId": "ALERT-001"}模版:
告警指纹: {{.alertId | md5}}输出:
告警指纹: 8c1b6fa97c4288cdf2e505475e9c4f8etoJson
功能: 将任意值(对象、数组、字符串等)序列化为 JSON 字符串
输入: 任意类型
输出: JSON 格式字符串
示例:
{"incident": {"title": "磁盘使用率超阈值", "level": "critical"}}模版:
告警详情: {{.incident | toJson}}输出:
告警详情: {"level":"critical","title":"磁盘使用率超阈值"}组合使用:
数据:
{"tags": ["prod", "k8s", "node"]}模版:
标签列表: {{.tags | toJson}}输出:
标签列表: ["prod","k8s","node"]输入为
nil时输出null。序列化失败时返回错误描述字符串(不中断渲染)。
可与
regexFindAll等返回数组的函数组合使用。
fromJson
功能: 将 JSON 字符串反序列化为可操作的 Go 对象,支持对象字段访问和数组遍历
输入: JSON 字符串 (string)
输出: 解析后的数组 ([]interface{}) 或对象 (map[string]interface{});解析失败时返回原始字符串
基本用法:
{{range (fromJson .字段名)}}...{{end}}
{{(fromJson .字段名).fieldName}}场景一 — 遍历日志告警数组
告警触发时 {{ $value }} 拿到的日志内容是一个 JSON 字符串(如 [{...}, {...}]),用 fromJson 解析后即可遍历:
数据($value 的值作为字符串存入 data):
{
"logs": "[{\"message\":\"ERROR disk full\",\"hostname\":\"node-01\"},{\"message\":\"WARN high cpu\",\"hostname\":\"node-02\"}]"
}模版:
触发日志列表:
{{- range (fromJson .logs)}}
- 主机: {{.hostname}} 内容: {{.message}}
{{- end}}输出:
触发日志列表:
- 主机: node-01 内容: ERROR disk full
- 主机: node-02 内容: WARN high cpu场景二 — 访问对象字段
数据:
{"detail": "{\"title\":\"磁盘告警\",\"level\":\"critical\"}"}模版:
告警标题: {{(fromJson .detail).title}}
告警级别: {{(fromJson .detail).level}}输出:
告警标题: 磁盘告警
告警级别: critical输入为 JSON 数组(
[...])时返回可range遍历的数组。输入为 JSON 对象(
{...})时返回可通过.field访问的 map。解析失败时返回原始字符串,不中断模板渲染。
与
toJson互为逆操作:toJson序列化,fromJson反序列化。
regexMatch
功能: 判断输入是否与正则表达式匹配,常用于 {{if}} 条件判断
输入: pattern (string),待匹配内容 (any)
输出: bool(匹配返回 true,否则返回 false);pattern 非法时返回 error
基本用法:
{{if regexMatch "正则表达式" .字段名}}...{{end}}示例:
{"phone": "13812345678"}模版:
{{if regexMatch "^1[3-9][0-9]{9}$" .phone}}手机号格式正确{{else}}手机号格式错误{{end}}输出:
手机号格式正确正则采用 Go
regexp标准语法。非字符串类型的输入会自动转换为字符串后匹配。
pattern 编译失败时模板渲染中止并返回错误。
regexFind
功能: 在输入中查找第一个符合正则表达式的子串
输入: pattern (string),待查找内容 (any)
输出: 第一个匹配的子串 (string);无匹配时返回空字符串;pattern 非法时返回 error
基本用法:
{{regexFind "正则表达式" .字段名}}示例:
{"message": "服务器 192.168.1.100 响应超时"}模版:
告警IP: {{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .message}}输出:
告警IP: 192.168.1.100只返回第一个匹配,如需全部匹配请使用
regexFindAll。非字符串类型的输入会自动转换为字符串后匹配。
regexFindAll
功能: 在输入中查找所有符合正则表达式的子串,返回字符串数组
输入: pattern (string),n (int,最大返回数量,-1 表示不限制),待查找内容 (any)
输出: 匹配子串数组 ([]string);无匹配时返回空数组;pattern 非法时返回 error
基本用法:
{{regexFindAll "正则表达式" -1 .字段名}}示例:
{"log": "ERROR at line 12, WARNING at line 34, ERROR at line 56"}模版:
错误行号: {{regexFindAll "[0-9]+" -1 .log | toJson}}输出:
错误行号: ["12","34","56"]场景:限制返回数量
模版:
前两个行号: {{regexFindAll "[0-9]+" 2 .log | toJson}}输出:
前两个行号: ["12","34"]场景:遍历所有匹配结果
{"hosts": "web-01,web-02,web-03"}模版:
{{range regexFindAll "web-[0-9]+" -1 .hosts}}
- 主机: {{.}}
{{end}}输出:
- 主机: web-01
- 主机: web-02
- 主机: web-03n=-1时返回全部匹配,n>0时最多返回 n 个。结果可直接通过
{{range}}遍历或通过toJson转为 JSON 字符串。
regexReplaceAll
功能: 将输入中所有匹配正则表达式的子串替换为指定字符串
输入: pattern (string),replacement (string),待处理内容 (any)
输出: 替换后的字符串 (string);pattern 非法时返回 error
基本用法:
{{regexReplaceAll "正则表达式" "替换内容" .字段名}}示例一 手机号脱敏:
{"phone": "13812345678"}模版:
联系电话: {{regexReplaceAll "([0-9]{3})[0-9]{4}([0-9]{4})" "${1}****${2}" .phone}}输出:
联系电话: 138****5678示例二 敏感信息过滤:
{"message": "用户 token=abc123xyz 登录失败"}模版:
{{regexReplaceAll "token=[a-zA-Z0-9]+" "token=***" .message}}输出:
用户 token=*** 登录失败replacement 中可使用
${1}、${2}等引用捕获组。非字符串类型的输入会自动转换为字符串后处理。
模版编写指南
使用示例
示例 1:格式化时间与百分比
{
"timestamp": 1634567890000,
"cpuUsage": 0.8567
}模板:
告警时间: {{.timestamp | humanizeDate}}
CPU使用率: {{.cpuUsage | humanizePercentage}}输出:
告警时间: 2021-10-18 21:04:50
CPU使用率: 85.67%示例 2:组合使用多个算子
{
"alertId": "ALERT-001",
"timestamp": 1634567890000,
"severity": 0.95,
"incident": {"service": "order-api", "region": "cn-hangzhou"}
}模板:
告警通知
告警ID: {{.alertId}}
告警指纹: {{.alertId | md5}}
告警时间: {{.timestamp | humanizeDate}}
严重程度: {{.severity | humanizePercentage}}
告警详情: {{.incident | toJson}}输出:
告警通知
告警ID: ALERT-001
告警指纹: 8c1b6fa97c4288cdf2e505475e9c4f8e
告警时间: 2021-10-18 21:04:50
严重程度: 95.00%
告警详情: {"region":"cn-hangzhou","service":"order-api"}示例 3:正则提取关键信息
{
"logLine": "2025-08-25 19:00:01 ERROR [order-service] 连接超时 192.168.1.100:8080"
}模板:
来源IP: {{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .logLine}}
日志级别: {{regexFind "ERROR|WARN|INFO" .logLine}}输出:
来源IP: 192.168.1.100
日志级别: ERROR示例 4:正则条件判断 + 脱敏
{
"phone": "13812345678",
"email": "user@example.com"
}模板:
{{if regexMatch "^1[3-9][0-9]{9}$" .phone}}
联系电话: {{regexReplaceAll "([0-9]{3})[0-9]{4}([0-9]{4})" "${1}****${2}" .phone}}
{{end}}
{{if regexMatch "^[^@]+@[^@]+$" .email}}
联系邮箱: {{regexReplaceAll "(.{2}).+(@.+)" "${1}***${2}" .email}}
{{end}}输出:
联系电话: 138****5678
联系邮箱: us***@example.com使用建议
使用清晰的字段命名
推荐:
{{.alertName}} {{.triggerTime}} {{.severity}}
不推荐:
{{.a}} {{.t}} {{.s}}添加默认值处理
{{if .description}}
描述: {{.description}}
{{else}}
描述: 无
{{end}}合理使用工具函数
# 时间戳字段使用 humanizeDate
发生时间: {{.timestamp | humanizeDate}}
# 比例字段使用 humanizePercentage
使用率: {{.usage | humanizePercentage}}
# 对象/数组字段使用 toJson 输出完整结构
告警详情: {{.incident | toJson}}
# 需要提取文本中特定信息时使用 regexFind
来源IP: {{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .message}}模版编写参考
告警内容模版
告警名称: {{.alertName}}
告警级别: {{.level}}
触发时间: {{.triggerTime | humanizeDate}}
告警对象: {{.target}}
{{if .description}}告警描述: {{.description}}{{end}}
当前值: {{.currentValue}}
阈值: {{.threshold}}告警详情模版
=== 告警详情 ===
• 告警名称: {{.alertName}}
• 告警级别: {{if eq .level "critical"}} 严重{{else if eq .level "warning"}}警告{{else}} 信息{{end}}
• 触发时间: {{.triggerTime | humanizeDate}}
• 告警对象: {{.target}}
{{if .metrics}}
• 监控指标:
{{range .metrics}} - {{.name}}: {{.value}}{{if .unit}}{{.unit}}{{end}}
{{end}}{{end}}
{{if .suggestion}}
• 处理建议: {{.suggestion}}
{{end}}列表渲染模版
告警主机列表:
{{range .hosts}}
- 主机: {{.hostname}}
IP: {{.ip}}
CPU: {{.cpu | humanizePercentage}}
内存: {{.memory | humanizePercentage}}
{{end}}最佳实践
解析 $value 日志JSON数组
告警触发时,如果$value 是一个 JSON 数组字符串,每个元素是一条完整的日志记录,下面说明如何用模板提取关键信息并进行数据脱敏。
$value 的原始结构
[
{
"message": "2026-03-13 14:10:59.706 - INFO ... Registered instance ...",
"tk": "2026-03-13 14:10:59.707",
"hostname": "gz-k8s-dev-cpu-node-008",
"podName": "logan-registry-0",
"namespace": "logan",
"containerName": "logan-registry",
"containerImage":"registry.example.com/logancloud/logan-registry:1.12.2",
"bootName": "logan-registry",
"idc": "gz1-105",
"delay": "31.732",
"ts": "2026-03-13T06:10:59.707Z",
"ts_ms": "1773382259707",
"topic": "",
"fluentbit": "1",
"sls": "1",
"tag:client_ip": "120.232.182.210",
"tag:receive_time": "1773382293",
"tag:pack_id": "B1EDCDCB732A9C3D-402E106",
"@timestamp": "2026-03-13T06:11:29.967326280Z",
"oam/application": "",
"raw": "{\"kubernetes\":{\"pod_id\":\"aef10ba8-...\",\"namespace_name\":\"logan\",\"labels\":{...},\"annotations\":{...},...}}"
}
]raw字段本身又是一个 JSON 字符串,需要二次fromJson才能访问其中的kubernetes信息。
字段访问速查
字段 | 访问方式 | 说明 |
|
| 日志正文,末尾通常含 |
|
| 日志产生时间(字符串) |
|
| 宿主机名 |
|
| Pod 名称 |
|
| K8s 命名空间 |
|
| 容器名 |
|
| 镜像全路径 |
|
| 机房标识 |
|
| 日志采集延迟(秒) |
|
| key 含冒号,必须用 |
|
| key 含 |
|
| key 含斜杠,必须用 |
|
| raw 是嵌套 JSON,需二次解析 |
|
| 同上 |
|
| key 含点,必须用 |
数据脱敏方案
日志中可能包含 IP 地址、主机名、镜像仓库地址等敏感信息,推荐在模板层统一脱敏后再发送告警通知。
IP 地址脱敏
{{- /* 保留前两段,后两段替换为 *.* */ -}}
{{regexReplaceAll "([0-9]{1,3}\\.[0-9]{1,3})\\.[0-9]{1,3}\\.[0-9]{1,3}" "${1}.*.*" (index . "tag:client_ip")}}120.232.182.210 → 120.232.*.*
主机名脱敏
{{- /* 保留集群前缀,节点编号替换为 *** */ -}}
{{regexReplaceAll "(gz-k8s-[a-z]+-[a-z]+-[a-z]+)-[0-9]+" "${1}-***" .hostname}}gz-k8s-dev-cpu-node-008 → gz-k8s-dev-cpu-node-***
镜像仓库地址脱敏
{{- /* 只保留镜像名和 tag,去掉私有仓库域名 */ -}}
{{regexReplaceAll "^[^/]+/[^/]+/(.+)$" "${1}" .containerImage}}registry.example.com/logancloud/logan-registry:1.12.2 → logan-registry:1.12.2
日志正文脱敏(去除 token / 密码等)
{{- /* 过滤 token=xxx 形式的凭证 */ -}}
{{regexReplaceAll "token=[a-zA-Z0-9._-]+" "token=***" .message}}{{- /* 过滤 password=xxx 形式 */ -}}
{{regexReplaceAll "(?i)password=[^\\s&]+" "password=***" .message}}去除 message 末尾换行符
{{regexReplaceAll "\\s+$" "" .message}}完整告警模板示例
以下模板展示了完整的日志告警渲染,包含字段提取、特殊 key 访问、二次 fromJson 解析以及各类脱敏处理:
{{- range (fromJson $value) -}}
=== 日志告警 ===
日志时间: {{.tk}}
采集延迟: {{.delay}}s
【主机信息】
主机: {{regexReplaceAll "(gz-k8s-[a-z]+-[a-z]+-[a-z]+)-[0-9]+" "${1}-***" .hostname}}
机房: {{.idc}}
客户端IP: {{regexReplaceAll "([0-9]{1,3}\\.[0-9]{1,3})\\.[0-9]{1,3}\\.[0-9]{1,3}" "${1}.*.*" (index . "tag:client_ip")}}
【容器信息】
命名空间: {{.namespace}}
Pod: {{.podName}}
容器: {{.containerName}}
镜像: {{regexReplaceAll "^[^/]+/[^/]+/(.+)$" "${1}" .containerImage}}
【K8s 信息】
Pod ID: {{(fromJson .raw).kubernetes.pod_id}}
StatefulSet:{{index (fromJson .raw).kubernetes.labels "statefulset.kubernetes.io/pod-name"}}
【日志内容】
{{regexReplaceAll "\\s+$" "" .message}}
{{- end}}渲染输出:
=== 日志告警 ===
日志时间: 2026-03-13 14:10:59.707
采集延迟: 31.732s
【主机信息】
主机: gz-k8s-dev-cpu-node-***
机房: gz1-105
客户端IP: 120.232.*.*
【容器信息】
命名空间: logan
Pod: logan-registry-0
容器: logan-registry
镜像: logan-registry:1.12.2
【K8s 信息】
Pod ID: aef10ba8-b870-4eb6-b2f4-74a5393cfcab
StatefulSet:logan-registry-0
【日志内容】
2026-03-13 14:10:59.706 - INFO 1 --- [http-nio-8761-exec-26] c.n.e.registry.AbstractInstanceRegistry : Registered instance XP-MOBILE-STATION-SEARCH-BOOT/...只显示指定级别的日志
{{- range (fromJson $value)}}
{{- if regexMatch "ERROR|WARN" .message}}
[{{regexFind "ERROR|WARN" .message}}] {{.tk}} {{.podName}}
{{regexReplaceAll "\\s+$" "" .message}}
{{- end}}
{{- end}}常见问题
如何处理数据中不存在的字段?
使用 if 条件判断来检查字段是否存在:
{{if .optionalField}}
字段值: {{.optionalField}}
{{else}}
字段值: 未设置
{{end}}时间戳格式不正确怎么办?
humanizeDate 函数接受毫秒时间戳(13位数字)。如果是秒级时间戳(10位),需要在数据准备时乘以 1000。
如何输出一个嵌套对象的完整内容?
使用 toJson 将对象序列化为 JSON 字符串输出:
{{.incident | toJson}}正则表达式中的反斜杠如何处理?
在 Go template 字符串中,反斜杠需要双写转义。例如匹配 IP 地址:
{{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .message}}regexFindAll 返回的结果如何遍历?
可以直接通过 range 遍历,也可以通过 toJson 转为 JSON 字符串:
# 遍历输出
{{range regexFindAll "[0-9]+" -1 .text}}- {{.}}
{{end}}
# 转为 JSON 数组字符串
{{regexFindAll "[0-9]+" -1 .text | toJson}}是否支持自定义函数?
目前系统提供了 humanizeDate、humanizePercentage、md5、toJson、quote、printf、len、regexMatch、regexFind、regexFindAll、regexReplaceAll 共 11 个工具函数。
Go Template 语法速查表
基础语法
功能 | 语法 | 示例 |
变量引用 |
|
|
嵌套字段 |
|
|
条件判断 |
|
|
条件-否则 |
|
|
多分支条件 |
|
|
循环遍历 |
|
|
带下标循环 |
|
|
管道传值 |
|
|
多级管道 |
|
|
变量赋值 |
|
|
访问数组元素 |
|
|
访问Map元素 |
|
|
获取长度 |
|
|
比较操作
功能 | 语法 | 示例 |
等于 |
|
|
不等于 |
|
|
大于 |
|
|
小于 |
|
|
大于等于 |
|
|
小于等于 |
|
|
逻辑操作
功能 | 语法 | 示例 |
与 |
|
|
或 |
|
|
非 |
|
|