Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"), README, CLI strings, and branch (coven → hack-house). Gitea repo renamed cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest + golden-vector test still green, binary is now `hack-house`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
127 lines
3.7 KiB
Python
127 lines
3.7 KiB
Python
import logging
|
|
|
|
from collections import defaultdict
|
|
from logging import LogRecord
|
|
from logging.handlers import QueueHandler
|
|
from multiprocessing import Manager
|
|
from queue import Empty, Full
|
|
from signal import SIGINT, SIGTERM
|
|
from signal import signal as signal_func
|
|
|
|
from sanic import Sanic
|
|
from sanic.log import logger as root_logger
|
|
from sanic.log import logger as server_logger
|
|
from sanic.logging.setup import setup_logging
|
|
|
|
from sanic_ext.extensions.logging.extractor import LoggingConfigExtractor
|
|
|
|
|
|
async def prepare_logger(app: Sanic, *_):
|
|
Logger.prepare(app)
|
|
|
|
|
|
async def setup_logger(app: Sanic, *_):
|
|
logger = Logger()
|
|
extractor = LoggingConfigExtractor()
|
|
for logger_name in app.config.LOGGERS:
|
|
extractor.add_logger(logging.getLogger(logger_name))
|
|
app.manager.manage(
|
|
"Logger",
|
|
logger,
|
|
{
|
|
"queue": app.shared_ctx.logger_queue,
|
|
"config": extractor.compile(),
|
|
},
|
|
transient=True,
|
|
)
|
|
|
|
|
|
class SanicQueueHandler(QueueHandler):
|
|
def emit(self, record: LogRecord) -> None:
|
|
try:
|
|
return super().enqueue(record)
|
|
except Full:
|
|
server_logger.warning(
|
|
"Background logger is full. Emitting log in process."
|
|
)
|
|
server_logger.handle(record)
|
|
|
|
|
|
async def setup_server_logging(app: Sanic):
|
|
qhandler = SanicQueueHandler(app.shared_ctx.logger_queue)
|
|
app.ctx._logger_handlers = defaultdict(list)
|
|
app.ctx._qhandler = qhandler
|
|
|
|
for logger_name in app.config.LOGGERS:
|
|
logger_instance = logging.getLogger(logger_name)
|
|
for handler in logger_instance.handlers:
|
|
logger_instance.removeHandler(handler)
|
|
logger_instance.addHandler(qhandler)
|
|
|
|
|
|
async def remove_server_logging(app: Sanic):
|
|
for logger, handlers in app.ctx._logger_handlers.items():
|
|
logger.removeHandler(app.ctx._qhandler)
|
|
for handler in handlers:
|
|
logger.addHandler(handler)
|
|
|
|
|
|
class Logger:
|
|
LOGGERS: list[str] = []
|
|
|
|
def __init__(self):
|
|
self.run = True
|
|
self.loggers = {
|
|
logger: logging.getLogger(logger) for logger in self.LOGGERS
|
|
}
|
|
|
|
def __call__(self, queue, config) -> None:
|
|
signal_func(SIGINT, self.stop)
|
|
signal_func(SIGTERM, self.stop)
|
|
|
|
logging.config.dictConfig(config)
|
|
|
|
setup_loggers = set(config["loggers"].keys())
|
|
enabled_loggers = set(self.loggers.keys())
|
|
missing = enabled_loggers - setup_loggers
|
|
root_logger.info(
|
|
f"Setup background logging for: {', '.join(setup_loggers)}"
|
|
)
|
|
if missing:
|
|
root_logger.warning(
|
|
f"Logger config not found for: {', '.join(missing)}"
|
|
)
|
|
setup_logging(True, no_color=False, log_extra=True)
|
|
|
|
while self.run:
|
|
try:
|
|
record: LogRecord = queue.get(timeout=0.05)
|
|
except Empty:
|
|
continue
|
|
logger = self.loggers.get(record.name)
|
|
logger.handle(record)
|
|
|
|
def stop(self, *_):
|
|
if self.run:
|
|
self.run = False
|
|
|
|
@classmethod
|
|
def update_cls_loggers(cls, logger_names: list[str]):
|
|
cls.LOGGERS = logger_names
|
|
|
|
@classmethod
|
|
def prepare(cls, app: Sanic):
|
|
sync_manager = Manager()
|
|
logger_queue = sync_manager.Queue(
|
|
maxsize=app.config.LOGGING_QUEUE_MAX_SIZE
|
|
)
|
|
app.shared_ctx.logger_queue = logger_queue
|
|
cls.update_cls_loggers(app.config.LOGGERS)
|
|
|
|
@classmethod
|
|
def setup(cls, app: Sanic):
|
|
app.main_process_start(prepare_logger)
|
|
app.main_process_ready(setup_logger)
|
|
app.before_server_start(setup_server_logging)
|
|
app.before_server_stop(remove_server_logging)
|