Django 多进程日志写入日志大小模式与时间切割模式

django logging 的实现,是基于python logging模块实现,而logging模块仅仅是线程安全的;而使用uwsgi启动项目时,是启动多个进程的;logging模块并没有保证多进程安全。

1.使用 concurrent-log-handler 包记录日志

这个包通过加锁的方式实现了多进程安全,并且可以在日志文件达到特定大小时,分割文件,但是不支持按时间分割。如果使用日志大小来分割日志文件的话,就可以使用concurrent-log-handler。

安装:

pip install concurrent-log-handler

以下为django配置文件settings.py下的配置:

 

import concurrent_log_handler

# 日志配置  只要将注释打开相应的日志就会写入到logs/demo.log 里面
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        # 详细
        'verbose': {
            #            错误等级    错误时间戳  错误魔抗   错误行  错误信息
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        # 简单
        'simple': {
            'format': '%(levelname)s:(%(asctime)s:%(module)s: %(filename)s:%(lineno)d):%(message)s'
        },
        # 错误日志格式
        'err_info': {
            'format': '%(levelname)s:(%(asctime)s:%(module)s: %(filename)s:%(lineno)d):%(message)s'
        }
    },
    # 日志过滤器
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        },
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            #   过滤错误模式
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 记录日志的错误等级
            'level': 'INFO',
            # logging.handlers.RotatingFileHandler
            'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler',
            # 日志位置,日志文件名,日志保存目录必须手动创建
            'filename': os.path.join(BASE_DIR, 'logs/demo.log'),
            # 日志文件的最大值,这里我们设置100M
            'maxBytes': 1 * 1024 * 1024,
            # 日志文件的数量,设置最大日志数量为20
            'backupCount': 10,
            # 日志格式:详细格式
            'formatter': 'verbose'
        },
        # 输出info日志
        'info': {
            'level': 'INFO',
            'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler',
            'filename': os.path.join(BASE_DIR, 'logs/demo.log'),
            'maxBytes': 100 * 1024 * 1024,
            'backupCount': 10,
            'formatter': 'verbose',  # 'standard',
            'encoding': 'utf-8',  # 设置默认编码
        },
        # 专门用来记错误日志
        'error': {
            'level': 'DEBUG',
            'class': 'concurrent_log_handler.ConcurrentRotatingFileHandler',  # 保存到文件,自动切
            'filters': ['require_debug_false'],
            'filename': os.path.join(BASE_DIR, "logs/demo.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 50,  # 日志大小 50M
            'backupCount': 10,
            'formatter': 'err_info',
            'encoding': 'utf-8',
        },
    },
    # 日志对象
    'loggers': {
        'django': {
            'handlers': ['console', 'file', 'error'],  #
            'propagate': True,  # 是否让日志信息继续冒泡给其他的日志处理系统,一般为Ture ,不保证其他第三方模块有记录
        },
        'log': {
            'handlers': ['info', 'console'],
            'level': 'INFO',
            'propagate': True
        },
        'error': {
            'handlers': ['console', 'file', 'error'],
            'level': 'DEBUG',
            'propagate': True
        },
    }

}

 

应用的demo:

import logging

logger = logging.getLogger('error')


logger.error("12345678")

 

 

这样访问这个接口,就会写入日志文件,并且messages12345678

2.使用  TimedRotatingFileHandler记录日志

 

以下为django配置文件settings.py下的配置:

 

LOGGING = {
    'version': 1,
    'disable_existing_logger': False,
    'formatters': {
        'verbose': {
            'format': '%(asctime)s \"%(pathname)s:%(module)s:%(funcName)s:%(lineno)d\" [%(levelname)s]-%(message)s'
        },
    },
    # 处理器
    'handlers': {
        # 输出控制台
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose'
        },
        # 输出文件
        'file': {
            'level': 'DEBUG',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': 'logs/blog.log',
            'formatter': 'verbose',
            # 每分钟切割一次日志
            'when': 'M',
            # 时间间隔
            'interval': 1,
            # 保留5份日志
            'backupCount': 5,
            'encoding': 'utf-8'
        },
    },
    # 记录器
    'loggers': {
        'django': {
            'handlers': ['file'],  # 'console',
            'level': 'INFO',
            'propagete': True,
        },
    }
}

 

这个可以实现,按时间的间隔进行日志切割。

 

 

3.使用CommonTimedRotatingFileHandler记录日志(暂时未成功)

重写 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

 

posted @ 2020-08-15 21:15  pycoder_hsz  阅读(1476)  评论(0编辑  收藏  举报