Generic Alertmanager support with rich formatter#15
Open
ornariece wants to merge 6 commits into
Open
Conversation
hasattr(dict, 'key') is always False — dicts expose keys via __getitem__, not as attributes. This meant the description annotation was never read, always falling back to summary.
- Catch IndexError and TypeError in addition to KeyError when probing webhook payloads, since malformed data may have wrong types or empty arrays rather than missing keys. - Fix if/elif chain in get_alert_messages — the slack-webhook branch used 'if' instead of 'elif', causing unintended fall-through. - Fix error message: e.with_traceback() returns None, use str(e).
The upstream Prometheus detection required a "job" label, which is only present in Prometheus-originated alerts. Replace with "alertname" detection, which matches ALL Alertmanager webhook payloads regardless of the alert source (Prometheus, custom rules, Thanos, Mimir, etc.). New alertmanager_to_markdown() produces structured messages optimized for mobile notification clients: - Emoji status line (firing/resolved) with alert name and severity - Description from annotations (with correct dict key lookup) - Label metadata as list items (all labels except alertname/severity) - Duration calculation for resolved alerts - Pre-filled silence URL from Alertmanager's externalURL - Source link from generatorURL Move request text reading into send_alert, reducing the webhook handler to routing and error handling.
CommonMark converts '- ' prefixed lines into <ul><li> HTML. Element X iOS renders the first <li> with indent but subsequent items without, causing visual misalignment. Replace the markdown list marker with a Unicode bullet character. CommonMark now emits <p> + <br> instead, which renders consistently across Matrix clients.
Fix upstream test that called non-existent find_alert_type (should be get_alert_type). Update expected type for Prometheus payloads from "prometheus-alert" to "alertmanager-alert" to match the generic Alertmanager detection. Add tests for: - All alert type classification paths including edge cases - Alertmanager-without-job-label detection - Malformed payload resilience (empty arrays, wrong types, None) - alertmanager_to_markdown: firing, resolved, duration, description vs summary fallback, label metadata, silence/source URLs, multi- alert groups, real Prometheus example payload - _format_duration, _format_timestamp, _build_silence_url
Add a supported webhook types table showing detection logic for each format. Expand the Alertmanager setup section with send_resolved, example message output for firing and resolved alerts, and generic example URLs. Fix the test alert curl example to reference the correct filename. Add a dependencies section for python-dateutil.
28678dd to
a7f6bdb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Webhook detection currently checks for
labels.jobto identify Prometheus alerts. Butjobis just a scrape-config label that Prometheus happens to add — it's not part of the Alertmanager webhook format. Alerts from custom recording rules, Thanos, Mimir, or any other source routed through Alertmanager don't necessarily have it, so they silently fall through to the raw "Data received" fallback.The formatter is also minimal — it shows status, a title, and three hardcoded labels. Annotations, timestamps, and actionable links (silence, source) are not surfaced.
There are also a few bugs:
hasattr(dict, 'key')is used for annotation lookup, which is alwaysFalseon dicts (keys are accessed via__getitem__, not attributes), so thedescriptionannotation is never read. Webhook detection only catchesKeyError, missingIndexError/TypeErrorfrom malformed payloads. The slack-webhook branch usesifinstead ofelif, causing fall-through.Solution
Generic detection: Check
labels.alertnameinstead oflabels.job.alertnameis mandatory in every Alertmanager webhook per the data model. Since Prometheus alerts are sent through Alertmanager, they always havealertnametoo — nothing is lost. Grafana alerts are unaffected (they match first ongrafana_folder).Rich formatter: New
alertmanager_to_markdown()produces one structured message per alert:descriptionpreferred,summaryfallback)externalURLgeneratorURLBug fixes (separate commits):
hasattr→infor dict key lookup on annotationsIndexErrorandTypeErrorin addition toKeyErrorin webhook detectionif/eliffall-through in the slack-webhook dispatch branche.with_traceback()(returnsNone) →str(e)in error messages•instead of-list marker to avoid inconsistent<li>indentation on Element X iOSTests: Fixed the existing test (it called
find_alert_typewhich doesn't exist — should beget_alert_type) and added coverage for the new formatter, helper functions, classification edge cases, and malformed payloads. 25 tests total.Breaking changes
prometheus-alert/prometheus-resolvedtype strings →alertmanager-alert/alertmanager-resolvedprometheus_alert_to_markdown()removed (replaced byalertmanager_to_markdown())send_alert()takes aRequestobject instead of pre-read text (internal method)