Files
audio2text/app/services/log_buffer.py
zikai 00e2a95fb7 Initial commit: audio2text 双语字幕生成服务
- 音频/视频转双语(英/中)SRT 字幕,Docker 容器化,CPU 开发/GPU 生产同一份代码
- faster-whisper ASR(词级时间戳) + 断句时间戳重算 + NLLB 翻译(模型不共驻)
- 分片上传(断点续传) + SQLite 持久化 + 主页/历史/日志页面
- 历史页文件名搜索;缓存定时清理(默认保留7天,可配置)
- 双 Dockerfile(cpu/gpu) + setup/start/stop 脚本
2026-07-06 06:54:19 +00:00

110 lines
3.6 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.

"""内存日志缓冲:捕获所有 audio2text.* 日志到有界 deque供 /logs 页面查询。
设计:
- MemoryLogHandler 挂到 `audio2text` logger子 logger 的记录经传播自动汇入。
- deque(maxlen=N) 有界,旧记录自动淘汰,避免内存无限增长。
- emit() 在 logging 内部锁下执行deque.append 线程安全。
- 查询时按级别过滤(≥指定级别)、取最近 N 条,返回结构化 dict。
"""
from __future__ import annotations
import logging
import threading
from collections import deque
from datetime import datetime, timezone
# 级别名 → 数值,用于过滤
_LEVELS = {
"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
}
class _BoundedLogRecord:
"""从 LogRecord 提取显示所需字段,避免持有完整对象引用。"""
__slots__ = ("ts", "level_no", "level_name", "logger_name", "message", "traceback")
def __init__(self, record: logging.LogRecord) -> None:
self.ts: float = record.created
self.level_no: int = record.levelno
self.level_name: str = record.levelname
self.logger_name: str = record.name
# getMessage() 应用 %-style 懒格式化
self.message: str = record.getMessage()
# exc_info 存的是 (type, value, tb) 三元组,格式化为字符串
self.traceback: str | None = None
if record.exc_info:
import traceback as _tb
self.traceback = "".join(_tb.format_exception(*record.exc_info))
class MemoryLogHandler(logging.Handler):
"""把日志记录存入有界 deque供 /api/logs 查询。"""
def __init__(self, buffer_size: int = 2000) -> None:
super().__init__()
self._buffer: deque[_BoundedLogRecord] = deque(maxlen=buffer_size)
self._lock = threading.Lock()
def emit(self, record: logging.LogRecord) -> None:
try:
entry = _BoundedLogRecord(record)
with self._lock:
self._buffer.append(entry)
except Exception: # pragma: no cover —— logging 不应抛
self.handleError(record)
def get_records(
self, level: str = "info", tail: int = 200,
) -> list[dict]:
"""返回最近 tail 条、≥指定级别的日志JSON 友好的 dict 列表)。
Args:
level: debug | info | warning | error最小级别
tail: 最多返回条数
"""
min_level = _LEVELS.get(level.lower(), logging.INFO)
with self._lock:
snapshot = list(self._buffer)
# 过滤 + 取最近 tail 条
filtered = [r for r in snapshot if r.level_no >= min_level]
result = []
for r in filtered[-tail:]:
ts_str = datetime.fromtimestamp(r.ts, tz=timezone.utc).strftime(
"%Y-%m-%d %H:%M:%S"
)
result.append({
"ts": ts_str,
"level": r.level_name,
"logger": r.logger_name,
"msg": r.message,
"traceback": r.traceback,
})
return result
def clear(self) -> None:
with self._lock:
self._buffer.clear()
# 进程级单例
_buffer: MemoryLogHandler | None = None
def get_log_buffer() -> MemoryLogHandler:
global _buffer
if _buffer is None:
_buffer = MemoryLogHandler()
return _buffer
def init_log_buffer(buffer_size: int) -> MemoryLogHandler:
"""(重新)创建缓冲并返回,供 main.py 启动时按配置初始化。"""
global _buffer
_buffer = MemoryLogHandler(buffer_size=buffer_size)
return _buffer