analyze performance upgrade
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Logging helpers: per-run log files and consistent formatting."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import threading
|
||||
from typing import Iterator, Optional
|
||||
|
||||
LOG_FORMAT = "%(asctime)s %(levelname)-7s [%(name)s] %(message)s"
|
||||
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
# Loggers that describe a capture analysis end to end.
|
||||
ANALYSIS_LOGGERS = ("analyzer", "IoTAnalyzer.analyze")
|
||||
|
||||
|
||||
class _ThreadFilter(logging.Filter):
|
||||
"""Only pass records emitted by the thread that opened the run log."""
|
||||
|
||||
def __init__(self, thread_id: int):
|
||||
super().__init__()
|
||||
self.thread_id = thread_id
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
return record.thread == self.thread_id
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def run_log(path: Optional[str], level: int = logging.DEBUG) -> Iterator[Optional[logging.Handler]]:
|
||||
"""Write everything this thread logs about the analysis to ``path``.
|
||||
|
||||
Concurrent analyses in other threads do not leak into the file.
|
||||
"""
|
||||
if not path:
|
||||
yield None
|
||||
return
|
||||
handler: Optional[logging.FileHandler] = None
|
||||
try:
|
||||
handler = logging.FileHandler(path, mode="w", encoding="utf-8")
|
||||
except OSError as exc:
|
||||
logging.getLogger("analyzer").warning("Run log %s could not be opened: %s", path, exc)
|
||||
yield None
|
||||
return
|
||||
handler.setLevel(level)
|
||||
handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT))
|
||||
handler.addFilter(_ThreadFilter(threading.get_ident()))
|
||||
loggers = [logging.getLogger(n) for n in ANALYSIS_LOGGERS]
|
||||
previous = [(lg, lg.level) for lg in loggers]
|
||||
for lg in loggers:
|
||||
lg.addHandler(handler)
|
||||
if lg.level == logging.NOTSET or lg.level > level:
|
||||
lg.setLevel(level)
|
||||
try:
|
||||
yield handler
|
||||
finally:
|
||||
for lg, lvl in previous:
|
||||
lg.removeHandler(handler)
|
||||
lg.setLevel(lvl)
|
||||
handler.close()
|
||||
|
||||
|
||||
def configure_console(level: str = "INFO") -> None:
|
||||
"""Console logging for CLI use."""
|
||||
logging.basicConfig(level=getattr(logging, level.upper(), logging.INFO),
|
||||
format=LOG_FORMAT, datefmt=DATE_FORMAT)
|
||||
|
||||
|
||||
def tail(path: str, lines: int = 400, max_bytes: int = 256 * 1024) -> str:
|
||||
"""Return the last ``lines`` lines of a text file."""
|
||||
try:
|
||||
with open(path, "rb") as fh:
|
||||
fh.seek(0, 2)
|
||||
size = fh.tell()
|
||||
fh.seek(max(0, size - max_bytes))
|
||||
data = fh.read().decode("utf-8", "replace")
|
||||
except OSError:
|
||||
return ""
|
||||
return "\n".join(data.splitlines()[-lines:])
|
||||
Reference in New Issue
Block a user