The CMS notification enrichment component, a core component of the Alibaba Cloud monitoring and alerting system, enriches, renders, and distributes alert messages. Its dual-template-engine architecture maintains compatibility with legacy Velocity templates while supporting the more powerful Go template engine.
Template engines
Go template engine - Based on Go Template, this engine provides powerful features for creating custom templates.
Velocity template engine - Based on Apache Velocity, this engine supports legacy alert templates and is built into Cloud Monitor.
Data flow architecture
alert event → EnrichHandler → TemplateRenderHandler → MessageRenderHandler → notification distribution
↓ ↓ ↓ ↓ ↓
Raw data variable enrichment annotation rendering message rendering Channel adaptationCustomizing alert content
Use Go Template syntax to define custom parameters within the alert content. User notifications will then include this custom content. Ensure that the Go Template syntax is correct.
For example, when you select a metric from a metric group, a built-in PromQL query and pre-configured templates for various notification channels are available. If a pre-configured template does not meet your needs, you can modify it. For example:
Node {{ $labels.instance }} CPU usage {{ $labels.metrics_params_opt }} {{ $labels.metrics_params_value }}%. Current CPU usage: {{ printf "%.2f" $value }}%When rendering a template, Go Template populates it with data from the context. The namespace {{$labels.namespace}} and Pod {{$labels.pod_name}} are extracted from the PromQL query results.
You can also add custom labels and annotations. You can reference custom labels using {{$labels.custom_label_key}}.
Template syntax
Alert templates use the Go template engine and support the following syntax:
Variable reference
{{.fieldName}} # Reference a field
{{.nested.field}} # Reference a nested fieldConditional statement
{{if .condition}}
Displayed if the condition is true
{{else}}
Displayed if the condition is false
{{end}}Iteration
{{range .items}}
- {{.name}}: {{.value}}
{{end}}Comparison operators
{{if eq .status "ok"}}Normal{{end}} # Equal to
{{if ne .status "error"}}Not an error{{end}} # Not equal to
{{if gt .value 100}}Greater than 100{{end}} # Greater than
{{if lt .value 50}}Less than 50{{end}} # Less than
{{if ge .value 80}}Greater than or equal to 80{{end}} # Greater than or equal to
{{if le .value 20}}Less than or equal to 20{{end}} # Less than or equal toLogical operators
{{if and .cond1 .cond2}}Both conditions are true{{end}}
{{if or .cond1 .cond2}}At least one condition is true{{end}}
{{if not .condition}}The condition is false{{end}}Pipelining
{{.value | functionName}} # Pass the value to a function
{{.timestamp | humanizeDate}} # Format a timestamp
{{.ratio | humanizePercentage}} # Format a percentageUtility functions
Operator | Syntax | Input type | Output type | Description |
humanizeDate |
| float64 | string | Converts a millisecond timestamp to a human-readable time. |
humanizePercentage |
| float64 | string | Converts a decimal to a percentage. |
md5 |
| string | string | Calculates the MD5 hash of a string. |
toJson |
| any | string | Converts a value to a JSON string. |
fromJson |
| string | any | Parses a JSON string into a value. |
quote |
| any | string | Encloses a string in double quotes, escaping any special characters. |
printf |
| string, ...any | string | Returns a formatted string, similar to Go's |
len | any | int | Returns the length of an array, slice, map, or string. | |
regexMatch |
| string, any | bool | Reports whether a string matches a regular expression. |
regexFind |
| string, any | string | Returns the first substring matching the regular expression. |
regexFindAll |
| string, int, any | []string | Returns all substrings matching the regular expression. |
regexReplaceAll |
| string, string, any | string | Replaces all substrings matching the regular expression with a replacement string. |
humanizeDate
function: Converts a millisecond timestamp to a human-readable date and time.
input: A millisecond timestamp (float64).
output: A formatted date string, such as 2006-01-02 15:04:05.
example:
{"timestamp": 1634567890000}template:
Alarm time: {{.timestamp | humanizeDate}}output:
Alarm time: 2021-10-18 21:04:50humanizePercentage
Function: Converts a float value to a percentage string with two decimal places.
Input: A float value (float64) ranging from 0 to 1.
Output: A percentage string in the format xx.xx%.
Example:
{"cpu": 0.8567}Template:
CPU utilization: {{.cpu | humanizePercentage}}Output:
CPU utilization: 85.67%md5
Function: Computes the 32-character lowercase hexadecimal md5 hash of a string.
Input: A string.
Output: A 32-character lowercase hexadecimal string.
Example:
{"alertId": "ALERT-001"}Template:
Alert fingerprint: {{.alertId | md5}}Output:
Alert fingerprint: 8c1b6fa97c4288cdf2e505475e9c4f8etoJson
Function: Serializes any value, such as an object, array, or string, to a JSON string.
Input: Any type.
Output: A JSON string.
Example:
{"incident": {"title": "Disk usage exceeded the threshold", "level": "critical"}}Template:
Alert details: {{.incident | toJson}}Output:
Alert details: {"level":"critical","title":"Disk usage exceeded the threshold"}Combined usage:
Data:
{"tags": ["prod", "k8s", "node"]}Template:
Tag list: {{.tags | toJson}}Output:
Tag list: ["prod","k8s","node"]When the input is
nil, the output isnull.If serialization fails, the function returns an error string without interrupting rendering.
You can combine it with
regexFindAlland other functions that return an array.
fromJson
Function: Deserializes a JSON string into a Go object, enabling object field access and array iteration.
Input: A JSON string.
Output: A parsed array ([]interface{}) or object (map[string]interface{}). If the parsing fails, the original string is returned.
Basic usage:
{{range (fromJson .fieldName)}}...{{end}}
{{(fromJson .fieldName).fieldName}}Scenario 1 — Iterate over an array of log alerts
When an alert is triggered, the log content in {{ $value }} is a JSON string, such as [{...}, {...}]. You can parse it with fromJson and then iterate over the result:
Data (the value of $value is stored in data as a string):
{
"logs": "[{\"message\":\"ERROR disk full\",\"hostname\":\"node-01\"},{\"message\":\"WARN high cpu\",\"hostname\":\"node-02\"}]"
}Template:
Triggered logs:
{{- range (fromJson .logs)}}
- Host: {{.hostname}} Content: {{.message}}
{{- end}}Output:
Triggered logs:
- Host: node-01 Content: ERROR disk full
- Host: node-02 Content: WARN high cpuScenario 2 — Access object fields
Data:
{"detail": "{\"title\":\"Disk alert\",\"level\":\"critical\"}"}Template:
Alert title: {{(fromJson .detail).title}}
Alert level: {{(fromJson .detail).level}}Output:
Alert title: Disk alert
Alert level: criticalWhen the input is a JSON array (
[...]), an array forrangeiteration is returned.Returns a map accessible via
.fieldwhen the input is a JSON object ({...}).On a parsing failure, the function returns the original string without interrupting template rendering.
The
fromJsonfunction is the inverse oftoJson:toJsonserializes, andfromJsondeserializes.
regexMatch
Function: Checks if an input matches a regular expression. This is commonly used in {{if}} conditional statements.
Input: A pattern (string) and the value to be matched (any).
Output: A boolean. Returns true if the input matches the pattern, otherwise false. An invalid pattern returns an error.
Basic usage:
{{if regexMatch "regular expression" .fieldName}}...{{end}}Example:
{"phone": "13812345678"}Template:
{{if regexMatch "^1[3-9][0-9]{9}$" .phone}}Valid phone number format{{else}}Invalid phone number format{{end}}Output:
Valid phone number formatThe regular expression uses the standard Go
regexpsyntax.regexMatch automatically converts non-string inputs to strings before matching.
If the pattern fails to compile, template rendering stops and the function returns an error.
regexFind
Function: Finds the first substring in a value that matches a regular expression.
Input: pattern (string), value (any)
Output: Returns the first matching substring (string), an empty string if no match is found, or an error if the pattern is invalid.
Basic usage:
{{regexFind "regular expression" .fieldName}}Example:
{"message": "server 192.168.1.100 response timeout"}Template:
alert IP: {{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .message}}Output:
alert IP: 192.168.1.100Returns only the first match. To find all matches, use
regexFindAll.It automatically converts non-string inputs to strings before matching.
regexFindAll
Function: Finds all substrings in the input that match a regular expression and returns a string array.
Input: pattern (string), n (int, the maximum number of matches, where -1 means no limit), source (any)
Output: An array of matched substrings ([]string). Returns an empty array if no matches are found, or an error if the pattern is invalid.
Basic usage:
{{regexFindAll "regular expression" -1 .fieldName}}Example:
{"log": "ERROR at line 12, WARNING at line 34, ERROR at line 56"}Template:
Error line numbers: {{regexFindAll "[0-9]+" -1 .log | toJson}}Output:
Error line numbers: ["12","34","56"]Scenario: Limit the number of matches
Template:
First two line numbers: {{regexFindAll "[0-9]+" 2 .log | toJson}}Output:
First two line numbers: ["12","34"]Scenario: Iterate over all matched results
{"hosts": "web-01,web-02,web-03"}Template:
{{range regexFindAll "web-[0-9]+" -1 .hosts}}
- host: {{.}}
{{end}}Output:
- host: web-01
- host: web-02
- host: web-03The function returns all matches if
n=-1, and at most n matches ifn>0.You can iterate over the results using
{{range}}or convert them to a JSON string usingtoJson.
regexReplaceAll
Function: Replaces all substrings that match a regular expression with a specified string.
Input: pattern (string), replacement (string), input value (any)
Output: Returns the modified string, or an error if the pattern is invalid.
Basic usage:
{{regexReplaceAll "regular expression" "replacement string" .fieldName}}Example 1: Masking a phone number
{"phone": "13812345678"}Template:
Contact phone: {{regexReplaceAll "([0-9]{3})[0-9]{4}([0-9]{4})" "${1}****${2}" .phone}}Output:
Contact phone: 138****5678Example 2: Redacting sensitive information
{"message": "User token=abc123xyz login failed"}Template:
{{regexReplaceAll "token=[a-zA-Z0-9]+" "token=***" .message}}Output:
User token=*** login failedIn the replacement string, you can use
${1},${2}, and so on to reference capture groups.The function automatically converts non-string inputs to strings before processing.
Template writing
Examples
Example 1: Format timestamps and percentages
{
"timestamp": 1634567890000,
"cpuUsage": 0.8567
}Template:
Alert time: {{.timestamp | humanizeDate}}
CPU usage: {{.cpuUsage | humanizePercentage}}Output:
Alert time: 2021-10-18 21:04:50
CPU usage: 85.67%Example 2: Combine multiple functions
{
"alertId": "ALERT-001",
"timestamp": 1634567890000,
"severity": 0.95,
"incident": {"service": "order-api", "region": "cn-hangzhou"}
}Template:
Alert Notification
Alert ID: {{.alertId}}
Alert fingerprint: {{.alertId | md5}}
Alert time: {{.timestamp | humanizeDate}}
Severity: {{.severity | humanizePercentage}}
Alert details: {{.incident | toJson}}Output:
Alert Notification
Alert ID: ALERT-001
Alert fingerprint: 8c1b6fa97c4288cdf2e505475e9c4f8e
Alert time: 2021-10-18 21:04:50
Severity: 95.00%
Alert details: {"region":"cn-hangzhou","service":"order-api"}Example 3: Extract key information with regular expressions
{
"logLine": "2025-08-25 19:00:01 ERROR [order-service] Connection timed out 192.168.1.100:8080"
}Template:
Source IP: {{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .logLine}}
Log level: {{regexFind "ERROR|WARN|INFO" .logLine}}Output:
Source IP: 192.168.1.100
Log level: ERRORExample 4: Conditional checks and masking with regular expressions
{
"phone": "13812345678",
"email": "user@example.com"
}Template:
{{if regexMatch "^1[3-9][0-9]{9}$" .phone}}
Contact phone: {{regexReplaceAll "([0-9]{3})[0-9]{4}([0-9]{4})" "${1}****${2}" .phone}}
{{end}}
{{if regexMatch "^[^@]+@[^@]+$" .email}}
Contact email: {{regexReplaceAll "(.{2}).+(@.+)" "${1}***${2}" .email}}
{{end}}Output:
Contact phone: 138****5678
Contact email: us***@example.comBest practices
Use descriptive field names
Recommended:
{{.alertName}} {{.triggerTime}} {{.severity}}
Not recommended:
{{.a}} {{.t}} {{.s}}Handle default values
{{if .description}}
Description: {{.description}}
{{else}}
Description: None
{{end}}Use utility functions effectively
# Use humanizeDate for timestamp fields
Occurrence time: {{.timestamp | humanizeDate}}
# Use humanizePercentage for ratio fields
Usage: {{.usage | humanizePercentage}}
# Use toJson to serialize object/array fields
Alert details: {{.incident | toJson}}
# Use regexFind to extract substrings from text
Source IP: {{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .message}}Template reference
Alert content template
Alert name: {{.alertName}}
Alert level: {{.level}}
Trigger time: {{.triggerTime | humanizeDate}}
Alert target: {{.target}}
{{if .description}}Alert description: {{.description}}{{end}}
Current value: {{.currentValue}}
Threshold: {{.threshold}}Alert details template
=== Alert Details ===
• Alert name: {{.alertName}}
• Alert level: {{if eq .level "critical"}}Critical{{else if eq .level "warning"}}Warning{{else}}Info{{end}}
• Trigger time: {{.triggerTime | humanizeDate}}
• Alert target: {{.target}}
{{if .metrics}}
• Metrics:
{{range .metrics}} - {{.name}}: {{.value}}{{if .unit}}{{.unit}}{{end}}
{{end}}{{end}}
{{if .suggestion}}
• Suggestion: {{.suggestion}}
{{end}}List rendering template
Alert host list:
{{range .hosts}}
- Host: {{.hostname}}
IP: {{.ip}}
CPU: {{.cpu | humanizePercentage}}
Memory: {{.memory | humanizePercentage}}
{{end}}Best practices
Parsing the $value log JSON array
When an alert is triggered, if $value is a JSON array string in which each element is a complete log record, this topic describes how to use a template to extract key information and perform data masking.
Original structure of $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\":{...},...}}"
}
]Therawfield is itself a JSON string, so you must usefromJsona second time to access thekubernetesinformation within it.
Parameter access quick reference
Parameter | Access method | Description |
|
| The log message body typically ends with |
|
| Log generation time (string). |
|
| The name of the host. |
|
| The name of the Pod. |
|
| Kubernetes namespace. |
|
| The name of the container. |
|
| Full image path. |
|
| IDC identifier. |
|
| Log collection delay (seconds). |
|
| If a key contains a colon, you must use |
|
| If a key contains |
|
| If a key contains a slash, you must use |
|
| The |
|
| Same as the preceding entry. |
|
| If a key contains a dot, you must use |
Data masking approaches
As a best practice, apply data masking at the template level before sending alert notifications.
Masking IP addresses
{{- /* Keep the first two octets and replace the last two with *.* */ -}}
{{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.*.*
Masking hostnames
{{- /* Keep the cluster prefix and replace the node number with *** */ -}}
{{regexReplaceAll "(gz-k8s-[a-z]+-[a-z]+-[a-z]+)-[0-9]+" "${1}-***" .hostname}}gz-k8s-dev-cpu-node-008 → gz-k8s-dev-cpu-node-***
Masking container registry addresses
{{- /* Keep only the image name and tag, removing the private registry domain */ -}}
{{regexReplaceAll "^[^/]+/[^/]+/(.+)$" "${1}" .containerImage}}registry.example.com/logancloud/logan-registry:1.12.2 → logan-registry:1.12.2
Masking the message body
{{- /* Filter credentials in the format token=xxx */ -}}
{{regexReplaceAll "token=[a-zA-Z0-9._-]+" "token=***" .message}}{{- /* Filter credentials in the format password=xxx */ -}}
{{regexReplaceAll "(?i)password=[^\\s&]+" "password=***" .message}}Removing trailing newline characters from message
{{regexReplaceAll "\\s+$" "" .message}}Complete alert template example
The following template demonstrates a complete log alert rendering, including field extraction, special key access, secondary fromJson parsing, and various types of data masking:
{{- range (fromJson $value) -}}
=== Log alert ===
Log time: {{.tk}}
Collection delay: {{.delay}}s
[Host information]
Host: {{regexReplaceAll "(gz-k8s-[a-z]+-[a-z]+-[a-z]+)-[0-9]+" "${1}-***" .hostname}}
IDC: {{.idc}}
Client IP: {{regexReplaceAll "([0-9]{1,3}\\.[0-9]{1,3})\\.[0-9]{1,3}\\.[0-9]{1,3}" "${1}.*.*" (index . "tag:client_ip")}}
[Container information]
Namespace: {{.namespace}}
Pod: {{.podName}}
Container: {{.containerName}}
Image: {{regexReplaceAll "^[^/]+/[^/]+/(.+)$" "${1}" .containerImage}}
[K8s information]
Pod ID: {{(fromJson .raw).kubernetes.pod_id}}
StatefulSet:{{index (fromJson .raw).kubernetes.labels "statefulset.kubernetes.io/pod-name"}}
[Log content]
{{regexReplaceAll "\\s+$" "" .message}}
{{- end}}Rendered output:
=== Log alert ===
Log time: 2026-03-13 14:10:59.707
Collection delay: 31.732s
[Host information]
Host: gz-k8s-dev-cpu-node-***
IDC: gz1-105
Client IP: 120.232.*.*
[Container information]
Namespace: logan
Pod: logan-registry-0
Container: logan-registry
Image: logan-registry:1.12.2
[K8s information]
Pod ID: aef10ba8-b870-4eb6-b2f4-74a5393cfcab
StatefulSet:logan-registry-0
[Log content]
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/...Displaying logs by level
{{- range (fromJson $value)}}
{{- if regexMatch "ERROR|WARN" .message}}
[{{regexFind "ERROR|WARN" .message}}] {{.tk}} {{.podName}}
{{regexReplaceAll "\\s+$" "" .message}}
{{- end}}
{{- end}}FAQ
Handling missing fields
Use an if conditional statement to check if a field exists:
{{if .optionalField}}
Field value: {{.optionalField}}
{{else}}
Field value: Not set
{{end}}Handling incorrect timestamp format
The humanizeDate function accepts a timestamp in milliseconds (a 13-digit number). If you have a timestamp in seconds (a 10-digit number), multiply it by 1,000 during data preparation.
Outputting a nested object
Use toJson to serialize the object into a JSON string:
{{.incident | toJson}}Handling backslashes in regular expressions
In a Go template string, escape backslashes by doubling them. For example, to match an IP address:
{{regexFind "[0-9]{1,3}(\\.[0-9]{1,3}){3}" .message}}regexFindAll: Iterating over results
Iterate through the results with range or convert them to a JSON string with toJson:
# Iterate and output
{{range regexFindAll "[0-9]+" -1 .text}}- {{.}}
{{end}}
# Convert to a JSON array string
{{regexFindAll "[0-9]+" -1 .text | toJson}}Custom function support
The system currently provides 11 utility functions: humanizeDate, humanizePercentage, md5, toJson, quote, printf, len, regexMatch, regexFind, regexFindAll, and regexReplaceAll.
Go template syntax quick reference
Basic syntax
Feature | Syntax | Example |
variable reference |
|
|
nested field |
|
|
conditional statement |
|
|
if-else statement |
|
|
multi-branch conditional |
|
|
loop |
|
|
loop with index |
|
|
pipeline |
|
|
chained pipeline |
|
|
variable assignment |
|
|
array element access |
|
|
map element access |
|
|
Length |
|
|
Comparison operations
Feature | Syntax | Example |
equal to |
|
|
not equal to |
|
|
greater than |
|
|
less than |
|
|
greater than or equal to |
|
|
less than or equal to |
|
|
Logical operations
Feature | Syntax | Example |
and |
|
|
or |
|
|
not |
|
|