要求:
实现一个能将函数调用信息记录到日志的装饰器:
- 把每次函数的调用时间、执行时间、调用次数写入日志;
- 可以对被装饰函数分组,调用信息记录到不同日志;
- 动态修改参数,比如日志格式;
- 动态打开关闭日志输出功能。
@call_info(arg1, arg2, arg3,...)def func(a, b): ...
解决方案:
为了让装饰器在使用上更加灵活,可以把类的实例方法作为装饰器,此时在包裹函数中就可以持有实例对象,便于修改属性和拓展功能。
- 对于内置函数
locals()
:
更新并返回表示当前本地符号表的字典。在函数代码块但不是类代码块中调用locals()
时将返回*变量。
请注意在模块层级上,locals()
和globals()
是同一个字典。
>>> def f(): x = 1 y = [1, 2, 3] z = 'abc' print(locals()) >>> f(){'x': 1, 'y': [1, 2, 3], 'z': 'abc'}
- 对于
logging
模块:
这个模块为应用与库定义了实现灵活的事件日志系统的函数与类。
使用标准库提提供的logging API最主要的好处是,所有的Python模块都可能参与日志输出,包括你的日志消息和第三方模块的日志消息。
logging.getLogger
函数:
logging.getLogger(name=None)
返回标准日志程序类,或者传递给setLoggerClass()
的最后一个类。
logging.Logger
类的addHandler
方法:
logging.Logger.addHandler(hdlr: Handler)
处理程序实例将日志事件分派到特定的目的地。
logging有一个日志处理的主对象,其它处理方式都是通过addHandler添加进去的。logging的几种handle方式如下:
logging.StreamHandler
:日志输出到流,可以是sys.stderr、sys.stdout或者文件;
logging.FileHandler
:日志输出到文件。
- 方案示例:
import time, logging, random DEFAULT_FORMAT = '%(func_name)s -> %(call_time)s\t%(used_time)s\t%(call_n)s'class CallInfo: def __init__(self, log_path, format_=DEFAULT_FORMAT, on_off=True): self.log = logging.getLogger(log_path) self.log.addHandler(logging.FileHandler(log_path)) self.log.setLevel(logging.INFO) #日志级别 self.format = format_ self.is_on = on_off #装饰器方法 def info(self, func): _call_n = 0 def wrap(*args, **kwargs): func_name = func.__name__ call_time = time.strftime(('%x %X'), time.localtime()) t0 = time.time() res = func(*args, **kwargs) used_time = time.time() - t0 nonlocal _call_n _call_n += 1 call_n = _call_n if self.is_on: self.log.info(self.format % locals()) return res return wrap #动态修改日志格式 def set_format(self, format_): self.format = format_ #动态开关日志输出 def turn_on_off(self, on_off): self.is_on = on_off#测试代码ci1 = CallInfo('mylog1.log')ci2 = CallInfo('mylog2.log')ci3 = CallInfo('mylog3.log')@ci1.infodef f(): sleep_time = random.randint(0, 6) * 0.1 time.sleep(sleep_time)@ci2.infodef g(): sleep_time = random.randint(0, 8) * 0.1 time.sleep(sleep_time)@ci3.infodef h(): sleep_time = random.randint(0, 7) * 0.1 time.sleep(sleep_time)for _ in range(30): random.choice([f, g, h])()ci1.set_format('%(func_name)s -> %(call_time)s\t%(call_n)s')for _ in range(10): random.choice([f])()
结果:
# tail -f mylog1.logf -> 09/22/19 15:04:54 0.20096874237060547 1 f -> 09/22/19 15:04:54 0.10093975067138672 2 f -> 09/22/19 15:04:54 0.4008469581604004 3 f -> 09/22/19 15:04:56 0.5006699562072754 4 f -> 09/22/19 15:04:58 0.2009880542755127 5 f -> 09/22/19 15:04:58 0.5009510517120361 6 f -> 09/22/19 15:04:59 0.4005923271179199 7 f -> 09/22/19 15:04:59 0.5016672611236572 8 f -> 09/22/19 15:05:00 0.601273775100708 9 f -> 09/22/19 15:05:01 1.0967254638671875e-05 10 f -> 09/22/19 15:05:03 11 f -> 09/22/19 15:05:03 12 f -> 09/22/19 15:05:04 13 f -> 09/22/19 15:05:04 14 f -> 09/22/19 15:05:04 15 f -> 09/22/19 15:05:04 16 f -> 09/22/19 15:05:05 17 f -> 09/22/19 15:05:05 18 f -> 09/22/19 15:05:05 19 f -> 09/22/19 15:05:06 20# tail -f mylog2.logg -> 09/22/19 15:04:53 0.40066051483154297 1 g -> 09/22/19 15:04:53 0.8015897274017334 2 g -> 09/22/19 15:04:55 0.20100760459899902 3 g -> 09/22/19 15:04:55 0.5013411045074463 4 g -> 09/22/19 15:04:55 0.8017439842224121 5 g -> 09/22/19 15:04:57 0.6015071868896484 6 g -> 09/22/19 15:04:58 0.20061421394348145 7 g -> 09/22/19 15:04:58 0.10062575340270996 8 g -> 09/22/19 15:05:03 7.62939453125e-06 9# tail -f mylog3.logh -> 09/22/19 15:04:52 0.40088629722595215 1 h -> 09/22/19 15:04:52 0.7009398937225342 2 h -> 09/22/19 15:04:53 0.10054636001586914 3 h -> 09/22/19 15:04:54 0.10030746459960938 4 h -> 09/22/19 15:04:57 0.4019501209259033 5 h -> 09/22/19 15:05:00 0.6007544994354248 6 h -> 09/22/19 15:05:01 6.4373016357421875e-06 7 h -> 09/22/19 15:05:01 0.7011284828186035 8 h -> 09/22/19 15:05:02 0.7015573978424072 9 h -> 09/22/19 15:05:02 0.4015517234802246 10 h -> 09/22/19 15:05:03 0.3012206554412842 11