If you’ve integrated Grafana Alerting with PagerDuty, you’ve probably noticed something frustrating: the PagerDuty incident details are cluttered with every single label and annotation from your alerts. Here’s what you typically see:
|
1 |
{<br> "details": {<br> "firing": "<br>Value: A=-1, C=-1<br>Labels:<br> - alertname = mysql_replication_lag<br> - agent_id = 7903a5c7-5976-46d8-84d9-cabdec770353<br> - agent_type = mysqld_exporter<br> - cluster = ps-replication-dev-cluster<br> - environment = ps-replication-dev<br> - grafana_folder = Experimental<br> - instance = 7903a5c7-5976-46d8-84d9-cabdec770353<br> - job = mysqld_exporter_7903a5c7-5976-46d8-84d9-cabdec770353_mr<br> - machine_id = 85a2cf29fa594a9e9a27aa88fce231ba<br> - master_host = ps_pmm_replication_8_0_1<br> - master_uuid = 8da41e19-0667-11f1-adeb-9247e0067a2c<br> - node_id = a1cc8cae-dd58-41e6-86eb-12c866fa4e8c<br> - node_name = 79e48ca85f38<br> - node_type = generic<br> - service_id = 01fb5b56-bb3e-4a33-9797-3b83306c6a20<br> - service_name = ps_pmm_replication_8_0_2_84438<br> - service_type = mysql<br>Annotations:<br> - datasource_uid = PA58DA793C7250F1B<br> - description = Whatever you want here<br> - grafana_state_reason = NoData<br> - ref_id = A<br> - summary = Whatever you want here<br>Source: https://localhost/graph/alerting/grafana/ffcx6mac9uoe8b/view?orgId=1<br>Silence: https://localhost/graph/alerting/silence/new?...<br>",<br> "num_firing": "1",<br> "num_resolved": "0"<br> }<br>} |
This wall of text makes it hard for your on-call engineers to quickly identify what’s wrong. And actually, this was also hard for our Managed Service DBAs. They complained about this for some while, and now I took the time to dig into this as much as I can.
Because you only need the essential information, example: alert name, service, cluster, and environment. But how do you customize this?
The official Grafana PagerDuty integration documentation only explains how to set up the basic integration: creating a service in PagerDuty, obtaining the integration key, and connecting it to Grafana. It says nothing about customizing the alert details or the fact that you can override the default verbose output. (If someone finds that this is documented on Grafana or on PagerDuty I will stand corrected and accept my defeat.)
The problem started with verbose PagerDuty alerts. To understand what Grafana was actually sending, I checked the Events API in PagerDuty to see the raw payload. Here’s what I found:
|
1 |
{<br> "client": "Grafana",<br> "client_url": "https://localhost/graph/",<br> "description": "Status: FIRING<br>Alert Name: pmm_mysql_replication_sql_running Alerting Rule<br>Severity: critical<br>Node: 79e48ca85f38<br>Service: ps_pmm_replication_8_0_2_84438<br>...",<br> "event_type": "trigger",<br> "service_key": "r03ej7zcyn796vhn72pqhfpum2szaftv",<br> "details": {<br> "firing": "<br>Value: A=1<br>Labels:<br>- alertname = pmm_mysql_replication_sql_running Alerting Rule<br>- agent_id = 7903a5c7-5976-46d8-84d9-cabdec770353<br>- agent_type = mysqld_exporter<br>- cluster = ps-replication-dev-cluster<br>- environment = ps-replication-dev<br>- grafana_folder = Experimental<br>- instance = 7903a5c7-5976-46d8-84d9-cabdec770353<br>- job = mysqld_exporter_7903a5c7-5976-46d8-84d9-cabdec770353_mr<br>- machine_id = 85a2cf29fa594a9e9a27aa88fce231ba<br>- master_host = ps_pmm_replication_8_0_1<br>- master_uuid = 8da41e19-0667-11f1-adeb-9247e0067a2c<br>- node_id = a1cc8cae-dd58-41e6-86eb-12c866fa4e8c<br>- node_name = 79e48ca85f38<br>- node_type = generic<br>- service_id = 01fb5b56-bb3e-4a33-9797-3b83306c6a20<br>- service_name = ps_pmm_replication_8_0_2_84438<br>- service_type = mysql<br>- severity = critical<br>Annotations:<br>- description = MySQL Replication Not Running on 7903a5c7...<br>- summary = MySQL Replication Not Running on (instance 7903a5c7...)<br>Source: https://localhost/graph/alerting/grafana/efcx5rs42z3swd/view?orgId=1<br>Silence: https://localhost/graph/alerting/silence/new?alertmanager=grafana&matcher=...<br>",<br> "num_firing": "1",<br> "num_resolved": "0",<br> "resolved": ""<br> }<br>} |
The issue was clear: the details.firing key contained a massive wall of text with every single label and annotation.
Back in the Grafana UI, I noticed the PagerDuty contact point configuration had a Details section with the description: “A set of arbitrary key/value pairs that provide further detail about the incident.”
I started experimenting:
{{ .CommonLabels.cluster }}
After a few iterations, I had a thought: What if I add a key called firing to override the default verbose one?
I added: – Key: firing – Value: {{ .CommonLabels.alertname }}
Triggered another alert, checked PagerDuty, and… it worked! The firing key now contained just the alert name instead of the entire label dump.
Now knowing what to look for, I searched for documentation and found: – A GitHub discussion from 2022 where user jquick mentioned this behavior – The actual implementation in Grafana’s alerting repository at receivers/pagerduty/v1/config.go:
|
1 |
var defaultDetails = map[string]string{<br> "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`,<br> "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`,<br> "num_firing": `{{ .Alerts.Firing | len }}`,<br> "num_resolved": `{{ .Alerts.Resolved | len }}`,<br>}<br><br>// mergeDetails merges the default details with the user-defined ones.<br>// Default values get overwritten in case of duplicate keys.<br>func mergeDetails(userDefinedDetails map[string]string) map[string]string {<br> mergedDetails := make(map[string]string)<br> for k, v := range defaultDetails {<br> mergedDetails[k] = v<br> }<br> for k, v := range userDefinedDetails {<br> mergedDetails[k] = v<br> }<br> return mergedDetails<br>} |
The comment is clear: “Default values get overwritten in case of duplicate keys.”
Next time I should go straight to the source code first! 🙂
So when you add a firing key in your PagerDuty contact point’s Details section, it replaces the default __text_alert_list template that dumps all labels and annotations.
Here’s how to customize your PagerDuty details to show only what matters.
In Grafana:
Click + Add and create these key-value pairs:
| Key | Value |
|---|---|
| firing | {{ .CommonLabels.alertname }} |
| cluster | {{ .CommonLabels.cluster }} |
| environment | {{ .CommonLabels.environment }} |
| node_name | {{ .CommonLabels.node_name }} |
| service_name | {{ .CommonLabels.service_name }} |
| status | {{ .Status }} |
Important: The firing key is what overrides the default verbose output!
Now your PagerDuty incidents will have clean, readable custom details:
|
1 |
{<br> "details": {<br> "firing": "mysql_replication_lag",<br> "cluster": "ps-replication-dev-cluster",<br> "environment": "ps-replication-dev",<br> "node_name": "79e48ca85f38",<br> "service_name": "ps_pmm_replication_8_0_2_84438",<br> "status": "firing",<br> "num_firing": "1",<br> "num_resolved": "0"<br> }<br>} |
Much better!
You must use .CommonLabels (not .Labels) in the Details section because you’re at the root template level, not inside a loop.
I hope you noticed the following in the code:
|
1 |
"resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, |
When an alert resolves it will dump all the labels into the resolved key. If you want to tidy up that message as well you need to add an override for the resolved key as well.
Based on the source code, these are the default PagerDuty detail keys you can override:
You can also add any custom keys you want—they’ll be added alongside the defaults.
Even the __text_alert_list can be overridden just by creating a custom template and name it __text_alert_list:
|
1 |
{{ define "__text_alert_list" }}<br>everything what you need here<br>{{ end }} |
But this could impact all the other contact points which might rely on this template.
For more complex formatting, create a notification template:
Go to Alerting → Contact points → Notification Templates tab → + Add notification template
Template Name: pagerduty_clean_firing
Template Content:
|
1 |
{{ define "pagerduty_clean_firing" }}<br>Alert: {{ .CommonLabels.alertname }}<br>{{ if .CommonLabels.node_name }}Node: {{ .CommonLabels.node_name }}{{ end }}<br>{{ if .CommonLabels.service_name }}Service: {{ .CommonLabels.service_name }}{{ end }}<br>{{ if .CommonLabels.cluster }}Cluster: {{ .CommonLabels.cluster }}{{ end }}<br>{{ if .CommonLabels.environment }}Environment: {{ .CommonLabels.environment }}{{ end }}<br>Status: {{ .Status }}<br>{{ if .CommonAnnotations.description }}Description: {{ .CommonAnnotations.description }}{{ end }}<br>{{ end }} |
In your PagerDuty contact point Details:
Key: firing
Value: {{ template "pagerduty_clean_firing" . }}
This gives you multi-line formatted output in PagerDuty with conditional fields.
You can also work on your alert expressions and only return the needed labels, example:
|
1 |
max by (service_name, cluster, environment, node_name, severity) (expression) |
In this case Grafana will send only these labels to PagerDuty. But this may require changing some of the logic in your alert expressions.
Clean PagerDuty details mean:
– Faster incident response – Engineers see critical info immediately
– Better filtering – PagerDuty can use custom details for routing rules
– Cleaner integrations – If you’re forwarding to Slack or other tools, you get clean data
– Professional appearance – No more walls of text in your incidents
And most importantly I hope we can finally make our DBAs happy.
Customizing PagerDuty details in Grafana is possible – you just need to know the secret: override the firing key in the Details section.
This isn’t a hack or workaround; it’s the intended design. The Grafana team implemented a clean merge function that prioritizes user-defined details over defaults. It just needs better documentation.
Hopefully, this guide saves you the hours of trial and error, GitHub digging and source code reading that it took to figure this out!