Go Template 模版工具支持手册

更新时间:
复制为 MD 格式

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

{{.x | humanizeDate}}

float64

string

时间戳(毫秒)→ 可读时间

humanizePercentage

{{.x | humanizePercentage}}

float64

string

小数 → 百分比

md5

{{.x | md5}}

string

string

字符串 → MD5

toJson

{{.x | toJson}}

any

string

任意值 → JSON 字符串

fromJson

{{fromJson .x}}

string

any

JSON 字符串 → 任意值

quote

{{.x | quote}}

any

string

字符串加引号(含转义)

printf

{{printf "fmt" .a .b}}

string, ...any

string

格式化字符串(同 fmt.Sprintf)

len

any

int

获取数组/切片/Map/字符串长度

regexMatch

{{if regexMatch "pattern" .x}}

string, any

bool

正则匹配,返回是否匹配

regexFind

{{regexFind "pattern" .x}}

string, any

string

正则查找,返回第一个匹配子串

regexFindAll

{{regexFindAll "pattern" n .x}}

string, int, any

[]string

正则查找所有匹配子串

regexReplaceAll

{{regexReplaceAll "pattern" "repl" .x}}

string, string, any

string

正则替换所有匹配子串

humanizeDate

功能: 将毫秒时间戳转换为可读的日期时间格式

输入: 数值型毫秒时间戳 (float64)

输出: 格式化的日期字符串 2006-01-02 15:04:05

示例:

{"timestamp": 1634567890000}

模版

告警时间: {{.timestamp | humanizeDate}}

输出

告警时间: 2021-10-18 21:04:50

humanizePercentage

功能: 将小数转换为百分比格式(保留两位小数)

输入: 小数值 (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}}

输出

告警指纹: 8c1b6fa97c4288cdf2e505475e9c4f8e

toJson

功能: 将任意值(对象、数组、字符串等)序列化为 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-03
说明
  • n=-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 信息。

字段访问速查

字段

访问方式

说明

message

{{.message}}

日志正文,末尾通常含 \n

tk

{{.tk}}

日志产生时间(字符串)

hostname

{{.hostname}}

宿主机名

podName

{{.podName}}

Pod 名称

namespace

{{.namespace}}

K8s 命名空间

containerName

{{.containerName}}

容器名

containerImage

{{.containerImage}}

镜像全路径

idc

{{.idc}}

机房标识

delay

{{.delay}}

日志采集延迟(秒)

tag:client_ip

{{index . "tag:client_ip"}}

key 含冒号,必须用 index

@timestamp

{{index . "@timestamp"}}

key 含 @,必须用 index

oam/application

{{index . "oam/application"}}

key 含斜杠,必须用 index

rawkubernetes.pod_id

{{(fromJson .raw).kubernetes.pod_id}}

raw 是嵌套 JSON,需二次解析

rawkubernetes.namespace_name

{{(fromJson .raw).kubernetes.namespace_name}}

同上

rawlabels 中含点的 key

{{index (fromJson .raw).kubernetes.labels "statefulset.kubernetes.io/pod-name"}}

key 含点,必须用 index

数据脱敏方案

日志中可能包含 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.210120.232.*.*

主机名脱敏
{{- /* 保留集群前缀,节点编号替换为 *** */ -}}
{{regexReplaceAll "(gz-k8s-[a-z]+-[a-z]+-[a-z]+)-[0-9]+" "${1}-***" .hostname}}

gz-k8s-dev-cpu-node-008gz-k8s-dev-cpu-node-***

镜像仓库地址脱敏
{{- /* 只保留镜像名和 tag,去掉私有仓库域名 */ -}}
{{regexReplaceAll "^[^/]+/[^/]+/(.+)$" "${1}" .containerImage}}

registry.example.com/logancloud/logan-registry:1.12.2logan-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}}

是否支持自定义函数?

目前系统提供了 humanizeDatehumanizePercentagemd5toJsonquoteprintflenregexMatchregexFindregexFindAllregexReplaceAll 共 11 个工具函数。

Go Template 语法速查表

基础语法

功能

语法

示例

变量引用

{{.var}}

{{.name}}

嵌套字段

{{.a.b}}

{{.incident.title}}

条件判断

{{if .x}}...{{end}}

{{if .active}}是{{end}}

条件-否则

{{if .x}}...{{else}}...{{end}}

{{if .ok}}成功{{else}}失败{{end}}

多分支条件

{{if .x}}...{{else if .y}}...{{else}}...{{end}}

{{if eq .level "critical"}}严重{{else if eq .level "warning"}}警告{{else}}信息{{end}}

循环遍历

{{range .list}}...{{end}}

{{range .items}}{{.name}}{{end}}

带下标循环

{{range $i, $v := .list}}...{{end}}

{{range $i, $v := .items}}{{$i}}:{{$v}}{{end}}

管道传值

{{.x | func}}

{{.timestamp | humanizeDate}}

多级管道

{{.x | func1 | func2}}

{{.incident | toJson | quote}}

变量赋值

{{$var := .x}}

{{$name := .alertName}}

访问数组元素

{{index .arr n}}

{{index .items 0}}

访问Map元素

{{index .map "key"}}

{{index .labels "env"}}

获取长度

{{len .x}}

{{len .items}}

比较操作

功能

语法

示例

等于

{{if eq .a .b}}

{{if eq .status "ok"}}正常{{end}}

不等于

{{if ne .a .b}}

{{if ne .code 0}}有错误{{end}}

大于

{{if gt .a .b}}

{{if gt .value 100}}超阈值{{end}}

小于

{{if lt .a .b}}

{{if lt .score 60}}不及格{{end}}

大于等于

{{if ge .a .b}}

{{if ge .value 80}}较高{{end}}

小于等于

{{if le .a .b}}

{{if le .value 20}}较低{{end}}

逻辑操作

功能

语法

示例

{{if and .a .b}}

{{if and .active .healthy}}正常运行{{end}}

{{if or .a .b}}

{{if or .error .timeout}}异常{{end}}

{{if not .x}}

{{if not .disabled}}启用中{{end}}