在編寫 Python 程序的時候,很容易直接調用 system
, subprocess.Popen
, subprocess.run
, subprocess.call
, subprocess.check_call
, subprocess.check_output
等方法執行命令。但是如果一個系統裏充滿了這樣的命令之後,整個系統變得難以分析和調試,在編程裏就是所謂的「可觀察性」很差。或者說,這樣的腳本系統不是「Hackable」的。
有什麼解法呢?有的,根據我多年手寫此類腳本系統的經驗,就是要把原子的命令執行統一記錄和 dump 下來,這樣在系統執行完的時候,只要分析這樣的命令序列文件就能快速定位問題。是不是和彙編很像?其實也和數據庫的SQL 語句日誌文件很像。
下面的代碼就可以做到:
import os
import subprocess
import json
from loguru import logger
g_all_commands = []
class Executor:
@staticmethod
def init():
g_all_commands = []
@staticmethod
def finish(log_file):
logger.info(f'\n')
logger.info(f'<commands>: --------------------')
logger.info(f'<commands>: all commands')
logger.info(f'<commands>: --------------------')
logger.info(f'<commands>: {log_file}')
logger.info(f'<commands>: --------------------')
logger.info(f'\n')
with open(log_file,'w') as f:
f.writelines([c+'\n' for c in g_all_commands])
@staticmethod
def run_phony(cmd):
g_all_commands.append(cmd)
@staticmethod
def system(command):
g_all_commands.append(command)
return os.system(command)
@staticmethod
def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs):
g_all_commands.append(Executor.__join(popenargs))
return subprocess.run(*popenargs, input=input, capture_output=capture_output, timeout=timeout, check=check, **kwargs)
@staticmethod
def Popen(*args, **kwargs):
g_all_commands.append(Executor.__join(args))
return subprocess.Popen(*args, **kwargs)
@staticmethod
def check_output(*popenargs, timeout=None, **kwargs):
g_all_commands.append(Executor.__join(popenargs))
return subprocess.check_output(*popenargs, timeout, **kwargs)
@staticmethod
def call(*popenargs, timeout=None, **kwargs):
g_all_commands.append(Executor.__join(popenargs))
return subprocess.call(*popenargs, timeout, **kwargs)
@staticmethod
def check_call(*popenargs, **kwargs):
g_all_commands.append(Executor.__join(popenargs))
return subprocess.check_call(*popenargs, **kwargs)
@staticmethod
def __join(*popenargs):
a = []
for e in popenargs:
if type(e)==type({}):
a.append(json.dumps(e))
elif type(e)==type([]) or type(e)==type(()):
a.append(Executor.__join(*e))
else:
a.append(str(e))
return ' '.join(a)
用法:
- 用來使用
system
的地方換成Executor.system
, 原來使用subprocess.xxx
的地方換成Executor.xxx
,參數完全一樣。 - 可以通過
Executor.init
和Executor.finish
來開始和結束,結束的時候把命令序列都記錄到一個日誌文件裏,用以分析。 - 還可以通過調用
Executor.run_phony("@some split")
方法來插入一些分隔行,便於問題診斷。phony
是 Makefile 裏.PHONY
一樣的意思,就是“僞造的,假的”的意思。