Python自定义log,logging与loguru模块

一、logging

logging是Python内置的l模块,无需额外安装三方库即可实现记录日志的功能,缺点是需要提前封装,较为繁琐

import sys
import logging
from pathlib import Path
from logging.handlers import TimedRotatingFileHandler
# 定义终端彩色日志处理器,不影响日志文件中记录的内容
class ColorStreamHandler(logging.StreamHandler):
  # console color
  COLOR_CODES = {
      logging.DEBUG: "\033[1;34m",  # Blue
      logging.INFO: "\033[1;37m",  # White
      logging.WARNING: "\033[1;33m",  # Yellow
      logging.ERROR: "\033[1;31m",  # Red
      logging.CRITICAL: "\033[1;41m"  # Red with background
  }
  RESET_CODE = "\033[0m"
  # html color
  # COLOR_CODES = {
  #     logging.DEBUG: "<span style='color:blue'>",  # Blue
  #     logging.INFO: "<span style='color:white'>",  # White
  #     logging.WARNING: "<span style='color:yellow'>",  # Yellow
  #     logging.ERROR: "<span style='color:red'>",  # Red
  #     logging.CRITICAL: "<span style='color:blue'>"  # Red with background
  # }
  # RESET_CODE = "</span>"
  def emit(self, record):
      try:
          message = self.format(record)
          self.stream.write(self.COLOR_CODES.get(record.levelno, "") + message + self.RESET_CODE)
          self.stream.write("\n")
          self.stream.flush()
      except Exception:
          self.handleError(record)
class MyLogger(logging.Logger):
    def __init__(self, tag, level=logging.DEBUG, colorful: bool = False,
                 name: str | None = 'run',
                 save_pth: str | Path = Path.cwd() / 'logs',
                 existing_counts: int = 14):
        """
        参数解释:
        :param tag: 用于区分MyLogger对象,相当于命名空间,不同命名空间里的参数配置也不同,避免多个实例之间的冲突
        :param level: 仅输出大于等于该级别的日志,日志记录级别--DEBUG < INFO < WARNING < ERROR < CRITICAL
        :param colorful: 是否以彩色文本输出至控制台
        :param name: 日志文件的名称,None则不保存
        :param save_pth: 日志文件保存路径, None则不记录日志
        :param existing_counts: 日志文件保存的个数,默认14个,按照天划分文件,即默认存2周
        """
        super().__init__(tag, level=level)
        # 日志输出格式: 时间(默认毫秒级)+级别(-7代表左对齐,输出固定长度7)+位置+信息
        """
        asctime - 时间
        levelname - 日志级别
        pathname - 记录日志的位置
        filename - 记录日志的文件名
        lineno - 文件中记录日志的行号
        funcName - 文件中记录日志的函数名称
        """
        fmt = "%(asctime)s | %(levelname)-8s | %(pathname)s::%(funcName)s:%(lineno)d | %(message)s"
        formatter = logging.Formatter(fmt, datefmt='%Y-%m-%d %H:%M:%S')
        # 保存日志: 格式,路径,保存天数
        name and self._save_log(formatter, name, save_pth, existing_counts)
        """
        输出流句柄StreamHandler,默认无颜色输出至console,即sys.stdout;
        若想对不同级别的日志进行颜色区分,应当自定义输出流类,继承StreamHandler
        """
        ch = logging.StreamHandler(stream=sys.stdout) if not colorful else ColorStreamHandler(stream=sys.stdout)
        ch.setFormatter(formatter)
        self.addHandler(ch)

    def _save_log(self, formatter: logging.Formatter,
                  filename: str,
                  save_pth: str | Path,
                  existing_counts: int):
        # Path.mkdir中的参数: parents和exist_ok均为True,即创建所有父目录,且已存在时不报错
        not Path(save_pth).exists() and Path(save_pth).mkdir(parents=True, exist_ok=True)
        file = f'{save_pth}/{filename}'
        """文件流句柄TimedRotatingFileHandler的参数介绍:

        :param when: 参数设置为"midnight",表示午夜(凌晨)作为切分日志文件的时间点; H, M, S, D时分秒天,都是以最后一次记录日志为起点计算,
                     即最后一次记录日志后,等待interval个时分秒天,这期间没有新的日志记录,那么下一次记录时,划分日志文件; W0-W6为周一到周日,
                     此时,interval参数无效
        :param interval: 参数设置为1,表示每1个midnight生成一个日志文件;
        :param backupCount: 参数设置为7,表示最多保留7个日志文件,由于是按照天进行划分,因此等同于日志文件仅保留7天.
        :param delay: 参数设置为True,表示延迟日志文件的切分,例:假设最后一次记录日志后,到了切分文件的时间点,并不会将日志文件进行切分,
                      而是延迟到下一次记录日志的时候,把旧日志文件重新命名,再重新记录新的日志文件;如果delay=False,那么会在记录日志时,立马对日志文件进行切分,
                      但是往文件中写入日志与重命名日志文件时同时进行的,就会引发PermissionError,所以必须要设置为True,先写入日志文件,下一次写入时,
                      将上一次的先重命名,再重新写入
        """
        fh = TimedRotatingFileHandler(file, when="midnight", interval=1, backupCount=existing_counts,
                                      delay=True, encoding='utf8')
        fh.suffix = "%Y-%m-%d.log"  # 切分日志时,给旧日志文件添加的后缀, 命名方式修改为由.namer决定
        fh.setFormatter(formatter)
        # 设置切分后的日志文件名格式
        # time_fmt = datetime.now().strftime(f"%Y-%m-%d-%H%M")
        # fh.namer = lambda name: name.split(".")[0] + f"-{time_fmt}" + ".log"
        self.addHandler(fh)


if __name__ == '__main__':
    clog = MyLogger(tag='colorlog', name='color', colorful=True)
    clog.debug('aaa')
    clog.info('bbb')
    clog.warning('ccc')
    clog.error('ddd')
    clog.critical('eee')
    log = MyLogger(tag='mylog', existing_counts=7)
    log.debug('fff')
    log.info('ggg')
    log.warning('hhh')
    log.error('iii')

二、loguru

需要导入三方库:pip install loguru

二次封装及使用起来都比较简单,下面的示例有两种输出方式,默认采用第2种输出方式,输出至控制台时是彩色的,但是如果有需求需要将log显示在html控件上时,会多出不该显示的颜色字符:\033[1;34m....(即上面封装的ColorStreamHandler类中的内容),这样就破坏了原本log的格式,输出不够美观,此时也可以将方式2注释,取消注释方式1.这样以默认的格式显示在html上,保证原来的log格式

import sys
from pathlib import Path

from loguru import logger


class VulcanLogger:
    def __init__(self, tag='Vulcan', log_file_name='Vulcan.log'):
        """
        log记录模块
        :param tag: 标签,用于区分log
        :param log_file_name: log名称,默认Vulcan.log
        """
        # 1.仅仅记录log,并将log的内容以print的方式输出到控制台(默认配色)
        # logger.configure(handlers=[{
        #     "sink": f'{Path.cwd()}/logs/{str(log_file_name).rstrip(".log")}-' +
        #             '{time:YYYY-MM-DD}.log',
        #     "rotation": '16 MB',
        #     "retention": '7 days',
        #     "compression": 'zip'
        # }])
        # logger.add(sys.stdout,
        #            format='{time:YYYY-MM-DD HH:mm:ss} | {level:<7} | {module}:{line} - {message}',
        #            filter=lambda record: record['extra'].get('name') == tag, level='DEBUG')

        # 2.记录log,并以更加美观的方式将log输出到控制台
        logger.add(f'{Path.cwd()}/logs/{str(log_file_name).rstrip(".log")}-' + '{time:YYYY-MM-DD}.log',
                   format='{time:YYYY-MM-DD HH:mm:ss} | {level:<7} | {module}:{line} - {message}',
                   filter=lambda record: record['extra'].get('name') == tag, level='DEBUG', rotation='16 MB',
                   retention='7 days', compression='zip')
        self.logger = logger.bind(name=tag)

    def get_logger(self):
        return self.logger




if __name__ == '__main__':
    run_logger = VulcanLogger('run', 'Run.log').get_logger()
    test_logger = VulcanLogger('test', 'Run.log').get_logger()

    # 日志输出到Run.log中
    run_logger.info('AAA')
    run_logger.warning('AAA')
    test_logger.error('AAA')
    test_logger.debug('AAA')

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值