87 lines
2.4 KiB
Python
87 lines
2.4 KiB
Python
"""读取日志文件末尾 N 行(v1.1.18)。
|
||
|
||
按 level 过滤:默认 INFO+。适用于 RotatingFileHandler 落地的日志文件。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from pathlib import Path
|
||
|
||
from util.runtime_paths import get_logs_dir
|
||
|
||
|
||
DEFAULT_TAIL = 200
|
||
_LEVEL_ORDER = {
|
||
"DEBUG": 10, "INFO": 20, "WARNING": 30, "ERROR": 40, "CRITICAL": 50,
|
||
}
|
||
|
||
|
||
def _resolve_log_path() -> Path:
|
||
"""解析日志文件路径。
|
||
|
||
优先级:
|
||
1. LOG_FILE env(绝对路径直接用,相对路径走 get_logs_dir())
|
||
2. 默认 get_logs_dir() / LOG_FILE_DEFAULT_NAME
|
||
"""
|
||
raw = os.environ.get("LOG_FILE", "")
|
||
if raw:
|
||
p = Path(raw)
|
||
if not p.is_absolute():
|
||
p = get_logs_dir() / raw
|
||
return p
|
||
from util.constants import LOG_FILE_DEFAULT_NAME
|
||
return get_logs_dir() / LOG_FILE_DEFAULT_NAME
|
||
|
||
|
||
def tail_log_file(tail: int = DEFAULT_TAIL, min_level: str = "INFO") -> dict:
|
||
"""读日志文件末尾 tail 行,过滤 level >= min_level。
|
||
|
||
Returns:
|
||
{
|
||
"log_file": str,
|
||
"exists": bool,
|
||
"total_lines": int,
|
||
"returned_lines": int,
|
||
"lines": [str, ...],
|
||
"min_level": str,
|
||
}
|
||
"""
|
||
log_path = _resolve_log_path()
|
||
if not log_path.exists():
|
||
return {
|
||
"log_file": str(log_path),
|
||
"exists": False,
|
||
"total_lines": 0,
|
||
"returned_lines": 0,
|
||
"lines": [],
|
||
"min_level": min_level,
|
||
}
|
||
|
||
text = log_path.read_text(encoding="utf-8", errors="replace")
|
||
all_lines = text.splitlines()
|
||
last_n = all_lines[-tail:] if tail > 0 else all_lines
|
||
threshold = _LEVEL_ORDER.get(min_level.upper(), 20)
|
||
|
||
filtered: list[str] = []
|
||
for line in last_n:
|
||
# 格式: "2026-09-15 12:34:56 [alert-contract-expiring-hr] INFO message"
|
||
# 取第 4 个 token (INFO/DEBUG/...)
|
||
parts = line.split()
|
||
if len(parts) >= 4 and parts[1].count(":") >= 2 and parts[3] in _LEVEL_ORDER:
|
||
line_level = parts[3]
|
||
if _LEVEL_ORDER.get(line_level, 0) >= threshold:
|
||
filtered.append(line)
|
||
else:
|
||
# 非标准行 / 解析不到 level:保留
|
||
filtered.append(line)
|
||
|
||
return {
|
||
"log_file": str(log_path),
|
||
"exists": True,
|
||
"total_lines": len(all_lines),
|
||
"returned_lines": len(filtered),
|
||
"lines": filtered,
|
||
"min_level": min_level,
|
||
}
|