From 4804df5079ffbb58d58b3960ae6d1d349128fd4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com>
Date: Tue, 24 Jan 2023 20:43:15 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD:=20=E5=AE=8C?=
=?UTF-8?q?=E5=96=84=E6=97=A5=E5=BF=97=E4=BF=A1=E6=81=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/application/settings.py | 98 ++++++++-----------
backend/docker_start.sh | 2 +-
backend/dvadmin/utils/exception.py | 2 +-
backend/dvadmin/utils/log.py | 112 ++++++++++++++++++++++
backend/{gunicorn.py => gunicorn_conf.py} | 2 +-
backend/requirements.txt | 1 +
6 files changed, 158 insertions(+), 59 deletions(-)
create mode 100644 backend/dvadmin/utils/log.py
rename backend/{gunicorn.py => gunicorn_conf.py} (95%)
diff --git a/backend/application/settings.py b/backend/application/settings.py
index ccc05de..d4161d6 100644
--- a/backend/application/settings.py
+++ b/backend/application/settings.py
@@ -93,7 +93,6 @@ TEMPLATES = [
WSGI_APPLICATION = "application.wsgi.application"
-
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
@@ -190,80 +189,67 @@ CHANNEL_LAYERS = {
# ================================================= #
# log 配置部分BEGIN #
-SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
-ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
+LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, "logs"))
-# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
-# 格式:[日期][模块.函数名称():行号] [级别] 信息
-STANDARD_LOG_FORMAT = (
- "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
-)
-CONSOLE_LOG_FORMAT = (
- "[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
-)
-
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
- "standard": {"format": STANDARD_LOG_FORMAT},
- "console": {
- "format": CONSOLE_LOG_FORMAT,
+ "servers": {
+ "format": "%(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
- },
- "file": {
- "format": CONSOLE_LOG_FORMAT,
- "datefmt": "%Y-%m-%d %H:%M:%S",
- },
+ }
},
"handlers": {
- "file": {
- "level": "INFO",
- "class": "logging.handlers.RotatingFileHandler",
- "filename": SERVER_LOGS_FILE,
+ "servers": {
+ "logging_levels": ["info", "error", "warning"],
+ "class": "dvadmin.utils.log.InterceptTimedRotatingFileHandler", # 这个路径看你本地放在哪里(下面的log文件)
+ "filename": os.path.join(LOGS_FILE, "server.log"),
+ "when": "D",
+ "interval": 1,
"maxBytes": 1024 * 1024 * 100, # 100 MB
- "backupCount": 5, # 最多备份5个
- "formatter": "standard",
+ "backupCount": 1,
+ "formatter": "servers",
"encoding": "utf-8",
- },
- "error": {
- "level": "ERROR",
- "class": "logging.handlers.RotatingFileHandler",
- "filename": ERROR_LOGS_FILE,
- "maxBytes": 1024 * 1024 * 100, # 100 MB
- "backupCount": 3, # 最多备份3个
- "formatter": "standard",
- "encoding": "utf-8",
- },
- "console": {
- "level": "INFO",
- "class": "logging.StreamHandler",
- "formatter": "console",
- },
+ }
},
"loggers": {
# default日志
- "": {
- "handlers": ["console", "error", "file"],
- "level": "INFO",
+ 'django': {
+ 'handlers': ['servers'],
+ 'propagate': False,
+ 'level': "INFO"
},
- "django": {
- "handlers": ["console", "error", "file"],
- "level": "INFO",
- "propagate": False,
+ '': {
+ 'handlers': ['servers'],
+ 'propagate': False,
+ 'level': "ERROR"
},
- "scripts": {
- "handlers": ["console", "error", "file"],
- "level": "INFO",
- "propagate": False,
+ 'celery': {
+ 'handlers': ['servers'],
+ 'propagate': False,
+ 'level': "INFO"
},
- # 数据库相关日志
- "django.db.backends": {
- "handlers": [],
- "propagate": True,
+ 'django.db.backends': {
+ 'handlers': ['servers'],
+ 'propagate': False,
+ 'level': "INFO"
+ },
+ 'django.request': {
+ 'handlers': ['servers'],
+ 'propagate': False,
+ 'level': "DEBUG"
+ },
+ "uvicorn.error": {
"level": "INFO",
+ "handlers": ["servers"],
+ },
+ "uvicorn.access": {
+ "handlers": ["servers"],
+ "level": "INFO",
+ "propagate": False
},
},
}
diff --git a/backend/docker_start.sh b/backend/docker_start.sh
index 6042fe6..1285d98 100755
--- a/backend/docker_start.sh
+++ b/backend/docker_start.sh
@@ -2,4 +2,4 @@
# python manage.py makemigrations
# python manage.py migrate
# python manage.py init -y
-gunicorn -c gunicorn.py application.asgi:application
+gunicorn -c gunicorn_conf.py application.asgi:application
diff --git a/backend/dvadmin/utils/exception.py b/backend/dvadmin/utils/exception.py
index a6996df..394814f 100644
--- a/backend/dvadmin/utils/exception.py
+++ b/backend/dvadmin/utils/exception.py
@@ -51,6 +51,6 @@ def CustomExceptionHandler(ex, context):
# set_rollback()
# msg = "接口服务器异常,请联系管理员"
elif isinstance(ex, Exception):
- logger.error(traceback.format_exc())
+ logger.exception(traceback.format_exc())
msg = str(ex)
return ErrorResponse(msg=msg, code=code)
diff --git a/backend/dvadmin/utils/log.py b/backend/dvadmin/utils/log.py
new file mode 100644
index 0000000..e8081f4
--- /dev/null
+++ b/backend/dvadmin/utils/log.py
@@ -0,0 +1,112 @@
+import logging
+import os.path
+from logging import LogRecord
+
+from django.core.servers.basehttp import WSGIRequestHandler
+from loguru import logger
+
+from logging.handlers import RotatingFileHandler
+
+# 1.🎖️先声明一个类继承logging.Handler(制作一件品如的衣服)
+from loguru._defaults import LOGURU_FORMAT
+
+
+class InterceptTimedRotatingFileHandler(RotatingFileHandler):
+ """
+ 自定义反射时间回滚日志记录器
+ 缺少命名空间
+ """
+
+ def __init__(self, filename, when='d', interval=1, backupCount=5, encoding="utf-8", delay=False, utc=False,
+ maxBytes=1024 * 1024 * 100, atTime=None, logging_levels="all"):
+ super(InterceptTimedRotatingFileHandler, self).__init__(filename)
+ filename = os.path.abspath(filename)
+ when = when.lower()
+ # 2.🎖️需要本地用不同的文件名做为不同日志的筛选器
+ self.logger_ = logger.bind(sime=filename, ip="-", port="-", username="张三")
+ self.filename = filename
+ key_map = {
+ 'h': 'hour',
+ 'w': 'week',
+ 's': 'second',
+ 'm': 'minute',
+ 'd': 'day',
+ }
+ # 根据输入文件格式及时间回滚设立文件名称
+ rotation = f"{maxBytes / 1024 / 1024}MB"
+ 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,
+ # format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {extra[ip]}:{extra[port]} | {level: <8}| {name}:{function}:{line} - {message}",
+ retention=retention,
+ encoding=encoding,
+ level=self.level,
+ rotation=rotation,
+ compression="zip", # 日志归档自行压缩文件
+ delay=delay,
+ enqueue=True,
+ backtrace=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
+ # 设置自定义属性
+ port = "-"
+ ip = "-"
+ locals_self = frame.f_locals.get('self', None)
+ msg = self.format(record)
+ if locals_self and hasattr(locals_self, 'client_address'):
+ ip, port = locals_self.client_address
+ # - 127.0.0.1:56525 -
+ msg = f"{ip}:{port} - {msg}"
+ self.logger_ \
+ .opt(depth=depth, exception=record.exc_info, colors=True) \
+ .bind(ip=ip, port=port) \
+ .log(level, msg)
diff --git a/backend/gunicorn.py b/backend/gunicorn_conf.py
similarity index 95%
rename from backend/gunicorn.py
rename to backend/gunicorn_conf.py
index 1772fbc..ca2fb32 100644
--- a/backend/gunicorn.py
+++ b/backend/gunicorn_conf.py
@@ -23,7 +23,7 @@ pidfile = './gunicorn.pid'
# 日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别无法设置
loglevel = 'info'
# 设置gunicorn访问日志格式,错误日志无法设置
-access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'
+access_log_format = '' # worker_class 为 uvicorn.workers.UvicornWorker 时,日志格式为Django的loggers
# 监听队列
backlog = 512
#进程名
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 64fee45..a9f8c3b 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -15,6 +15,7 @@ ua-parser==0.16.1
pyparsing==3.0.9
openpyxl==3.0.10
requests==2.28.2
+loguru==0.6.0
typing-extensions==4.4.0
smmap==5.0.0
tzlocal==4.1