Skip to content

Commit 3b060c7

Browse files
authored
Refactor global log default handler (#347)
* 🦄 refactor: optimize log output Unified log output using the loguru logging library * 🌈 style: pre-commit fix * 🌈 style: pre-commit fix * Clean up and update some code * Update default log output style
1 parent 85c16de commit 3b060c7

File tree

4 files changed

+113
-46
lines changed

4 files changed

+113
-46
lines changed

backend/common/log.py

Lines changed: 93 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,103 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
3-
from __future__ import annotations
4-
3+
import inspect
4+
import logging
55
import os
66

7-
from typing import TYPE_CHECKING
7+
from sys import stderr, stdout
88

99
from loguru import logger
1010

1111
from backend.core import path_conf
1212
from backend.core.conf import settings
1313

14-
if TYPE_CHECKING:
15-
import loguru
16-
17-
18-
class Logger:
19-
def __init__(self):
20-
self.log_path = path_conf.LOG_DIR
21-
22-
def log(self) -> loguru.Logger:
23-
if not os.path.exists(self.log_path):
24-
os.mkdir(self.log_path)
25-
26-
# 日志文件
27-
log_stdout_file = os.path.join(self.log_path, settings.LOG_STDOUT_FILENAME)
28-
log_stderr_file = os.path.join(self.log_path, settings.LOG_STDERR_FILENAME)
29-
30-
# loguru 日志: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add
31-
log_config = dict(rotation='10 MB', retention='15 days', compression='tar.gz', enqueue=True)
32-
# stdout
33-
logger.add(
34-
log_stdout_file,
35-
level='INFO',
36-
filter=lambda record: record['level'].name == 'INFO' or record['level'].no <= 25,
37-
**log_config,
38-
backtrace=False,
39-
diagnose=False,
40-
)
41-
# stderr
42-
logger.add(
43-
log_stderr_file,
44-
level='ERROR',
45-
filter=lambda record: record['level'].name == 'ERROR' or record['level'].no >= 30,
46-
**log_config,
47-
backtrace=True,
48-
diagnose=True,
49-
)
50-
51-
return logger
52-
53-
54-
log = Logger().log()
14+
15+
class InterceptHandler(logging.Handler):
16+
"""
17+
Default handler from examples in loguru documentation.
18+
See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
19+
"""
20+
21+
def emit(self, record: logging.LogRecord):
22+
# Get corresponding Loguru level if it exists
23+
try:
24+
level = logger.level(record.levelname).name
25+
except ValueError:
26+
level = record.levelno
27+
28+
# Find caller from where originated the logged message.
29+
frame, depth = inspect.currentframe(), 0
30+
while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__):
31+
frame = frame.f_back
32+
depth += 1
33+
34+
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
35+
36+
37+
def setup_logging():
38+
"""
39+
From https://pawamoy.github.io/posts/unify-logging-for-a-gunicorn-uvicorn-app/
40+
https://github.com/pawamoy/pawamoy.github.io/issues/17
41+
"""
42+
# Intercept everything at the root logger
43+
logging.root.handlers = [InterceptHandler()]
44+
logging.root.setLevel(settings.LOG_LEVEL)
45+
46+
# Remove all log handlers and propagate to root logger
47+
for name in logging.root.manager.loggerDict.keys():
48+
logging.getLogger(name).handlers = []
49+
if 'uvicorn.access' in name or 'watchfiles.main' in name:
50+
logging.getLogger(name).propagate = False
51+
else:
52+
logging.getLogger(name).propagate = True
53+
54+
logging.debug(f'{logging.getLogger(name)}, {logging.getLogger(name).propagate}')
55+
56+
# Remove every other logger's handlers
57+
logger.remove()
58+
59+
# Configure logger before starts logging
60+
logger.configure(handlers=[{'sink': stdout, 'level': settings.LOG_LEVEL, 'format': settings.LOG_FORMAT}])
61+
logger.configure(handlers=[{'sink': stderr, 'level': settings.LOG_LEVEL, 'format': settings.LOG_FORMAT}])
62+
63+
64+
def set_customize_logfile():
65+
log_path = path_conf.LOG_DIR
66+
if not os.path.exists(log_path):
67+
os.mkdir(log_path)
68+
69+
# log files
70+
log_stdout_file = os.path.join(log_path, settings.LOG_STDOUT_FILENAME)
71+
log_stderr_file = os.path.join(log_path, settings.LOG_STDERR_FILENAME)
72+
73+
# loguru logger: https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger.add
74+
log_config = {
75+
'rotation': '10 MB',
76+
'retention': '15 days',
77+
'compression': 'tar.gz',
78+
'enqueue': True,
79+
'format': settings.LOG_FORMAT,
80+
}
81+
82+
# stdout
83+
logger.add(
84+
log_stdout_file,
85+
level='INFO',
86+
filter=lambda record: record['level'].name == 'INFO' or record['level'].no <= 25,
87+
**log_config,
88+
backtrace=False,
89+
diagnose=False,
90+
)
91+
92+
# stderr
93+
logger.add(
94+
log_stderr_file,
95+
level='ERROR',
96+
filter=lambda record: record['level'].name == 'ERROR' or record['level'].no >= 30,
97+
**log_config,
98+
backtrace=True,
99+
diagnose=True,
100+
)
101+
102+
103+
log = logger

backend/core/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ def validate_openapi_url(cls, values):
9292
]
9393

9494
# Log
95+
LOG_LEVEL: str = 'INFO'
96+
LOG_FORMAT: str = '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</> | <lvl>{level: <8}</> | <lvl>{message}</>'
9597
LOG_STDOUT_FILENAME: str = 'fba_access.log'
9698
LOG_STDERR_FILENAME: str = 'fba_error.log'
9799

backend/core/registrar.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from backend.app.router import route
1111
from backend.common.exception.exception_handler import register_exception
12+
from backend.common.log import set_customize_logfile, setup_logging
1213
from backend.core.conf import settings
1314
from backend.core.path_conf import STATIC_DIR
1415
from backend.database.db_mysql import create_table
@@ -56,6 +57,9 @@ def register_app():
5657
lifespan=register_init,
5758
)
5859

60+
# 日志
61+
register_logger()
62+
5963
# 静态文件
6064
register_static_file(app)
6165

@@ -74,6 +78,16 @@ def register_app():
7478
return app
7579

7680

81+
def register_logger() -> None:
82+
"""
83+
系统日志
84+
85+
:return:
86+
"""
87+
setup_logging()
88+
set_customize_logfile()
89+
90+
7791
def register_static_file(app: FastAPI):
7892
"""
7993
静态文件交互开发模式, 生产使用 nginx 静态资源服务

backend/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
if __name__ == '__main__':
1313
# 如果你喜欢在 IDE 中进行 DEBUG,main 启动方法会很有帮助
14-
# 如果你喜欢通过日志方式进行调试,建议使用 fastapi cli 方式启动服务
14+
# 如果你喜欢通过 print 方式进行调试,建议使用 fastapi cli 方式启动服务
1515
try:
16-
uvicorn.run(app=f'{Path(__file__).stem}:app', reload=True)
16+
config = uvicorn.Config(app=f'{Path(__file__).stem}:app', reload=True)
17+
server = uvicorn.Server(config)
18+
server.run()
1719
except Exception as e:
1820
raise e

0 commit comments

Comments
 (0)