diff --git a/app/api/health.py b/app/api/health.py index 3a59010..04ff174 100644 --- a/app/api/health.py +++ b/app/api/health.py @@ -1,4 +1,3 @@ -import logging from typing import Annotated from fastapi import APIRouter, Depends, Query, Request, status @@ -34,7 +33,7 @@ async def redis_check(request: Request): try: redis_info = await redis_client.info() except Exception as e: - logging.error(f"Redis error: {e}") + await logger.aerror(f"Redis error: {e}") return redis_info @@ -88,7 +87,7 @@ async def smtp_check( "subject": subject, } - logger.info("Sending email with data: %s", email_data) + await logger.info("Sending email.", email_data=email_data) await run_in_threadpool( smtp.send_email, diff --git a/app/utils/logging.py b/app/utils/logging.py index 7ba940a..e153a8c 100644 --- a/app/utils/logging.py +++ b/app/utils/logging.py @@ -10,25 +10,60 @@ from whenever._whenever import Instant from app.utils.singleton import SingletonMetaNoArgs +class RotatingBytesLogger: + """Logger that respects RotatingFileHandler's rotation capabilities.""" -# TODO: merge this wrapper with the one in structlog under one hood of AppLogger -class BytesToTextIOWrapper: - def __init__(self, handler, encoding="utf-8"): + def __init__(self, handler): self.handler = handler - self.encoding = encoding - def write(self, b): - if isinstance(b, bytes): - self.handler.stream.write(b.decode(self.encoding)) - else: - self.handler.stream.write(b) - self.handler.flush() + def msg(self, message): + """Process a message and pass it through the handler's emit method.""" + if isinstance(message, bytes): + message = message.decode("utf-8") - def flush(self): - self.handler.flush() + # Create a log record that will trigger rotation checks + record = logging.LogRecord( + name="structlog", + level=logging.INFO, + pathname="", + lineno=0, + msg=message.rstrip("\n"), + args=(), + exc_info=None + ) - def close(self): - self.handler.close() + # Check if rotation is needed before emitting + if self.handler.shouldRollover(record): + self.handler.doRollover() + + # Emit the record through the handler + self.handler.emit(record) + + # Required methods to make it compatible with structlog + def debug(self, message): + self.msg(message) + + def info(self, message): + self.msg(message) + + def warning(self, message): + self.msg(message) + + def error(self, message): + self.msg(message) + + def critical(self, message): + self.msg(message) + + +class RotatingBytesLoggerFactory: + """Factory that creates loggers that respect file rotation.""" + + def __init__(self, handler): + self.handler = handler + + def __call__(self, *args, **kwargs): + return RotatingBytesLogger(self.handler) @define(slots=True) @@ -40,8 +75,7 @@ class AppStructLogger(metaclass=SingletonMetaNoArgs): _log_path = Path(f"{_log_date}_{os.getpid()}.log") _handler = RotatingFileHandler( filename=_log_path, - mode="a", - maxBytes=10 * 1024 * 1024, + maxBytes=1000, backupCount=5, encoding="utf-8" ) @@ -55,11 +89,9 @@ class AppStructLogger(metaclass=SingletonMetaNoArgs): structlog.processors.TimeStamper(fmt="iso", utc=True), structlog.processors.JSONRenderer(serializer=orjson.dumps), ], - logger_factory=structlog.BytesLoggerFactory( - file=BytesToTextIOWrapper(_handler) - ) + logger_factory=RotatingBytesLoggerFactory(_handler) ) self._logger = structlog.get_logger() def get_logger(self) -> structlog.BoundLogger: - return self._logger + return self._logger \ No newline at end of file