- name
- generate-message-template
- description
- Generate or modify Nightingale (n9e) alert notification message templates. Use when the user asks to write a notification template, change the message format, add hostname/recovery value/severity, or create DingTalk/Feishu/Lark/email/SMS/voice templates.
- tags
- ["internal"]
# Nightingale (n9e) Notification Message Template Generation
Nightingale message templates use Go `text/template` / `html/template` syntax (email uses `text/template`, others use `html/template` and then escape). Users edit them on the "Notification Management → Message Templates" page; once saved they are referenced by notification rules, and when an alert fires the variables are substituted with rendered data and sent to each channel.
This skill focuses on **writing/modifying the template fragment itself**, and does not cover creating notification rules or channel configuration.
---
## Render Context
The `renderData` passed in when the backend executes:
| key | type | description |
|---|---|---|
| `.events` | `[]*AlertCurEvent` | The list of alert events in the current batch, usually just 1 |
| `.domain` | `string` | The n9e site URL, used to build redirect links |
**Automatically injected shorthands** (no need to write by hand):
```gotemplate
{{ $events := .events }}
{{ $event := index $events 0 }}
{{ $labels := $event.TagsMap }}
{{ $value := $event.TriggerValue }}
```
So inside the template you can directly use `$event.xxx`, `$labels.agent_hostname`, and `$value`.
---
## Available `$event` Fields (`*AlertCurEvent`)
### Common Fields
| field | type | description |
|---|---|---|
| `.Id` | int64 | Alert event ID (used to build the redirect link) |
| `.RuleId` | int64 | Alert rule ID |
| `.RuleName` | string | Rule name |
| `.RuleNote` | string | Rule note |
| `.Severity` | int | Alert severity: 1=Critical, 2=Warning, 3=Info |
| `.PromQl` | string | Alert trigger expression |
| `.RuleAlgo` | string | Rule algorithm type |
| `.TriggerTime` | int64 | Trigger time, unix seconds |
| `.TriggerValue` | string | Metric value at trigger time (already a string) |
| `.FirstTriggerTime` | int64 | First abnormal time (for consecutive alerts) |
| `.LastEvalTime` | int64 | Most recent evaluation time, **used as the recovery time on recovery** |
| `.IsRecovered` | bool | Whether it has recovered |
| `.NotifyCurNumber` | int | Which notification number this send is |
| `.TargetIdent` | string | Monitored object (usually agent_hostname) |
| `.TargetNote` | string | Object note |
| `.GroupId` / `.GroupName` | int64 / string | Business group |
| `.Cluster` | string | Data source cluster name |
| `.Cate` | string | Data source type (prometheus / host / mysql / ...) |
| `.RunbookUrl` | string | Runbook link |
### Labels / Annotations / Trigger Value Objects
| field | type | description |
|---|---|---|
| `.TagsMap` | `map[string]string` | Event labels, preferred: `{{$labels.agent_hostname}}` |
| `.TagsJSON` | `[]string` | A string array like `["k=v", ...]` (commonly used in early templates) |
| `.AnnotationsJSON` | `map[string]string` | Annotations, commonly `.AnnotationsJSON.recovery_value` |
| `.TriggerValuesJson.ValuesWithUnit` | map | Trigger values with units (multi-value scenarios) |
### Notification Objects
| field | type | description |
|---|---|---|
| `.NotifyUsersObj` | `[]*User` | The user objects to notify this time (with Username/Nickname/Phone/Email) |
| `.NotifyGroupsObj` | `[]*UserGroup` | Associated user groups |
> User objects are commonly used in DingTalk/Feishu/Lark templates to build @-mentions; see the `ats` family of helpers below.
---
## Available Helper Functions (`tplx.TemplateFuncMap`)
### Time
- `timeformat <unix>` — defaults to `"2006-01-02 15:04:05"`; pass a second argument to override: `{{timeformat $event.TriggerTime "15:04:05"}}`
- `timestamp` — current time string
- `now.Unix` — current unix seconds (`now` is a Go template builtin)
- `humanizeDuration <sec>` — `"3m15s"`
- `humanizeDurationInterface <interface>` — same as above but accepts an interface
- `toTime <unix>` — returns a `time.Time`, can be chained
- `parseDuration <"5m">` — returns a `time.Duration`
### Numbers / Formatting
- `formatDecimal <v> <n>` — keep n decimal places (`{{formatDecimal $event.TriggerValue 2}}`)
- `humanize <v>` — K/M/G units (base 1000, SI)
- `humanize1024 <v>` — Ki/Mi/Gi (base 1024)
- `humanizePercentage <v>` / `humanizePercentageH <v>` — percentage
- `add / sub / mul / div <a> <b>` — arithmetic
- `printf "%.2f" <v>` — Go `fmt.Sprintf`
### Strings
- `toUpper / toLower / title` — case conversion
- `contains <s> <sub>` — substring
- `match <regex> <s>` — regex match (bool)
- `reReplaceAll <regex> <repl> <s>` — regex replace
- `split <s> <sep>` / `join <slice> <sep>`
- `stripPort <host:port>` / `stripDomain <host.domain>`
- `b64enc` / `b64dec` — base64
### Links / Escaping
- `escape <s>` — URL path escape
- `unescaped <s>` — output raw HTML (no escaping)
- `safeHtml <s>` — same as above
- `urlconvert <s>`
### Labels / Trigger Values
- `label <key> <labelMap>` / `value <key> <m>` / `strvalue <v>`
- `first <slice>` — take the first
- `tagsMapToStr <map>` — join labels into `k=v,k=v`
- `sortByLabel <items> <key>` — sort by label
### @-mention (DingTalk/Feishu/Lark only)
- `ats <users> <platform>` — generate an at fragment
- `batchContactsAts <contacts> <platform>` — batch at
- `batchContactsAtsInFeishuEmail <contacts>` / `batchContactsAtsInFeishuId <contacts>` — Feishu only
- `batchContactsJoinComma <contacts>` / `batchContactsJsonMarshal <contacts>`
- `mappingAndJoin <map> <kvSep> <itemSep>`
### Others
- `jsonMarshal <v>` — serialize to a JSON string
- `mapDifference <a> <b>` — set difference
---
## Syntax Differences Across Channels (`notify_channel_ident`)
A message template is bound to a specific channel; the channel ident determines the text engine and escaping behavior:
| Ident | Engine | Description |
|---|---|---|
| `email` | `text/template` | No re-escaping; HTML templates can write tags directly |
| `slackwebhook` / `slackbot` | `html/template` | After rendering, `"` / `\n` are escaped and wrapped into `template.HTML`; usually written in Markdown |
| Others (`dingtalk` / `feishu` / `feishucard` / `larkcard` / `wecom` / `tx-sms` / `ali-voice` …) | `html/template` | After rendering, `"` `\n` `\r` are JSON-string-escaped, suitable for stuffing directly into a webhook payload |
> Important: `html/template` automatically escapes `<` `>` `&`. **Where you don't want escaping, use `{{unescaped "…"}}` or `{{safeHtml .X}}`**, otherwise DingTalk will render a literal `<`.
---
## Template Writing Workflow
1. **Confirm the channel ident**: when the user says "DingTalk" it is `dingtalk`, "Feishu card" is `feishucard`, "email" is `email`. Different channels differ significantly in style.
2. **Decide whether to render the recovery state**: by default you should split into two branches on `$event.IsRecovered`. Only SMS/voice may omit it.
3. **Pick fields**:
- For labels, prefer `$labels.<key>` rather than `$event.TagsJSON`.
- For the trigger value use `$event.TriggerValue`; for two decimal places use `{{formatDecimal $event.TriggerValue 2}}`.
- Always pass timestamps through `timeformat`.
- Build the redirect link as `{{.domain}}/share/alert-his-events/{{$event.Id}}`.
4. **Severity display**:
- Number: `{{$event.Severity}}`
- Worded: `{{if eq $event.Severity 1}}Critical{{else if eq $event.Severity 2}}Warning{{else}}Info{{end}}`
- English: `Critical / Warning / Info`
5. **@-mention (DingTalk/Feishu)**:
- DingTalk `@everyone`: add a line `@all` at the end of the template (DingTalk matches by splitting on spaces). To at an exact phone number: `{{range $event.NotifyUsersObj}}@{{.Phone}} {{end}}`.
- Feishu card: use `{{batchContactsAtsInFeishuEmail $event.NotifyUsersObj}}` to build `<at email=...></at>` fragments.
6. **Output**: wrap the template body in a markdown ``` ```gotemplate ``` code block, and at the end provide a **variable/function explanation** with each non-trivial variable/function on its own line.
---
## Output Format
When replying to the user:
1. One or two sentences of introduction explaining what the template is for and which channel it fits.
2. ```gotemplate
<template content>
```
3. `**Variable explanation**`: list the non-trivial fields and functions used in the template (such as `$event.TargetIdent`, `formatDecimal`, `timeformat`), one per line.
4. If the user's request is ambiguous (for example "add the hostname" — is it `target_ident` or some tag), **state directly in the introduction what assumption you made**, rather than asking back.
The language follows the user's input (use Chinese if the input is Chinese).
---
## Built-in Reference Templates (Starting Points for Customization)
### DingTalk markdown (full version)
```gotemplate
#### {{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}}
- **Alert Level**: S{{$event.Severity}}
{{- if $event.RuleNote}}
- **Rule Note**: {{$event.RuleNote}}
{{- end}}
{{- if $event.TargetIdent}}
- **Target**: {{$event.TargetIdent}}
{{- end}}
{{- if not $event.IsRecovered}}
- **Trigger Value**: {{$event.TriggerValue}}
- **Trigger Time**: {{timeformat $event.TriggerTime}}
- **Duration**: {{humanizeDurationInterface $duration}}
{{- else}}
- **Recovery Time**: {{timeformat $event.LastEvalTime}}
- **Duration**: {{humanizeDurationInterface $duration}}
{{- end}}
- **Event Tags**:
{{- range $k, $v := $labels}}
{{- if ne $k "rulename"}}
- {{$k}}: {{$v}}
{{- end}}
{{- end}}
[Event Detail]({{.domain}}/share/alert-his-events/{{$event.Id}}) | [Mute 1h]({{.domain}}/alert-mutes/add?__event_id={{$event.Id}})
```
### Feishu card (concise version)
```gotemplate
{{- if $event.IsRecovered}}
**Severity/State:** S{{$event.Severity}} Recovered
**Alert Name:** {{$event.RuleName}}
**Event Tags:** {{$event.TagsJSON}}
**Recovery Time:** {{timeformat $event.LastEvalTime}}
{{- else}}
**Severity/State:** S{{$event.Severity}} Triggered
**Alert Name:** {{$event.RuleName}}
**Event Tags:** {{$event.TagsJSON}}
**Trigger Time:** {{timeformat $event.TriggerTime}}
**Trigger Value:** {{$event.TriggerValue}}
{{- if $event.RuleNote}}
**Alert Description:** {{$event.RuleNote}}
{{- end}}
{{- end}}
عرض على GitHub