python内置模块之Log日志模块
Log日志模块
前言
logging 是python中的一个包,封装所有日志功能。
例如获取日志器 logging.getLogger 是logging包的 __init__ 文件中定义的函数(包的 __init__文件中的函数直接可以使用包名.函数名调用),如下 getLogger 代码:
def getLogger(name=None): """ Return a logger with the specified name, creating it if necessary. If no name is specified, return the root logger. """ if not name or isinstance(name, str) and name == root.name: return root return Logger.manager.getLogger(name)
root = RootLogger(WARNING)
class RootLogger(Logger): """ A root logger is not that different to any other logger, except that it must have a logging level and there is only one instance of it in the hierarchy. """ def __init__(self, level): """ Initialize the logger with the name "root". """ Logger.__init__(self, "root", level) def __reduce__(self): return getLogger, ()
即 getLogger 函数返回值为Logger类的实例对象。
例如为日志器设置默认的日志级别 logger.setLevel() 为logging包的 __init__ 文件 Logger 类中的 setLevel 函数,代码如下:
def setLevel(self, level): """ Set the logging level of this logger. level must be an int or a str. """ self.level = _checkLevel(level) self.manager._clear_cache()
def _checkLevel(level): if isinstance(level, int): rv = level elif str(level) == level: if level not in _nameToLevel: raise ValueError("Unknown level: %r" % level) rv = _nameToLevel[level] else: raise TypeError("Level not an integer or a valid string: %r" % level) return rv
_nameToLevel = { 'CRITICAL': CRITICAL, 'FATAL': FATAL, 'ERROR': ERROR, 'WARN': WARNING, 'WARNING': WARNING, 'INFO': INFO, 'DEBUG': DEBUG, 'NOTSET': NOTSET, }
CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0
一、logging函数根据它们用来跟踪的事件的级别或严重程度来命名。标准级别及其适用性描述如下(以严重程度递增排序):
默认等级是WARNING,只有高于默认等级以上的级别才会打印到控制台。
①将日志直接输出到屏幕
""" 由于默认设置的等级是warning,所有只有warning的信息会输出到控制台。 """ import logging logging.debug('debug 信息') logging.warning('只有这个会输出。。。') logging.info('info 信息')
输出结果:
②通过logging.basicConfig函数对日志的输出格式及方式做相关配置
import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='myapp.log', filemode='w') logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message') #./myapp.log文件中内容为: #Sun, 24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message #Sun, 24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message #Sun, 24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message
logging.basicConfig参数:
#logging.basicConfig函数各参数:
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.WARNING【高于该日志级别才会打印到控制台上】 stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略
③将日志同时输出到多个Handler
定义handler,并使用addHander()添加到日志器,实现日志输出到多个handler。
a、同时输出到文件和屏幕
import logging #设置一个basicConfig只能输出到一个Handler logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='myapp.log', filemode='w') #定义一个StreamHandler,将INFO级别或更高的日志信息打印到标准错误,并将其添加到当前的日志处理对象# console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logging.getLogger('').addHandler(console) #输出到文件的log级别为debug,输出到stream的log级别为info logging.debug('This is debug message') logging.info('This is info message') logging.warning('This is warning message')
b、添加一个handler:输出到文件,并根据文件大小滚动存储
在a的基础上添加一个handler
from logging.handlers import RotatingFileHandler #定义一个RotatingFileHandler,最多备份5个日志文件,每个日志文件最大10M Rthandler = RotatingFileHandler('myapp.log', maxBytes=10*1024*1024,backupCount=5) Rthandler.setLevel(logging.INFO) formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') Rthandler.setFormatter(formatter) logging.getLogger('').addHandler(Rthandler)
logging几种Handler类型:
logging.StreamHandler(默认): 日志输出到流,可以是sys.stderr、sys.stdout或者文件 logging.FileHandler: 日志输出到文件 logging.handlers.RotatingFileHandler 日志输出到文件,基于文件大小滚动存储日志 logging.handlers.TimedRotatingFileHandler 日志输出到文件,基于时间周期滚动存储日志 logging.handlers.SocketHandler: 远程输出日志到TCP/IP sockets logging.handlers.DatagramHandler: 远程输出日志到UDP sockets logging.handlers.SMTPHandler: 远程输出日志到邮件地址 logging.handlers.SysLogHandler: 日志输出到syslog logging.handlers.NTEventLogHandler: 远程输出日志到Windows NT/2000/XP的事件日志 logging.handlers.MemoryHandler: 日志输出到内存中的制定buffer logging.handlers.HTTPHandler: 通过"GET"或"POST"远程输出到HTTP服务器
④通过配置文件配置logger
a、定义配置文件 logger.conf
#logger.conf ############################################### [loggers] keys=root,example01,example02 [logger_root] level=DEBUG handlers=hand01,hand02 [logger_example01] handlers=hand01,hand02 qualname=example01 propagate=0 [logger_example02] handlers=hand01,hand03 qualname=example02 propagate=0 ############################################### [handlers] keys=hand01,hand02,hand03 [handler_hand01] class=StreamHandler level=INFO formatter=form02 args=(sys.stderr,) [handler_hand02] class=FileHandler level=DEBUG formatter=form01 args=('myapp.log', 'a') [handler_hand03] class=handlers.RotatingFileHandler level=INFO formatter=form02 args=('myapp.log', 'a', 10*1024*1024, 5) ############################################### [formatters] keys=form01,form02 [formatter_form01] format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s datefmt=%a, %d %b %Y %H:%M:%S [formatter_form02] format=%(name)-12s: %(levelname)-8s %(message)s datefmt=
b、 logging.config 获取配置
import logging import logging.config logging.config.fileConfig("logger.conf") logger = logging.getLogger("example01") logger.debug('This is debug message') logger.info('This is info message') logger.warning('This is warning message')
import logging import logging.config logging.config.fileConfig("logger.conf") logger = logging.getLogger("example02") logger.debug('This is debug message') logger.info('This is info message') logger.warning('This is warning message')
二、logging库【python中已封装好的功能模块】采取了模块化的设计,
提供了许多组件:记录器(日志器logger)、处理器(handles)、过滤器和格式化器(formatters)。
1、Logger 暴露了应用程序代码能直接使用的接口。
2、Handler将(记录器产生的)日志记录发送至合适的目的地。
3、Filter提供了更好的粒度控制,它可以决定输出哪些日志记录。
4、Formatter 指明了最终输出中日志记录的布局。
三、Loggers
Logger 对象要做三件事情。
首先,它们向应用代码暴露了许多方法,这样应用可以在运行时记录消息。
其次,记录器对象通过严重程度(默认的过滤设施)或者过滤器对象来决定哪些日志消息需要记录下来。
第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理器。
常用的记录器对象的方法分为两类:配置和发送消息。
这些是最常用的配置方法:
①Logger.setLevel()指定logger将会处理的最低的安全等级日志信息:
debug是最低的内置安全等级;
critical是最高的内置安全等级。
例如,如果严重程度为INFO,记录器将只处理INFO,WARNING,ERROR和CRITICAL消息,DEBUG消息被忽略。
②Logger.addHandler()和Logger.removeHandler()从记录器对象中添加和删除处理程序对象。处理器详见Handlers。
③Logger.addFilter()和Logger.removeFilter()从记录器对象添加和删除过滤器对象。
四、Handlers
处理程序对象负责将适当的日志消息(基于日志消息的严重性)分派到处理程序的指定目标。【根据分配到的Handlers不同,又将不同的日志信息以不同的方式输出】
Logger 对象可以通过addHandler()方法增加零个或多个handler对象。
举个例子:①一个应用可以将所有的日志消息发送至日志文件;②所有的错误级别(error)及以上级别的日志消息发送至标准输出;③所有的严重级别(critical)日志消息发送至某个电子邮箱。
在这个例子中需要三个独立的处理器,每一个负责将特定级别的日志消息发送至特定的位置。
常用的有4种:
1、 logging.StreamHandler : 控制台输出
向类似与 sys.stdout 或者 sys.stderr 的任何文件对象(file object)输出信息。
它的构造函数是: StreamHandler([strm])
参数介绍:strm参数是一个文件对象。默认是sys.stderr(不传strm参数时,默认将日志信息输出至控制台)。
2、 logging.FileHandler : 文件输出
用于向一个文件输出日志信息。
构造函数是: FileHandler(filename[,mode])
参数介绍:①filename文件名:必须指定一个文件名;②mode是文件的打开方式。默认是’a',即添加到文件末尾。
3、logging.handlers.RotatingFileHandler : 按照大小自动分割日志文件,一旦达到指定的大小重新生成文件
这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出。
例子:比如日志文件是chat.log。
当chat.log达到指定的大小之后,RotatingFileHandler自动把文件改名为chat.log.1。
不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。
构造函数: RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]])
参数介绍:①filename文件名:必须指定一个文件名。②mode是文件的打开方式。默认是’a',即添加到文件末尾。③maxBytes用于指定日志文件的最大文件大小。【如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。】。④backupCount用于指定保留的备份文件的个数。【如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。】
4、 logging.handlers.TimedRotatingFileHandler :按照时间自动分割日志文件
这个Handler和RotatingFileHandler类似;
不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。
重命名的过程与RotatingFileHandler类似:不过新的文件不是附加数字,而是当前时间。
构造函数: TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])
参数介绍:
①filename文件名:必须指定一个文件名。
②mode是文件的打开方式。默认是’a',即添加到文件末尾。③backupCount用于指定保留的备份文件的个数。
④interval是时间间隔。
⑤when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:
S 秒 M 分 H 小时 D 天 W 每星期(interval==0时代表星期一) midnight 每天凌晨
配置方法:
① handlers.setLevel() :记录器的级别决定了消息是否要传递给处理器。每个处理器的级别决定了消息是否要分发。【 logger.setLevel() 为记录器设置日志级别(根据设置的日志级别决定日志信息是否传递给处理器), handlers.setLevel 为处理器设置日志级别(根据每个处理器设置的日志级别决定日志信息是否输出至指定地方)。】
② handlers.setFormatter() :为该处理器选择一个格式化器。
③ handlers.addFilter() 和 handlers.removeFilter():分别配置和取消配置处理器上的过滤器对象。
五、Formatters
Formatter对象设置日志信息最后的规则、结构和内容;【日志生成时的样式】
默认的时间格式为%Y-%m-%d %H:%M:%S。
如下图为Formatter常用的一些信息:
六、代码示例
代码示例1:get_logger(获取日志器):
# 导包 import logging.handlers import os import time # 新建 类 class Logs: root_path = os.path.abspath(os.path.dirname(__file__)).split('shippingSchedule')[0] # 新建一个日志器变量 __logger = None # 定义生成日志文件的时间 __log_time = time.strftime("%Y-%m-%d", time.localtime()) # 新建获取日志器的方法 @classmethod def get_logger(cls): # 判断日志器为空: if cls.__logger is None: # 获取日志器【Logger 暴露了应用程序代码能直接使用的接口】 cls.__logger = logging.getLogger() # 包名.方法名直接调用logging包下的初始化模块中的getLogger()返回RootLogger # 修改默认级别 cls.__logger.setLevel(logging.DEBUG) log_path = cls.root_path + os.sep + "python--log" + os.sep + "info.python--log" + "_" + cls.__log_time # 获取处理器【Handler将(记录器产生的)日志记录发送至合适的目的地】 th = logging.handlers.TimedRotatingFileHandler(filename=log_path, when="midnight", interval=1, backupCount=3, encoding="utf-8") # 获取格式化器【Filter提供了更好的粒度控制,它可以决定输出哪些日志记录】 fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s" fm = logging.Formatter(fmt) # Formatter 指明了最终输出中日志记录的布局。 # 将格式化器添加到处理器中 th.setFormatter(fm) # 将处理器添加到日志器中 cls.__logger.addHandler(th) # 返回日志器 return cls.__logger if __name__ == '__main__': log = Logs.get_logger() log.info("测试信息级别日志") log.error("测试错误级别")
代码示例2:输出log到控制台以及同时将log写入log文件
# encoding:utf-8 import logging from logging import handlers class Logger(object): level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'crit': logging.CRITICAL } # 日志级别关系映射 def __init__(self, filename, level='info', when='D', backCount=3, fmt='%(asctime)s -%(name)s- %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'): self.logger = logging.getLogger(filename) # 获取日志器(filename是日志器的名称,如果不填,默认日志器名称为root) format_str = logging.Formatter(fmt) # 设置日志格式 self.logger.setLevel(self.level_relations.get(level)) # 设置日志级别 sh = logging.StreamHandler() # 输出至控制台 sh.setFormatter(format_str) # 设置输出至控制台上日志格式 th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount, encoding='utf-8') # 往文件里写入#指定间隔时间自动生成文件的处理器 # 实例化TimedRotatingFileHandler # interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种: # S 秒 # M 分 # H 小时、 # D 天、 # W 每星期(interval==0时代表星期一) # midnight 每天凌晨 th.setFormatter(format_str) # 设置文件里写入的格式 self.logger.addHandler(sh) # 把对象加到logger里 self.logger.addHandler(th) if __name__ == '__main__': log = Logger('all.log', level='debug') log.logger.debug('debug') log.logger.info('info') log.logger.warning('警告') log.logger.error('报错') log.logger.critical('严重') Logger('error.log', level='error').logger.error('error')
代码示例3:输出log日志到控制台以及同时将log日志写入log文件+装饰器函数代替测试用例脚本中函数调用。
# -*- encoding:utf-8 -*- import logging import os import time import traceback from functools import wraps """handlers是什么?""" # logging模块中包含的类 # 用来自定义日志对象的规则(比如:设置日志输出格式、等级等) # 常用子类:StreamHandler、FileHandler # StreamHandler 控制台输出日志 # FileHandler 日志输出到文件 # 日志文件路径 LOG_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "log") if not os.path.exists(LOG_PATH): os.mkdir(LOG_PATH) class Logger: def __init__(self): # 创建日志路径和时间 self.log_path = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d"))) # 创建一个logger日志对象,并传参为log日志器命名为log self.logger = logging.getLogger("log") # 为日志器设置默认的日志级别 self.logger.setLevel(logging.DEBUG) # 创建日志格式对象 self.formater = logging.Formatter('[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s') # 创建FileHandler对象(输出log至文件) self.file_handlers = logging.FileHandler(self.log_path, mode='a', encoding="UTF-8") # 创建StreamHandler对象(输出log至控制台) self.console_handlers = logging.StreamHandler() # FileHandler对象定义日志级别 self.file_handlers.setLevel(logging.DEBUG) # StreamHandler对象定义日志级别 self.console_handlers.setLevel(logging.DEBUG) # 设置FileHandler处理器的格式 self.file_handlers.setFormatter(self.formater) # 设置StreamHandler处理器的格式 self.console_handlers.setFormatter(self.formater) # logger日志对象加载FileHandler对象 self.logger.addHandler(self.file_handlers) # logger日志对象加载StreamHandler对象 self.logger.addHandler(self.console_handlers) Logger = Logger().logger # 定义一个装饰器,为修饰测试方法提供附加操作(测试方法调用前,测试方法调用后) def decorate_log(func): @wraps(func) def log(*args, **kwargs): Logger.info(f'------开始执行{func.__name__}------') try: func(*args, **kwargs) except Exception as e: Logger.error(f'------{func.__name__}执行失败,失败原因:{e}------') Logger.error(f"{func.__name__} is error,here are details:{traceback.format_exc()}") raise e else: Logger.info(f'------{func.__name__}执行成功------') return log @decorate_log def hbq(): assert 1 == 1 if __name__ == '__main__': hbq() # Logger.info("---测试开始---") # Logger.error("---测试结束---") # Logger.debug("---测试结束---")
衍生:结合代码示例3:
python装饰器functools.wraps(func)详解
1、先看一段代码:
def is_login(func): def foo(*args, **kwargs): return func(*args, **kwargs) return foo def test(): print('我是:', test.__name__) @is_login def test1(): print('我是:', test1.__name__) @is_login def test2(): print('我是:', test2.__name__) if __name__ == '__main__': test() test1() test2()
运行结果:
我是: test
我是: foo
我是: foo
可以发现函数的函数名即 func.__name__已被装饰器改变,变成了装饰器内返回的函数的函数名
2、在装饰器内返回的函数的函数名上新增 @wraps 装饰器/ functools.wraps(func) 装饰器
from functools import wraps def is_login(func): @wraps(func) def foo(*args, **kwargs): return func(*args, **kwargs) return foo def test(): print('我是:', test.__name__) @is_login def test1(): print('我是:', test1.__name__) @is_login def test2(): print('我是:', test2.__name__) if __name__ == '__main__': test() test1() test2()
运行结果:
我是: test
我是: test1
我是: test2
结论:
@wraps 可以保证被装饰器修饰的函数的 func.__name__ 的值即函数名保持不变。
装饰器的优化
以时间装饰器为例,进行优化
- 装饰器的统一模板
from functools import wraps # 对函数的装饰器, 对类func最好为cls def decorate(func): @wraps(func) # 增添或修改功能的函数 def wrapper(*args,**kwargs): # 执行被装饰的函数 result = func(*args,**kwargs) # 返回结果 return result # 返回内层函数 return wrapper
普通--时间装饰器
from functools import wraps import time from random import randint def use_time(func): @wraps(func) def wrapper(*args,**kwargs): st_time = time.time() result = func(*args,**kwargs) end_time = time.time() print(f'{func.__name__}函数use_time:{end_time-st_time}s') return wrapper @use_time def foo(): time.sleep(randint(1,3)) for _ in range(3): foo()
运行结果:
foo函数use_time:3.0130558013916016s foo函数use_time:1.0116651058197021s foo函数use_time:1.01423978805542s
下面对改装饰器进行优化(解耦)
- 可以发先上面时间装饰器计算的结果,只能在控制台上打印
- 那我们怎样才能将它输出为日志呢???
- 我们需要将他的结果进行自定输出
# 在增加一层函数 from functools import wraps import time from random import randint def record(output): def use_time(func): @wraps(func) def wrapper(*args, **kwargs): st_time = time.time() func(*args, **kwargs) end_time = time.time() # print(f'{func.__name__}函数use_time:{end_time-st_time}s') output(func.__name__, 'use', end_time - st_time) return wrapper return use_time # 改装饰器的结果就可以自定义了,下面以print函数为例 @record(print) def foo(): time.sleep(randint(2, 5)) if __name__ == '__main__': foo()
结果输出日志
# 在增加一层函数 from functools import wraps import time from random import randint def record(output): def use_time(func): @wraps(func) def wrapper(*args, **kwargs): st_time = time.time() func(*args, **kwargs) end_time = time.time() # print(f'{func.__name__}函数use_time:{end_time-st_time}s') output(func.__name__, end_time - st_time) return wrapper return use_time def write_log(name, content): with open('./time.log', 'a', encoding='utf-8')as f: f.write(f'{name}耗时:{content}\r\n') # \r\n 换行 # 只需要将装饰器改为@record(write_log) @record(write_log) def foo(): time.sleep(randint(2, 5)) if __name__ == '__main__': foo()