Can I get Grafana Unified Alerts to look like the old alerts?

One of the biggest issues with monitoring and alerting systems is that alerting just goes bad and people end up ignoring it. That can be because of false positives, but also because alert messages are unclear.

Upgrading from Grafana 8.4.4 to 9.* automatically opted me in to unified alerts. I was going to move to that anyway and all the alerts technically work… but the default alert message is a mess. For example, here is a Slack alert from the old Grafana; please excuse the heavy obfuscation and tyops:

Here’s the same alert from Grafana 9.2.0:

This is the kind of message people absolutely ignore.

I’ve gone through the templating system and, honestly, it’s a huge mess, especially since I’m not familiar with Go, and being a template means it’ll not really be easily human-readable (i.e., no indenting, etc).

I did manage to struggle through and finally got this:

…which apparently I can’t attach because I’m a new user. I’ve added it in a comment below.

It’s pretty close but the problem is that I can’t use any Slack markdown, can’t use a title (because it repeats multiple times), and if I have multiple hosts, I can’t show those because the hosts are in the ValueString variable which is not really processable easily. I’ve seen many, many posts about this variable and no actual solutions. In fact there are some conversations ending with “Oh well”.

So my questions are, basically, can I just tell it to use the same “template” it did before unified alerting?

Barring that, is there a better way to process the string ValueString as an array? None of the functions I tried on it work, and I had to do some dirty, dirty regex to compress it down to one single hostname.

Here is the template I’m using now:

[{{.Status | toUpper }}] {{ .Labels.alertname }}
{{ if gt (len .ValueString) 0 }} {{ $hostname := reReplaceAll "^(.*?)hostname=" "" .ValueString }} {{ $hostname := reReplaceAll ",.*$" "" $hostname }} {{ if gt (len $hostname) 0 }}
Hostname: {{ $hostname }}{{ end }}{{ end }}
{{ if gt (len .Annotations) 0 }}
{{ .Annotations.message }}{{ end }}
{{ end }}
2 Likes

For reference, here is what I have after the template is applied.

I’m impressed there are zero replies here.

I also upgraded from Grafana 8 to 10 and the notifications are really hard to parse.

One issue I can’t understand is why markdown formatting is ignored in the notification templates.

1 Like

We have same issues, the new default template is useless.
We managed to create an almost acceptable template but it is limited, does not work all with all alerts.
Old legacy template was just way better.

1 Like

It was a LOT of pain but I managed to write a custom grafana template for new Unified Alerts that parse the ValueString where important information is (which was visible in old alerts). I needed to parse json raw text via regexReplace, big pain! I also covered all the edge cases where new grafana handles NoData and similar alerts in a special way so that they are showed correctly. I think the end result is actually even better then the old grafana default. It is not perfect but we have changed all 500 alerts in our company to use this template instead of default one and people are happy again.

This template handles both legacy alerts that were migrated and new multiseries alerts. It was fine-tuned for Microsoft Teams but we also use it for email (no time at the moment to prettify it for email seperatelly, if someone has time I would be happy to use it :)). Here are all 3 templates that you need to add (and then change for every contact point to use this template):

{{ define “customtemplate.title” }}
{{- if gt (.Alerts.Firing | len) 0 }}:fire: FIRING:{{ " " }}
{{- else }}:white_check_mark: RESOLVED:{{ " " }}
{{- end }}
{{- if (ne .CommonLabels.alertname “”) }}{{- .CommonLabels.alertname }}
{{- else }}{{- range .CommonLabels.SortedPairs }}{{- .Value }}{{- " | " }}{{- end }}
{{- end }}
{{- end }}

{{- /* for documentation look here: Notification template reference | Grafana documentation /}}
{{- /
INSTRUCTIONS /}}
{{- /
Use Summary field for per series extra info (can be dynamic) /}}
{{- /
This template will parse the first query per series (make first query returns nice column names) /}}
{{- /
Use Description field to add custom text (NOT dynamic) to begining of alert /}}
{{- /
Do NOT use legacy condition for best experiance (multiseries alert) /}}
{{ define “customtemplate.message” }}
{{- /
handle special alerts with prettier message /}}
{{- if (eq .CommonAnnotations.grafana_state_reason “Updated”) }}
{{- “Alert Rule was modified by someone (alerts autoresolved)” }}
{{- else if (and (eq .CommonAnnotations.grafana_state_reason “NoData”) (.Alerts.Firing)) }}
{{- “Alert returned no data, please fix alert (NoData)” }}
{{- else if (and (eq .CommonAnnotations.grafana_state_reason “MissingSeries”) (.Alerts.Firing)) }}
{{- “Alert returned no data, please fix alert (MissingSeries)” }}
{{- else if (eq .CommonAnnotations.grafana_state_reason “Error”) }}
{{- if .Alerts.Firing }}
{{- “Error while executing alert, please fix alert” }}
{{- else }}
{{- “Execution error was resolved” }}
{{- end }}
{{- else }}
{{- /
handle special alerts when set to Nodata/Error (in that case AlertName is different!) /}}
{{- if (eq .CommonLabels.alertname “DatasourceNoData”) }}
{{- if .Alerts.Firing }}
{{- “Alert returned no data, please fix alert.” }}
{{- else }}
{{- “No Data issue was resolved.” }}
{{- end }}
{{- else if (eq .CommonLabels.alertname “DatasourceError”) }}
{{- if .Alerts.Firing }}
{{- “Error while executing alert, please fix alert.” }}
{{- else }}
{{- “Execution error was resolved.” }}
{{- end }}
{{- else }}
{{- /
migrated alerts from old grafana use message annotation, new alerts use description /}}
{{- /
only show when at least one in alerting state (since description may containt a warning that is irelevant if resolved) /}}
{{- if .Alerts.Firing }}
{{- range .CommonAnnotations.SortedPairs }}
{{- if eq .Name “description” }}
{{- .Value }}
{{ “” }}
{{- end }}
{{- if eq .Name “message” }}
{{- .Value }}
{{ “” }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{ if .Alerts.Firing }}
{{- /
care so no spaces before len for prettier formating */}}
{{ len .Alerts.Firing }}{{- " firing alert(s):" }}
{{- template “__customtemplate.alertdetails” .Alerts.Firing }}
{{- end }}
{{ if .Alerts.Resolved }}
{{ len .Alerts.Resolved }}{{- " resolved alert(s):" }}
{{- template “__customtemplate.alertdetails” .Alerts.Resolved }}
{{- end }}
{{- end }}
{{- end }}

{{- /* This is subtempalte, not to be used directly (__ prefix makes sure it can not be) /}}
{{ define “__customtemplate.alertdetails” }}
{{- /
for debuging change “.” to “.Alerts” (do not forget to change back before saving!) */}}
{{- range . }}

  • {{- " " }}
    {{- /* If summary field was set manually by alert author take that and ignore all other fields /}}
    {{- if (ne .Annotations.summary “”) }}
    {{- .Annotations.summary }}{{- “.” }}
    {{ " Details: " }}
    {{- end}}
    {{- /
    Take all custom labels/values /}}
    {{- /
    parse metric and value and query labels (NOT the same as Grafana labels) /}}
    {{- /
    we must change all \ to \ for escaping in regex /}}
    {{- /
    ignore if metric value is ‘value’ in some queries since this is spam /}}
    {{- /
    ignore system labels that start with * /}}
    {{- /
    the first replace deletes everything but the first occurance of […] (some alerts spam duplicates if they have multiple query, we only want data from first query) /}}
    {{-
    reReplaceAll "\](.
    )" “” .ValueString
    | reReplaceAll "
    [^=]=[^ ] " “”
    | reReplaceAll “'^\s+|\s+$” “”
    | reReplaceAll “,\[” “[”
    | reReplaceAll “[\[\]]” “”
    | reReplaceAll " var=‘[^’]’ " “”
    | reReplaceAll “metric=‘value’” “”
    | reReplaceAll “metric='” “”
    | reReplaceAll “'labels={” “”
    | reReplaceAll “labels={” “”
    | reReplaceAll "}\s
    value=" " | Value="
    | reReplaceAll “,” " |"
    }}
    {{- /* Extra info from labels for special datasource “DatasourceNoData” and “DatasourceError” alerts (or we would not know for which alert this fired) / -}}
    {{- if (match “Datasource” .Labels.alertname) }}
    {{- range (.Labels.Remove (stringSlice “grafana_folder” “alertname” “ref_id” “datasource_uid”)).SortedPairs }}
    {{- .Name }}={{- .Value }}{{- ", " }}
    {{- end }}
    {{- end }}
    {{- if (and (ne .Annotations.grafana_state_reason “”) (lt (len .ValueString) 3)) }}
    {{- “Reason=Alert” }}{{- .Annotations.grafana_state_reason }}
    {{- end }}
    {{- /
    Time Alert Fired */ -}}
    {{- " (Fired at: " }}{{- .StartsAt | tz “Europe/Ljubljana” | date “02-01 15:04” }}{{- “)”}}
    {{- end }}
    {{- end }}
1 Like

Wild timing but I just upgraded from v6 to latest and all our alerts are unreadable, thanks for this! It seems the forum software broke your text, any chance you can edit your message and put it between Preformatted text quotes so it becomes readable?

By the way, your post is the first thing that shows up on google!

Edit is not available anymore on my original post. Here is template with some additional optimization and formatted properly.

There are 3 templates. You need to add all 3 of them separately under ContactPoints/Templates and then edit all your Contact Point to use the .message and .title template:

{{ define “customtemplate.title” }}
{{- if gt (.Alerts.Firing | len) 0 }}:fire: FIRING:{{ " " }}
{{- else }}:white_check_mark: RESOLVED:{{ " " }}
{{- end }}
{{- if (ne .CommonLabels.alertname “”) }}{{- .CommonLabels.alertname }}
{{- else }}{{- range .CommonLabels.SortedPairs }}{{- .Value }}{{- " | " }}{{- end }}
{{- end }}
{{- end }}

{{- /* for documentation look here: Notification template reference | Grafana documentation /}}
{{- /
INSTRUCTIONS /}}
{{- /
Use Summary field for per series extra info (can be dynamic) /}}
{{- /
This template will parse the first query per series (make first query returns nice column names) /}}
{{- /
Use Description field to add custom text (NOT dynamic) to begining of alert /}}
{{- /
Do NOT use legacy condition for best experiance (multiseries alert) /}}
{{ define “customtemplate.message” }}
{{- /
handle special alerts with prettier message /}}
{{- if (eq .CommonAnnotations.grafana_state_reason “Updated”) }}
{{- “Alert Rule was modified by someone (alerts autoresolved)” }}
{{- else if (and (eq .CommonAnnotations.grafana_state_reason “NoData”) (.Alerts.Firing)) }}
{{- “Alert returned no data, please fix alert (NoData)” }}
{{- else if (and (eq .CommonAnnotations.grafana_state_reason “MissingSeries”) (.Alerts.Firing)) }}
{{- “Alert returned no data, please fix alert (MissingSeries)” }}
{{- else if (eq .CommonAnnotations.grafana_state_reason “Error”) }}
{{- if .Alerts.Firing }}
{{- “Error while executing alert, please fix alert” }}
{{- else }}
{{- “Execution error was resolved” }}
{{- end }}
{{- else }}
{{- /
handle special alerts when set to Nodata/Error (in that case AlertName is different!) /}}
{{- if (eq .CommonLabels.alertname “DatasourceNoData”) }}
{{- if .Alerts.Firing }}
{{- “Alert returned no data, please fix alert.” }}
{{- else }}
{{- “No Data issue was resolved.” }}
{{- end }}
{{- else if (eq .CommonLabels.alertname “DatasourceError”) }}
{{- if .Alerts.Firing }}
{{- “Error while executing alert, please fix alert.” }}
{{- else }}
{{- “Execution error was resolved.” }}
{{- end }}
{{- else }}
{{- /
migrated alerts from old grafana use message annotation, new alerts use description /}}
{{- /
only show when at least one in alerting state (since description may containt a warning that is irelevant if resolved) /}}
{{- if .Alerts.Firing }}
{{- range .CommonAnnotations.SortedPairs }}
{{- if eq .Name “description” }}
{{- .Value }}
{{ “” }}
{{- end }}
{{- if eq .Name “message” }}
{{- .Value }}
{{ “” }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{ if .Alerts.Firing }}
{{- /
care so no spaces before len for prettier formating */}}
{{ len .Alerts.Firing }}{{- " firing alert(s):" }}
{{- template “__customtemplate.alertdetails” .Alerts.Firing }}
{{- end }}
{{ if .Alerts.Resolved }}
{{ len .Alerts.Resolved }}{{- " resolved alert(s):" }}
{{- template “__customtemplate.alertdetails” .Alerts.Resolved }}
{{- end }}
{{- end }}
{{- end }}

{{- /* This is subtempalte, not to be used directly (__ prefix makes sure it can not be) /}}
{{ define “__customtemplate.alertdetails” }}
{{- /
for debuging change “.” to “.Alerts” (do not forget to change back before saving!) */}}
{{- range . }}

  • {{- " " }}
    {{- /* If summary field was set manually by alert author take that and ignore all other fields /}}
    {{- if (ne .Annotations.summary “”) }}
    {{- .Annotations.summary }}{{- “.” }}
    {{ " Details: " }}
    {{- end}}
    {{- /
    Take all custom labels/values /}}
    {{- /
    parse metric and value and query labels (NOT the same as Grafana labels) /}}
    {{- /
    we must change all \ to \ for escaping in regex /}}
    {{- /
    ignore if metric value is ‘value’ in some queries since this is spam /}}
    {{- /
    ignore system labels that start with * /}}
    {{- /
    the first replace deletes everything but the first occurance of […] (some alerts spam duplicates if they have multiple query, we only want data from first query) /}}
    {{-
    reReplaceAll "\](.
    )" “” .ValueString
    | reReplaceAll "
    [^=]=[^ ] " “”
    | reReplaceAll “'^\s+|\s+$” “”
    | reReplaceAll “,\[” “[”
    | reReplaceAll “[\[\]]” “”
    | reReplaceAll " var=‘[^’]’ " “”
    | reReplaceAll “metric=‘value’” “”
    | reReplaceAll “metric='” “”
    | reReplaceAll “'labels={” “”
    | reReplaceAll “labels={” “”
    | reReplaceAll "}\s
    value=" " | Value="
    | reReplaceAll “,” " |"
    }}
    {{- /* Extra info from labels for special datasource “DatasourceNoData” and “DatasourceError” alerts (or we would not know for which alert this fired) / -}}
    {{- if (match “Datasource” .Labels.alertname) }}
    {{- range (.Labels.Remove (stringSlice “grafana_folder” “alertname” “ref_id” “datasource_uid”)).SortedPairs }}
    {{- .Name }}={{- .Value }}{{- ", " }}
    {{- end }}
    {{- end }}
    {{- if (and (ne .Annotations.grafana_state_reason “”) (lt (len .ValueString) 3)) }}
    {{- “Reason=Alert” }}{{- .Annotations.grafana_state_reason }}
    {{- end }}
    {{- /
    Time Alert Fired */ -}}
    {{- " (Fired at: " }}{{- .StartsAt | tz “Europe/Ljubljana” | date “02-01 15:04” }}{{- “)”}}
    {{- end }}
    {{- end }}