Python/logging

维基教科书,自由的教学读本

Python 的 logging 模块提供了标准的日志接口,通过它可存储各种格式的日志。使用Logging模块的主要好处是所有Python模块都可以参与日志记录。

日志级别等级排序:critical > error > warning > info > debug( notset 等同于 debug )

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

  • 一种方式是使用 Logging 提供的模块级别的函数
  • 另一种方式是使用 Logging 日志系统的四大组件记录

Logging 定义的模块级别函数[编辑]

import logging

# 打印日志级别
def test_logging():
    logging.basicConfig(filename='F:/example.log', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p',level=logging.DEBUG) #需要在开头就设置,在中间设置并无作用 可以设置 root 的日志级别和日志输出格式。
    logging.debug('Python debug')
    logging.info('Python info')
    logging.warning('Python warning')
    logging.error('Python Error')
    logging.critical('Python critical')

test_logging()

logging.basicConfig函数各参数:

*encoding:如'utf-8'
  • filename:指定日志文件名;
  • filemode:和file函数意义相同,指定日志文件的打开模式,'w'或者'a';
  • format:指定输出的格式和内容,format可以输出很多有用的信息,
    • %(levelno)s:打印日志级别的数值
    • %(levelname)s:打印日志级别的名称
    • %(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]
    • %(filename)s:打印当前执行程序名
    • %(funcName)s:打印日志的当前函数
    • %(lineno)d:打印日志的当前行号
    • %(asctime)s:打印日志的时间
    • %(thread)d:打印线程ID
    • %(threadName)s:打印线程名称
    • %(process)d:打印进程ID
    • %(message)s:打印日志信息
  • datefmt:指定时间格式,同time.strftime();
  • level:设置日志级别,默认为logging.WARNNING;
  • stream:指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略;

logging 模块四大组件[编辑]

  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;在当前 Logger 对象中查找 Handlers,如果找不到任何 Handler,则往上到该 Logger 对象的父 Logger 中查找;如果找到一个或多个 Handler,则依次用 Handler 来处理日志信息。但在每个 Handler 处理日志信息过程中,会首先判断日志信息的等级是否大于该 Handler 的等级,如果大于,则往下执行(由 Logger 对象进入 Handler 对象中),否则,处理流程结束。
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;只要有一个过滤器返回假,则过滤结束,且该日志信息将丢弃,不再处理,而处理流程也至此结束。
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

第一次导入 logging 模块或使用 reload 函数重新导入 logging 模块,logging 模块中的代码将被执行,这个过程中将产生 logging 日志系统的默认配置。

可选的自定义配置logging标准模块方式:

  • dictConfig 是通过一个字典进行配置 Logger,Handler,Filter,Formatter;
  • fileConfig 则是通过一个文件进行配置;
  • listen 则监听一个网络端口,通过接收网络数据来进行配置。
  • 也可以直接调用 Logger,Handler 等对象中的方法在代码中来显式配置。

一个简单示例:

import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

console = logging.StreamHandler()#将日志同时输出到屏幕和日志文件
console.setLevel(logging.INFO)
 
logger.addHandler(handler)
logger.addHandler(console)
 
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")

日志器- Logger[编辑]

Logger 持有日志记录器的方法,日志记录器不直接实例化,而是通过模块级函数 logger.getlogger (name) 来实例化,使用相同的名称多次调用 getLogger() 总是会返回对相同 Logger 对象的引用。

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

logger.setLevel(logging.DEBUG),Logging 中有 NOTSET < DEBUG < INFO < WARNING < ERROR < CRITICAL这几种级别,日志会记录设置级别以上的日志

logger.addHandler(handler_name) # 为 Logger 实例增加一个处理器

logger.removeHandler(handler_name) # 为 Logger 实例删除一个处理器

使用 Logger 对象中的 debug,info,error,warn,critical 等方法记录日志信息。

首先在主模块定义了logger叫做'mainModule',并对它进行了配置。在解释器进程里面的其他地方通过getLogger('mainModule')得到的logger对象都是一样的,不需要重新配置,可以直接使用。定义的该logger的子logger,都可以共享父logger的定义和配置。所谓的父子logger是通过命名来识别,任意以'mainModule'开头的logger都是它的子logger,例如'mainModule.sub'。

处理器- Handler[编辑]

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

创建方法:sh = logging.StreamHandler(stream=None)

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

ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略
ch.setFormatter(formatter_name) # 设置一个格式化器formatter
ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个
ch.removeFilter(filter_name) # 删除一个过滤器
logging中包含的handler
handler名称 位置 作用
StreamHandler logging.StreamHandler 日志输出到流,可以是sys.stderr,sys.stdout或者文件
FileHandler logging.FileHandler 日志输出到文件
BaseRotatingHandler logging.handlers.BaseRotatingHandler 基本的日志回滚方式
RotatingHandler logging.handlers.RotatingHandler 日志回滚方式,支持日志文件最大数量和日志文件回滚
TimeRotatingHandler logging.handlers.TimeRotatingHandler 日志回滚方式,在一定时间区域内回滚日志文件
SocketHandler logging.handlers.SocketHandler 远程输出日志到TCP/IP sockets
DatagramHandler logging.handlers.DatagramHandler 远程输出日志到UDP sockets
SMTPHandler logging.handlers.SMTPHandler 远程输出日志到邮件地址
SysLogHandler logging.handlers.SysLogHandler 日志输出到syslog
NTEventLogHandler logging.handlers.NTEventLogHandler 远程输出日志到Windows NT/2000/XP的事件日志
MemoryHandler logging.handlers.MemoryHandler 日志输出到内存中的指定buffer
HTTPHandler logging.handlers.HTTPHandler 通过"GET"或者"POST"远程输出到HTTP服务器

过滤器- 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 是日期字符串。如果不指明 fmt,将使用 '%(message)s' 。如果不指明 datefmt,将使用 ISO8601 日期格式。

例子1[编辑]

使用RotatingFileHandler,可以实现日志回滚,

    import logging
    from logging.handlers import RotatingFileHandler
    logger = logging.getLogger(__name__)
    logger.setLevel(level = logging.INFO)
    #定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K
    rHandler = RotatingFileHandler("log.txt",maxBytes = 1*1024,backupCount = 3)
    rHandler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    rHandler.setFormatter(formatter)
     
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(formatter)
     
    logger.addHandler(rHandler)
    logger.addHandler(console)
     
    logger.info("Start print log")
    logger.debug("Do something")
    logger.warning("Something maybe fail.")
    logger.info("Finish")

可以在工程目录中看到,备份的日志文件:

   2016/10/09  19:36               732 log.txt
   2016/10/09  19:36               967 log.txt.1
   2016/10/09  19:36               985 log.txt.2
   2016/10/09  19:36               976 log.txt.3

例子2[编辑]

Python中的traceback模块被用于跟踪异常返回信息,可以在logging中记录下traceback,

    import logging
    logger = logging.getLogger(__name__)
    logger.setLevel(level = logging.INFO)
    handler = logging.FileHandler("log.txt")
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
     
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
     
    logger.addHandler(handler)
    logger.addHandler(console)
     
    logger.info("Start print log")
    logger.debug("Do something")
    logger.warning("Something maybe fail.")
    try:
        open("sklearn.txt","rb")
    except (SystemExit,KeyboardInterrupt):
        raise
    except Exception:
        logger.error("Faild to open sklearn.txt from logger.error",exc_info = True)
        #logger.exception("Failed to open sklearn.txt from logger.exception")#等效于上一行
    logger.info("Finish")

控制台和日志文件log.txt中输出:

   Start print log
   Something maybe fail.
   Faild to open sklearn.txt from logger.error
   Traceback (most recent call last):
     File "G:\zhb7627\Code\Eclipse WorkSpace\PythonTest\test.py", line 23, in <module>
       open("sklearn.txt","rb")
   IOError: [Errno 2] No such file or directory: 'sklearn.txt'
   Finish

例子3[编辑]

下面使用 Python 代码配置一个非常简单的记录器,一个控制台处理程序和一个简单的格式化程序:

logging.conf 配置文件:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

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

config_logging.py 配置器:

import logging
# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

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

recorder 记录器:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# 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')

运行结果:

2019-10-16 19:45:34,440 - simple_example - DEBUG - debug message
2019-10-16 19:45:34,440 - simple_example - INFO - info message
2019-10-16 19:45:34,440 - simple_example - WARNING - warn message
2019-10-16 19:45:34,440 - simple_example - ERROR - error message
2019-10-16 19:45:34,441 - simple_example - CRITICAL - critical message

例子:通过JSON文件配置logger[编辑]

{
    "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":["info_file_handler"],
            "propagate":"no"
        }
    },
    "root":{
        "level":"INFO",
        "handlers":["console","info_file_handler","error_file_handler"]
    }
}
import json
import logging.config
import os
 
def setup_logging(default_path = "logging.json",default_level = logging.INFO,env_key = "LOG_CFG"):
    path = default_path
    value = os.getenv(env_key,None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path,"r") as f:
            config = json.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level = default_level)
 
def func():
    logging.info("start func")
 
    logging.info("exec func")
 
    logging.info("end func")
 
if __name__ == "__main__":
    setup_logging(default_path = "logging.json")
    func()

例子:通过YAML文件配置logger[编辑]

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: [info_file_handler]
            propagate: no
root:
    level: INFO
    handlers: [console,info_file_handler,error_file_handler]
import yaml
import logging.config
import os
 
def setup_logging(default_path = "logging.yaml",default_level = logging.INFO,env_key = "LOG_CFG"):
    path = default_path
    value = os.getenv(env_key,None)
    if value:
        path = value
    if os.path.exists(path):
        with open(path,"r") as f:
            config = yaml.load(f)
            logging.config.dictConfig(config)
    else:
        logging.basicConfig(level = default_level)
 
def func():
    logging.info("start func")
 
    logging.info("exec func")
 
    logging.info("end func")
 
if __name__ == "__main__":
    setup_logging(default_path = "logging.yaml")
    func()