Logger从来不直接实例化,经常通过logging模块级方法(Module-Level Function)logging.getLogger(name)来获得,其中如果name不给定就用root。
名字是以点号分割的命名方式命名的(a.b.c)。对同一个名字的多个调用logging.getLogger()方法会返回同一个logger对象。这种命名方式里面,后面的loggers是前面logger的子logger,自动继承父loggers的log信息,正因为此,没有必要把一个应用的所有logger都配置一遍,只要把顶层的logger配置好了,然后子logger根据需要继承就行了。
Logger对象扮演了三重角色:
首先,它暴露给应用几个方法(logger.info(),logger.debug(),...)以便应用可以在运行时写log.
其次,Logger对象按照log信息的严重程度(信息等级)或者根据filter对象来决定如何处理log信息(默认的信息过滤功能).
最后,logger还负责把log信息传送给相关的handlers(信息处理功能).
logging框架中主要由四个部分组成:
Loggers: 可供程序直接调用的接口(logger.info(),logger.debug(),...)
Handlers: 决定将日志记录分配至正确的目的地(file,stream,emile,...)
Filters: 提供更细粒度的日志是否输出的判断
Formatters: 制定最终记录打印的格式布局(信息记录内容有哪些及格式)
loggers
loggers 就是程序可以直接调用的一个日志接口,可以直接向logger写入日志信息。logger并不是直接实例化使用的,而是通过logging.getLogger(name)来获取对象,事实上logger对象是单例模式,logging是多线程安全的,也就是无论程序中哪里需要打日志获取到的logger对象都是同一个。但是不幸的是logger并不支持多进程,这个在后面的章节再解释,并给出一些解决方案。
【注意】loggers对象是有父子关系的,当没有父logger对象时它的父对象是root,当拥有父对象时父子关系会被修正。举个例子logging.getLogger("abc.xyz")会创建两个logger对象,一个是abc父对象,一个是xyz子对象,同时abc没有父对象所以它的父对象是root。但是实际上abc是一个占位对象(虚的日志对象),可以没有handler来处理日志。但是root不是占位对象,如果某一个日志对象打日志时,它的父对象会同时收到日志,所以有些使用者发现创建了一个logger对象时会打两遍日志,就是因为他创建的logger打了一遍日志,同时root对象也打了一遍日志。
每个logger都有一个日志的级别。
当一个logger收到日志信息后先判断是否符合level,如果决定要处理就将信息传递给Handlers进行处理。
Handlers
Handlers 将logger发过来的信息进行准确地分配,送往正确的地方。举个栗子,送往控制台或者文件或者both或者其他地方(进程管道之类的)。它决定了每个日志的行为,是之后需要配置的重点区域。
每个Handler同样有一个日志级别,一个logger可以拥有多个handler也就是说logger可以根据不同的日志级别将日志传递给不同的handler。当然也可以相同的级别传递给多个handlers这就根据需求来灵活的设置了。
Filters
Filters 提供了更细粒度的判断,来决定日志是否需要打印。原则上handler获得一个日志就必定会根据级别被统一处理,但是如果handler拥有一个Filter可以对日志进行额外的处理和判断。例如Filter能够对来自特定源的日志进行拦截or修改甚至修改其日志级别(修改后再进行级别判断)。
logger和handler都可以安装filter甚至可以安装多个filter串联起来。
Formatters
Formatters 指定了最终某条记录打印的格式布局。Formatter会将传递来的信息拼接成一条具体的字符串,默认情况下Format只会将信息%(message)s直接打印出来。Format中有一些自带的LogRecord属性可以使用,如下表格:
一个Handler只能拥有一个Formatter 因此如果要实现多种格式的输出只能用多个Handler来实现。
配置logging基本的设置,然后在控制台输出日志
import logging
# 配置全局config,可以设置不同的信息等级
# level = logging.DEBUG,INFO,WARNING,ERROR,CRITICAL严重程度一次递增。
# format : 信息记录格式
logging.basicConfig(level = logging.INFO,
format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
logging.basicConfig的参数
filename:指定日志文件名;
filemode:和file函数意义相同,指定日志文件的打开模式,'w'或者'a';
format:指定输出的格式和内容,format可以输出很多有用的信息 # '%(asctime)s - %(name)s - %(message)s'
datefmt:指定时间格式,同time.strftime(), # %Y-%m-%d %H:%M:%S
level:设置日志级别,默认为logging.WARNNING;
stream:指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略;
format的格式说明
logging.Foamatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
属性名称 |
格式 |
说明 |
name |
%(name)s |
日志的名称 |
asctime |
%(asctime)s |
可读时间,默认格式‘2003-07-08 16:49:45,896’,逗号之后是毫秒 |
filename |
%(filename)s |
文件名,pathname的一部分 |
pathname |
%(pathname)s |
文件的全路径名称 |
funcName |
%(funcName)s |
调用日志多对应的方法名 |
levelname |
%(levelname)s |
日志的等级 |
levelno |
%(levelno)s |
数字化的日志等级 |
lineno |
%(lineno)d |
被记录日志在源码中的行数 |
module |
%(module)s |
模块名 |
msecs |
%(msecs)d |
时间中的毫秒部分 |
process |
%(process)d |
进程的ID |
processName |
%(processName)s |
进程的名称 |
thread |
%(thread)d |
线程的ID |
threadName |
%(threadName)s |
线程的名称 |
relativeCreated |
%(relativeCreated)d |
日志被创建的相对时间,以毫秒为单位 |
可以发现,logging有一个日志处理的主对象,其他处理方式都是通过addHandler添加进去,logging中包含的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服务器
FATAL:致命错误
CRITICAL:特别糟糕的事情,如内存耗尽、磁盘空间为空,一般很少使用
ERROR:发生错误时,如IO操作失败或者连接问题
WARNING:发生很重要的事件,但是并不是错误时,如用户登录密码错误
INFO:处理请求或者状态变化等日常事务
DEBUG:调试过程中使用DEBUG等级,如算法中每个循环的中间状态
对应数值
级别 |
数值 |
CRITICAL |
50 |
ERROR |
40 |
WARNING |
30 |
INFO |
20 |
DEBUG |
10 |
NOTSET |
0 |
创建一个FileHandler,并对输出消息的格式进行设置,将其添加到logger,然后将日志写入到指定的文件中
import logging
# 初始化一个logger
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
# 创建FileHandler
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# 记录信息
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
logger中添加StreamHandler,可以将日志输出到屏幕上
import logging
# 初始化一个logger
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
# 创建FileHandler
filehandler = logging.FileHandler("log.txt")
filehandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
filehandler.setFormatter(formatter)
logger.addHandler(filehandler)
# stream
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(console)
# 记录信息
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
Python中的traceback模块被用于跟踪异常返回信息,可以在logging中记录下traceback
设置 exc_info = True
import logging
# 初始化一个logger
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
# 创建FileHandler
filehandler = logging.FileHandler("log.txt")
filehandler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
filehandler.setFormatter(formatter)
logger.addHandler(filehandler)
# stream
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logger.addHandler(console)
# 记录信息
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
# 配合try except使用,捕获异常。
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.info("Finish")
"""
Modified from https://github.com/microsoft/Swin-Transformer/blob/main/logger.py
"""
import os
import sys
import logging
import functools
from termcolor import colored # 命令行颜色
@functools.lru_cache() # 使用缓存技术,加快处理速度
def create_logger(output_dir, dist_rank=0, name=""):
# create logger
logger = logging.getLogger(name) # 也可以是__name__ is the module’s name
logger.setLevel(logging.DEBUG)
logger.propagate = False
# create formatter
fmt = "[%(asctime)s %(name)s] (%(filename)s %(lineno)d): %(levelname)s %(message)s"
color_fmt = (
colored("[%(asctime)s %(name)s]", "green")
+ colored("(%(filename)s %(lineno)d)", "yellow")
+ ": %(levelname)s %(message)s"
)
# create console handlers for master process
if dist_rank == 0:
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(
logging.Formatter(fmt=color_fmt, datefmt="%Y-%m-%d %H:%M:%S")
)
logger.addHandler(console_handler)
# create file handlers
file_handler = logging.FileHandler(
os.path.join(output_dir, f"log_rank{dist_rank}.txt"), mode="a"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(fmt=fmt, datefmt="%Y-%m-%d %H:%M:%S"))
logger.addHandler(file_handler)
return logger
特此说明:仅仅作为自己学习记录使用,防止忘记了,收藏的太多以免找不到,所有权归作者所有。
主要参考(超级详细):Python logger模块 - 浅雨凉 - 博客园 (cnblogs.com)
logging介绍部分:python logging 日志模块以及多进程日志_程序员大咖的博客-CSDN博客