一键导入
n9e-generate-message-template
生成或修改夜莺(n9e)告警通知消息模板。当用户要求写通知模板、改消息格式、加主机名/恢复值/级别、钉钉/飞书/Lark/邮件/短信/电话模板时使用。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
生成或修改夜莺(n9e)告警通知消息模板。当用户要求写通知模板、改消息格式、加主机名/恢复值/级别、钉钉/飞书/Lark/邮件/短信/电话模板时使用。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
**创建告警规则**。优先复用 integrations 里验证过的规则(标准组件 Linux/MySQL/Redis/Kafka/PostgreSQL/Elasticsearch 等都有现成规则包),导入几条按用户需求来——单条、一批、或整套都行;integration 里没有贴合的规则时再手写自定义规则。支持 Prometheus / Loki / ES / OpenSearch / MySQL / PG / TDengine / ClickHouse / Doris / VictoriaLogs / Host 全部数据源。 ⚠️ **不要用这个 skill 做批量 YAML 导入**——用户给的是 URL 或 YAML 文件、awesome-prometheus-alerts、node-exporter.yml 之类,请改用 n9e-import-prom-rule。 触发:创建一条/加一条告警 / 帮我建个 CPU 告警 / 给 MySQL 加套告警规则 / 给主机配上常用告警 / 我要监控某个指标。
夜莺(n9e)告警屏蔽规则(alert_mute)的创建、编辑、排障一站式助手。当用户要求"创建屏蔽规则 / 屏蔽告警 / 静默告警 / 维护窗口免打扰 / 周期性屏蔽 / 每天凌晨屏蔽 / 调整或延长屏蔽 / 屏蔽不生效排查"时使用。屏蔽作用于事件评估阶段(被屏蔽的事件不落库不通知);要配"哪些事件通知给谁"用通知规则(→ n9e-notify-rule-copilot),要查"为什么没收到通知"走告警排障(→ n9e-alert-rule-troubleshoot)。
夜莺(n9e)告警订阅规则(alert_subscribe)的创建、编辑、排障一站式助手。当用户要求"创建订阅规则 / 订阅告警 / 告警事件转发 / 抄送另一个团队 / 告警升级(持续 N 分钟未处理再通知谁)/ 跨业务组收告警 / 订阅不生效排查"时使用。订阅是通知阶段对事件的"复制+二次路由";要配"事件直接通知给谁"用通知规则(→ n9e-notify-rule-copilot),要不收告警用屏蔽(→ n9e-alert-mute-copilot)。
夜莺(n9e)通知规则(notify_rule)的创建、编辑、复制、排障一站式助手。当用户要求"创建通知规则 / 添加通知策略 / 配置告警通知方式 / 编辑调整通知规则 / 分级通知 / 按业务组或标签路由 / 工作时间与非工作时间不同动作 / 恢复时不打电话 / 修复规则匹配不上"时使用——尤其擅长把自然语言路由需求拆成正确的 NotifyConfig 数组。本技能专注通知规则的路由层:不动通知媒介本身(→ n9e-notify-channel-copilot),不动消息模板(→ n9e-generate-message-template),不查"为什么没发出"(→ n9e-alert-rule-troubleshoot 流程 B)。
创建监控仪表盘。当用户要求创建仪表盘、监控大盘、Dashboard 时使用。
**批量导入 Prometheus 告警规则 YAML 文件**(一次性建一组规则)。专用于处理远端 URL 或本地 YAML 文本,自动解析 `groups` / 纯 `rules` 数组 / 单条 rule 三种格式。 ⚠️ **不要用这个 skill 做单条创建**——用户用自然语言描述一条告警需求时,请改用 n9e-create-alert-rule。 触发:导入 / import / 批量 / URL / .yml 文件 / .yaml 文件 / awesome-prometheus-alerts / node-exporter.yml / prometheus rule file。
| name | n9e-generate-message-template |
| description | 生成或修改夜莺(n9e)告警通知消息模板。当用户要求写通知模板、改消息格式、加主机名/恢复值/级别、钉钉/飞书/Lark/邮件/短信/电话模板时使用。 |
| tags | ["internal"] |
夜莺的消息模板是 Go text/template / html/template 语法(邮件走 text/template,其他走 html/template 再做转义)。用户在「通知管理 → 消息模板」页面编辑,保存后被通知规则引用,触发告警时按渲染数据替换变量后发送到各通道。
本技能专注于写/改模板片段本身,不涉及创建通知规则或通道配置。
后端执行时传入的 renderData:
| key | 类型 | 说明 |
|---|---|---|
.events | []*AlertCurEvent | 当前批次的告警事件列表,通常只有 1 条 |
.domain | string | n9e 站点 URL,用于拼跳转链接 |
自动注入的简写(不用手写):
{{ $events := .events }}
{{ $event := index $events 0 }}
{{ $labels := $event.TagsMap }}
{{ $value := $event.TriggerValue }}
所以模板里可以直接使用 $event.xxx、$labels.agent_hostname、$value。
$event 可用字段(*AlertCurEvent)| 字段 | 类型 | 说明 |
|---|---|---|
.Id | int64 | 告警事件 ID(拼跳转链用) |
.RuleId | int64 | 告警规则 ID |
.RuleName | string | 规则名称 |
.RuleNote | string | 规则备注 |
.Severity | int | 告警级别:1=Critical, 2=Warning, 3=Info |
.PromQl | string | 告警触发表达式 |
.RuleAlgo | string | 规则算法类型 |
.TriggerTime | int64 | 触发时间 unix 秒 |
.TriggerValue | string | 触发时的指标值(已为字符串) |
.FirstTriggerTime | int64 | 首次异常时间(连续告警) |
.LastEvalTime | int64 | 最近一次评估时间,恢复时作为恢复时间使用 |
.IsRecovered | bool | 是否已恢复 |
.NotifyCurNumber | int | 本次通知是第几次发送 |
.TargetIdent | string | 监控对象(通常是 agent_hostname) |
.TargetNote | string | 对象备注 |
.GroupId / .GroupName | int64 / string | 业务组 |
.Cluster | string | 数据源集群名 |
.Cate | string | 数据源类型(prometheus / host / mysql / ...) |
.RunbookUrl | string | 运行手册链接 |
| 字段 | 类型 | 说明 |
|---|---|---|
.TagsMap | map[string]string | 事件标签,优先用:{{$labels.agent_hostname}} |
.TagsJSON | []string | 形如 ["k=v", ...] 的字符串数组(早期模板常用) |
.AnnotationsJSON | map[string]string | 注解,常用 .AnnotationsJSON.recovery_value |
.TriggerValuesJson.ValuesWithUnit | map | 带单位的触发值(多值场景) |
| 字段 | 类型 | 说明 |
|---|---|---|
.NotifyUsersObj | []*User | 本次要通知的用户对象(有 Username/Nickname/Phone/Email) |
.NotifyGroupsObj | []*UserGroup | 关联用户组 |
用户对象在钉钉/飞书/Lark 模板里常用来拼 at,见下文
ats系列 helper。
tplx.TemplateFuncMap)timeformat <unix> — 默认 "2006-01-02 15:04:05";传第二个参数覆盖:{{timeformat $event.TriggerTime "15:04:05"}}timestamp — 当前时间字符串now.Unix — 当前 unix 秒(now 是 Go template 内置)humanizeDuration <sec> — "3m15s"humanizeDurationInterface <interface> — 同上但接受 interfacetoTime <unix> — 返回 time.Time,可链式调用parseDuration <"5m"> — 返回 time.DurationformatDecimal <v> <n> — 保留 n 位小数({{formatDecimal $event.TriggerValue 2}})humanize <v> — K/M/G 单位(1000 进制,SI)humanize1024 <v> — Ki/Mi/Gi(1024 进制)humanizePercentage <v> / humanizePercentageH <v> — 百分比add / sub / mul / div <a> <b> — 四则printf "%.2f" <v> — Go fmt.SprintftoUpper / toLower / title — 大小写contains <s> <sub> — 子串match <regex> <s> — 正则匹配(bool)reReplaceAll <regex> <repl> <s> — 正则替换split <s> <sep> / join <slice> <sep>stripPort <host:port> / stripDomain <host.domain>b64enc / b64dec — base64escape <s> — URL 路径转义unescaped <s> — 输出 raw HTML(不转义)safeHtml <s> — 同上urlconvert <s>label <key> <labelMap> / value <key> <m> / strvalue <v>first <slice> — 取第一个tagsMapToStr <map> — 标签拼成 k=v,k=vsortByLabel <items> <key> — 按标签排序ats <users> <platform> — 生成 at 片段batchContactsAts <contacts> <platform> — 批量 atbatchContactsAtsInFeishuEmail <contacts> / batchContactsAtsInFeishuId <contacts> — 飞书专用batchContactsJoinComma <contacts> / batchContactsJsonMarshal <contacts>mappingAndJoin <map> <kvSep> <itemSep>jsonMarshal <v> — 序列化为 JSON 字符串mapDifference <a> <b> — 集合差notify_channel_ident)语法差异消息模板绑在具体通道上,通道 ident 决定文本引擎和转义行为:
| Ident | 引擎 | 说明 |
|---|---|---|
email | text/template | 不再次转义,HTML 模板直接写标签 |
slackwebhook / slackbot | html/template | 渲染后把 " / \n 转义并包成 template.HTML;通常写 Markdown |
其它(dingtalk / feishu / feishucard / larkcard / wecom / tx-sms / ali-voice …) | html/template | 渲染后对 " \n \r 做 JSON 字符串转义,适合直接塞入 webhook payload |
重要:
html/template会对<>&自动转义。不想转义的地方要用{{unescaped "…"}}或{{safeHtml .X}},否则钉钉里渲染出来会看到字面<。
dingtalk,"飞书卡片"是 feishucard,"邮件"是 email。不同通道风格差异大。$event.IsRecovered 两个分支。只有短信/语音可以省略。$labels.<key>,而不是 $event.TagsJSON。$event.TriggerValue;要两位小数就 {{formatDecimal $event.TriggerValue 2}}。timeformat。{{.domain}}/share/alert-his-events/{{$event.Id}}。{{$event.Severity}}{{if eq $event.Severity 1}}一级{{else if eq $event.Severity 2}}二级{{else}}三级{{end}}Critical / Warning / Info@全员:在模板末尾加一行 @all(钉钉按空格分词匹配)。精确 at 手机号:{{range $event.NotifyUsersObj}}@{{.Phone}} {{end}}。{{batchContactsAtsInFeishuEmail $event.NotifyUsersObj}} 拼出 <at email=...></at> 片段。 gotemplate ``` 包裹模板主体,末尾给一段变量/函数说明,每个非平凡变量/函数单独一行。回复给用户时:
<模板内容>
**变量说明**:列出模板里用到的非平凡字段和函数($event.TargetIdent、formatDecimal、timeformat 之类),每项一行。target_ident 还是某个 tag),在导语里直接点出做了什么假设,不要反问。语言跟随用户输入(中文输入就用中文)。
#### {{if $event.IsRecovered}}<font color="#008800">💚{{$event.RuleName}}</font>{{else}}<font color="#FF0000">💔{{$event.RuleName}}</font>{{end}}
---
{{$duration := sub now.Unix $event.FirstTriggerTime}}{{if $event.IsRecovered}}{{$duration = sub $event.LastEvalTime $event.FirstTriggerTime}}{{end}}
- **告警级别**: S{{$event.Severity}}
{{- if $event.RuleNote}}
- **规则备注**: {{$event.RuleNote}}
{{- end}}
{{- if $event.TargetIdent}}
- **监控对象**: {{$event.TargetIdent}}
{{- end}}
{{- if not $event.IsRecovered}}
- **触发时值**: {{$event.TriggerValue}}
- **触发时间**: {{timeformat $event.TriggerTime}}
- **持续时长**: {{humanizeDurationInterface $duration}}
{{- else}}
- **恢复时间**: {{timeformat $event.LastEvalTime}}
- **持续时长**: {{humanizeDurationInterface $duration}}
{{- end}}
- **事件标签**:
{{- range $k, $v := $labels}}
{{- if ne $k "rulename"}}
- {{$k}}: {{$v}}
{{- end}}
{{- end}}
[事件详情]({{.domain}}/share/alert-his-events/{{$event.Id}}) | [屏蔽1小时]({{.domain}}/alert-mutes/add?__event_id={{$event.Id}})
{{- if $event.IsRecovered}}
**级别状态:** S{{$event.Severity}} Recovered
**告警名称:** {{$event.RuleName}}
**事件标签:** {{$event.TagsJSON}}
**恢复时间:** {{timeformat $event.LastEvalTime}}
{{- else}}
**级别状态:** S{{$event.Severity}} Triggered
**告警名称:** {{$event.RuleName}}
**事件标签:** {{$event.TagsJSON}}
**触发时间:** {{timeformat $event.TriggerTime}}
**触发时值:** {{$event.TriggerValue}}
{{- if $event.RuleNote}}
**告警描述:** {{$event.RuleNote}}
{{- end}}
{{- end}}
级别状态: S{{$event.Severity}} {{if $event.IsRecovered}}Recovered{{else}}Triggered{{end}} 规则: {{$event.RuleName}} 对象: {{$event.TargetIdent}}
- **主机**: {{$event.TargetIdent}}
{{- if $labels.ip}}
- **IP**: {{$labels.ip}}
{{- end}}
说明:
target_ident一般就是主机名;如果需要 IP,优先看标签ip、instance、host。
- **触发时值**: {{formatDecimal $event.TriggerValue 2}}
TriggerValue本身是字符串,formatDecimal会先转浮点再格式化,非数字会原样返回。
...模板正文...
{{- range $event.NotifyUsersObj}}@{{.Phone}} {{end}}
钉钉按 "空格 + 手机号" 识别被 at 的用户。或者统一
@all。
{{- if $event.IsRecovered}}
{{- if $event.AnnotationsJSON.recovery_value}}
- **恢复时值**: {{formatDecimal $event.AnnotationsJSON.recovery_value 4}}
{{- end}}
- **恢复时间**: {{timeformat $event.LastEvalTime}}
{{- end}}
恢复值由告警引擎在恢复时写入
AnnotationsJSON.recovery_value,仅在有恢复值的情况下存在,用if保护。
- **级别**: {{if eq $event.Severity 1}}一级(紧急){{else if eq $event.Severity 2}}二级(重要){{else}}三级(提示){{end}}
模板本身不做过滤——过滤应放在通知规则的 attributes / label_keys。模板里只负责展示。如果用户问到这里要提示一下。
钉钉/邮件用 <font color>,飞书卡片靠 template 字段填颜色 keyword:
{{/* 钉钉 markdown:标题前置 emoji + 颜色 */}}
#### {{if $event.IsRecovered}}<font color="#008800">✅ {{$event.RuleName}}</font>{{else}}<font color="#FF0000">🚨 {{$event.RuleName}}</font>{{end}}
{{/* 飞书 feishucard 的 template 字段(决定卡片头部色块) */}}
{{if $event.IsRecovered}}turquoise{{else}}{{if eq $event.Severity 1}}red{{else if eq $event.Severity 2}}orange{{else}}grey{{end}}{{end}}
飞书卡片只认枚举色:
red / orange / yellow / green / turquoise / blue / indigo / purple / carmine / grey,hex 颜色码会被忽略。
{{$duration := sub now.Unix $event.FirstTriggerTime}}
{{- if $event.IsRecovered}}{{$duration = sub $event.LastEvalTime $event.FirstTriggerTime}}{{end}}
- **持续**: {{humanizeDurationInterface $duration}}
触发态:
当前时间 - FirstTriggerTime;恢复态:LastEvalTime - FirstTriggerTime。humanizeDurationInterface输出1h3m5s这种人类可读形式。
[查看仪表盘]({{.domain}}/dashboards/123?ident={{urlquery $event.TargetIdent}}&host={{urlquery (index $labels "host")}})
[屏蔽1小时]({{.domain}}/alert-mutes/add?__event_id={{$event.Id}})
直接
{{$event.TargetIdent}}拼进 URL,遇到空格、中文或&会被 IM 端二次转义(典型表现:&变&,链接打不开)。所有进入 URL query 的变量都用urlquery包。
- **应用**: {{index $labels "app-name"}}
- **K8s 域**: {{index $labels "k8s.io/cluster"}}
- **业务面板**: {{index $event.AnnotationsJSON "dashboard_url"}}
$labels.app-name会被 Go template 解析成"app减name"必然失败。Key 只要不是纯字母数字下划线,一律index取,Annotations 同理。
- **触发时值**: {{if or (eq $event.TriggerValue "+Inf") (eq $event.TriggerValue "NaN")}}N/A{{else}}{{formatDecimal $event.TriggerValue 2}}{{end}}
PromQL 的
/0会返回+Inf,缺数据的聚合会返回NaN。直接渲染到飞书/钉钉里就是字面+Inf,看着像 bug。
{{if gt $event.Id 0}}
[事件详情]({{.domain}}/share/alert-his-events/{{$event.Id}})
{{else}}
(Edge 模式事件,请到中心端查看详情)
{{end}}
已知问题:Edge 模式下事件 Id 写入是异步的,渲染时拿到 0,跳转链接会落到
/alert-his-events/0(404)。模板层加gt $event.Id 0守护。
{{range $i, $e := .events}}
{{- if $i}}
---
{{end -}}
- 规则:{{$e.RuleName}}
- 对象:{{$e.TargetIdent}}
- 触发值:{{$e.TriggerValue}}
- 时间:{{timeformat $e.TriggerTime}}
{{end}}
默认场景下一条通知就一个事件,模板按
$event = index .events 0渲染。当一条通知聚合了多个事件(订阅聚合、批量发送)时必须range .events才能展开。
html/template 会 HTML 转义。钉钉/飞书/Lark 里 <font>、<at> 等标签必须用 unescaped 包裹或放在内容里由 Go template 自己识别。邮件走 text/template 不受此限。TriggerValue 是字符串:直接比较大小要用 parseDuration/自定义,常规只做显示就好。{{$events := .events}} 等头部——系统会自动注入。NotifyRecovered=1 的规则会复用同一模板发恢复消息。$labels.agent_hostname 能用,但 $labels.app-name 不行,要用 index $labels "app-name"。int64),不要直接 {{$event.TriggerTime}} 当文本,用 timeformat 或 toTime。.events 可能有多条,钉钉/飞书默认模板只渲染第 0 条($event);如果用户要批量展示,用 {{range .events}}。.domain:不要硬编码 http://localhost:17000,否则切环境就坏。❌ {{$event.TriggerValue | printf "%.2f"}} — printf 第一个参数才是 format,且 TriggerValue 是 string。
✅ {{formatDecimal $event.TriggerValue 2}}
❌ {{$event.TriggerTime}} 直接展示 — 会输出 unix 秒。
✅ {{timeformat $event.TriggerTime}}
❌ 钉钉里写 <font color="red"> 不转义 — html/template 会转义成 <font>。
✅ 放在条件分支里(Go template 的 {{if}} 分支字面量不会被 HTML 转义内部标签),或整体 {{unescaped "…"}}。夜莺官方钉钉模板直接写 <font> 能生效,是因为外层是纯文本区段(不在属性值里)——遵循官方样例即可。
❌ 忘记分恢复分支 — 恢复通知里触发时间误导人。
✅ 所有动态内容先用 {{if $event.IsRecovered}}…{{else}}…{{end}} 包好。
❌ {{if .IsRecoverd}} — 拼写少一个 e。Go template 对不存在的字段静默走 else 分支(不报错),后果是恢复通知发的是触发态文案,且很难定位。
✅ {{if $event.IsRecovered}}。同类拼写陷阱:Resoverd Recoverd recovered(小写)都不行。
❌ {{if lt $value 10}} 或 {{if gt $event.TriggerValue 80}} — TriggerValue 是 string,lt/gt 跟数字比较会报 incompatible types,或被解释成字符串字典序得到错误结果。当前版本没有内置 toFloat helper。数值阈值判断请放回告警规则的条件表达式里,模板层只做展示。
❌ 拼 URL 时直接 ?ident={{$event.TargetIdent}} — TargetIdent 含空格/中文/& 会破坏链接。
✅ ?ident={{urlquery $event.TargetIdent}},所有进入 URL query 的变量都过 urlquery。
❌ {{$labels.app-name}} 或 {{$labels.k8s.io/cluster}} — 含中划线/点/斜杠的 key 会被解析失败。
✅ {{index $labels "app-name"}}、{{index $labels "k8s.io/cluster"}}。