Полный трейс показал настоящую причину сбоя: падало не распознавание, а метрика separation_quality из 0.5.0 - она скармливала модели отпечатков реплики целиком, и на 190-секундной та не выдержала. Теперь берётся кусок из середины реплики, а сбой оценки лишь обнуляет её, не трогая расшифровку. Добавлен /v1/logs: последние записи журнала с фильтром по уровню, чтобы разбирать сбои без копирования консоли вручную. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
59 lines
2.1 KiB
Python
59 lines
2.1 KiB
Python
"""Кольцевой буфер последних записей журнала.
|
|
|
|
Нужен, чтобы смотреть логи через API, а не копировать их из окна консоли.
|
|
Хранится в памяти: файл на диске пришлось бы чистить, а история глубже
|
|
последних сотен строк для разбора сбоя не нужна.
|
|
"""
|
|
import logging
|
|
import threading
|
|
from collections import deque
|
|
|
|
__all__ = ["LogBuffer", "install"]
|
|
|
|
|
|
class LogBuffer(logging.Handler):
|
|
def __init__(self, capacity: int = 500):
|
|
super().__init__()
|
|
self._records: deque = deque(maxlen=capacity)
|
|
self._lock = threading.Lock()
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
try:
|
|
message = record.getMessage()
|
|
if record.exc_info:
|
|
message += "\n" + self.format(record).split("\n", 1)[-1]
|
|
except Exception: # noqa: BLE001 - журнал не должен падать сам
|
|
message = "не удалось разобрать запись журнала"
|
|
with self._lock:
|
|
self._records.append({
|
|
"time": record.created,
|
|
"level": record.levelname,
|
|
"logger": record.name,
|
|
"message": message,
|
|
})
|
|
|
|
def tail(self, limit: int = 100, level: str | None = None) -> list[dict]:
|
|
with self._lock:
|
|
rows = list(self._records)
|
|
if level:
|
|
wanted = level.upper()
|
|
rows = [r for r in rows if r["level"] == wanted]
|
|
return rows[-limit:]
|
|
|
|
|
|
_buffer: LogBuffer | None = None
|
|
|
|
|
|
def install(capacity: int = 500) -> LogBuffer:
|
|
"""Подключает буфер к корневому журналу. Повторный вызов вернёт тот же буфер."""
|
|
global _buffer
|
|
if _buffer is None:
|
|
_buffer = LogBuffer(capacity)
|
|
_buffer.setFormatter(logging.Formatter("%(message)s"))
|
|
logging.getLogger().addHandler(_buffer)
|
|
return _buffer
|
|
|
|
|
|
def get() -> LogBuffer:
|
|
return install()
|