fix(mcp): enrich host log evidence params
This commit is contained in:
@@ -619,17 +619,30 @@ def _build_tool_params(incident: "Incident") -> dict[str, Any]:
|
||||
or target
|
||||
or pod_name
|
||||
)
|
||||
container_name = (
|
||||
labels.get("container_name")
|
||||
or labels.get("container")
|
||||
or labels.get("name")
|
||||
or service_name
|
||||
)
|
||||
log_severity = labels.get("log_severity") or labels.get("severity", "")
|
||||
if not log_severity and any(k in alertname.lower() for k in ("error", "fatal", "critical")):
|
||||
log_severity = "ERROR,FATAL,CRITICAL"
|
||||
|
||||
return {
|
||||
"namespace": namespace,
|
||||
"pod_name": pod_name,
|
||||
"deployment": labels.get("deployment", ""),
|
||||
"host": host,
|
||||
"container": labels.get("container", labels.get("name", "")),
|
||||
"container_name": container_name,
|
||||
"filter_name": labels.get("filter_name") or container_name,
|
||||
"target": target,
|
||||
"service": labels.get("unit") or labels.get("service") or service_name,
|
||||
"service_name": service_name,
|
||||
"alertname": alertname,
|
||||
"search_text": alertname,
|
||||
"severity": labels.get("severity", ""),
|
||||
"severity": log_severity,
|
||||
"time_window_minutes": 30,
|
||||
"limit": 100,
|
||||
# P0.4 fix 2026-04-24 ogt + Claude Sonnet 4.6: Prometheus tool 需要 query 欄位
|
||||
|
||||
@@ -433,8 +433,8 @@ class SignOzClient:
|
||||
"""
|
||||
從 SignOz/ClickHouse 查詢日誌 (Phase 13.1 #77)
|
||||
|
||||
SignOz 日誌儲存在 signoz_logs.distributed_logs 表
|
||||
Schema: timestamp, severity_text, body, resources, attributes
|
||||
SignOz v0.8+ 日誌儲存在 signoz_logs.distributed_logs_v2 表
|
||||
Schema: timestamp, severity_text, body, resources_string, attributes_string
|
||||
|
||||
Args:
|
||||
service_name: 服務名稱 (過濾 resources.service.name)
|
||||
@@ -458,7 +458,8 @@ class SignOzClient:
|
||||
|
||||
if service_name:
|
||||
# SignOz 儲存 service.name 在 resources 欄位
|
||||
conditions.append(f"resources['service.name'] = '{service_name}'")
|
||||
safe_service = service_name.replace("'", "''")
|
||||
conditions.append(f"resources_string['service.name'] = '{safe_service}'")
|
||||
|
||||
if severity:
|
||||
# 支援多個級別 (如 'ERROR,WARN')
|
||||
@@ -478,11 +479,11 @@ class SignOzClient:
|
||||
timestamp,
|
||||
severity_text,
|
||||
body,
|
||||
resources,
|
||||
attributes,
|
||||
resources_string AS resources,
|
||||
attributes_string AS attributes,
|
||||
trace_id,
|
||||
span_id
|
||||
FROM signoz_logs.distributed_logs
|
||||
FROM signoz_logs.distributed_logs_v2
|
||||
WHERE {where_clause}
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT {limit}
|
||||
@@ -532,11 +533,11 @@ class SignOzClient:
|
||||
severity_text,
|
||||
count() as count,
|
||||
any(body) as sample_message
|
||||
FROM signoz_logs.distributed_logs
|
||||
FROM signoz_logs.distributed_logs_v2
|
||||
WHERE
|
||||
timestamp >= {start_ns}
|
||||
AND timestamp <= {end_ns}
|
||||
AND resources['service.name'] = '{service_name}'
|
||||
AND resources_string['service.name'] = '{service_name.replace("'", "''")}'
|
||||
AND severity_text IN ('ERROR', 'FATAL', 'CRITICAL')
|
||||
GROUP BY severity_text
|
||||
ORDER BY count DESC
|
||||
|
||||
@@ -166,6 +166,9 @@ def test_build_tool_params_uses_host_alias_and_service_from_affected_service() -
|
||||
assert params["host"] == "192.168.0.188"
|
||||
assert params["target"] == "ollama"
|
||||
assert params["service_name"] == "ollama"
|
||||
assert params["container_name"] == "ollama"
|
||||
assert params["filter_name"] == "ollama"
|
||||
assert params["severity"] == "ERROR,FATAL,CRITICAL"
|
||||
assert params["search_text"] == "HostErrorLogFlood"
|
||||
assert params["time_window_minutes"] == 30
|
||||
|
||||
|
||||
61
apps/api/tests/test_signoz_client_logs.py
Normal file
61
apps/api/tests/test_signoz_client_logs.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from src.services.signoz_client import SignOzClient
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_logs_uses_live_signoz_v2_table(monkeypatch) -> None:
|
||||
client = SignOzClient()
|
||||
queries: list[str] = []
|
||||
|
||||
async def fake_query(query: str) -> list[dict]:
|
||||
queries.append(query)
|
||||
return [
|
||||
{
|
||||
"timestamp": 1,
|
||||
"severity_text": "ERROR",
|
||||
"body": "boom",
|
||||
"resources": {"service.name": "ollama"},
|
||||
"attributes": {"container.name": "ollama"},
|
||||
"trace_id": "trace-1",
|
||||
"span_id": "span-1",
|
||||
}
|
||||
]
|
||||
|
||||
monkeypatch.setattr(client, "_query_clickhouse", fake_query)
|
||||
|
||||
logs = await client.get_logs(
|
||||
service_name="ollama",
|
||||
severity="ERROR",
|
||||
search_text="HostErrorLogFlood",
|
||||
time_window_minutes=5,
|
||||
limit=3,
|
||||
)
|
||||
|
||||
assert logs[0]["service"] == "ollama"
|
||||
assert "FROM signoz_logs.distributed_logs_v2" in queries[0]
|
||||
assert "resources_string['service.name'] = 'ollama'" in queries[0]
|
||||
assert "attributes_string AS attributes" in queries[0]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_logs_summary_uses_live_signoz_v2_table(monkeypatch) -> None:
|
||||
client = SignOzClient()
|
||||
queries: list[str] = []
|
||||
|
||||
async def fake_query(query: str) -> list[dict]:
|
||||
queries.append(query)
|
||||
return [{"severity_text": "ERROR", "count": 2, "sample_message": "boom"}]
|
||||
|
||||
monkeypatch.setattr(client, "_query_clickhouse", fake_query)
|
||||
|
||||
summary = await client.get_error_logs_summary(
|
||||
service_name="ollama",
|
||||
time_window_minutes=5,
|
||||
)
|
||||
|
||||
assert summary["total_errors"] == 2
|
||||
assert "FROM signoz_logs.distributed_logs_v2" in queries[0]
|
||||
assert "resources_string['service.name'] = 'ollama'" in queries[0]
|
||||
Reference in New Issue
Block a user