Loading

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,  # 是否传递给父记录器
        },
    }
}

 

posted @ 2018-06-26 11:03  上官飞鸿  阅读(2623)  评论(0编辑  收藏  举报