84 lines
3.4 KiB
Python
84 lines
3.4 KiB
Python
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
|