
Alertmanager 的 Go 模板不支持内置排序函数,需在传入模板前通过 Go 代码对 Alerts.Firing 切片按 StartsAt 字段预排序,这是最安全、符合模板设计原则的实现方式。
alertmanager 的 go 模板不支持内置排序函数,需在传入模板前通过 go 代码对 `alerts.firing` 切片按 `startsat` 字段预排序,这是最安全、符合模板设计原则的实现方式。
在 Alertmanager 自定义邮件通知中,若希望邮件正文中的告警列表按触发时间(StartsAt)升序或降序排列,不能依赖模板内排序——因为 Go 标准库 text/template 默认未提供 sort 等数据处理函数,直接使用 {{ range sort .Alerts.Firing }} 会报错 function "sort" not defined。
正确做法是在模板渲染前、数据准备阶段完成排序。这既符合“模板只负责展示、逻辑由代码控制”的最佳实践,也避免了运行时动态解析带来的安全与可维护性风险。
以下是具体实现步骤:
-
定义排序类型:为
[]*types.Alert实现sort.Interface接口 -
在
Notify方法中排序:调用sort.Sort()对告警切片排序 -
传递已排序数据:确保
n.tmpl.Data(...)接收的是有序切片
import (
"sort"
"github.com/prometheus/alertmanager/types"
)
// ByStart 实现 sort.Interface,按 StartsAt 升序排列
type ByStart []*types.Alert
func (a ByStart) Len() int { return len(a) }
func (a ByStart) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByStart) Less(i, j int) bool { return a[i].StartsAt.Before(a[j].StartsAt) }
func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
// ✅ 关键:排序必须在构建模板数据前完成
if len(as) > 0 {
sort.Sort(ByStart(as))
}
data := n.tmpl.Data(
receiverName(ctx),
groupLabels(ctx),
as..., // 此时 as 已按 StartsAt 升序排列
)
// 后续执行邮件发送逻辑...
return true, nil
}
? 提示:如需降序排列,只需将
Less方法改为return a[i].StartsAt.After(a[j].StartsAt)即可。
⚠️ 注意事项:
- 不要尝试在模板中注册自定义排序函数(如通过
template.Funcs()注入sortFunc),这需在模板解析前全局注册,且易引入不可控副作用;Alertmanager 官方模板机制也不鼓励此类扩展。 -
StartsAt是time.Time类型,确保其值有效(非零时间);若存在nil或零值告警,建议在排序前过滤:as = filterValidAlerts(as)。 - 模板中仍使用原语法
{{ range .Alerts.Firing }},因数据已在 Go 层排序完毕,无需修改模板逻辑。
总结:模板是视图层,排序是数据准备逻辑——把 sort.Sort(ByStart(as)) 放进 Notify 方法,是最简洁、可靠、符合 Prometheus 生态规范的解决方案。










