Решить проблему неупорядоченных записей журналов в многопроцессорной среде Django.

Python Django

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

Два исключения заключаются в следующем:

  1. Расстройство записи журнала;
  2. Логи не разбиваются по дням и теряются.

Поискав некоторую информацию в Интернете, я нашел причину:

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

Опишем этот аномальный процесс подробнее, допустим, мы каждый день генерируем лог-файл error.log и разбиваем лог каждое утро. Итак, в однопроцессной среде это выглядит так:

  1. Создать файл error.log;
  2. Ведите дневник дня;
  3. В нуле оцените, существует ли error.log-2020-05-15, и удалите его, если он есть, если не существует, переименуйте файл error.log в error.log-2020-05-15;
  4. Восстановите файл error.log и укажите дескриптор регистратора на новый error.log.

Давайте посмотрим на ситуацию с несколькими процессами:

  1. Создать файл error.log;
  2. Ведите дневник дня;
  3. В 0:00 процесс №1 определяет, существует ли error.log-2020-05-15, и если да, то удаляет его, если нет, переименовывает файл error.log в error.log-2020-05- 15;
  4. В это время процесс № 2 может все еще писать в файл error.log.Поскольку дескриптор файла записи был открыт, он будет писать в error.log-2020-05-15;
  5. Операцию разделения файла выполняет процесс № 2. Поскольку error.log-2020-05-15 уже существует, процесс № 2 удаляет его, а затем переименовывает в error.log, что приводит к потере журнала;
  6. Поскольку процесс № 2 переименовал error.log в error.log-2020-05-15, это также заставило процесс № 1 продолжить запись в error.log-2020-05-15, что вызвало путаницу при записи.

Причина ясна, так как же решение? Два варианта:

Использование пакета concurrent-log-handler

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

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

Поскольку мои потребности разделены по времени, очевидно, что это не может быть использовано, поэтому напишите это сами.

Переопределить TimedRotatingFileHandler

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

Итак, по этим двум пунктам мои контрмеры таковы: первое — убрать логику удаления файлов, а другое — обновить дескриптор записи до последнего по времени при вырезании файлов.

код показывает, как показано ниже:

# 解决多进程日志写入混乱问题


import os
import time
from logging.handlers import TimedRotatingFileHandler


class CommonTimedRotatingFileHandler(TimedRotatingFileHandler):

    @property
    def dfn(self):
        currentTime = int(time.time())
        # get the time that this sequence started at and make it a TimeTuple
        dstNow = time.localtime(currentTime)[-1]
        t = self.rolloverAt - self.interval
        if self.utc:
            timeTuple = time.gmtime(t)
        else:
            timeTuple = time.localtime(t)
            dstThen = timeTuple[-1]
            if dstNow != dstThen:
                if dstNow:
                    addend = 3600
                else:
                    addend = -3600
                timeTuple = time.localtime(t + addend)
        dfn = self.rotation_filename(self.baseFilename + "." + time.strftime(self.suffix, timeTuple))

        return dfn

    def shouldRollover(self, record):
        """
        是否应该执行日志滚动操作:
        1、存档文件已存在时,执行滚动操作
        2、当前时间 >= 滚动时间点时,执行滚动操作
        """
        dfn = self.dfn
        t = int(time.time())
        if t >= self.rolloverAt or os.path.exists(dfn):
            return 1
        return 0

    def doRollover(self):
        """
        执行滚动操作
        1、文件句柄更新
        2、存在文件处理
        3、备份数处理
        4、下次滚动时间点更新
        """
        if self.stream:
            self.stream.close()
            self.stream = None
        # get the time that this sequence started at and make it a TimeTuple

        dfn = self.dfn

        # 存档log 已存在处理
        if not os.path.exists(dfn):
            self.rotate(self.baseFilename, dfn)

        # 备份数控制
        if self.backupCount > 0:
            for s in self.getFilesToDelete():
                os.remove(s)

        # 延迟处理
        if not self.delay:
            self.stream = self._open()

        # 更新滚动时间点
        currentTime = int(time.time())
        newRolloverAt = self.computeRollover(currentTime)
        while newRolloverAt <= currentTime:
            newRolloverAt = newRolloverAt + self.interval

        # If DST changes and midnight or weekly rollover, adjust for this.
        if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
            dstAtRollover = time.localtime(newRolloverAt)[-1]
            dstNow = time.localtime(currentTime)[-1]
            if dstNow != dstAtRollover:
                if not dstNow:  # DST kicks in before next rollover, so we need to deduct an hour
                    addend = -3600
                else:           # DST bows out before next rollover, so we need to add an hour
                    addend = 3600
                newRolloverAt += addend
        self.rolloverAt = newRolloverAt

Просто импортируйте указанный выше класс в настройки handles.

Производственная среда находится в сети уже две недели и работает стабильно без каких-либо исключений.

над.

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

Краткое описание.com/fear/665694966…

nuggets.capable/post/684490…

Технический блог:

GitHub.com/Делайте это с душой/Особые…

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