跳转至

6.14.logging

from toolkit import H
import logging
Windows 10
Python 3.7.7 @ MSC v.1916 64 bit (AMD64)
Latest build date 2020.08.14
logging version:  0.5.1.2
h = H(logging)
d = h.dicts
module

常量

sort_func = lambda k: eval(f"logging.{k}") \
    if (isinstance(eval(f"logging.{k}"), int) and eval(f"logging.{k}") != 1) \
    else 100

d["attr"].sort(key=sort_func)

print("{0:<20} {1}\n".format("field", "value"))
for constant in d["attr"]:
    value = eval(f"logging.{constant}")
    print("{0:<20}:{1}".format(constant, value))
field                value

NOTSET              :0
DEBUG               :10
INFO                :20
WARN                :30
WARNING             :30
ERROR               :40
CRITICAL            :50
FATAL               :50
BASIC_FORMAT        :%(levelname)s:%(name)s:%(message)s
lastResort          :<_StderrHandler stderr (WARNING)>
logMultiprocessing  :True
logProcesses        :True
logThreads          :True
raiseExceptions     :True
root                :<RootLogger root (WARNING)>

日志级别越高打印的日志越少,默认的级别是WARNING,意味着只会追踪该级别及以上的事件。notset等同于debug

记录日志的模块函数

Logging 模块提供了两种日志记录方式:

  • 使用 Logging 提供的模块级别的函数
  • 使用 Logging 日志系统的四大组件
logging.debug('Python debug')
logging.debug('Python debug')
logging.info('Python info')
# logging.warn is deprecated
logging.warning('Python warning')
logging.error('Python Error')
logging.critical('Python critical')
logging.fatal('Python fatal')
logging.log(logging.WARNING, "log function")
try:
    1/0
except Exception as e:
    logging.exception(e)

日志输出结果:

WARNING:root:Python warning
ERROR:root:Python Error
CRITICAL:root:Python critical
CRITICAL:root:Python fatal
WARNING:root:log function
ERROR:root:division by zero
Traceback (most recent call last):
  File "<ipython-input-16-d24461d1cf55>", line 11, in <module>
    1/0
ZeroDivisionError: division by zero

设置日志显示级别

通过 logging.basicConfig() 可以设置 root 的日志级别和日志输出格式。

logging.basicConfig() 关键字参数

注意logging.basicConfig()需要在开头就设置,在中间设置并无作用

关键字 描述
filename 创建一个 FileHandler,使用指定的文件名,而不是使用 StreamHandler。
filemode 如果指明了文件名,指明打开文件的模式(如果没有指明 filemode,默认为 ‘a’)。
format handler 使用指明的格式化字符串。
datefmt handler 使用指明的格式化字符串。
level 指明根 logger 的级别。
stream 使用指明的流来初始化 StreamHandler。该参数与 ‘filename’ 不兼容,如果两个都有,’stream’ 被忽略。

format 格式

格式 描述
%(levelno)s 打印日志级别的数值
%(levelname)s 打印日志级别名称
%(pathname)s 打印当前执行程序的路径
%(filename)s 打印当前执行程序名称
%(funcName)s 打印日志的当前函数
%(lineno)d 打印日志的当前行号
%(asctime)s 打印日志的时间
%(thread)d 打印线程 ID
%(threadName)s 打印线程名称
%(process)d 打印进程 ID
%(message)s 打印日志信息
import logging

# 打印日志级别
def test():
    logging.basicConfig(level=logging.DEBUG)
    logging.debug('Python debug')
    logging.info('Python info')
    logging.warning('Python warning')
    logging.error('Python Error')
    logging.critical('Python critical')
    logging.log(2, 'test')
test()

输出:

DEBUG:root:Python debug
INFO:root:Python info
WARNING:root:Python warning
ERROR:root:Python Error
CRITICAL:root:Python critical

将日志信息记录到文件

# 日志信息记录到文件
logging.basicConfig(filename='example.log', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

在相应的路径下会有 example.log 日志文件,内容如下:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too

多个模块记录日志信息

如果程序包含多个模块,则用以下实例来显示日志信息: 实例中有两个模块,一个模块通过导入另一个模块的方式用日志显示另一个模块的信息:

myapp.py 模块

import logging
import mylib
def main():
    logging.basicConfig(filename='myapp.log',level=logging.DEBUG)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()

mylib.py 模块

import logging

def do_something():
    logging.info('Doing something')

执行 myapp.py 模块会打印相应日志,在文件 myapp.log 中显示信息如下:

INFO:root:Started
INFO:root:Doing something
INFO:root:Finishe

logging 模块四大组件

组件名称 对应类名 功能描述
日志器 Logger 暴露函数给应用程序,基于日志记录器和过滤器级别决定哪些日志有效
处理器 Handler 将 logger 创建的日志记录发送到合适的目的输出
过滤器 Filter 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
格式器 Formatter 决定日志记录的最终输出格式

日志器 - Logger

Logger 持有日志记录器的方法,应用程序代码通过Logger对象调用日志接口,日志记录器不直接实例化,而是通过模块级函数logging.getLogger(name) 来实例化。

logger = logging.getLogger(logger_name)

getLogger() 函数最好加上日志所记录的模块的名字,配置文件和打印日志格式中的%(name)s对应的是这里的模块名字,如果不指定name则返回root对象。

使用相同的名称多次调用 getLogger() 总是会返回对相同 Logger 对象的引用。

Logger最常用的操作有两类:配置和发送日志消息。

logger = logging.getLogger(__name__)
# 设置日志级别
logger.setLevel(logging.DEBUG)
# 为 Logger 实例增加一个处理器
logger.addHandler(handler_name)
# 为 Logger 实例删除一个处理器
logger.removeHandler(handler_name)

处理器 - Handler

Handler 处理器类型有很多种,比较常用的有三个

  1. StreamHandler
  2. FileHandler
  3. NullHandler

StreamHandler

创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter

# 创建 StreamHandler 实例
sh = logging.StreamHandler(stream=None)
# 指定日志级别,低于WARN级别的日志将被忽略
sh.setLevel(logging.WARN)
# 设置一个格式化器formatter
sh.setFormatter(formatter_name)
# 增加一个过滤器,可以增加多个
sh.addFilter(filter_name)
# 删除一个过滤器
ch.removeFilter(filter_name)

过滤器 - Filter

Handlers 和 Loggers 可以使用 Filters 来完成比级别更复杂的过滤。 Filter 基类只允许特定 Logger 层次以下的事件。 例如用 ‘A.B’ 初始化的 Filter 允许Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’ 等记录的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串来初始化,所有的事件都接受。

filter = logging.Filter(name='')

格式器 - Formatter

使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S

formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明datefmt,将使用‘%(message)s’ 。如果不指明datefmt,将使用 ISO8601 日期格式。

组件之间的关系

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

  • 一个 Logger 对象可以包含一个或多个 Handler 和 Filter。

  • 一个 Handler 对象可以新增多个格式化器或多个过滤器,而且日志级别将会继承。

配置 logging

虽然我们可以直接调用 Logger、Handler、Filter、Formatter 对象中的方法在代码中来显式配置logging,但这种硬编码的方式不是好的编程规范。在 Python2.7 之后,可以从一个字典加载 logging 配置,这意味着可以从 JSON 或 YAML 文件中加载 logging 配置。虽然也可以通过 old.ini 方式加载 logging 配置,但是这对于可读性和可编辑来说是困难的。从最佳实践的角度来说,logging 模块支持三种配置方式:

logging.config模块负责配置logging。

  1. dictConfig:通过一个字典进行配置

  2. fileConfig:通过一个文件进行配置

  3. listen:监听一个网络端口,通过接收网络数据来进行配置。

FileConfig

logging.config模块提供了logging.config.fileConfig()方法可以从配置文件中进行日志配置。

ini 文件

先创建一个配置文件 logconfig.ini,配置文件的格式具有如下的形式:

[loggers]
keys=root

[handlers]
keys=fileHandler, errorFileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=fileHandler, errorFileHandler

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=simpleFormatter
args=('example.log', 'a')

[handler_errorFileHandler]
class=FileHandler
level=ERROR
formatter=simpleFormatter
args=('example-error.log', 'a')

[formatter_simpleFormatter]
format=%(name)s - %(levelname)s - %(message)s

[loggers] 配置项指定需要配置的logger,root 是logging系统默认logger。若有多个值,用逗号分隔keys=root, testLogger。然后在[logger_%(name)s] 配置项配置各个logger的具体配置信息。

程序调用配置文件:

import logging
import logging.config

# 文件的后缀名似乎不必须为.ini
logging.config.fileConfig('logconfig.ini')

# create logger
logger = logging.getLogger('simpleExample')

# application code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')

YAML 文件

version: 1
disable_existing_loggers: False
formatters:
    simple:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

handlers:
    console:
        class: logging.StreamHandler
        level: DEBUG
        formatter: simple
        stream: ext://sys.stdout

    info_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: INFO
        formatter: simple
        filename: info.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

    error_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: ERROR
        formatter: simple
        filename: errors.log
        maxBytes: 10485760 # 10MB
        backupCount: 20
        encoding: utf8

loggers:
    my_module:
        level: ERROR
        handlers: [console]
        propagate: no

root:
    level: INFO
    handlers: [console, info_file_handler, error_file_handler]

JSON 文件

{
    "version": 1,
    "disable_existing_loggers": false,
    "formatters": {
        "simple": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        }
    },

    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "DEBUG",
            "formatter": "simple",
            "stream": "ext://sys.stdout"
        },

        "info_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "INFO",
            "formatter": "simple",
            "filename": "info.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        },

        "error_file_handler": {
            "class": "logging.handlers.RotatingFileHandler",
            "level": "ERROR",
            "formatter": "simple",
            "filename": "errors.log",
            "maxBytes": 10485760,
            "backupCount": 20,
            "encoding": "utf8"
        }
    },

    "loggers": {
        "my_module": {
            "level": "ERROR",
            "handlers": ["console"],
            "propagate": "no"
        }
    },

    "root": {
        "level": "INFO",
        "handlers": ["console", "info_file_handler", "error_file_handler"]
    }
}

dictConfig

同时向控制台和文件输出日志。

import logging
from logging.config import dictConfig

level = logging.DEBUG
logging_config = {
    'version': 1,
    'formatters': {
        'default':
            {'format': '%(asctime)s [%(process)d] [%(levelname)s] %(message)s',
                    'datefmt': '%Y-%m-%d %H:%M:%S'}
    },
    'handlers': {
        'console': {
            'level': level,
            'class': 'logging.StreamHandler',
            'formatter': 'default',
        },
        'file': {
            'level': level,
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'default',
            'filename': './log/mtwoai.log',
            'maxBytes': 1024 * 1024 * 10,
            'backupCount': 1
        }
    },
    'loggers': {
        'test': {
            'level': level,
            'handlers': ['console', 'file']
        }
    },
    'root': {
        'level': level,
        'handlers': ['console', 'file']
    },
    'disable_existing_loggers': False
}

dictConfig(logging_config)

在python代码中配置

import logging

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.debug('often makes a very good meal of %s', 'visiting tourists')

参考

  1. 第32天:Python logging 模块详解
  2. python 的 logging 模块日志功能使用详解
  3. Python logging 模块详述
  4. python logging 重复写日志问题
  5. Python + logging 输出到屏幕,将log日志写入文件
  6. logging - Python 的 Logging 工具
  7. python logging详解及自动添加上下文信息
  8. Python logging 最佳实践-译
  9. python3的logging模块的最佳实践