python内置模块之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 = 50FATAL = 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 logginglogging.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 loggingimport 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 loggingimport 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-8import 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 wrapsdef 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 wrapsimport 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.0130558013916016sfoo函数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()
以上是 python内置模块之Log日志模块 的全部内容, 来源链接: utcz.com/z/388175.html