hy-app-monitor/monitors/yumi/service_logs.py

84 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
# 容器日志命令里需要安全拼接容器名。
import shlex
from typing import Any
from .compose_templates import container_name
from core.config import SERVICE_LOG_EXPORT_MAX_LINES, SERVICE_LOG_PREVIEW_LINES, flatten_services, load_hosts, utc_now
from .ssh_remote import run_host_script
def loggable_service_records() -> list[dict[str, Any]]:
# 当前日志查看只开放业务服务实例,不扩展到 nacos / mongo 这类非业务容器。
return [service for service in flatten_services(load_hosts()) if service.get("kind") in {"java", "golang"}]
def find_service_record(service_name: str, host_name: str) -> dict[str, Any]:
# 日志查看必须定位到单实例,避免多机部署时把不同节点的日志混到一起。
normalized_service = str(service_name or "").strip()
normalized_host = str(host_name or "").strip()
for service in loggable_service_records():
if service.get("service") == normalized_service and service.get("host") == normalized_host:
return service
raise KeyError(f"service instance not found: {normalized_service}@{normalized_host}")
def normalize_export_lines(raw_value: str | int | None) -> int:
# 导出条数允许用户自定义,但要限制上限,避免一次把过多日志塞进页面进程。
if raw_value in {None, ""}:
return SERVICE_LOG_PREVIEW_LINES
value = int(raw_value)
if value <= 0:
raise ValueError("lines must be greater than 0")
return min(value, SERVICE_LOG_EXPORT_MAX_LINES)
def fetch_service_log_text(service_name: str, host_name: str, lines: int) -> tuple[dict[str, Any], str]:
# 统一执行 docker logs并返回实例元数据给接口层复用。
service = find_service_record(service_name, host_name)
container = container_name(service["host"], service["service"])
script = f"""set -eu
docker logs --timestamps --tail {int(lines)} {shlex.quote(container)} 2>&1
"""
result = run_host_script(
service,
script,
command_name=f"service-log-{service['host']}-{service['service']}",
timeout_seconds=120,
)
if result.get("status") != "SUCCESS":
raise RuntimeError(str(result.get("output") or result.get("status") or "docker logs failed").strip())
return (
{
"service": service["service"],
"host": service["host"],
"ip": service["ip"],
"kind": service["kind"],
"container": container,
"updatedAt": utc_now(),
"lines": int(lines),
},
str(result.get("output") or ""),
)
def get_service_log_preview_payload(service_name: str, host_name: str) -> dict[str, Any]:
# 页面预览固定最近 1000 条,避免日志面板过重。
meta, content = fetch_service_log_text(service_name, host_name, SERVICE_LOG_PREVIEW_LINES)
return {
"ok": True,
**meta,
"content": content,
"previewLines": SERVICE_LOG_PREVIEW_LINES,
"exportMaxLines": SERVICE_LOG_EXPORT_MAX_LINES,
}
def get_service_log_export_payload(service_name: str, host_name: str, raw_lines: str | int | None) -> tuple[dict[str, Any], str]:
# 导出接口允许指定条数,但会按统一上限裁剪。
lines = normalize_export_lines(raw_lines)
meta, content = fetch_service_log_text(service_name, host_name, lines)
return meta, content