python完美logging日志实现,按日期存储+解决多线程,可直接复制使用

封装python logging日志,命名为log_config.py

Posted by Valuebai on February 10, 2020

具体代码

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @Time    : 2019/4/2 14:46
# @Software: PyCharm
# @Author  : https://github.com/Valuebai/
"""
    封装python logging日志,命名为log_config.py
    ~~~~~~~~~~~~~~~

    - 修改日志保存路径,否则使用默认上一层目录的./logs/
    - 使用:from common.log_config import logger     # common表示本文件放在的文件夹
        logger.info('打印info日志')
        logger.error('打印error日志')


    注意:
    - flask 有自带的log,使用本文件后会覆盖flask自带的

    - 无法自动删除日志 & 日志没有分隔记录
      使用TimedRotatingFileHandler创建时间循环日志,suffix需写成对应格式,如下
      参数中的when="D", or MIDNIGHT 天,file_handler.suffix = "%Y-%m-%d.log"
      参数中的when="S" 秒,file_handler.suffix = "%Y-%m-%d_%H-%M-%S.log"

    - 多进程写入同一日志文件冲突问题
      >> PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。

      >> 类似:2.使用多进程初始化同一日志模块,会导致日志备份报错,因为两个进程同时打开了日志文件,在重命名时会出现
                 WindowsError: [Error 32]错误,该错误是由于文件已被打开,
        按照官方文档的介绍,logging 是线程安全的,也就是说,在一个进程内的多个线程同时往同一个文件写日志是安全的。但是(对,
        这里有个但是)多个进程往同一个文件写日志不是安全的,为了解决这个问题,可使用 ConcurrentLogHandler,
        ConcurrentLogHandler 可以在多进程环境下安全的将日志写入到同一个文件,并且可以在日志文件达到特定大小时,分割日志文件。
      >> 在默认的 logging 模块中,有个 TimedRotatingFileHandler 类,可以按时间分割日志文件,
        可惜 ConcurrentLogHandler 不支持这种按时间分割日志文件的方式。(用单例模式无法解决!)

      >> 解决方法:1、继承TimedRotatingFileHandler重载,修改里面的东西
      >> 解决方法:2、直接使用开源的代码来用,本文使用的是concurrent_log  !!!
                     安装:pip install concurrent_log
                     url: https://github.com/huanghyw/concurrent_log
                     使用from concurrent_log import ConcurrentTimedRotatingFileHandler
                     直接在用TimedRotatingFileHandler替换为ConcurrentTimedRotatingFileHandler即可,其他代码不需要任何改动
                     !!!后面在使用过程中有问题再看看!!!

      >> 解决方法:3、建议使用sentry 来记录日志
"""

import os
import logging.config
from concurrent_log import ConcurrentTimedRotatingFileHandler  # 解决logging多线程问题


class GetLogger:
    """
    自定义logging,方便使用
    """

    def __init__(self, logs_dir=None, logs_level=logging.INFO):
        self.logs_dir = logs_dir  # 日志路径
        self.log_name = r'log.log'  # 日志名称
        self.logs_level = logs_level  # 日志级别
        # 日志的输出格式
        self.log_formatter = logging.Formatter(
            '%(asctime)s [%(filename)s] [%(funcName)s] [%(levelname)s] [%(lineno)d] %(message)s')

        if logs_dir is None:
            sep = os.sep  # 自动匹配win,mac,linux 下的路径分隔符
            self.logs_dir = os.path.abspath(
                os.path.join(__file__, f"..{sep}..{sep}logs{sep}"))  # 设置日志保存路径

        # 如果logs文件夹不存在,则创建
        if os.path.exists(self.logs_dir) is False:
            os.mkdir(self.logs_dir)

    def get_logger(self):
        """在logger中添加日志句柄并返回,如果logger已有句柄,则直接返回"""
        # 实例化root日志对象
        log_logger = logging.getLogger('root')

        # 设置日志的输出级别
        log_logger.setLevel(self.logs_level)
        print('111', log_logger.handlers)
        for handler in log_logger.handlers:
            print(handler)
        if log_logger.handlers == []:  # 避免重复日志
            # 创建一个handler,用于输出到cmd窗口控制台
            console_handler = logging.StreamHandler()

            console_handler.setLevel(logging.INFO)  # 设置日志级别
            console_handler.setFormatter(self.log_formatter)  # 设置日志格式
            log_logger.addHandler(console_handler)

            # 建立一个循环文件handler来把日志记录在文件里
            file_handler = ConcurrentTimedRotatingFileHandler(
                filename=self.logs_dir + os.sep + self.log_name,  # 定义日志的存储
                when="MIDNIGHT",  # 按照日期进行切分when = D: 表示按天进行切分,or self.when == 'MIDNIGHT'
                interval=1,  # interval = 1: 每天都切分。 比如interval = 2就表示两天切分一下。
                backupCount=30,  # 最多存放日志的数量
                encoding="UTF-8",  # 使用UTF - 8的编码来写日志
                delay=False,
                # utc = True: 使用UTC + 0的时间来记录 (一般docker镜像默认也是UTC + 0)
            )
            file_handler.doRollover()
            file_handler.suffix = "%Y-%m-%d.log"
            file_handler.setLevel(logging.DEBUG)  # 设置日志级别
            file_handler.setFormatter(self.log_formatter)  # 设置日志格式
            log_logger.addHandler(file_handler)

        return log_logger


logger = GetLogger().get_logger()

if __name__ == "__main__":
    # 对上面代码进行测试
    logger = GetLogger().get_logger()

    # 在具体需要的地方
    logger.info('INFO日志打印...')
    logger.error('ERROR日志打印...')

    # # 打印日志保存路径
    # sep = os.sep
    # set_log_path = os.path.abspath(
    #     os.path.join(__file__, f"..{sep}..{sep}logs{sep}"))
    # print('测试Log路径:', set_log_path)

    import time

    while True:
        logger.info('每隔X打印一下')
        time.sleep(2)

【坑】logging的坑

https://github.com/Valuebai/awesome-python-io/issues/17

【Me】 https://github.com/Valuebai/ https://valuebai.github.io/