CloudMonitor uses a dual-template-engine architecture for alert notifications. The Go template engine supports custom templates with variables, conditionals, loops, and utility functions. The Velocity engine remains available for legacy templates.
Template engines
-
Go template engine - Based on Go Template, supports custom templates with variables, conditionals, loops, and utility functions.
-
Velocity template engine - Based on Apache Velocity, supports legacy alert templates and is built into CloudMonitor.
Data flow architecture
alert event → EnrichHandler → TemplateRenderHandler → MessageRenderHandler → notification distribution
↓ ↓ ↓ ↓ ↓
Raw data variable enrichment annotation rendering message rendering Channel adaptation
Customizing alert content
Use Go Template syntax to define custom parameters in alert content. Notifications include this custom content after rendering.
When you select a metric from a metric group, built-in PromQL queries and pre-configured templates are available for each notification channel. You can modify these templates. For example:
Node {{ $labels.instance }} CPU usage {{ $labels.metrics_params_opt }} {{ $labels.metrics_params_value }}%. Current CPU usage: {{ printf "%.2f" $value }}%
During rendering, Go Template populates variables with context data. For example, {{$labels.namespace}} and {{$labels.pod_name}} are extracted from PromQL query results.
You can add custom labels and annotations, then reference them with {{$labels.custom_label_key}}.
Template syntax
Alert templates use the Go template engine and support the following syntax elements:
Variable reference
{{.fieldName}} # Reference a field {{.nested.field}} # Reference a nested field
Conditional 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 to
Logical 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 percentage
Utility 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:50
humanizePercentage
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: 8c1b6fa97c4288cdf2e505475e9c4f8e
toJson
Function: Serializes any value (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 triggers, the log content in {{ $value }} is a JSON string, such as [{...}, {...}]. Parse it with fromJson and 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 cpu
Scenario 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: critical
-
When 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 whether an input matches a regular expression. 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 format
-
The 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: The first matching substring (string). Returns 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.100
-
Returns 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 matching 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 match is 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-03
-
The 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: 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****5678
Example 2: Redacting sensitive information
{"message": "User token=abc123xyz login failed"}
Template:
{{regexReplaceAll "token=[a-zA-Z0-9]+" "token=***" .message}}
Output:
User token=*** login failed
-
In 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: ERROR
Example 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.com
Best 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 triggers and $value is a JSON array string where each element is a complete log record, you can use templates to extract key information and apply 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
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 complete log alert rendering, including field extraction, special key access, nested fromJson parsing, and 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 statement to check whether a field exists:
{{if .optionalField}} Field value: {{.optionalField}}
{{else}} Field value: Not set {{end}}
Handling incorrect timestamp format
humanizeDate accepts a millisecond timestamp (13-digit number). If you have a seconds timestamp (10-digit number), multiply it by 1,000 during data preparation.
Outputting a nested object
Use toJson to serialize the object to a JSON string:
{{.incident | toJson}}
Handling backslashes in regular expressions
In Go template strings, 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 with range or convert 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 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 |
|
|