Как изящно войти в Django

Python Django

Технический блог:GitHub.com/Делайте это с душой/Особые…

В то же время, вы также можете обратить внимание на мой публичный аккаунт WeChat.AlwaysBeta, вас ждет еще больше захватывающего контента.

Логи вещь хорошая, но не все готовы их вести, о них не жалеют, пока что-то не пойдет не так.

Но ведение дневника — это техническая работа, вы не можете запомнить все, но вы не можете запомнить все. Если вы будете запоминать много бесполезной информации, это добавит массу сложностей в процесс проверки логов и устранения неполадок.

Поэтому лог должен быть записан на ключевых узлах программы, а содержание должно быть лаконичным, а информация точной. Необходимо четко отражать состояние, время, сообщение об ошибке и т.д. программы в этот момент.

Только так мы сможем найти проблему и решить ее с первого раза.

структура регистрации

Используйте модуль ведения журнала стандартной библиотеки Python для записи журналов в Django.Я не буду здесь слишком много рассказывать о конфигурации ведения журнала, а только напишу четыре наиболее важные части:Loggers,Handlers,FiltersиFormatters.

Loggers

LoggerкоторыйРекордер, является записью лог-системы. У него есть три важные задачи:

  • Предоставьте приложению (также известному как ваш проект) несколько методов для регистрации сообщений во время выполнения.
  • Согласно переходу наLoggerсерьезность сообщения, чтобы определить, нужно ли обрабатывать сообщение
  • Передайте сообщение, которое необходимо обработать, всем заинтересованным процессорамHandler

каждый пишетLoggerСообщение является записью журнала, и каждая запись журнала содержит уровень, который представляет серьезность соответствующего сообщения. Обычно используются следующие уровни:

  • DEBUG: системная информация низкого уровня, используемая для устранения неполадок, обычно используемая во время разработки.
  • INFO: Общая системная информация, не проблема
  • WARNING: информация, описывающая незначительную проблему с системой, но обычно не влияющая на функциональность.
  • ERROR: Информация, описывающая серьезную проблему с системой, которая может привести к неисправности.
  • CRITICAL: информация, описывающая серьезную проблему с системой, приложение может аварийно завершить работу.

когдаLoggerПри обработке сообщения он сравнивает свой уровень журнала с уровнем, настроенным для сообщения. Если уровень сообщения соответствует или превышаетLoggerlog, оно будет обработано дальше, иначе сообщение будет проигнорировано.

когдаLoggerПосле определения того, что сообщение необходимо обработать, оно будет переданоHandler.

Handlers

Handlerкоторыйпроцессор, его основная функция состоит в том, чтобы решить, что делать сLoggerКаждое сообщение в , например вывод сообщения на экран, в файл или по электронной почте.

иLoggerТакой же,HandlerСуществует также понятие уровней. Если уровень записи журнала не соответствует или нижеHandlerуровень журнала, это будетHandlerпренебрегать.

ОдинLoggerможет иметь несколькоHandler,КаждыйHandlerМогут быть разные уровни логирования. Это позволяет предоставлять различные типы вывода в зависимости от важности сообщения. Например, вы можете добавитьHandlerПучокERRORиCRITICALсообщение на ваш Email, добавьте ещеHandlerположить все сообщения (включаяERRORиCRITICALсообщение) в файл.

Filters

Filterкоторыйфильтр. в журнале изLoggerраспространиться наHandlerпроцесс, используяFilterдля дополнительного контроля. Например, разрешить толькоERRORвывод сообщения.

FilterТакже используется для изменения записей журнала перед их выходом из системы. Например, при выполнении определенных условий изменить уровень журнала сERRORвплоть доWARNING.

FilterсуществуетLoggerиHandlerможно добавить несколькоFilterМожно объединить в цепочку для выполнения нескольких операций фильтрации.

Formaters

Formatterкоторыйформатер, основной функцией которого является определение формы и содержания конечного вывода.

Метод реализации

Сказав так много теорий, пришло время посмотреть, как это реализовано.

На самом деле проще всего прямо в начале файлаimport, а затем вызывается в программе следующим образом:

# import the logging library
import logging

# Get an instance of a logger
logging.basicConfig(
    format='%(asctime)s - %(pathname)s[%(lineno)d] - %(levelname)s: %(message)s',
    level=logging.INFO)
logger = logging.getLogger(__name__)

def my_view(request, arg1, arg):
    ...
    if bad_mojo:
        # Log an error message
        logger.error('Something went wrong!')

Но этот способ не годится.Если так писать в начале каждого файла, то первое беда, а второе то, что если вы хотите однажды изменить формат лога вывода, то каждый файл надо менять заново, и он не исчерпан.

Очевидно, что если вы можете инкапсулировать его в класс, вызывать этот класс, когда используете его, и вам нужно будет только изменить это место, когда вы его модифицируете, будет ли решена эта проблема?

пользовательский класс

Давайте посмотрим на то, как конкретная категория пакет:

class CommonLog(object):
    """
    日志记录
    """
    def __init__(self, logger, logname='web-log'):
        self.logname = os.path.join(settings.LOGS_DIR, '%s' % logname)
        self.logger = logger
        self.logger.setLevel(logging.DEBUG)
        self.logger.propagate = False
        self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s', '%Y-%m-%d %H:%M:%S')

    def __console(self, level, message):
        # 创建一个FileHandler,用于写到本地
        fh = logging.handlers.TimedRotatingFileHandler(self.logname, when='MIDNIGHT', interval=1, encoding='utf-8')
        # fh = logging.FileHandler(self.logname, 'a', encoding='utf-8')
        fh.suffix = '%Y-%m-%d.log'
        fh.setLevel(logging.DEBUG)
        fh.setFormatter(self.formatter)
        self.logger.addHandler(fh)

        # 创建一个StreamHandler,用于输出到控制台
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        ch.setFormatter(self.formatter)
        self.logger.addHandler(ch)

        if level == 'info':
            self.logger.info(message)
        elif level == 'debug':
            self.logger.debug(message)
        elif level == 'warning':
            self.logger.warning(message)
        elif level == 'error':
            self.logger.error(message)

        # 这两行代码是为了避免日志输出重复问题
        self.logger.removeHandler(ch)
        self.logger.removeHandler(fh)
        # 关闭打开的文件
        fh.close()

    def debug(self, message):
        self.__console('debug', message)

    def info(self, message):
        self.__console('info', message)

    def warning(self, message):
        self.__console('warning', message)

    def error(self, message):
        self.__console('error', message)

Это фрагмент кода, который я все еще использую в проекте, и сгенерированные файлы разделены по дням.

В то время, когда я писал этот код, была проблема, которая меня давно беспокоила, то есть проблема отображения количества строк ошибок в коде. когдаformatterнастроить%(lineno)dКаждый раз, вместо отображения фактической строки ошибки, он отображает строку кода в классе журнала, но это отображение бессмысленно, поэтому нет настройки, используйте%(name)sчтобы показать фактический вызывающий файл.

На самом деле, если это просто для удобства устранения неполадок и записи некоторых журналов, этот класс может в основном соответствовать требованиям. Но если вы хотите записывать все логи запросов на доступ к системе, то тут ничего не поделаешь, потому что вручную добавлять логи в каждый код интерфейса нельзя, да и не нужно.

В это время я, естественно, думаю о промежуточном программном обеспечении Django.

Промежуточное ПО Django

Код журнала промежуточного программного обеспечения разделен на три части:Filtersкод,middlewareкод,settingsНастройте следующим образом:

local = threading.local()


class RequestLogFilter(logging.Filter):
    """
    日志过滤器
    """

    def filter(self, record):
        record.sip = getattr(local, 'sip', 'none')
        record.dip = getattr(local, 'dip', 'none')
        record.body = getattr(local, 'body', 'none')
        record.path = getattr(local, 'path', 'none')
        record.method = getattr(local, 'method', 'none')
        record.username = getattr(local, 'username', 'none')
        record.status_code = getattr(local, 'status_code', 'none')
        record.reason_phrase = getattr(local, 'reason_phrase', 'none')

        return True
      

class RequestLogMiddleware(MiddlewareMixin):
    """
    将request的信息记录在当前的请求线程上。
    """

    def __init__(self, get_response=None):
        self.get_response = get_response
        self.apiLogger = logging.getLogger('web.log')

    def __call__(self, request):

        try:
            body = json.loads(request.body)
        except Exception:
            body = dict()

        if request.method == 'GET':
            body.update(dict(request.GET))
        else:
            body.update(dict(request.POST))

        local.body = body
        local.path = request.path
        local.method = request.method
        local.username = request.user
        local.sip = request.META.get('REMOTE_ADDR', '')
        local.dip = socket.gethostbyname(socket.gethostname())

        response = self.get_response(request)
        local.status_code = response.status_code
        local.reason_phrase = response.reason_phrase
        self.apiLogger.info('system-auto')

        return response

settings.pyКонфигурация файла:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
  	# 自定义中间件添加在最后
    'lib.log_middleware.RequestLogMiddleware'
]

LOGGING = {
    # 版本
    'version': 1,
    # 是否禁止默认配置的记录器
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '{"time": "%(asctime)s", "level": "%(levelname)s", "method": "%(method)s", "username": "%(username)s", "sip": "%(sip)s", "dip": "%(dip)s", "path": "%(path)s", "status_code": "%(status_code)s", "reason_phrase": "%(reason_phrase)s", "func": "%(module)s.%(funcName)s:%(lineno)d",  "message": "%(message)s"}',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        }
    },
    # 过滤器
    'filters': {
        'request_info': {'()': 'lib.log_middleware.RequestLogFilter'},
    },
    'handlers': {
        # 标准输出
        'console': {
            'level': 'ERROR',
            'class': 'logging.StreamHandler',
            'formatter': 'standard'
        },
        # 自定义 handlers,输出到文件
        'restful_api': {
            'level': 'DEBUG',
            # 时间滚动切分
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': os.path.join(LOGS_DIR, 'web-log.log'),
            'formatter': 'standard',
            # 调用过滤器
            'filters': ['request_info'],
            # 每天凌晨切分
            'when': 'MIDNIGHT',
            # 保存 30 天
            'backupCount': 30,
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': 'ERROR',
            'propagate': False
        },
        'web.log': {
            'handlers': ['restful_api'],
            'level': 'INFO',
            # 此记录器处理过的消息就不再让 django 记录器再次处理了
            'propagate': False
        },
    }
}

Таким образом, будет журнал для каждого запроса, который проходит через Django, будь то Интернет или администратор Django. В частности, запишите, какие поля можно получить и настроить в соответствии с потребностями проекта.

Следует отметить, что поrequest.userполучить имя пользователя работает только дляsessionметод проверки подлинности, посколькуsessionПосле аутентификации имя пользователя будет присвоеноrequest.user, поэтому его можно получить.

предполагается использоватьjwtспособ аутентификации,request.userне представляет никакой ценности. Есть два способа получить имя пользователя: один — проанализировать его в промежуточном программном обеспечении журнала.jwt cookieполучить имя пользователя, но этот способ не годится, лучше переписатьjwtАутентификация, назначьте имя пользователяrequest.user, чтобы его можно было вызывать в любом другом местеrequest.userчтобы получить значение.

Выше приведено все содержание логирования в Django, я надеюсь, что каждый сможет хорошо логировать, потому что это определенно будет полезно.

Справочная документация:

docs.Django project.com/en/2.1/topi…

Woohoo. Подключи photo.com/article/decent…

nuggets.capable/post/684490…

Woohoo.Pony stack.com/2019/01/11/…