要求:
實現一個能將函數調用信息記錄到日誌的裝飾器:
- 把每次函數的調用時間、執行時間、調用次數寫入日誌;
- 可以對被裝飾函數分組,調用信息記錄到不同日誌;
- 動態修改參數,比如日誌格式;
- 動態打開關閉日誌輸出功能。
@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.info
def f():
sleep_time = random.randint(0, 6) * 0.1
time.sleep(sleep_time)
@ci2.info
def g():
sleep_time = random.randint(0, 8) * 0.1
time.sleep(sleep_time)
@ci3.info
def 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.log
f -> 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.log
g -> 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.log
h -> 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