python日志模块 --- logging
logging
Python标准库中提供了方便的日志记录模块,可以根据日志的不同等级进行过滤和分别记录。根据我们的需要快速的自定义日志消息记录。其中有三个重要的对象
- logger:程序中执行记录日志操作的对象,调用对应的方法即可产生一条日志消息
- handler:负责管理日志消息的处理,例如写入文件还是作为标准输出
- fromater:定义日志消息的格式
日志的等级划分
日志的级别分为以下几个等级,每个等级使用一个整数值对其进行量化指代,是一个常量值,其中logging模块中的默认的响应级别是
日志级别Level | 数值 | 对应方法 | |
CRITICAL | 50 | critical() | 严重错误级别,出现该错误已经影响到整体运行 |
ERROR | 40 | error() | 错误级别,一般用于记录程序出现错误,但不影响整体运行 |
WARNING | 30 默认级别 | warning() | 警告级别,,一般用于记录程序出现潜在错误的情形 |
INFO | 20 | info() | 事件级别,一般用于记录程序的运行过程 |
DEBUG | 10 | debug | 调试级别,一般用于记录程序运行的详细信息 |
NOTSET | 0 |
formatter
一条日志消息记录可以由用户自定义消息格式,日志消息中提供了以下信息的字符串占位符。
占位符格式 | 描述 |
%(levelno)s | 日志级别的数值 |
%(levelname)s | 日志级别名称 |
%(name)s | 该logger对象的名字 |
%(module)s | 当前执行程序名 |
%(funcName)s | 日志调用的当前函数 |
%(lineno)d | 日志调用的当前行行号 |
%(asctime)s | 日志的时间,默认为2003-07-08 16.49.45,789 格式 |
%(thread)d | 线程ID |
%(threadName)s | 线程名称 |
%(process)d | 进程ID |
%(processName) | 进程名称 |
%(message)s | 日志信息 |
定义一个日志消息格式
# 获取了一个formatter对象,该对象需要被handler使用方可生效
formatter = logging.Formatter(fmt="%(asctime)s - %(name) - %(levelname)s - %(module)s: %(message)s")
Handler
logging中两种基本类型的handler,负责处理这些日志信息,但是需要根据formatter决定日志的输出结构信息。
-
FileHandler:将日志消息写入文件
-
StreamHandler:将日志消息写入标准输出
file_handle = logging.FileHandler(filename="log.txt", mode="a+", encoding="utf-8") stream_handler = logging.StreamHandler()
fmt = logging.Formatter(fmt="%(asctime)s - %(name) - %(levelname)s - %(module)s: %(message)s") # formatter
file_handle.setFormatter(fmt) # 绑定formatter
Logger
每一个logger实例就是一个日志记录器,他们有自己属性,例如:名字,日志级别,父logger等
class Logger(Filterer): def __init__(self, name, level=NOTSET): Filterer.__init__(self) self.name = name # logger名称 self.level = _checkLevel(level) # logger级别,定义了0-50整十数极其常量, logging.WARNING = 30,也使用"WARNING"字符串,_chackLevel(level)已做处理 self.parent = None # 继承的父类,命名时使用.作为继承的标志。例如m.m1表示m1继承于m,m必须存在。 self.propagate = True # 通过下层logger的消息默认向父类传播,给父类handler判断处理 self.handlers = [] # 处理消息handler们,每一个分别处理 self.disabled = False # 标记为不可用
通常我们会通过logger = logging.getLogger(name, level)去获取一个logger,而不是示例化,该方法是logger提供的工厂方法,再创建前会根据name实现同名单例,也就不会产生同名的两个logger。
logger产生一条日志消息
使用对应的方法将会产生一条日志消息,同时还需要绑定handler和formater才能将这个消息输出记录。同时消息的等级必须同时高于logger以及handler的等级,日志消息才会被输出。
# 用于产生对应消息方法 debug(self, msg, *args, **kwargs) info(msg, *args, **kwargs) warning(msg, *args, **kwargs) critical(msg, *args, **kwargs) error(msg, *args, **kwargs)
setLevel(level) # 设置level级别,高于该级别才会交给handler处理 getEffectiveLevel() # 有效level,子logger向父logger继承的特性 addFilter(filter) # 增加一个过滤对象 addHandler(hdlr) # 增加一个handler对象 removeHandler(hdlr) # 移除handler
常见的使用
有了三个对象,可以结合使用
file_handle = logging.FileHandler(filename="log.txt", mode="a+", encoding="utf-8") # 创建handler,将日志写入log.txt文件 fmt = logging.Formatter(fmt="%(asctime)s - %(name) - %(levelname)s - %(module)s: %(message)s") # 日志格式 file_handle.setFormatter(fmt) # 添加格式 logger = logging.Logger("cmdb", level=level) # 获取handler,设置等级
logger.addHandler(file_handle) # 添加到logger中
# 使用
logger.info("一条日志消息")
# 记录栈信息
try:
process()
except Exception as e:
logger.error("错误信息", exc_info=True) # exc_info=True将会同时记录这个错误的traceback信息记录日志,方便查询错误信息
root-logger
当我们加载logger模块时,会自动创建一个root(一个logger),默认level为warning(30),只有消息等级高于warning的消息才能通过,使用logging.basicConfig()
函数可以对root进行方便的初始化配置,它的参数有
1 logging.basicConfig( 2 filename= # 如果消息写入文件,提供文件路径 3 filemode= # 设置读写模式,默认为"a" 4 format= # 格式化字符串例如:format = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s' 5 6 datefmt= # 时间格式字符:"%Y-%M-%D %H:%m:%S" 7 style= 8 level= # root的level 9 stream= # 如果是流,指定一个流对象,例如 stream=os.stderr 10 handlers= # 指定一个handdle 11 )
format
参数中不但可以写入自带占位符,还可以写入自定义占位符,例如:format="%(key1)s"
,打印消息时候,指定该key的消息value即可。
日志轮转
在logging.handers模块中,提供了多种handler,这里常用的两种轮转日志handler类
RotatingFileHandler
logging提供了将日志写入文件中,但是服务在长期运行时会产生大量的信息,写入一个文件并不方便管理,同时也是不安全的。日志信息从始至终操作一个文件,如果在运行时出现故障造成日志文件丢失,将会造成所有日志信息的丢失,同时,随着时间的积累,我们可能需要对时间线过长的日志信息进行清理,这时候使用日志回滚(RotatingFile)
日志轮转:使用多个文件分层次的去记录日志信息。随着时间的推移,新的日志文件会将旧文件进行更迭,新日志消息的会将最早的信息清除。同时这些日志内容按照顺序被分片的放在不同文件中,这些文件个数和大小可以自定义,这样我们不再担心一个巨大的日志文件出现了。在logging模块中使用了RotatingHandler
实现该功能,将处理日志文件时使用RotatingHandler
来处理消息即可。
1 import logging 2 from logging import RotatingHandler 3 4 5 logger = logging.getLogger(__name__) 6 logger.setLevel(logging.INFO) 7 8 formatter = logging.Formatter("'%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%D %H:%M:%S") 9 10 # 设置一个log.txt和两个回滚文件,每个文件最多储存1024字节大小的数据,当log.txt字节大于1024,会将数据写入log.txt.1文件,log.txt文件重置。 11 # log.txt.1文件再次被写入时会向log.txt.2文件写入内容,在接受从log.txt写入的新内容。 12 # log.txt.2文件写满后,继续写入将丢失最早的信息。最新的消息始终在log.txt中,之前的日志内容在log.txt.1和log.txt.2两个文件中轮转 13 file_hendler = logging.handlers.RotatingFileHandler(filename="log.txt", maxBytes=1024, backupCount=2) 14 # maxBytes 单个轮转文件最大字节数 15 # backupCount 轮转文件的个数;backupCount = 0时,log.txt可以不受maxBytes影响,一直写入数据,和普通日志文件相同 16 file_headler.setFormatter(formatter) 17 file_handler.setLevel(logging.INFO) 18 logger.addHandler(file) 19 20 for i in range(100): 21 logger.warning("warning %d" % i)
TimedRotatingFileHandler
该handler和上面的轮转方式一样,使用多个文件来分别记录,但是分割方式是按照每天进行分割,每个日志文件记录当天的日志消息,可以设置最长的记录日期,这里设置了一个月
1 logger = logging.getLogger("abc") 2 logger.setLevel(logging.INFO) 3 formatter = logging.Formatter("'%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%D %H:%M:%S") 4 day_handler = TimedRotatingFileHandler("log.txt", when='D', interval=1, backupCount=30, encoding="utf-8") 5 6 day_handler.setFormatter(formatter) 7 day_handler.setLevel(logging.INFO)
使用这种时间轮状的方式可以将日志按照每天进行分类,方便我们快速的对日志文件内容进行分析处理,也更加方便的管理。