Add incident creation and resolution
This commit is contained in:
+187
-10
@@ -37,9 +37,9 @@ def get_app_services():
|
|||||||
|
|
||||||
rows = con.execute(
|
rows = con.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id
|
SELECT id, health_url
|
||||||
FROM apps
|
FROM apps
|
||||||
WHERE status IS NOT NULL
|
WHERE is_enabled = 1
|
||||||
ORDER BY id
|
ORDER BY id
|
||||||
"""
|
"""
|
||||||
).fetchall()
|
).fetchall()
|
||||||
@@ -50,7 +50,12 @@ def get_app_services():
|
|||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
app_id = row["id"]
|
app_id = row["id"]
|
||||||
services[app_id] = f"http://appfactory-caddy/apps/{app_id}/health"
|
health_url = row["health_url"] or "/health"
|
||||||
|
|
||||||
|
if health_url.startswith("http://") or health_url.startswith("https://"):
|
||||||
|
services[app_id] = health_url
|
||||||
|
else:
|
||||||
|
services[app_id] = f"http://appfactory-caddy/apps/{app_id}{health_url}"
|
||||||
|
|
||||||
return services
|
return services
|
||||||
|
|
||||||
@@ -76,10 +81,10 @@ def save_health_result(
|
|||||||
http_status: int | None,
|
http_status: int | None,
|
||||||
response_time_ms: int | None,
|
response_time_ms: int | None,
|
||||||
error_text: str | None,
|
error_text: str | None,
|
||||||
):
|
) -> int:
|
||||||
con = get_connection()
|
con = get_connection()
|
||||||
|
|
||||||
con.execute(
|
cur = con.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO service_health (
|
INSERT INTO service_health (
|
||||||
service_id,
|
service_id,
|
||||||
@@ -100,9 +105,157 @@ def save_health_result(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
health_id = cur.lastrowid
|
||||||
|
|
||||||
con.commit()
|
con.commit()
|
||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
|
return int(health_id)
|
||||||
|
|
||||||
|
|
||||||
|
def get_open_incident(service_id: str):
|
||||||
|
con = get_connection()
|
||||||
|
|
||||||
|
row = con.execute(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM service_incidents
|
||||||
|
WHERE service_id = ?
|
||||||
|
AND status = 'open'
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
(service_id,),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
|
def open_incident(service_id: str, health_id: int, status: str, error_text: str | None):
|
||||||
|
if get_open_incident(service_id):
|
||||||
|
return
|
||||||
|
|
||||||
|
title = f"Služba {service_id} není zdravá"
|
||||||
|
description = error_text or f"Health status: {status}"
|
||||||
|
|
||||||
|
con = get_connection()
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO service_incidents (
|
||||||
|
service_id,
|
||||||
|
status,
|
||||||
|
started_at,
|
||||||
|
start_health_id,
|
||||||
|
title,
|
||||||
|
description
|
||||||
|
)
|
||||||
|
VALUES (?, 'open', CURRENT_TIMESTAMP, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
service_id,
|
||||||
|
health_id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO audit_events (
|
||||||
|
username,
|
||||||
|
action,
|
||||||
|
target_type,
|
||||||
|
target_id,
|
||||||
|
source,
|
||||||
|
metadata,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'system',
|
||||||
|
'service.incident.opened',
|
||||||
|
'service',
|
||||||
|
?,
|
||||||
|
'monitor',
|
||||||
|
?,
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
service_id,
|
||||||
|
f'{{"health_id": {health_id}, "status": "{status}"}}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_incident(service_id: str, health_id: int):
|
||||||
|
incident = get_open_incident(service_id)
|
||||||
|
|
||||||
|
if not incident:
|
||||||
|
return
|
||||||
|
|
||||||
|
con = get_connection()
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
"""
|
||||||
|
UPDATE service_incidents
|
||||||
|
SET status = 'resolved',
|
||||||
|
ended_at = CURRENT_TIMESTAMP,
|
||||||
|
end_health_id = ?,
|
||||||
|
duration_seconds = CAST(
|
||||||
|
(julianday(CURRENT_TIMESTAMP) - julianday(started_at)) * 86400
|
||||||
|
AS INTEGER
|
||||||
|
)
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
health_id,
|
||||||
|
incident["id"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
con.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO audit_events (
|
||||||
|
username,
|
||||||
|
action,
|
||||||
|
target_type,
|
||||||
|
target_id,
|
||||||
|
source,
|
||||||
|
metadata,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'system',
|
||||||
|
'service.incident.resolved',
|
||||||
|
'service',
|
||||||
|
?,
|
||||||
|
'monitor',
|
||||||
|
?,
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
service_id,
|
||||||
|
f'{{"incident_id": {incident["id"]}, "health_id": {health_id}}}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
con.commit()
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
|
||||||
|
def update_incident_state(service_id: str, health_id: int, status: str, error_text: str | None):
|
||||||
|
if status == "healthy":
|
||||||
|
resolve_incident(service_id, health_id)
|
||||||
|
else:
|
||||||
|
open_incident(service_id, health_id, status, error_text)
|
||||||
|
|
||||||
|
|
||||||
def check_service(service_id: str, url: str):
|
def check_service(service_id: str, url: str):
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
@@ -116,31 +269,55 @@ def check_service(service_id: str, url: str):
|
|||||||
elapsed_ms = int((time.monotonic() - start) * 1000)
|
elapsed_ms = int((time.monotonic() - start) * 1000)
|
||||||
|
|
||||||
if 200 <= response.status_code < 300:
|
if 200 <= response.status_code < 300:
|
||||||
save_health_result(
|
health_id = save_health_result(
|
||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
status="healthy",
|
status="healthy",
|
||||||
http_status=response.status_code,
|
http_status=response.status_code,
|
||||||
response_time_ms=elapsed_ms,
|
response_time_ms=elapsed_ms,
|
||||||
error_text=None,
|
error_text=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
update_incident_state(
|
||||||
|
service_id=service_id,
|
||||||
|
health_id=health_id,
|
||||||
|
status="healthy",
|
||||||
|
error_text=None,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
save_health_result(
|
error_text = response.text[:500]
|
||||||
|
|
||||||
|
health_id = save_health_result(
|
||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
status="unhealthy",
|
status="unhealthy",
|
||||||
http_status=response.status_code,
|
http_status=response.status_code,
|
||||||
response_time_ms=elapsed_ms,
|
response_time_ms=elapsed_ms,
|
||||||
error_text=response.text[:500],
|
error_text=error_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
update_incident_state(
|
||||||
|
service_id=service_id,
|
||||||
|
health_id=health_id,
|
||||||
|
status="unhealthy",
|
||||||
|
error_text=error_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
elapsed_ms = int((time.monotonic() - start) * 1000)
|
elapsed_ms = int((time.monotonic() - start) * 1000)
|
||||||
|
error_text = str(exc)
|
||||||
|
|
||||||
save_health_result(
|
health_id = save_health_result(
|
||||||
service_id=service_id,
|
service_id=service_id,
|
||||||
status="unreachable",
|
status="unreachable",
|
||||||
http_status=None,
|
http_status=None,
|
||||||
response_time_ms=elapsed_ms,
|
response_time_ms=elapsed_ms,
|
||||||
error_text=str(exc),
|
error_text=error_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
update_incident_state(
|
||||||
|
service_id=service_id,
|
||||||
|
health_id=health_id,
|
||||||
|
status="unreachable",
|
||||||
|
error_text=error_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user