场景简介
python的logging功能很强大,但是很热loguru同样迷人。一个是原配,一个是小三,家花还是野花?作为一个成熟的程序猿,我当然想要一切。我怎样才能不惊?logging使用loguru神不知鬼不觉接管Django后宫日志大权,就是穿着品如的衣服,还有爱丽的风骚【手动滑稽】。
ps:这个操作需要一定的编程基础,请忽略。
- 不改变任何代码或使用低侵入性代码logging的接口logging.info(),没有loguru包一样起飞
- 可读性添加不同类别的日志文件分类(debug、error、sql_debug),妈妈再也不用担心我不会看日志了。
loguru简介
简介个屁
代码原理
建一个handler代理,一切logging日志,这个handler(你可以理解为品如的衣服),其实是loguru在emit日志(有种NTR使用不同的日志管理器,可以通过一些简单的配置来实现上层接口的不变,除了loguru、colorlog、nb_log你甚至可以自己做一个,替换它。
- settings.py
# 指定日志的目录所在,如果不存在,则创建 LOG_ROOT = os.path.join(BASE_DIR, 'log') if not os.path.exists(LOG_ROOT): os.mkdir(LOG_ROOT) # 日志配置(基本跟原生的TimedRotatingFileHandler一样) LOGGING = { 'version': 1, 'disable_existing_loggers': True, 'formatters': { 'standard': { 'format': '[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] ' '[%(levelname)s]- %(message)s'}, 'simple': { # 简单格式 'format': '%(levelname)s %(message)s' }, }, 'handlers': { 'servers': { 'class': 'common.utils.log.InterceptTimedRotatingFileHandler', # 这条路径取决于你本地放在哪里(下面的路径log文件) 'filename': os.path.join(LOG_ROOT, 'srap.log'), 'when': "D", 'interval': 1
, 'backupCount' : 1 , 'formatter' : 'standard' , 'encoding' : 'utf-8' , } , 'db' : { 'class' : 'common.utils.log.InterceptTimedRotatingFileHandler' , # 这个路径看你本地放在哪里 'filename' : os .path .join (LOG_ROOT , 'srap_db.log' ) , 'when' : "D" , 'interval' : 1 , 'backupCount' : 1 , 'formatter' : 'standard' , 'encoding' : 'utf-8' , 'logging_levels' : [ 'debug' ] # 😒注意这里,这是 自定义类多了一个参数,因为我只想让db日志有debug文件,所以我只看sql,这个可以自己设置 } } , 'loggers' : { # Django全局绑定 'django' : { 'handlers' : [ 'servers' ] , 'propagate' : True , 'level' : "INFO" } , 'celery' : { 'handlers' : [ 'servers' ] , 'propagate' : False , 'level' : "INFO" } , 'django.db.backends' : { 'handlers' : [ 'db' ] , 'propagate' : False , 'level' : "DEBUG" } , 'django.request' : { 'handlers' : [ 'servers' ] , 'propagate' : False , 'level' : "DEBUG" } , } }
- log.py
import logging
import os.path
from loguru import logger
# 1.🎖️先声明一个类继承logging.Handler(制作一件品如的衣服)
class InterceptTimedRotatingFileHandler(logging.Handler):
""" 自定义反射时间回滚日志记录器 缺少命名空间 """
def __init__(self, filename, when='d', interval=1, backupCount=15, encoding="utf-8", delay=False, utc=False,
atTime=None, logging_levels="all"):
super(InterceptTimedRotatingFileHandler, self).__init__()
filename = os.path.abspath(filename)
when = when.lower()
# 2.🎖️需要本地用不同的文件名做为不同日志的筛选器
self.logger_ = logger.bind(sime=filename)
self.filename = filename
key_map = {
'h': 'hour',
'w': 'week',
's': 'second',
'm': 'minute',
'd': 'day',
}
# 根据输入文件格式及时间回滚设立文件名称
rotation = "%d %s" % (interval, key_map[when])
retention = "%d %ss" % (backupCount, key_map[when])
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
if when == "s":
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
elif when == "m":
time_format = "{time:%Y-%m-%d_%H-%M}"
elif when == "h":
time_format = "{time:%Y-%m-%d_%H}"
elif when == "d":
time_format = "{time:%Y-%m-%d}"
elif when == "w":
time_format = "{time:%Y-%m-%d}"
level_keys = ["info"]
# 3.🎖️构建一个筛选器
levels = {
"debug": lambda x: "DEBUG" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"error": lambda x: "ERROR" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"info": lambda x: "INFO" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"warning": lambda x: "WARNING" == x['level'].name.upper() and x['extra'].get('sime') == filename}
# 4. 🎖️根据输出构建筛选器
if isinstance(logging_levels, str):
if logging_levels.lower() == "all":
level_keys = levels.keys()
elif logging_levels.lower() in levels:
level_keys = [logging_levels]
elif isinstance(logging_levels, (list, tuple)):
level_keys = logging_levels
for k, f in {
_: levels[_] for _ in level_keys}.items():
# 5.🎖️为防止重复添加sink,而重复写入日志,需要判断是否已经装载了对应sink,防止其使用秘技:反复横跳。
filename_fmt = filename.replace(".log", "_%s_%s.log" % (time_format, k))
# noinspection PyUnresolvedReferences,PyProtectedMember
file_key = {
_._name: han_id for han_id, _ in self.logger_._core.handlers.items()}
filename_fmt_key = "'{}'".format(filename_fmt)
if filename_fmt_key in file_key:
continue
# self.logger_.remove(file_key[filename_fmt_key])
self.logger_.add(
filename_fmt,
retention=retention,
encoding=encoding,
level=self.level,
rotation=rotation,
compression="tar.gz", # 日志归档自行压缩文件
delay=delay,
enqueue=True,
filter=f
)
def emit(self, record):
try:
level = self.logger_.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
# 6.🎖️把当前帧的栈深度回到发生异常的堆栈深度,不然就是当前帧发生异常而无法回溯
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
self.logger_.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
附图一张。
-
db_debug.log
-
error.log
- info.log