From 5cb10a6d2d417d8af2a0f906cd2483f644ddf3a9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 18 May 2026 12:23:39 +0800 Subject: [PATCH] fix(mcp): enrich host log evidence params --- .../src/services/pre_decision_investigator.py | 15 ++++- apps/api/src/services/signoz_client.py | 17 +++--- .../tests/test_pre_decision_investigator.py | 3 + apps/api/tests/test_signoz_client_logs.py | 61 +++++++++++++++++++ 4 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 apps/api/tests/test_signoz_client_logs.py diff --git a/apps/api/src/services/pre_decision_investigator.py b/apps/api/src/services/pre_decision_investigator.py index 69ec45e8..c6ae6375 100644 --- a/apps/api/src/services/pre_decision_investigator.py +++ b/apps/api/src/services/pre_decision_investigator.py @@ -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 欄位 diff --git a/apps/api/src/services/signoz_client.py b/apps/api/src/services/signoz_client.py index 865792c8..3b1bbd26 100644 --- a/apps/api/src/services/signoz_client.py +++ b/apps/api/src/services/signoz_client.py @@ -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 diff --git a/apps/api/tests/test_pre_decision_investigator.py b/apps/api/tests/test_pre_decision_investigator.py index 4188d845..c4c2dd90 100644 --- a/apps/api/tests/test_pre_decision_investigator.py +++ b/apps/api/tests/test_pre_decision_investigator.py @@ -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 diff --git a/apps/api/tests/test_signoz_client_logs.py b/apps/api/tests/test_signoz_client_logs.py new file mode 100644 index 00000000..b7b1aba2 --- /dev/null +++ b/apps/api/tests/test_signoz_client_logs.py @@ -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]