Sanic 默认是通过 LOGGING来记录日志的,目前日志记录多半使用Loguru,且Loguru支持LOGGING接口,因此我们选择将LOGGING用Loguru替代。
以下代码兼容多workers,兼容sqlalchemy等,不会导致重复打印。
原理:注册InterceptHandler,接管日志打印。
根目录下的logger.py:
import logging
import sys
from typing import Dict, Any
from loguru import logger
LOG_FORMAT = '{time:YYYY-MM-DD HH:mm:ss} [{level}] {module}:{name}:{line} - {message}'
logger.add("info.log", filter=lambda record: "INFO" in record['level'].name, rotation="10 MB",
retention="3 days", level="INFO", format=LOG_FORMAT)
logger.add("trace.log", filter=lambda record: "TRACE" in record['level'].name, level="TRACE")
logger.add("debug.log", filter=lambda record: "DEBUG" in record['level'].name, rotation="10 MB",
retention="3 days", level="DEBUG", format=LOG_FORMAT)
logger.add("error.log", filter=lambda record: "ERROR" in record['level'].name, rotation="10 MB",
retention="1 days", level="ERROR", format=LOG_FORMAT)
S_LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov
version=1,
disable_existing_loggers=False,
loggers={
"sanic.root": {"level": "INFO", "handlers": ["console"], "propagate": False},
"sanic.error": {
"level": "INFO",
"handlers": ["error_console"],
"propagate": False,
"qualname": "sanic.error",
},
"sanic.access": {
"level": "INFO",
"handlers": ["access_console"],
"propagate": False,
"qualname": "sanic.access",
},
"sanic.server": {
"level": "INFO",
"handlers": ["console"],
"propagate": False,
"qualname": "sanic.server",
},
},
handlers={
"console": {
"class": "logger.InterceptHandler",
},
"error_console": {
"class": "logger.InterceptHandler",
},
"access_console": {
"class": "logger.InterceptHandler",
},
}
)
class InterceptHandler(logging.Handler):
def emit(self, record: logging.LogRecord):
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
def setup_log():
logging.root.handlers = [InterceptHandler()]
logging.root.setLevel("DEBUG")
for name in logging.root.manager.loggerDict.keys():
logging.getLogger(name).handlers = []
logging.getLogger(name).propagate = True
logger.configure(handlers=[{"sink": sys.stdout, "serialize": False}])
配置文件:
S_LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov
version=1,
disable_existing_loggers=False,
loggers={
"sanic.root": {"level": "INFO", "handlers": ["console"],"propagate": False},
"sanic.error": {
"level": "INFO",
"handlers": ["error_console"],
"propagate": False,
"qualname": "sanic.error",
},
"sanic.access": {
"level": "INFO",
"handlers": ["access_console"],
"propagate": False,
"qualname": "sanic.access",
},
"sanic.server": {
"level": "INFO",
"handlers": ["console"],
"propagate": False,
"qualname": "sanic.server",
},
},
handlers={
"console": {
"class": "logger.InterceptHandler",
},
"error_console": {
"class": "logger.InterceptHandler",
},
"access_console": {
"class": "logger.InterceptHandler",
},
}
)
说明,Access Log未作特别处理,感觉也用不到,如果你需要记录访问日志建议用别的方式实现,如nginx。
上面的handlers是我自己根目录下的logger.py,自己的路径自己替换下。
APP启动方式:
确保先setup_log,再创建sanic app,且要配置log_config。
setup_log()
app = Sanic('demo-app', log_config=S_LOGGING_CONFIG_DEFAULTS)
app内需要使用日志时,可以直接使用sanic.log下的logger,正常打印即可。