Detailed explanation of python logging module

1 Log related concepts

1.1 The role of the log

  • Program debugging
  • Know if the program is running normally
  • Failure analysis and problem location
  • User Behavior Analysis

1.2 Level of the log

grade meaning
DEBUG The most detailed log information, a typical application scenario is problem diagnosis
INFO The level of information detail is second only to DEBUG, usually only key node information is recorded to confirm that everything is working as expected
WARNING Information recorded when something unexpected happens (for example, disk free space is low), but the application is still running normally at this time
ERROR A message recorded when some functions do not work properly due to a more serious problem
CRITICAL Information recorded when a serious error occurs that prevents the application from continuing to run

By default, the logging module prints the log information with the level of WARNING and above to the console

1.3 Two ways to use logging module

There are two ways to use the logging module

  • The first way is to use the module-level functions provided by logging
  • The second way is to use the four major components of the Logging system

2 Use the module-level functions provided by logging

2.1 The logging module defines common functions

function Description
logging.debug(msg,*args,**kwargs) Create a log record with a severity level of DEBUG
logging.info(msg,*args,*kwargs) Create a log record with severity level INFO
logging.warning(msg,*args,*kwargs) Create a log record with severity level of WARNING
logging.error(msg,*args,*kwargs) Create a log record with severity level ERROR
logging.critical(msg,*args,**kwargs) Create a log record with severity level CRITICAL
logging.log(level,*args,*kwargs) Create a log record of severity level
logging.basicConfig(**kwargs) One-time configuration of root logger

The following is a demonstration of use:

2.2 Usage 1: Simple configuration

import logging

logging.debug("debug message")
logging.info("info message")
logging.warning("warning message")
logging.error("error message")
logging.critical("critical message")
logging.log(level=logging.ERROR, msg = "error in logging.log function")

Output result:

WARNING:root:warning message
ERROR:root:error message
CRITICAL:root:critical message
ERROR:root:error in logging.log function

By default, the logging module prints logs to standard output, and only displays logs with a level greater than or equal to WARNING , which means that the default log level is set to WARNING (log level level CRITICAL> ERROR> WARNING> INFO> DEBUG)

2.3 Usage 2: Use logging.basicConfig() function

Use logging.basicConfig() function to adjust the log level, output format, etc.

logging.basicConfig() function description

parameter name description
filename Specify the file name of the log output target file. After specifying this setting item, the log information will not be output to the console.
format Specify the log format string, that is, specify the field information contained in the log output and their order. The format fields defined by the logging module are listed below.
datefmt Specify the date/time format. It should be noted that this option is valid only when the time field %(asctime)s is included in the format
level Specify the log level of the logger
stream Specify the log output target stream, such as sys.stdout, sys.stderr, and network stream. It should be noted that stream and filename cannot be provided at the same time, otherwise ValueError will be raised
style Newly added configuration items in Python3.2. Specify the style of the format string, the possible values ​​are'%','{' and'$', and the default is'%'
handlers New configuration items added in Python 3.3. If this option is specified, it should be an iterable object with multiple Handlers created, and these handlers will be added to the rootlogger. It should be noted that only one of the three configuration items of filename, stream and handlers can exist, and two or three of them cannot be present at the same time, otherwise a ValueError will be raised.

Format string of logging module

Field/attribute name Use format description
asctime %(asctime)s The time the log event occurred-human readable time, such as: 2003-07-08 16:49:45,896
created %(created)f The time when the log event occurred-the timestamp is the value returned by calling the time.time() function at that time
relativeCreated %(relativeCreated)d The relative milliseconds of the time when the log event occurred relative to the loading time of the logging module (I don't know why it is used at present)
msecs %(msecs)d The millisecond part of the event that occurred in the log event
level name %(levelname)s The log level of the log record in text form ('DEBUG','INFO','WARNING','ERROR','CRITICAL')
levelno %(levelno)s The numerical log level of this log record (10, 20, 30, 40, 50)
name %(name)s The name of the logger used, the default is'root', because rootLogger is used by default
message %(message)s The text content of the log record, calculated by msg% args
pathname %(pathname)s The full path of the source file that calls the logging function
filename %(filename)s The file name part of pathname, including the file suffix
module %(module)s The name part of filename, excluding the suffix
lineno %(lineno)d The line number of the source code that calls the logging function
funcName %(funcName)s The name of the function that calls the logging function
process %(process)d 进程 ID
processName %(processName)s 进程名称,Python 3.1 新增
thread %(thread)d 线程 ID
threadName %(thread)s 线程名称
# coding=utf-8
import logging

MY_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(lineno)d %(message)s"  # 配置输出日志格式
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a '  #配置输出时间的格式

logging.basicConfig(
    filename="my.log",  # 指定日志写入到文件
    level=logging.INFO,
    datefmt=DATE_FORMAT,
    format=MY_FORMAT,
)

logging.debug("debug")
logging.info("info")
logging.warning("warning")
logging.error("error")
logging.critical("critical")

打开文件 my.log,内容如下:

2020-11-22  19:18:58 Sun  root INFO E:/prapy/python_project/testcase/test1.py 15 info
2020-11-22  19:18:58 Sun  root WARNING E:/prapy/python_project/testcase/test1.py 16 warning
2020-11-22  19:18:58 Sun  root ERROR E:/prapy/python_project/testcase/test1.py 17 error
2020-11-22  19:18:58 Sun  root CRITICAL E:/prapy/python_project/testcase/test1.py 18 critical

说明:

  • logging.basicConfig() 函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。
  • 日志器(Logger)是有层级关系的,上面调用的 logging 模块级别的函数所使用的日志器是 RootLogger 类的实例,其名称为 ‘root’,它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。
  • 如果要记录的日志中包含变量数据,可使用一个格式字符串作为这个事件的描述消息(logging.debug、logging.info 等函数的第一个参数),然后将变量数据作为第二个参数 *args 的值进行传递,
>>> import logging
>>> logging.warning('%s is %d years old.', 'Tom', 10)
WARNING:root:Tom is 10 years old.

3 使用 Logging 日志系统的四大组件

上面我们了解到了 logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()(分别用以记录不同级别的日志信息),logging.basicConfig()(用默认日志格式(Formatter)为日志系统建立一个默认的流处理器(StreamHandler),设置基础配置(如日志级别等)并加到 root logger(根 Logger)中)这几个 logging 模块级别的函数。

下面介绍第二种打印日志的方法,日志流处理,使用函数 logging.getLogger([name])(返回一个 logger 对象,如果没有指定名字将返回 root logger)。

在介绍 logging 模块的日志流处理流程之前,我们先来介绍下 logging 模块的四大组件:

组件名称 对应类名 功能描述
日志器 Logger 提供了应用程序可一直使用的接口
处理器 Handler 将 logger 创建的日志记录发送到合适的目的输出
过滤器 Filter 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
格式器 Formatter 决定日志记录的最终输出格式

这些组件之间的关系描述:

  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
  • 不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。

logging 日志模块相关类及其常用方法介绍

与 logging 四大组件相关的类:Logger, Handler, Filter, Formatter。

3.1 Logger 类

Logger 对象有 3 个任务要做:

  1. 向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
  2. 基于日志严重等级(默认的过滤设施)或 filter 对象来决定要对哪些日志进行后续处理;
  3. 将日志消息传送给所有感兴趣的日志 handlers。

Logger 对象最常用的方法分为两类:配置方法和消息发送方法

Logger 类相关方法

方法 描述
Logger.setLevel() 设置日志器将会处理的日志消息的最低严重级别
Logger.addHandler() 和 Logger.removeHandler() 为该logger对象添加 和 移除一个handler对象
Logger.addFilter() 和 Logger.removeFilter() 为该logger对象添加 和 移除一个filter对象

logger对象配置完成后,可以使用下面的方法来创建日志记录:

方法 描述
Logger.debug(), Logger.info(), Logger.warning(),
Logger.error(), Logger.critical()
创建一个与它们的方法名对应等级的日志记录
Logger.exception() 创建一个类似于 Logger.error() 的日志消息
Logger.log() 需要获取一个明确的日志 level 参数来创建一个日志记录

一个 Logger 对象呢?一种方式是通过 Logger 类的实例化方法创建一个 Logger 类的实例,但是我们通常都是用第二种方式–logging.getLogger() 方法。

logging.getLogger() 方法有一个可选参数 name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为 ‘root’。若以相同的 name 参数值多次调用 getLogger() 方法,将会返回指向同一个 logger 对象的引用。

多次使用注意不能创建多个logger,否则会出现重复输出日志现象。

关于logger的层级结构与有效等级的说明:

  • logger的名称是一个以 ‘.’ 分割的层级结构,每个 ‘.’ 后面的 logger 都是 ‘.’ 前面的 logger 的 children,例如,有一个名称为 foo 的 logger,其它名称分别为 foo.bar, foo.bar.baz 和 foo.bam 都是 foo 的后代。
  • logger 有一个"有效等级(effective level)"的概念。如果一个 logger 上没有被明确设置一个 level,那么该 logger 就是使用它 parent 的 level;如果它的 parent 也没有明确设置 level 则继续向上查找 parent 的 parent 的有效 level,依次类推,直到找到个一个明确设置了 level 的祖先为止。需要说明的是,root logger 总是会有一个明确的 level 设置(默认为 WARNING)。当决定是否去处理一个已发生的事件时,logger 的有效等级将会被用来决定是否将该事件传递给该 logger 的 handlers 进行处理。
  • child loggers 在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先 loggers 相关的 handlers。因此,我们不必为一个应用程序中所使用的所有 loggers 定义和配置 handlers,只需要为一个顶层的 logger 配置 handlers,然后按照需要创建 child loggers 就可足够了。我们也可以通过将一个 logger 的 propagate 属性设置为 False 来关闭这种传递机制。

3.2 Handler 类

Handler 对象的作用是(基于日志消息的 level)将消息分发到 handler 指定的位置(文件、网络、邮件等)。Logger 对象可以通过 addHandler() 方法为自己添加 0 个或者更多个 handler 对象。比如,一个应用程序可能想要实现以下几个日志需求:

方法 描述
Handler.setLevel(lel) 指定被处理的信息级别,低于 lel 级别的信息将被忽略
Handler.setFormatter() 给这个 handler 选择一个格式
Handler.addFilter(filt)、Handler.removeFilter(filt) 新增或删除一个 filter 对象

需要说明的是,应用程序代码不应该直接实例化和使用 Handler 实例。因为 Handler 是一个基类,它只定义了所有 handlers 都应该有的接口,同时提供了一些子类可以直接使用或覆盖的默认行为。下面是一些常用的 Handler:

Handler 描述
logging.StreamHandler 将日志消息发送到输出到 Stream,如 std.out, std.err 或任何 file-like 对象。
logging.FileHandler 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
logging.handlers.RotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按大小切割
logging.hanlders.TimedRotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按时间切割
logging.handlers.HTTPHandler 将日志消息以 GET 或 POST 的方式发送给一个 HTTP 服务器
logging.handlers.SMTPHandler 将日志消息发送给一个指定的 email 地址
logging.NullHandler 该 Handler 实例会忽略 error messages,通常被想使用 logging 的 library 开发者使用来避免 ‘No handlers could be found for logger XXX’ 信息的出现。

3.3 Formater 类

Formater 对象用于配置日志信息的最终顺序、结构和内容。与 logging.Handler基类不同的是,应用代码可以直接实例化 Formatter 类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个 Formatter 的子类来完成。

Formatter 类的构造方法定义如下:

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

可见,该构造方法接收 3 个可选参数:

  • fmt:指定消息格式化字符串,如果不指定该参数则默认使用 message 的原始值
  • datefmt:指定日期格式字符串,如果不指定该参数则默认使用 “%Y-%m-%d %H:%M:%S”
  • style:Python 3.2 新增的参数,可取值为 ‘%’,’{’ 和 ‘$’,如果不指定该参数则默认使用 ‘%’

一般直接用 logging.Formatter(fmt, datefmt)

3.4 Filter类(了解即可)

Filter 可以被 Handler 和 Logger 用来做比 level 更细粒度的、更复杂的过滤功能。Filter 是一个过滤器基类,它只允许某个 logger 层级下的日志事件通过过滤。该类定义如下:

class logging.Filter(name='')
    filter(record)

比如,一个 filter 实例化时传递的 name 参数值为 ‘A.B’,那么该 filter 实例将只允许名称为类似如下规则的 loggers 产生的日志记录通过滤:‘A.B’,‘A.B,C’,‘A.B.C.D’,‘A.B.D’,而名称为 ‘A.BB’,‘B.A.B’ 的 loggers 产生的日志则会被过滤掉。如果 name 的值为空字符串,则允许所有的日志事件通过过滤。

filter 方法用于具体控制传递的 record 记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非 0 表示可以通过过滤。

3.5 日志流处理简要流程

1、创建一个 logger
2、设置下 logger 的日志的等级
3、创建合适的 Handler(FileHandler 要有路径)
4、设置下每个 Handler 的日志等级
5、创建下日志的格式
6、向 Handler 中添加上面创建的格式
7、将上面创建的 Handler 添加到 logger 中
8、打印输出 logger.debug\logger.info\logger.warning\logger.error\logger.critical

# coding=utf-8
import logging

# 创建logger,如果参数为空则返回 root logger
logger = logging.getLogger("mylogger")
logger.setLevel(logging.DEBUG)  # 设置logger日志等级

# 创建handler
fh = logging.FileHandler("test.log", encoding="utf-8")
ch = logging.StreamHandler()

# 设置输出日志格式, 注意 logging.Formatter的大小写
formatter = logging.Formatter(
    fmt="%(asctime)s %(name)s %(filename)s %(message)s",
    datefmt="%Y/%m/%d %X"
)

# 为handler指定输出格式,注意大小写
fh.setFormatter(formatter)
ch.setFormatter(formatter)

# 为logger添加的日志处理器
logger.addHandler(fh)
logger.addHandler(ch)

# 输出不同级别的log
logger.warning("warning message")
logger.info("info message")
logger.error("error message")

运行结果

2020/11/22 21:00:24 mylogger test3.py warning message
2020/11/22 21:00:24 mylogger test3.py info message
2020/11/22 21:00:24 mylogger test3.py error message

python logging 重复写日志问题

用 Python 的 logging 模块记录日志时,可能会遇到重复记录日志的问题,第一条记录写一次,第二条记录写两次,第三条记录写三次

原因:没有移除 handler 解决:在日志记录完之后 removeHandler

# coding=utf-8
import logging

def log(msg):
    #创建logger,如果参数为空则返回root logger
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)  #设置logger日志等级

    #创建handler
    fh = logging.FileHandler("test.log",encoding="utf-8")
    ch = logging.StreamHandler()

    #设置输出日志格式
    formatter = logging.Formatter(
        fmt="%(asctime)s %(name)s %(filename)s %(message)s",
        datefmt="%Y/%m/%d %X"
        )

    #为handler指定输出格式
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)

    #为logger添加的日志处理器
    logger.addHandler(fh)
    logger.addHandler(ch)

    # 输出不同级别的log
    logger.info(msg)

# 输出不同级别的log
log("message1")
log("message2")
log("message3")

运行结果

2020/11/22 21:08:04 mylogger test3.py message1
2020/11/22 21:08:04 mylogger test3.py message2
2020/11/22 21:08:04 mylogger test3.py message2
2020/11/22 21:08:04 mylogger test3.py message3
2020/11/22 21:08:04 mylogger test3.py message3
2020/11/22 21:08:04 mylogger test3.py message3

分析:可以看到输出结果有重复打印

原因:第二次调用 log 的时候,根据 getLogger(name) 里的 name 获取同一个logger,而这个 logger 里已经有了第一次你添加的 handler,第二次调用又添加了一个 handler,所以,这个 logger 里有了两个同样的 handler,以此类推,调用几次就会有几个 handler。

解决方案 1:添加 removeHandler 语句

# coding=utf-8
import logging


def log(msg):
    # 创建logger,如果参数为空则返回root logger
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)  # 设置logger日志等级

    # 创建handler
    fh = logging.FileHandler("test.log", encoding="utf-8")
    ch = logging.StreamHandler()

    # 设置输出日志格式
    formatter = logging.Formatter(
        fmt="%(asctime)s %(name)s %(filename)s %(message)s",
        datefmt="%Y/%m/%d %X"
    )

    # 为handler指定输出格式
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)

    # 为logger添加的日志处理器
    logger.addHandler(fh)
    logger.addHandler(ch)

    # 输出不同级别的log
    logger.info(msg)

    # 解决方案1,添加removeHandler语句,每次用完之后移除Handler
    logger.removeHandler(fh)
    logger.removeHandler(ch)


# 输出不同级别的log
log("message1")
log("message2")
log("message3")

解决方案 2:在 log 方法里做判断,如果这个 logger 已有 handler,则不再添加 handler。

# coding=utf-8
import logging


def log(msg):
    # 创建logger,如果参数为空则返回root logger
    logger = logging.getLogger("mylogger")
    logger.setLevel(logging.DEBUG)  # 设置logger日志等级

    if not logger.handlers:
        # 创建handler
        fh = logging.FileHandler("test.log", encoding="utf-8")
        ch = logging.StreamHandler()

        # 设置输出日志格式
        formatter = logging.Formatter(
            fmt="%(asctime)s %(name)s %(filename)s %(message)s",
            datefmt="%Y/%m/%d %X"
        )

        # 为handler指定输出格式
        fh.setFormatter(formatter)
        ch.setFormatter(formatter)

        # 为logger添加的日志处理器
        logger.addHandler(fh)
        logger.addHandler(ch)

    # 输出不同级别的log
    logger.info(msg)


# 输出不同级别的log
log("message1")
log("message2")
log("message3")

logger 调用方法的例子

# coding=utf-8
import logging.handlers
import datetime


def get_logger():
    logger = logging.getLogger('mylogger')  # mylogger为日志器的名称标识,如果不提供该参数,默认为'root'
    logger.setLevel(logging.DEBUG)  # 设置logger处理等级

    # 这里进行判断,如果logger.handlers列表为空,则添加,否则,直接去写日志
    if not logger.handlers:
        # rf_handler将所有的日志信息写到 all.log 中
        # when:字符串,定义了日志切分的间隔时间单位
        # interval:间隔时间单位的个数,指等待多少个when的时间后Logger会自动重建新闻继续进行日志记录
        # backupCount 是保留日志的文件个数,日志文件最多backupCount个,多余的删除,默认为0,表示不会自动删除
        rf_handler = logging.handlers.TimedRotatingFileHandler('all.log', when='midnight', interval=1, backupCount=7,
                                                               atTime=datetime.time(0, 0, 0, 0))

        # 设置输出日志格式
        rf_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
        # 为handler指定输出格式
        rf_handler.setFormatter(rf_formatter)

        # f_handler 将等级大于等于 error的信息写到error.log文件中
        f_handler = logging.FileHandler('error.log')
        f_handler.setLevel(logging.ERROR)

        # 设置输出日志格式
        f_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s")
        # 为handler指定输出格式
        f_handler.setFormatter(f_formatter)

        # 为logger添加的日志处理器
        logger.addHandler(rf_handler)
        logger.addHandler(f_handler)

    return logger


logger = get_logger()
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical('critical message')
logger.log(level=logging.ERROR, msg="logger.log message")

参考:https://www.cnblogs.com/Nicholas0707/p/9021672.html#_label1_1

Guess you like

Origin blog.csdn.net/happyjacob/article/details/109922504