Files
LLM-Zabbix-HABR/alert-receiver/app/normalizer.py
T

152 lines
4.2 KiB
Python

from __future__ import annotations
import json
from datetime import datetime, timezone
from typing import Any
SEVERITY_MAP = {
0: "Not classified",
1: "Information",
2: "Warning",
3: "Average",
4: "High",
5: "Disaster",
}
def _first_non_empty(payload: dict[str, Any], keys: list[str]) -> Any:
for key in keys:
value = payload.get(key)
if value is not None and str(value).strip() != "":
return value
return None
def _parse_timestamp(value: Any) -> datetime | None:
if value is None:
return None
if isinstance(value, (int, float)):
return datetime.fromtimestamp(value, tz=timezone.utc)
if isinstance(value, str):
raw = value.strip()
if not raw:
return None
if raw.isdigit():
return datetime.fromtimestamp(int(raw), tz=timezone.utc)
try:
return datetime.fromisoformat(raw.replace("Z", "+00:00"))
except ValueError:
return None
return None
def _parse_severity(payload: dict[str, Any]) -> tuple[str | None, int | None]:
raw = _first_non_empty(
payload,
[
"severity",
"severity_name",
"event_severity",
"event_severity_name",
"priority",
],
)
if raw is None:
return None, None
if isinstance(raw, int):
return SEVERITY_MAP.get(raw), raw
raw_str = str(raw).strip()
if raw_str.isdigit():
code = int(raw_str)
return SEVERITY_MAP.get(code, raw_str), code
return raw_str, None
def _parse_tags(raw_tags: Any) -> dict[str, str]:
if raw_tags is None:
return {}
if isinstance(raw_tags, dict):
return {str(k): str(v) for k, v in raw_tags.items()}
if isinstance(raw_tags, list):
result: dict[str, str] = {}
for item in raw_tags:
if isinstance(item, dict):
tag = item.get("tag") or item.get("name") or item.get("key")
value = item.get("value")
if tag is not None:
result[str(tag)] = "" if value is None else str(value)
return result
if isinstance(raw_tags, str):
raw = raw_tags.strip()
if not raw:
return {}
try:
parsed = json.loads(raw)
return _parse_tags(parsed)
except json.JSONDecodeError:
return {}
return {}
def normalize_zabbix_payload(
payload: dict[str, Any],
correlation_id: str,
remote_addr: str | None,
) -> dict[str, Any]:
severity, severity_code = _parse_severity(payload)
timestamp = _parse_timestamp(
_first_non_empty(payload, ["timestamp", "clock", "event_time"])
)
tags = _parse_tags(_first_non_empty(payload, ["tags", "event_tags"]))
trigger_name = _first_non_empty(
payload,
["trigger_name", "name", "event_name", "problem_name", "subject"],
)
service = _first_non_empty(
payload,
["service", "component", "application", "item_name"],
)
normalized = {
"source": "zabbix",
"correlation_id": correlation_id,
"received_at": datetime.now(tz=timezone.utc),
"remote_addr": remote_addr,
"event_id": _first_non_empty(payload, ["event_id", "eventid"]),
"problem_id": _first_non_empty(payload, ["problem_id", "problemid"]),
"event_type": _first_non_empty(payload, ["event_type", "status"]) or "problem",
"timestamp": timestamp,
"severity": severity,
"severity_code": severity_code,
"host": _first_non_empty(payload, ["host", "host_name", "hostname"]),
"host_id": _first_non_empty(payload, ["host_id", "hostid"]),
"service": service,
"trigger_name": trigger_name,
"trigger_id": _first_non_empty(payload, ["trigger_id", "triggerid"]),
"item_id": _first_non_empty(payload, ["item_id", "itemid"]),
"value": _first_non_empty(payload, ["value", "status_value"]),
"opdata": _first_non_empty(payload, ["opdata", "message", "description"]),
"zabbix_url": _first_non_empty(payload, ["zabbix_url", "url", "event_url"]),
"tags": tags,
"raw_payload": payload,
}
return normalized