feat(syslog): pattern alerts: telling phrases counted per source each minute for alert rules, storage-trouble presets (#6001)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m31s
CI / integration (push) Failing after 1m40s
CI / publish (push) Skipped

A pattern (phrases, a severity, a source, a window) is counted every minute
into plugin_metrics as lines_matching_<key> per added source, zero included,
so an ordinary alert rule fires on a match and clears once the window passes.
Each count reads only the lines stored since the last, by line id; a new or
changed pattern reads its window once.

Patterns page with ready-made storage-trouble presets and "Alert on it",
which opens the rule form aimed at the count. Source names are kept unique,
and a rename moves its counts and the rules on them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 06:22:54 -04:00
co-authored by Claude Opus 5.5
parent dadec4e355
commit 8eae3d3825
14 changed files with 892 additions and 16 deletions
+7 -2
View File
@@ -423,9 +423,14 @@ async def _cover_choices(db, source: str, rule: AlertRule | None = None) -> dict
@alerts_bp.get("/rules/new")
@require_role(UserRole.operator)
async def new_rule():
first_source = alert_svc.source_modules()[0]
"""The rule form, empty, or aimed at a series when a page links here with
source_module, resource_name and metric_name (a syslog pattern's count)."""
sources = alert_svc.source_modules()
asked = request.args.get("source_module", "")
first_source = asked if asked in sources else sources[0]
async with current_app.db_sessionmaker() as db:
ctx = {**await _cover_choices(db, first_source), **await _target(db, first_source)}
ctx = {**await _cover_choices(db, first_source),
**(await _target_from_args(db) if asked == first_source else await _target(db, first_source))}
return await render_template(
"alerts/rules_form.html",
rule=None, **ctx,