前言
SLS告警最近升级了通知的模板语法,在保持对原有模板语法兼容的情况下,增加了动态渲染的功能,不仅支持条件语句、循环语句等控制流,还内置了50+函数,可以非常方便地对数据进行各种处理,从而可以非常灵活方便地对通知内容进行定制,让通知内容呈现的效果更加丰富、内容一目了然。
旧模板语法回顾
在配置告警通知的时候,我们需要配置通知的内容模板,例如下面的一个钉钉通知模板:
- 所属项目: ${project} - 告警名称: ${alert_name} - 首次触发: ${fire_time} - 告警时间: ${alert_time} - 告警状态: ${status} - 告警严重度: ${severity} - 告警标签: ${labels} - 告警标注: ${annotations} - 触发日志: ${fire_results}
假如告警内容为:
{ "project": "test-project", "alert_id": "nginx-error", "alert_name": "Nginx访问错误", "fire_time": 1632802350, "alert_time": 1632802351, "resolve_time": 0, "status": "firing", "severity": 10, "labels": { "service": "nginx", "region": "cn-hangzhou" }, "annotations": { "title": "Nginx访问错误", "desc": "Nginx最近1分钟内有96条错误日志" }, "fire_results": [{ "msg": "ERROR: Internal Server Error" }, { "msg": "ERROR: uri /foo/bar not exist" }] }
则渲染后的通知内容为:
- 所属项目: test-project - 告警名称: Nginx访问错误 - 首次触发: 2021-09-28 12:12:30 - 告警时间: 2021-09-28 12:12:31 - 告警状态: 触发 - 告警严重度: 严重 - 告警标签: {"service": "nginx", "region": "cn-hangzhou"} - 告警标注: {"title": "Nginx访问错误", "desc": "Nginx最近1分钟内有96条错误日志"} - 触发日志: [{"msg": "ERROR: Internal Server Error"}, {"msg": "ERROR: uri /foo/bar not exist"}]
这里模板的渲染逻辑比较简单,可以简单理解为近似于变量直接替换。通过上面的例子我们可以发现旧的模板语法存在的几个问题:
- 逻辑处理能力缺失,无法做到动态渲染
- 例如希望根据告警状态来进行动态渲染:告警触发是展示触发日志,告警恢复时无需展示触发日志
- 例如希望遍历 fire_results,将错误日志以列表形式展示
- 数据处理能力弱
- 例如希望将触发日志里的 ERROR 前缀去掉
- 例如希望告警触发时间展示为其它格式
- 内容与样式耦合
- 告警的时间、状态、严重度等属性,都是根据当前的语言环境自动进行了格式化(例如时间由时间戳转成了“2021-09-28 12:12:30”;告警状态由“firing” 转成了“触发”;严重度由数字10自动转成了“严重”)
- 如果希望根据原始属性值,自定义展示形式,则无法做到
这些能力的缺失,一方面会导致最终的通知展示不够友好,另一方面,也会导致某些场景无法支持。例如Webhook通知渠道的模板配置为JSON格式:
{ "msg": "${fire_results[0].msg}" }
假如实际告警的触发日志中,包含了换行符或者双引号,则上面的模板渲染后就不是一个合法的JSON。例如原始 msg 内容为:
Uncaught Error: unexpected error at <anonymous>:1:7
则渲染后的内容为:
{ "msg": "Uncaught Error: unexpected error at <anonymous>:1:7" }
由于渲染后的结果不是合法JSON,因此就会导致告警通知发送失败。
新模板语法
为了解决上述痛点,我们重新设计了告警通知的内容模板语法,不仅可以解决上述问题,而且内置了非常多的数据处理函数,可以按需来对原始的告警内容进行处理,从而达到自己想要的通知效果。新旧版本的告警通知模板语法比较如下:
新版通知模板语法 |
旧版通知模板语法 |
|
变量引用 |
通过 |
通过 |
操作符 |
支持常用的算数操作和逻辑操作,包括:
|
不支持 |
条件判断 |
支持 |
不支持 |
循环语句 |
支持 |
不支持 |
数据处理函数 |
内置了50+函数用于数据的处理和格式化,包括:
|
不支持 |
针对上面的例子,我们使用新版本的模板语法,可以配置通知模板如下:
- 所属项目: {{ alert.project }} - 告警名称: {{ alert.alert_name }} - 首次触发: {{ alert.fire_time | format_date }} - 告警时间: {{ alert.alert_time | format_date }} - 告警状态: {{ alert.status | format_status }} - 告警严重度: {{ alert.severity | format_severity }} - 告警标签: {%- for key, value in alert.labels.items() %} - {{ key }}: {{ value }} {%- endfor %} - 告警标注: {%- for key, value in alert.annotations.items() %} - {{ key }}: {{ value }} {%- endfor %} - 触发日志: {% for result in alert.fire_results %} - {{ result.msg }} {% endfor %}
则最终渲染后的内容为:
- 所属项目: test-project - 告警名称: Nginx访问错误 - 首次触发: 2021-09-28 12:12:30 - 告警时间: 2021-09-28 12:12:31 - 告警状态: 触发 - 告警严重度: 严重 - 告警标签: - service: nginx - region: cn-hangzhou - 告警标注: - title: Nginx访问错误 - desc: Nginx最近1分钟内有96条错误日志 - 触发日志: - ERROR: Internal Server Error - ERROR: uri /foo/bar not exist
案例
案例一:格式化钉钉通知内容
旧版模板 |
新版模板 |
|
模板配置 |
- 所在项目: ${project} - 告警名称: ${alert_name} - 触发时间: ${fire_time} - 告警状态: ${status} - 严重度: ${severity} - 标签: ${labels} - 标注: ${annotations} [[查看详情](${query_url})] [[告警设置](${alert_url})] |
- 所在项目: {{ alert.project }} - 告警名称: {{ alert.alert_name }} - 触发时间: {{ alert.fire_time | format_date }} - 告警状态: {{ alert.status | format_status }} - 严重度: {{ alert.severity | format_severity }} - 标签: {{ alert.labels | to_list | blockquote }} - 标注: {{ alert.annotations | annotations_to_list | blockquote }} [[查看详情]({{ alert.query_url }})] [[告警设置]({{ alert.alert_url }})] |
展示效果 |
这里我们使用了如下内置函数:
函数处理 |
作用 |
|
format_date 用来对时间进行格式化,默认格式化微 yyyy-MM-dd HH:mm:ss 格式 |
|
format_status 用来对告警状态进行格式化:
|
|
format_severity 用来对告警严重度进行格式化,类似于 format_status:
|
|
|
|
annotations_to_list 功能与 to_list 非常类似,只不过会额外将一些字段进行翻译,例如 title 会根据语言环境展示为 "标题" 或 "Title" |
使用内置的格式化函数有如下优点:
- 开箱即用:默认对告警状态、严重度等属性,以及列表、引用等常见格式做了包装。
- 屏蔽了不同渠道的差异:不同的通知渠道对格式的支持是有区别的,例如钉钉支持彩色字体,但是有些渠道可能就不执行。另外同样都是列表展示,在钉钉下是 Markdown 格式,在邮件下是 HTML 格式。内置的函数对此进行了封装,屏蔽了不同渠道的差异,对外提供出来的是一套统一的语法,从而方便使用。
案例二:Webhook JSON 内容配置
在使用 Webhook 通知渠道的时候,通常我们都会使用 JSON 格式的内容。
旧版模板 |
新版模板 |
|
模板配置 |
{ "project": "${project}", "alert_name": "${alert_name}", "fire_time": ${fire_time}, "status": "${status}", "severity": "${severity}", "labels": ${labels}, "annotations": ${annotations} } |
{ "project": "{{ alert.project }}", "alert_name": "{{ alert.alert_name }}", "fire_time": {{ alert.fire_time }}, "status": {{ alert.status | format_status | quote }}, "severity": {{ alert.severity | format_severity | quote }}, "labels": {{ alert.labels | to_json }}, "annotations": {{ alert.annotations | to_json }} } |
这里新版模板使用了如下函数:
函数处理 |
作用 |
|
|
|
to_json 用于将数据转为 JSON 格式,这里我们可以非常明显地看到告警标签是以 JSON 格式来进行展示的。 |
这里我们可以看到新版模板语法的一个显著特点,即内容与样式分离,例如通过 quote 可以显式的看出渲染后是一个字符串,通过 to_json 可以显式的看到是一个 JSON 格式。通过内容与样式分离,可以通过内置的函数来自定义需要的展示样式,从而更加灵活。
案例三:动态渲染
例如原始数据如下:
希望将错误的 host 和 remote_uri 遍历展示出来,可以配置内容模板如下:
- 所在项目: {{ alert.project }} - 告警名称: {{ alert.alert_name }} - 触发时间: {{ alert.fire_time | format_date }} - 告警状态: {{ alert.status | format_status }} - 严重度: {{ alert.severity | format_severity }} - 标签: {{ alert.labels | to_list | blockquote }} - 触发详情: {%- if alert.status == 'firing' %} {%- for result in alert.fire_results %} - {{ result.host }}{{ result.request_uri }} {%- endfor %} {%- endif %} [[查看详情]({{ alert.query_url }})] [[告警设置]({{ alert.alert_url }})]
通知效果如下:
这里我们通过 if 条件判断和 for 循环结合,从而可以在告警是触发状态的时候,遍历渲染出所有的触发数据。
进一步参考
更多相关信息,可以参考:
对我们工作感兴趣的,可以通过如下方式了解更多,谢谢关注!
- SLS首页:https://www.aliyun.com/product/sls
- 知乎:https://zhuanlan.zhihu.com/aliyunlog
- 微信公众号:日志服务 or LogAnalytics
- 哔哩哔哩:https://space.bilibili.com/630680534