Skip to content

Commit 6ec8a3c

Browse files
committed
wip: add RotatingBytesLogger
1 parent 8e7692b commit 6ec8a3c

File tree

2 files changed

+54
-23
lines changed

2 files changed

+54
-23
lines changed

app/api/health.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import logging
21
from typing import Annotated
32

43
from fastapi import APIRouter, Depends, Query, Request, status
@@ -34,7 +33,7 @@ async def redis_check(request: Request):
3433
try:
3534
redis_info = await redis_client.info()
3635
except Exception as e:
37-
logging.error(f"Redis error: {e}")
36+
await logger.aerror(f"Redis error: {e}")
3837
return redis_info
3938

4039

@@ -88,7 +87,7 @@ async def smtp_check(
8887
"subject": subject,
8988
}
9089

91-
logger.info("Sending email with data: %s", email_data)
90+
await logger.info("Sending email.", email_data=email_data)
9291

9392
await run_in_threadpool(
9493
smtp.send_email,

app/utils/logging.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,60 @@
1010

1111
from app.utils.singleton import SingletonMetaNoArgs
1212

13+
class RotatingBytesLogger:
14+
"""Logger that respects RotatingFileHandler's rotation capabilities."""
1315

14-
# TODO: merge this wrapper with the one in structlog under one hood of AppLogger
15-
class BytesToTextIOWrapper:
16-
def __init__(self, handler, encoding="utf-8"):
16+
def __init__(self, handler):
1717
self.handler = handler
18-
self.encoding = encoding
1918

20-
def write(self, b):
21-
if isinstance(b, bytes):
22-
self.handler.stream.write(b.decode(self.encoding))
23-
else:
24-
self.handler.stream.write(b)
25-
self.handler.flush()
19+
def msg(self, message):
20+
"""Process a message and pass it through the handler's emit method."""
21+
if isinstance(message, bytes):
22+
message = message.decode("utf-8")
2623

27-
def flush(self):
28-
self.handler.flush()
24+
# Create a log record that will trigger rotation checks
25+
record = logging.LogRecord(
26+
name="structlog",
27+
level=logging.INFO,
28+
pathname="",
29+
lineno=0,
30+
msg=message.rstrip("\n"),
31+
args=(),
32+
exc_info=None
33+
)
34+
35+
# Check if rotation is needed before emitting
36+
if self.handler.shouldRollover(record):
37+
self.handler.doRollover()
38+
39+
# Emit the record through the handler
40+
self.handler.emit(record)
41+
42+
# Required methods to make it compatible with structlog
43+
def debug(self, message):
44+
self.msg(message)
45+
46+
def info(self, message):
47+
self.msg(message)
48+
49+
def warning(self, message):
50+
self.msg(message)
51+
52+
def error(self, message):
53+
self.msg(message)
54+
55+
def critical(self, message):
56+
self.msg(message)
57+
58+
59+
class RotatingBytesLoggerFactory:
60+
"""Factory that creates loggers that respect file rotation."""
61+
62+
def __init__(self, handler):
63+
self.handler = handler
2964

30-
def close(self):
31-
self.handler.close()
65+
def __call__(self, *args, **kwargs):
66+
return RotatingBytesLogger(self.handler)
3267

3368

3469
@define(slots=True)
@@ -40,8 +75,7 @@ def __attrs_post_init__(self):
4075
_log_path = Path(f"{_log_date}_{os.getpid()}.log")
4176
_handler = RotatingFileHandler(
4277
filename=_log_path,
43-
mode="a",
44-
maxBytes=10 * 1024 * 1024,
78+
maxBytes=1000,
4579
backupCount=5,
4680
encoding="utf-8"
4781
)
@@ -55,11 +89,9 @@ def __attrs_post_init__(self):
5589
structlog.processors.TimeStamper(fmt="iso", utc=True),
5690
structlog.processors.JSONRenderer(serializer=orjson.dumps),
5791
],
58-
logger_factory=structlog.BytesLoggerFactory(
59-
file=BytesToTextIOWrapper(_handler)
60-
)
92+
logger_factory=RotatingBytesLoggerFactory(_handler)
6193
)
6294
self._logger = structlog.get_logger()
6395

6496
def get_logger(self) -> structlog.BoundLogger:
65-
return self._logger
97+
return self._logger

0 commit comments

Comments
 (0)