Files
Neo-ZQYY/apps/etl/connectors/feiqiu/utils/task_log_buffer.py

102 lines
3.1 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.
"""任务级日志缓冲区,收集单个任务的所有日志,任务完成后一次性输出。
解决多任务并行执行时日志行交叉混乱的问题:每个任务维护独立的缓冲区,
任务完成后将完整日志按时间顺序一次性输出到父 logger添加 [task_code] 前缀。
"""
from __future__ import annotations
import logging
import threading
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class LogEntry:
"""日志条目。"""
timestamp: datetime
level: int
task_code: str
message: str
class TaskLogBuffer:
"""任务级日志缓冲区,收集单个任务的所有日志,任务完成后一次性输出。
所有写入操作线程安全(内部使用 threading.Lock
"""
def __init__(self, task_code: str, parent_logger: logging.Logger) -> None:
"""初始化日志缓冲区。
Args:
task_code: 任务代码,用于日志前缀标识。
parent_logger: 父 loggerflush() 时日志输出的目标。
"""
self.task_code = task_code
self._parent = parent_logger
self._buffer: list[LogEntry] = []
self._lock = threading.Lock()
def log(self, level: int, message: str, *args: object) -> None:
"""线程安全地缓冲一条日志。
Args:
level: 日志级别(如 logging.INFO
message: 日志消息,支持 % 格式化。
*args: 格式化参数。
"""
formatted = message % args if args else message
entry = LogEntry(
timestamp=datetime.now(),
level=level,
task_code=self.task_code,
message=formatted,
)
with self._lock:
self._buffer.append(entry)
# ---- 便捷方法 ----
def debug(self, message: str, *args: object) -> None:
self.log(logging.DEBUG, message, *args)
def info(self, message: str, *args: object) -> None:
self.log(logging.INFO, message, *args)
def warning(self, message: str, *args: object) -> None:
self.log(logging.WARNING, message, *args)
def error(self, message: str, *args: object) -> None:
self.log(logging.ERROR, message, *args)
# ---- 输出 ----
def flush(self) -> list[LogEntry]:
"""将缓冲区内容按时间顺序一次性输出到父 logger并清空缓冲区。
输出时每条日志添加 [task_code] 前缀,保证日志归属可识别。
Returns:
按时间戳升序排列的日志条目列表(副本)。
"""
with self._lock:
entries = sorted(self._buffer, key=lambda e: e.timestamp)
for entry in entries:
self._parent.log(
entry.level,
"[%s] %s",
entry.task_code,
entry.message,
)
self._buffer.clear()
return list(entries)
@property
def entries(self) -> list[LogEntry]:
"""返回当前缓冲区条目的副本(用于测试/检查)。"""
with self._lock:
return list(self._buffer)