• 为什么不建议使用Python自带的logging?


    B站|公众号:啥都会一点的研究生

    包括我在内的大多数人,当编写小型脚本时,习惯使用print来debug,肥肠方便,这没问题,但随着代码不断完善,日志功能一定是不可或缺的,极大程度方便问题溯源以及甩锅,也是每个工程师必备技能

    Python自带的logging我个人不推介使用,不太Pythonic,而开源的Loguru库成为众多工程师及项目中首选,本期将同时对loggingLoguru进行使用对比,希望有所帮助

    插播,更多文字总结·指南·实用工具·科技前沿动态第一时间更新在公粽号【啥都会一点的研究生

    快速示例

    logging中,默认的日志功能输出的信息较为有限

    import logging
    
    logger = logging.getLogger(__name__)
    
    def main():
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
    
    if __name__ == "__main__":
        main()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输出(logging默认日志等级为warning,故此处未输出info与debug等级的信息)

    WARNING:root:This is a warning message
    ERROR:root:This is an error message
    
    • 1
    • 2

    再来看看loguru,默认生成的信息就较为丰富了

    from loguru import logger
    
    def main():
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    提供了执行时间、等级、在哪个函数调用、具体哪一行等信息

    格式化日志

    格式化日志允许我们向日志添加有用的信息,例如时间戳、日志级别、模块名称、函数名称和行号

    logging中使用%达到格式化目的

    import logging
    
    # Create a logger and set the logging level
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
    
    logger = logging.getLogger(__name__)
    
    def main():
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出

    2023-10-18 15:47:30 | INFO | tmp::186 - This is an info message
    2023-10-18 15:47:30 | WARNING | tmp::187 - This is a warning message
    2023-10-18 15:47:30 | ERROR | tmp::188 - This is an error message
    
    • 1
    • 2
    • 3

    loguru使用和f-string相同的{}格式,更方便

    from loguru import logger
    
    logger.add(
        sys.stdout,
        level="INFO",
        format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {module}:{function}:{line} - {message}",
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    日志保存

    logging中,实现日志保存与日志打印需要两个额外的类,FileHandlerStreamHandler

    import logging
    
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
        handlers=[
            logging.FileHandler(filename="/your/save/path/info.log", level=logging.INFO),
            logging.StreamHandler(level=logging.DEBUG),
        ],
    )
    
    logger = logging.getLogger(__name__)
    
    def main():
        logging.debug("This is a debug message")
        logging.info("This is an info message")
        logging.warning("This is a warning message")
        logging.error("This is an error message")
    
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    但是在loguru中,只需要使用add方法即可达到目的

    from loguru import logger
    
    logger.add(
        'info.log',
        format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {module}:{function}:{line} - {message}",
        level="INFO",
    )
    
    
    def main():
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
    
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    日志轮换

    日志轮换指通过定期创建新的日志文件并归档或删除旧的日志来防止日志变得过大

    logging中,需要一个名为 TimedRotatingFileHandler 的附加类,以下代码示例代表每周切换到一个新的日志文件 ( when=“WO”, interval=1 ),并保留最多 4 周的日志文件 ( backupCount=4 )

    import logging
    from logging.handlers import TimedRotatingFileHandler
    
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    
    # Create a formatter with the desired log format
    formatter = logging.Formatter(
        "%(asctime)s | %(levelname)-8s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
    
    file_handler = TimedRotatingFileHandler(
        filename="debug2.log", when="WO", interval=1, backupCount=4
    )
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)
    
    
    def main():
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
    
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    loguru中,可以通过将 rotationretention 参数添加到 add 方法来达到目的,如下示例,同样肥肠方便

    from loguru import logger
    
    logger.add("debug.log", level="INFO", rotation="1 week", retention="4 weeks")
    
    
    def main():
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
    
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    日志筛选

    日志筛选指根据特定条件有选择的控制应输出与保存哪些日志信息

    logging中,实现该功能需要创建自定义日志过滤器类

    import logging
    
    
    logging.basicConfig(
        filename="test.log",
        format="%(asctime)s | %(levelname)-8s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
        level=logging.INFO,
    )
    
    
    class CustomFilter(logging.Filter):
        def filter(self, record):
            return "Cai Xukong" in record.msg
    
    
    # Create a custom logging filter
    custom_filter = CustomFilter()
    
    # Get the root logger and add the custom filter to it
    logger = logging.getLogger()
    logger.addFilter(custom_filter)
    
    
    def main():
        logger.info("Hello Cai Xukong")
        logger.info("Bye Cai Xukong")
    
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    loguru中,可以简单地使用lambda函数来过滤日志

    from loguru import logger
    
    logger.add("test.log", filter=lambda x: "Cai Xukong" in x["message"], level="INFO")
    
    
    def main():
        logger.info("Hello Cai Xukong")
        logger.info("Bye Cai Xukong")
    
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    捕获异常

    logging中捕获异常较为不便且难以调试,如

    import logging
    
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)s | %(levelname)s | %(module)s:%(funcName)s:%(lineno)d - %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
    
    
    def division(a, b):
        return a / b
    
    
    def nested(c):
        try:
            division(1, c)
        except ZeroDivisionError:
            logging.exception("ZeroDivisionError")
    
    
    if __name__ == "__main__":
        nested(0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    Traceback (most recent call last):
      File "logging_example.py", line 16, in nested
        division(1, c)
      File "logging_example.py", line 11, in division
        return a / b
    ZeroDivisionError: division by zero
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面输出的信息未提供触发异常的c值信息,而在loguru中,通过显示包含变量值的完整堆栈跟踪来方便用户识别

    from loguru import logger
    
    
    def division(a, b):
        return a / b
    
    def nested(c):
        try:
            division(1, c)
        except ZeroDivisionError:
            logger.exception("ZeroDivisionError")
    
    
    if __name__ == "__main__":
        nested(0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    值得一提的是,loguru中的catch装饰器允许用户捕获函数内任何错误,且还会标识发生错误的线程

    from loguru import logger
    
    
    def division(a, b):
        return a / b
    
    
    @logger.catch
    def nested(c):
        division(1, c)
    
    
    if __name__ == "__main__":
        nested(0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    OK,作为普通玩家以上功能足以满足日常日志需求,通过对比loggingloguru应该让大家有了直观感受,哦对了,loguru如何安装?

    pip install loguru
    
    • 1

    以上就是本期的全部内容,期待点赞在看,我是啥都生,下次再见

  • 相关阅读:
    网络与VPC之动手实验
    pip 安装 livetest 失败
    字节跟踪偷拍前员工到快手上班,告他违反竞业协议,前员工被判赔偿字节30万!...
    java八股文面试[数据库]——数据库锁的种类
    【启明智显技术分享】SOM2D02-2GW核心板适配ALSA(适用Sigmastar ssd201/202D)
    jdk21(最新版) download 配置(linux window mac)
    Docker 入门笔记
    npm ERR! code ERESOLVE错误解决
    听说,你想做大模型时代的应用层创业!
    【教程】遥感数据与作物模型同化实践
  • 原文地址:https://blog.csdn.net/zzh516451964zzh/article/details/133907740