152 lines
4.2 KiB
Python
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 |