zoukankan      html  css  js  c++  java
  • python logging模块,升级print调试到logging。

    简介:

    我们在写python程序的时候,很多时候都有bug,都是自己写的,自己造的孽,又的时候报错又是一堆,不知道是那部分出错了。

    我这初学者水平,就是打print,看哪部分执行了,哪部分没执行,由此来看问题大概在什么地方。

    其实python有更好的处理方案,logging模块。

    从Python2.3起,Python的标准库加入了logging模块.logging模块给运行中的应用提供了一个标准的信息输出接口.典型的logging机制实现是把要输出的数据简单地写到一个txt文件中去.写log文件的方式是一种常见的打log的方式,而logging模块提供的更多,它可以把输出信息输出到所有类文件的对象中去,甚至TCP和UDP的sockets,email服务器,Unix的syslog系统,NT系列的事件log系统,内存的buffer和HTTP服务器,当然还有”真正的”文件中去.

    Logging库被设计成模块的方式,它提供了以下几个子模块:loggers,handlers,filters和formatters.Loggers把应用需要直接调用的接口暴露出来.Handlers把log记录发到相应的目的地.Filters决定哪些记录需要发给handler.Formatters定义了log记录的输出格式.

    Logger对象扮演了三重角色.首先,它暴露给应用几个方法以便应用可以在运行时写log.其次,Logger对象按照log信息的严重程度或者根据filter对象来决定如何处理log信息(默认的过滤功能).最后,logger还负责把log信息传送给相关的loghandlers.

    Logger中最长使用的方法分成两部分中:configuration和message sending.

    用于Configuration的方法:

        setLevel(level)
        addFilter(filter)
        removeFilter(filter)
        addHandler(handler)
        removeHandler(handler)
        
        
    setLevel()方法定义了一个logger处理的最底严重程度(比如说中/高/底三种,我定义为中,那么只有严重程度为中或者高的log才会被处理).debug级别是内置的最低级别,critical是最高级别.举例来说,如果严重级别设为info级,logger仅仅处理info,warning,error和critical级的log,而debug级别的则忽略掉.


    根据logger对象的设置,以下的方法被用来写log:

        debug(log_message, [*args[, **kwargs]])
        info(log_message, [*args[, **kwargs]])
        warning(log_message, [*args[, **kwargs]])
        error(log_message, [*args[, **kwargs]])
        critical(log_message, [*args[, **kwargs]])
        exception(message[, *args])
        log(log_level, log_message, [*args[, **kwargs]])

        
    Handler对象负责分配合适的log信息(基于log信息的严重程度)到handler指定的目的地.Logger对象可以用addHandler()方法添加零个或多个handler对象到它自身.一个常见的场景是,一个应用可能希望把所有的log信息都发送到一个log文件中去,所有的error级别以上的log信息都发送到stdout,所有critical的log信息通过email发送.这个场景里要求三个不同handler处理,每个handler负责把特定的log信息发送到特定的地方.

    标准库里面包括以下的handlers:

        StreamHandler       (流式,控制台模式?)
        FileHandler          (文件式)
        RotatingFileHandler    (自动覆盖文件)
        TimedRotatingFileHandler  (按时间自动覆盖文件)
        SocketHandler
        DatagramHandler
        SysLogHandler
        NTEventLogHandler
        SMTPHandler            (SMTP邮件处理)
        MemoryHandler
        HTTPHandler

    一:基本使用

    1.无脑测试

    import logging
    import sys
    
    # 获取logger实例,如果参数为空则返回root logger
    logger = logging.getLogger("AppName")
    
    # 指定logger输出格式
    formatter = logging.Formatter('%(asctime)s %(levelname)-8s: (%(name)s)%(pathname)s %(message)s')
    
    # 文件日志
    file_handler = logging.FileHandler("test.log")
    file_handler.setFormatter(formatter)  # 可以通过setFormatter指定输出格式
    
    # 控制台日志
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.formatter = formatter  # 也可以直接给formatter赋值
    
    # 为logger添加的日志处理器
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    # 指定日志的最低输出级别,默认为WARN级别
    logger.setLevel(logging.DEBUG)
    
    # 输出不同级别的log
    logger.debug('this is debug info')
    logger.info('this is information')
    logger.warning('this is warning message')
    logger.error('this is error message')
    logger.fatal('this is fatal message, it is same as logger.critical')
    logger.critical('this is critical message')
    # 记录异常信息
    
    try:
        1 / 0
    except:
        logger.exception('except:')
    
    # 移除文件日志处理器,那么log文件就不记录这些日志了
    logger.removeHandler(file_handler)
    logger.debug('this is debug info----2')
    #修改日志输出级别
    logger.setLevel(logging.ERROR)
    logger.info('this is information----2')
    logger.warning('this is warning message----2')
    logger.error('this is error message----2')
    logger.fatal('this is fatal message, it is same as logger.critical----2')
    logger.critical('this is critical message----2')

    2.结果

    控制台有输出,文件有记录,在改变了日志等级以后,会有部分信息被隐藏。

    这不正式我们需要的么?

    以前自己打print,调试完了还要再去注释掉或者删除。

    用这个就好了。

    写代码调试的时候,用logging.debug

    调试完了,把logging配置为warning,或者error。debug就不输出了。

    二:配置

    1.logging自身的方法设置

    上面的无脑测试,就是通过logging自身的方法进行的设置,只是不方便统一调用

    2.通过文件加载配置(fileConfig)

    通过配置文件,加载配置,据说版本比较老,部分配置参数不支持,推荐用字典加载。

    3.通过字典加载配置(dictConfig)

    推荐用这种方式加载配置信息。

    logging的字典配置信息主要分5个部分:基本设置、日志内容格式、过滤器、处理器和管理器。

    三:字典加载配置

    1.无脑测试

    # !/usr/bin/python3
    # -*- coding: utf-8 -*-
    # @Time    : 2018-06-26 9:10
    # @Author  : Jackadam
    # @Email   :jackadam@sina.com
    # @File    : logging_conf.py
    # @Software: PyCharm
    
    import logging.config, logging, os
    
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
    DEBUG = True  # 标记是否在开发环境
    # 给过滤器使用的判断
    class RequireDebugTrue(logging.Filter):
        # 实现filter方法
        def filter(self, record):
            return DEBUG
    
    
    LOGGING = {
        # 基本设置
        'version': 1,  # 日志级别
        'disable_existing_loggers': False,  # 是否禁用现有的记录器
    
        # 日志格式集合
        'formatters': {
            # 标准输出格式
            'standard': {
                # [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
                'format': '[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:%(levelname)s(%(lineno)d)][%(module)s:%(funcName)s]:%(message)s'
            }
        },
    
        # 过滤器
        'filters': {
            'require_debug_true': {
                '()': RequireDebugTrue,
            }
        },
    
        # 处理器集合
        'handlers': {
            # 输出到控制台
            'console': {
                'level': 'DEBUG',  # 输出信息的最低级别
                'class': 'logging.StreamHandler',
                'formatter': 'standard',  # 使用standard格式
                'filters': ['require_debug_true', ],  # 仅当 DEBUG = True 该处理器才生效
            },
            # 输出到文件
            'log': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',
                'formatter': 'standard',
                'filename': os.path.join(BASE_DIR, 'debug.log'),  # 输出位置
                'maxBytes': 1024 * 1024 * 5,  # 文件大小 5M
                'backupCount': 5,  # 备份份数
                'encoding': 'utf8',  # 文件编码
            },
        },
    
        # 日志管理器集合
        'loggers': {
            # 管理器
            'default': {
                'handlers': ['console', 'log'],
                'level': 'DEBUG',
                'propagate': True,  # 是否传递给父记录器
            },
            # 管理器
            'shoujitiku': {
                'handlers': ['console', 'log'],
                'level': 'DEBUG',
                'propagate': True,  # 是否传递给父记录器
            },
    
        }
    }
    
    
    
    
    
    def log_main():
        # 加载前面的标准配置
        logging.config.dictConfig(LOGGING)
    
        # 获取loggers其中的一个日志管理器
        logger = logging.getLogger("shoujitiku")
        return logger
    
    
    loger = log_main()
    loger.debug('hello')

    2.调用方式

    函数log_main():

    代码中定义了一个字典LOGGING,

    加载配置就用

    logging.config.dictConfig(LOGGING)

    然后使用其中一个日志管理器叫做shoujitiku

    logger=logging.getLogger('shoujitiku')

    然后返回logger

    3.配置文件中的调用

    logging.getLogger('shoujitiku'),首先获取日志管理器集合当中的一个管理器shoujitiku,其中handlers,包括了两个处理器,一个是控制台输出console,用方法'class': 'logging.StreamHandler',,一个是日志文件循环输出log,用方法'class': 'logging.handlers.RotatingFileHandler',

    每个处理器当中都有'level': 'DEBUG', # 输出信息的最低级别 'class': 'logging.StreamHandler', # 使用什么方法 'formatter': 'standard', # 使用standard格式

    4.class

    • StreamHandler instances send error messages to streams (file-like objects).   控制台输出
    • FileHandler instances send error messages to disk files.                                  文件记录
    • RotatingFileHandler instances send error messages to disk files, with support for maximum log file sizes and log file rotation.            滚动文件记录
    • TimedRotatingFileHandler instances send error messages to disk files, rotating the log file at certain timed intervals.                       滚动时间记录
    • SocketHandler instances send error messages to TCP/IP sockets.           TCP端口?不懂,没用。
    • DatagramHandler instances send error messages to UDP sockets.          UDP端口?不懂,没用。
    • SMTPHandler instances send error messages to a designated email address.         SMTP发邮件

    5.formatter

    Formatter对象定义了log信息的结构和内容,构造时需要带两个参数:

    • 一个是格式化的模板fmt,默认会包含最基本的levelmessage信息
    • 一个是格式化的时间样式datefmt,默认为 2003-07-08 16:49:45,896 (%Y-%m-%d %H:%M:%S)

    fmt中允许使用的变量可以参考下表。

    • %(name)s Logger的名字
    • %(levelno)s 数字形式的日志级别
    • %(levelname)s 文本形式的日志级别
    • %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
    • %(filename)s 调用日志输出函数的模块的文件名
    • %(module)s 调用日志输出函数的模块名|
    • %(funcName)s 调用日志输出函数的函数名|
    • %(lineno)d 调用日志输出函数的语句所在的代码行
    • %(created)f 当前时间,用UNIX标准的表示时间的浮点数表示|
    • %(relativeCreated)d 输出日志信息时的,自Logger创建以来的毫秒数|
    • %(asctime)s 字符串形式的当前时间。默认格式是“2003-07-08 16:49:45,896”。逗号后面的是毫秒
    • %(thread)d 线程ID。可能没有
    • %(threadName)s 线程名。可能没有
    • %(process)d 进程ID。可能没有
    • %(message)s 用户输出的消息

    6.过滤器

    一个日志管理器可以包含多个处理器。有时在某些环境或场景下,希望不使用某些处理器。

    以console那个处理器为例。若用py2exe等打包Python脚本,不要添加console处理器。

    打包之后的程序会把控制台的消息也输出一个log日志(而且还弹窗提示)。此时我要通过过滤器判断是否需要输出到控制台该处理器。

    过滤器同样放在一个过滤器集合中,如下代码:

    1. #过滤器
    2. 'filters':{
    3.     'require_debug_true': {
    4.         '()': RequireDebugTrue,
    5.     }
    6. },

    其中键名是过滤器的名称,键值是过滤器的内容。

    过滤器内容只需要设置一个属性,该属性值是继承了logging.Filter的类。如下代码:

    1. DEBUG = True #标记是否在开发环境
    2.  
    3. #给过滤器使用的判断
    4. class RequireDebugTrue(logging.Filter):
    5.     #实现filter方法
    6.     def filter(self, record):
    7.         return DEBUG

    继承该类需要实现filter方法,返回一个布尔值。

    在开发环境,我设置DEBUG为True;在客户端,我设置DEBUG为False。从而控制是否需要使用某些处理器。

    当然,该值你也可以想办法通过一下判断动态设置。

    貌似是设置一个类,里面有默认方法filter,返回一个布尔值。再过滤器中引用这个类就可以了。

    四:SMTPHandler

    1.处理器

    # 发个邮件
            'email':{
                'level':'DEBUG',
                'class':'logging.handlers.SMTPHandler',
                'formatter': 'standard',
                'mailhost':'smtp.163.com',     #SMTP地址
                'fromaddr':'jackadam@163.com', #发件人邮箱
                'toaddrs':'jackadam@sina.com', #收件人邮箱,可以['a@163.com','a@sina.com','a@google.com']
                'subject':'log info',           #邮件标题
                'credentials':['jackadam','**********']  #邮箱登陆信息,用户名 ***是密码
            },

    五:TimedRotatingFileHandler

    1.处理器

    'log_debug': {
                'level': 'DEBUG',
                'class': 'logging.handlers.TimedRotatingFileHandler',
                'formatter': 'standard',
                'filename': os.path.join(BASE_DIR, 'TESTdebug.log'),  # 输出位置
                'when': 'S', #分割单位
                'interval': 1,#单位长度
                'backupCount': 10,  # 备份份数
                'encoding': 'utf8',  # 文件编码
            },

    2.常用参数

      • when:是一个字符串,用于描述滚动周期的基本单位,字符串的值及意义如下:
        “S”: Seconds 秒
        “M”: Minutes 分钟
        “H”: Hours 小时
        “D”: Days 天
        “W”: Week day (0=Monday)  周几
        “midnight”: Roll over at midnight  午夜
      • interval: 滚动周期,单位有when指定,比如:when=’D’,interval=1,表示每天产生一个日志文件;
      • backupCount: 表示日志文件的保留个数;

    3.注意事项

    除了上述参数之外,TimedRotatingFileHandler还有两个比较重要的成员变量,它们分别是suffix和extMatch。

    suffix是指日志文件名的后缀,suffix中通常带有格式化的时间字符串,filename和suffix由“.”连接构成文件名(例如:filename=“runtime”, suffix=“%Y-%m-%d.log”,生成的文件名为runtime.2015-07-06.log)。

    extMatch是一个编译好的正则表达式,用于匹配日志文件名的后缀,它必须和suffix是匹配的,如果suffix和extMatch匹配不上的话,过期的日志是不会被删除的。

    比如,suffix=“%Y-%m-%d.log”, extMatch的只应该是re.compile(r”^d{4}-d{2}-d{2}.log$”)。

    默认情况下,在TimedRotatingFileHandler对象初始化时,suffxi和extMatch会根据when的值进行初始化:
    ‘S’: suffix=”%Y-%m-%d_%H-%M-%S”, extMatch=r”^d{4}-d{2}-d{2}_d{2}-d{2}-d{2}”;
    ‘M’:suffix=”%Y-%m-%d_%H-%M”,extMatch=r”^d{4}-d{2}-d{2}_d{2}-d{2}”;
    ‘H’:suffix=”%Y-%m-%d_%H”,extMatch=r”^d{4}-d{2}-d{2}_d{2}”;
    ‘D’:suffxi=”%Y-%m-%d”,extMatch=r”^d{4}-d{2}-d{2}”;
    ‘MIDNIGHT’:”%Y-%m-%d”,extMatch=r”^d{4}-d{2}-d{2}”;
    ‘W’:”%Y-%m-%d”,extMatch=r”^d{4}-d{2}-d{2}”;
    如果对日志文件名没有特殊要求的话,可以不用设置suffix和extMatch,如果需要,一定要让它们匹配上。

    貌似这是老版本的信息,我在python3.6中没能实现。

    六:RotatingFileHandler

    1.处理器

    'log': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',
                'formatter': 'standard',
                'filename': os.path.join(BASE_DIR, 'TESTdebug.log'),  # 输出位置
                'maxBytes': 1024 * 1024 * 5,  # 文件大小 5M
                'backupCount': 5,  # 备份份数
                'encoding': 'utf8',  # 文件编码
            },

    这个是根据文件大小进行滚动记录的。

    会自动添加数字后缀。

    七:优先级

    我们使用getLogger方法时,读取的是日志管理器集合,由日志管理器集合来决定使用哪个或哪几个日志管理器。

    而我们在日志管理器集合和处理器都定义了level,那么以级别高的为准,级别低的就不输出了。

     

    八:总结

    1.定义日志格式集合,或单个

    2.定义处理器,并确定使用什么格式

    3.定义日志管理器集合,决定使用什么处理器,控制台 文件 邮件,任意组合。

    4.再任意文件中引入

    loger = log_main()
    loger.debug('hello')

    九:新用法--定时循环的任务,发送一个循环的记录到指定邮箱。

    1.定义一个文件处理器:

            'log_email': {
                'level': 'INFO',
                'class': 'logging.FileHandler',
                'formatter': 'email',
                'filename': os.path.join(BASE_DIR, 'email.log'),  # 输出位置
                'encoding': 'utf8',  # 文件编码
            },

    2.加入日志管理器集合

    3.定义一个函数来进行读取email.log文件,并把内容返回回去,然后删除这个email.log

    import os
    
    def Smtp_log():
        file=os.getcwd() + '/log/email.log'
        fp=open(file,'r+',encoding='utf-8')
        text = fp.readlines()
        resoult=''
        for i in text:
            print(i)
            resoult=resoult+i
        os.remove(file)
        return resoult

    4.在任意文件中引入

    loger.warning(SMTP_log.Smtp_log())

    4.一个问题

    由于原来发邮件的日志等级高,所以warning的内容也记录在info debug级的日志中。

    现在warning发送的是一段时间的日志,造成了info debug日志的重复,虽然有时间记录,可以用来区分,还是不方便,且经常造成软件崩溃。

    目前的做法时,另外实例化一个logging

    专门用来处理发邮件。

    相关代码如下:

    def log_main():
        # 加载前面的标准配置
        logging.config.dictConfig(LOGGING)
        # 获取loggers其中的一个日志管理器
        logger = logging.getLogger("default")
        return logger
    
    
    def loger_email():
        # 加载前面的标准配置
        logging.config.dictConfig(LOGGING)
        # 获取loggers其中的一个日志管理器
        logger = logging.getLogger("email")
        return logger
    
    def Smtp_log():
        file=os.getcwd() + '/log/email.log'
        fp=open(file,'r+',encoding='utf-8')
        text = fp.readlines()
        resoult=''
        for i in text:
            print(i)
            resoult=resoult+i
        os.remove(file)
        return resoult
    LOGGING = {
        # 基本设置
        'version': 1,  # 日志级别
        'disable_existing_loggers': False,  # 是否禁用现有的记录器
        'addLevelName': '5, "EMAIL"',
    
        # 日志格式集合
        'formatters': {
            # 标准输出格式
            'standard': {
                # [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
                'format': '[%(asctime)s]:%(levelname)s(%(lineno)d)][%(module)s:%(funcName)s]:%(message)s'
            },
            'email': {
                # [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
                'format': '[%(asctime)s]:%(message)s'
            }
        },
    
        # 过滤器
        'filters': {
            'require_debug_true': {
                '()': RequireDebugTrue,
            }
        },
    
        # 处理器集合
        'handlers': {
            # 输出到控制台
            'console': {
                'level': 'INFO',  # 输出信息的最低级别
                'class': 'logging.StreamHandler',
                'formatter': 'standard',  # 使用standard格式
                'filters': ['require_debug_true', ],  # 仅当 DEBUG = True 该处理器才生效
            },
            # 输出到文件
    
            'log_debug': {
                'level': 'INFO',
                'class': 'logging.handlers.TimedRotatingFileHandler',
                'formatter': 'standard',
                'filename': os.path.join(BASE_DIR, 'debug.log'),  # 输出位置
                'when': 'midnight',
                'interval': 1,
                'backupCount': 31,  # 备份份数
                'encoding': 'utf8',  # 文件编码
            },
            'log_info': {
                'level': 'WARNING',
                'class': 'logging.handlers.TimedRotatingFileHandler',
                'formatter': 'standard',
                'filename': os.path.join(BASE_DIR, 'info.log'),  # 输出位置
                'when': 'midnight',
                'interval': 1,
                'backupCount': 31,  # 备份份数
                'encoding': 'utf8',  # 文件编码
            },
            'log_email': {
                'level': 'INFO',
                'class': 'logging.FileHandler',
                'formatter': 'email',
                'filename': os.path.join(BASE_DIR, 'email.log'),  # 输出位置
                'encoding': 'utf8',  # 文件编码
            },
            'email': {
                'level': 'DEBUG',
                'class': 'logging.handlers.SMTPHandler',
                'formatter': 'email',
                'mailhost': 'smtp.163.com',  # SMTP地址
                'fromaddr': 'jackadam@163.com',  # 发件人邮箱
                'toaddrs': 'jackadam@sina.com',  # 收件人邮箱,可以['a@163.com','a@sina.com','a@google.com']
                'subject': '答题机报告',  # 邮件标题
                'credentials': ['jackadam', '********']  # 邮箱登陆信息,用户名 ***是密码
            },
        },
    
        # 日志管理器集合
        'loggers': {
            # 管理器
            'default': {
                'handlers': ['console', 'log_debug', 'log_info', 'log_email'],
                'level': 'DEBUG',
                'propagate': True,  # 是否传递给父记录器
            },
            'email': {
                'handlers': ['email'],
                'level': 'DEBUG',
                'propagate': True,  # 是否传递给父记录器
            },
        }
    }
  • 相关阅读:
    线程交互
    线程死锁
    多线程的同步-sychronized
    线程常见方法
    创建多线程
    消费!
    Redis基本认识
    在右键菜单中加入"在IDEA中打开" (Open in IDEA)
    安装coc.nvim时 报[coc.nvim] javascript file not found 错误的解决方案
    汇编语言的种类
  • 原文地址:https://www.cnblogs.com/jackadam/p/9228116.html
Copyright © 2011-2022 走看看