前言
由于每次写代码的时候都需要写日志记录,就想着把它封装起来,做成装饰器,这样用着也方便,代码也简洁,所以就进入对python装饰器的研究中。
一、python装饰器的基本知识
在这里也不进行太多概念的描述了,有问题可以直接百度查找相关概念。
装饰器说白了就是将一个函数包装起来,在该函数的外面嵌套一层函数,将该函数装饰起来,做一些操作之后再返回该函数的运行结果,也就是将函数当参数传入到另一个函数中。如下代码可以很直观得看出,在目标函数之前打印”新添加的功能“
#最简单的装饰器了
def outer(func):
def inner(*args, **kwargs):
#do something
print('新添加的功能')
return func(*args, **kwargs)
return inner
python装饰器分几种,分别是函数装饰器和类装饰器,再分为装饰类和装饰函数,在此也不多赘述,可以自行百度,本次只是想简单做一个记录而已。
二、逐步进行
第一步:制作一个在函数执行错误时,自动将错误写入文件保存起来
import time
import os
import logging
from functools import wraps
#先制作了一个带参数的日志logger函数
def setExceptionLogger(name):
logger = logging.getLogger('exception')
logger.setLevel(logging.INFO)
if not os.path.exists('logs'):
os.makedirs('logs')
fh = logging.FileHandler(os.path.realpath(os.path.join('logs', 'log_{}_{}.log'.format(name, time.strftime('%Y%m%d')))))
fmt = "\n[%(asctime)s-%(name)s-%(levelname)s]: %(message)s"
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
def outer(func):
@wraps(func)
def inner(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception as e:
print(e)
# 在此将错误写入文件中
setExceptionLogger('请随意就是文件名而已').exception("[Error in {}] msg: {}".format(__name__, str(e)))
return inner
@outer()
def test():
...
虽然这样可以勉强满足要求,但是这样每个函数都给添加这个装饰器,而且对于文件的句柄没有关闭有可能会出现问题,而且除了满足对错误的记录,还应该在运行过程中对一些代码执行的记录。
第二步:给代码类添加类装饰器
正常的代码都是用类封装的,所以通过给目标类的所有函数添加如上的错误记录装饰器,同时还要添加一个logger属性,可以让人手动将代码的某一个记录代码进度写入日志文件中,其作用跟print(),控制台打印差不多。
想法1:可以用函数装饰器来装饰类,但是这样一来就显得有点low,毕竟封装成一个函数肯定没有封装成一个类来的舒服。
想法2:将目标类传入类装饰器,添加一个logger属性倒是没什么问题,但是给目标类的所有方法都添加装饰器就需要用到__getattribute__函数,这样写的话代码又长又不好看,嵌套的层数太多,那就干脆新建一个子类,继承目标类的所有属性和方法,再将其返回,这样一来最终使用的将是这个子类。
想法3:如果同时新建多个实例化的话,那如果写入同一个文件的话,有可能会造成日志文件的资源抢占问题,因此需要再返回一个文件流句柄,添加一个关闭文件流的方法。
不多说,直接上代码
import time
import os
import logging
from functools import wraps
class YoungerLogDecorator:
def __init__(self,logPath=None,logName=None):
#wraps(cls)(self)
self.logPath=logPath if logPath else './'
if not os.path.exists(self.logPath):
os.makedirs(self.logPath)
self.logName=logName
def __call__(self,cls):
if not self.logName:
self.logName = cls.__name__
@wraps(cls)
def inner(*args, **kwargs):
if not hasattr(cls, 'log'):
logger,filehandler = self.getLogger()
setattr(cls, 'log', logger)
setattr(cls,'handler',filehandler)
#ChildCls = self.retChildCls(cls)
return self.retChildCls(cls)(*args, **kwargs)
return inner
def getLogger(self):
file = os.path.realpath(os.path.join(self.logPath,f'{self.logName}.log'))
logger = logging.getLogger(self.logName)
#设置日志等级
logger.setLevel(logging.INFO)
#添加文件输出流
filehandler = logging.FileHandler(file, mode='a',encoding='utf8')
#设置日志输出格式
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s - %(message)s')
filehandler.setLevel(logging.INFO)
filehandler.setFormatter(formatter)
#添加文件流到logger
logger.addHandler(filehandler)
return logger,filehandler
def retChildCls(self,cls):
"""新建一个子类并返回,继承父类所有属性,同时给所有方法都添加装饰器"""
class ChildCls(cls):
def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
def __getattribute__(self,item):
attrs = super().__getattribute__(item)
if str(type(attrs)) == "<class 'method'>":
def decorator(*args, **kwargs):
try:
res = attrs(*args, **kwargs)
#self.closeHandler()
return res
except Exception as e:
print(e)
self.log.exception(e)
#self.closeHandler()
return decorator
else:
return attrs
def closeHandler(self):
self.handler.close()
return ChildCls
@YoungerLogDecorator()
class Test:
def __init__(self):
self.log.info('使用__init__函数')
def func1(self):
self.log.info('使用func1函数')
def func2(self):
self.log.info('使用func2函数')
def func(self):
self.log.warning('hsdfsdf')
self.log.info('this is info msg')
def func3(self):
self.log.info('使用func3函数')
def func4(self):
self.log.info('使用func4函数,关闭文件')
t = Test()
t.func()
t.closeHandler()
t1 = Test()
t1.func2()
t1.closeHandler()
总结
虽然写的不是很好,但是基本可以满足要求,能用就行,后期如果有新的需求,那就继续研究。有缘人看到的话,也可以指正我的写法,帮助我改进,加深理解。