• Python日志方案


    一、常用日志方案

    • 自定义封装log类

       rq = time.strftime('%Y%m%d', time.localtime(time.time()))
       class Log(object):    
        '''日志类 ''' 
        def __init__(self, name):    
             self.path = "/User/aaa/log/" # 定义日志存放路径
             self.filename = self.path + rq + '.log'    # 日志文件名称
             self.name = name    # 为%(name)s赋值
             self.logger = logging.getLogger(self.name)    
              #控制日志文件中记录级别    
             self.logger.setLevel(logging.INFO)    
              #控制输出到控制台日志格式、级别    
             self.ch = logging.StreamHandler()    
             gs = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s[line:%(lineno)d] - %(message)s')    
             self.ch.setFormatter(gs)    
            # self.ch.setLevel(logging.NOTSET)    写这个的目的是为了能控制控制台的日志输出级别,但是实际中不生效,不知道为啥,留着待解决
              #日志保留10天,一天保存一个文件    
             self.fh = logging.handlers.TimedRotatingFileHandler(self.filename, 'D', 1, 10)    
              #定义日志文件中格式
             self.formatter = logging.Formatter('%(asctime)s - %(levelname)s -   %(name)s[line:%(lineno)d] - %(message)s')
             self.fh.setFormatter(self.formatter)
             self.logger.addHandler(self.fh)
             self.logger.addHandler(self.ch) 
      
       class customError(Exception):  
       u'''    自定义异常类,用在主动输出异常时使用,用 raise关键字配合使用,例:            
                if True:                
                      pass
                else:
                      raise customError(msg)    '''
           def __init__(self, msg=None):
                self.msg = msg   
           def __str__(self):
                 if self.msg:
                        return self.msg
                 else:
                        return u"某个不符合条件的语法出问题了"
      

      在其他模块中引用的用法:

      from Function.Log_main_class import *
      try:
         log = Log("casename")
         log.info("msg")
         if True:                
                 pass
         else:
                 raise customError(msg)
      except BaseException as msg:
         log.exception(msg)
      

      特别说明:def exception(self, msg)方法的使用,可以将控制台中输出的错误日志全部保存到日志文件中,对于根据日志分析问题的场景需求,特别有用。这个函数一般配合try使用:

      try:
      	r = 10/0
      	print r
      except Exception as ex:
      	log.exception(ex)
      
    • Python3彩色日志包

      • 安装

        pip install colorful-logger
        
      • 使用

        • 默认logger

          可以直接使用默认的logger实例输出日志,默认的日志等级是warning

          from colorful_logger.logger import logger
          
          with logger:
            logger.debug("This is a debug message.")
            logger.info("This is a info message.")
            logger.warning("This is a warning message.")
            logger.error("This is a error message.")
            logger.critical("This is a critical message.")
            logger.fatal("This is a fatal message.")
          

          如你所见,logger需要在with语句中执行,因为本包使用的是QueueListener调用日志输出,使用logger输出日志前需要调用start()方法,使用结束后需要调用stop()方法,我将这两个方法封装到了with语句中,非特殊场景下,不需要单独调用start()和stop()方法。

          如果显式调用了start()方法,一定要在调用日志后执行stop()方法

          • 自定义logger

            也可以自定义name,日志等级、是否在终端显示、是否保存到文件:

            from colorful_logger.logger import get_logger, DEBUG
            
            logger = get_logger(name="sample_logger", level=DEBUG, file_path="./test.log")
            
            with get_logger(name="sample_logger", level=DEBUG, file_path="./test.log", file_colorful=True) as logger:
              logger.debug("This is a debug message.")
              logger.info("This is a info message.")
              logger.warning("This is a warning message.")
              logger.error("This is a error message.")
              logger.critical("This is a critical message.")
              logger.fatal("This is a fatal message.")
            

            在with语句外输出日志时可能会有意外情况,达不到预期结果。

            输出到文件的日志默认不是彩色日志

            如果你需要在文件中保存彩色日志,将file_colorful参数设置为True即可,本例中保存的就是彩色日志。

            彩色日志文件的作用特只有一个,就是在终端查看实时日志:

            tailf -f -n 1000 xxx.log
            

            这样查看日志才是彩色的

            FATALCRITICAL本就是影响程序运行的严重错误,而 python 默认的日志管理器中此方法与其他方法没有什么区别,这让我觉得莫名其妙,在本包中,我在fatal方法中加入了sys.exit(1)用来退出程序。如果在程序出现严重错误时不想退出程序,可以调用critical方法。

            get_logger方法:

            def get_logger(
                name: Optional[str] = None,
                level: int = logging.WARNING,
                show: bool = True,
                file_path: Optional[str] = None,
                file_colorful: bool = False,
            ) -> Logger: ...
            
            • name logger实例名,可以在不同的实例对象调用日志时为日志命名
            • level日志等级
            • show是否在终端显示。如果你想用此彩色日志包的话,通常是想在终端显示的吧
            • file_path是否保存到文件。默认是None,当其不是None时,会保存到对应的文件中
            • file_colorful保存到文件的日志是否为彩色,默认为False,以python默认的日志格式保存
        • 子logger

          子logger除了name与父logger不同,其余均相同,也不会输出第三方库的日志

          子logger在父logger的with语句中执行并不意味着一定在with语句中直接调用,在with语句中的某个函数中执行就可以,比如:

          # main.py
          from colorful_logger import get_logger
          from colorful_logger.logger import DEBUG
          
          from other_file import test
          
          # parent logger
          logger = get_logger(name="sample_logger", level=DEBUG, file_path="./test.log")
          
          with logger:
            test()
          # other_file.py
          
          test_logger = child_logger("test_logger", logger)
          
          def test():
            test_logger.error("test error")
          
    • Python中更优雅的记录日志

      • 安装使用

        pip install loguru
        
      • 快速上手

        如图logging一样,loguru也有定义日志等级。不同的日志等级,输出效果也不一样(默认的等级由低到高是DEBUGINFOWARNINGERRORCRITICAL,也可以自己使用level函数定义)

        from loguru  import logger 
        
        logger.debug("That's it, beautiful and simple logging!")
        
        logger.info("this is a info message!")
        
        

        类似logging中的logger.addHandler,loguru统一使用add函数来管理格式、文件输出、过滤等操作,它提供了许多参数来实现logger.addHandler中的配置更加简单方便。

        其中sink是最重要的参数,可以传入不同的数据类型。传入文件路径、文件句柄、sys.stderr、甚至logging模块的HandlerFileHandlerStreamHandler等,这样就可以快速实现自定义的Handler配置。

        通过给remove方法传递add方法返回的对象, 可以删除add方法添加的sink,这里的remove并不是删除test2.log文件,而是停止向该文件输出日志,需要需要继续记录日志则需要重新add日志文件。

        from loguru import logger
        
        obj = logger.add(sink='info.txt', format="{time} {level} {message}", filter="my_module", level="INFO")
        logger.info("That's it, beautiful and simple logging!")
        logger.remove(obj)
        

        rotationretentioncompression进行日志窗口、更新、压缩管理。

        logger.add("file_1.log", rotation="10 MB") # 日志文件的大小是10M,过大就会重新生成一个文件
        logger.add("file_2.log", rotation="00:00") # 每天0点创建新日志文件
        logger.add("file_3.log", rotation="1 week") # 自动更新旧文件
        logger.add("file_X.log", retention="3 days") # 每3天自动清理旧文件
        logger.add("file_Y.log", compression="zip") # zip压缩文件
        

        支持控制台输出添加颜色, 除了基础色,loguru甚至允许16进制、RGB格式的颜色值和加粗、下划线等样式。

        logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>") 
        

        使用装饰器@logger.catch可以和logging一样使用logger.exception函数来记录异常信息。

        @logger.catch
        def my_function(x, y, z):
            # An error? It's caught anyway!
            return 1 / (x + y + z)
            
        def func(a, b):
            return a / b
        
        def nested(c):
            try:
                func(5, c)
            except ZeroDivisionError:
                logger.exception("What?!")    
                
        nested(0) 
        

        使用exception方法输出的异常信息包含堆栈信息和当前变量的值,方便问题定位。

        2018-07-17 01:38:43.975 | ERROR    | __main__:nested:10 - What?!
        Traceback (most recent call last):
        
          File "test.py", line 12, in <module>
            nested(0)
            └ <function nested at 0x7f5c755322f0>
        
        > File "test.py", line 8, in nested
            func(5, c)
            │       └ 0
            └ <function func at 0x7f5c79fc2e18>
        
          File "test.py", line 4, in func
            return a / b
                   │   └ 0
                   └ 5
        
        ZeroDivisionError: division by zero
        

        使用serialize 可以将日志转换为JSON格式,enqueue 可以保证多线程、多进程安全。

        logger.add("somefile.log", serialize=True) # 序列化为json 
        logger.add("somefile.log", enqueue=True) 
        

        修改时间格式:

        logger.add("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}") 
        
    • Python中logging记录日志,使用@log装饰器

      • 导入依赖包

        import logging #日志
        import time # 时间
        import os # 路径
        
      • 创建一个logger

        logger = logging.getLogger() #获取logger
        logger.setLevel(logging.DEBUG) # 设置记录等级时debug
        
      • 创建日志文件

        rq = time.strftime('%Y-%m-%d-%H', time.localtime(time.time())) #以小时分割记录日志
        log_path = os.getcwd() + '/../Logs/all/' #设置日志路径
        log_name = log_path + rq + '_all.log' #设置日志名称
        logfile = log_name
        if not os.path.exists(log_path):  # 创建路径
            os.makedirs(log_path)
        for root, dirs, files in os.walk(os.path.dirname(log_path)): # 删除空文件
            for i in files:
                fpath = os.path.join(root, i)
                if os.path.getsize(fpath) == 0:
                    os.remove(fpath)
        if not os.path.exists(logfile): # 创建文件
            f = open(logfile, mode='w', encoding="utf-8")
            f.close()
        fh_all = logging.FileHandler(logfile, mode='a', encoding='utf-8')  # 输出到文件
        fh_all.setLevel(logging.DEBUG) 
        # 以上是所有日志,其他分类日志同上
        ch = logging.StreamHandler()
        ch.setLevel(logging.WARNING)  # 控制台输出的日志级别
        
      • 定义输出格式

        formatter = logging.Formatter(
            "%(asctime)s - %(filename)s[line:%(lineno)d](%(funcName)s) - %(levelname)s: %(message)s") 
        fh_debug.setFormatter(formatter) # 添加格式
        logger.addHandler(fh_debug) # 保存
        ch.setFormatter(formatter)
        logger.addHandler(ch)
        
      • 完整代码

           import logging
           import os
           import time
           
           
           logger = logging.getLogger()
           logger.setLevel(logging.DEBUG)  # Log等级总开关
           
           # 第二步,创建一个 file handler,用于写入日志文件
        def log(func):
            @functools.wraps(func)
            def wrapper(*args, **kw):
                dicc = {}
                dinp = {}
                varnames = func.__code__.co_varnames
                deft = func.__defaults__
                if deft is None:
                    deft = ()
        
                for i in range(len(args)):
                    dinp[varnames[i]] = str(args[i])
                for j in range(len(deft)):
                    dinp[varnames[i + j + 1]] = str(deft[j])
                for i, j in kw.items():
                    dinp[i] = str(j)
                # print(str(func.__name__))
                filter = ContextFilter(
                    os.path.basename(str(func.__code__.co_filename)), int(func.__code__.co_firstlineno), str(func.__name__))
        
                try:
                    aa = func(*args, **kw)
                except Exception as e:
                    aa = 'err:' + str(e)
                    if aa is None:
                        dretrun = ''
                    elif isinstance(aa, str):
                        dretrun = aa
                    elif isinstance(aa, tuple):
                        dretrun = list(aa)
                    else:
                        dretrun = str(aa)
                    # dicc['run_info'] = dinfo
                    dicc['run_input'] = dinp
                    dicc['run_return'] = dretrun
                    logger.addFilter(filter)
                    logger.debug(dicc)
                    logger.error(func.__name__ + '运行错误:', exc_info=True)
                    logger.removeFilter(filter)
        
                    raise e
        
                if aa is None:
                    dretrun = ''
                elif isinstance(aa, str):
                    dretrun = aa
                elif isinstance(aa, tuple):
                    dretrun = list(aa)
                else:
                    dretrun = str(aa)
                # dicc['run_info'] = dinfo
                dicc['run_input'] = dinp
                dicc['run_return'] = dretrun
                logger.addFilter(filter)
                logger.debug(dicc)
                logger.removeFilter(filter)
        
                return aa
        
            return wrapper
        
        
      • 使用logger记录日志

        logger.error('err', exc_info=True) #exc_info 记录错误信息
        
      • 使用@log装饰器记录log

        • 导入依赖包

          import functools 
          
        • 完整代码

          def log(func):
           @functools.wraps(func)
           def wrapper(*args, **kw):
               dicc = {}
               dinp = {}
               varnames = func.__code__.co_varnames
               deft = func.__defaults__
               if deft is None:
                   deft = ()
          
               for i in range(len(args)):
                   dinp[varnames[i]] = str(args[i])
               for j in range(len(deft)):
                   dinp[varnames[i + j + 1]] = str(deft[j])
               for i, j in kw.items():
                   dinp[i] = str(j)
               # print(str(func.__name__))
               filter = ContextFilter(
                   os.path.basename(str(func.__code__.co_filename)), int(func.__code__.co_firstlineno), str(func.__name__))
          
               try:
                   aa = func(*args, **kw)
               except Exception as e:
                   aa = 'err:' + str(e)
                   if aa is None:
                       dretrun = ''
                   elif isinstance(aa, str):
                       dretrun = aa
                   elif isinstance(aa, tuple):
                       dretrun = list(aa)
                   else:
                       dretrun = str(aa)
                   # dicc['run_info'] = dinfo
                   dicc['run_input'] = dinp
                   dicc['run_return'] = dretrun
                   logger.addFilter(filter)
                   logger.debug(dicc)
                   logger.error(func.__name__ + '运行错误:', exc_info=True)
                   logger.removeFilter(filter)
          
                   raise e
          
               if aa is None:
                   dretrun = ''
               elif isinstance(aa, str):
                   dretrun = aa
               elif isinstance(aa, tuple):
                   dretrun = list(aa)
               else:
                   dretrun = str(aa)
               # dicc['run_info'] = dinfo
               dicc['run_input'] = dinp
               dicc['run_return'] = dretrun
               logger.addFilter(filter)
               logger.debug(dicc)
               logger.removeFilter(filter)
          
               return aa
          
           return wrapper
          
        • 发现输出文件格式不是喜欢的类型,在最上方添加方法

          class ContextFilter(logging.Filter):
              # filename = 'IP'
              # lineno = 'USER'
              def __init__(self, filename, lineno, funcname):
                  self.filename = filename
                  self.lineno = lineno
                  self.funcname = funcname
          
              def filter(self, record):
                  record.filename = self.filename
                  record.lineno = self.lineno
                  record.funcName = self.funcname
                  return True
          

    二、原文链接

    https://www.jianshu.com/p/a02e013bc935?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

    https://www.jianshu.com/p/2898de660cb7

    https://www.jianshu.com/p/aff42e5e6099

    https://www.jianshu.com/p/843d4189bb42

    https://www.jianshu.com/p/3f184414d781

    https://loguru.readthedocs.io/en/stable/overview.html

    命令行参数解析: https://www.jianshu.com/p/2fc01be2e6e1

  • 相关阅读:
    道路和航线(最短路SPFA优化算法)
    走廊泼水节 (最大生成树)
    大数板子
    强连通分量
    path(CCPC网络赛)
    Plug It In!(网络流板子)
    链式前向星上DFS(Pants On Fire)
    手环定理
    (x+y)%p==b ;(x*y)%p==c; 知道b,c,求x,y;
    牛客多校第六场-H-Pair
  • 原文地址:https://www.cnblogs.com/huaibin/p/15011422.html
Copyright © 2020-2023  润新知