在 Python 中,使用 logging 模块记录日志时,如果从另一个 Python 脚本调用该脚本,则日志记录可能会挂起。即使在使用 threading 模块创建线程并调用 communicate() 方法来获取子进程的输出后,问题依然存在。该错误现象可能出现在 2 个脚本中,其中一个脚本调用另一个脚本,前一个脚本等待后一个脚本结束,而后一个脚本使用 logging.info 记录大量日志。
2. 解决方案
问题所在在于 stdout 缓冲区已满。process.communicate() 调用并未执行,因为它出现在 while process.poll() is None: 语句之后。因此,writeIssue.py 尝试向 stdout 写入过多的字节,而所有这些字节都缓冲在 subprocess.PIPE 中,并且不会被提取出来,直到调用 communicate 为止。缓冲区具有有限的大小。当缓冲区已满时,stream.write 会阻塞,直到缓冲区有空间为止。如果缓冲区从未清空(就像在代码中发生的那样),则进程会陷入僵局。
解决方法是,在缓冲区完全填满之前调用 communicate()。可以在线程中启动 writeIssue.py,并在主线程中运行 while-thread-is-alive 循环的同时并发调用 communicate()。
# script.py
import subprocess
import time
import sys
import threading
def launch():
command = ['python', 'script2.py']
process = subprocess.Popen(command)
process.communicate()
t = threading.Thread(target=launch)
t.start()
chars = ["/","-","\","|"]
i = 0
while t.is_alive():
print chars[i],
sys.stdout.flush()
time.sleep(.3)
print "\b\b\b",
sys.stdout.flush()
i = (i + 1)%4
t.join()
# script2.py
import sys
import logging
import time
class UpgradeStatus():
def __init__(self):
logFormatter = logging.Formatter("%(asctime)s [%(levelname)-5.5s] %(message)s")
self.logger = logging.getLogger()
self.logger.setLevel(logging.DEBUG)
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG)
consoleHandler.setFormatter(logFormatter)
self.logger.addHandler(consoleHandler)
def status_change(self, status):
self.logger.info(str(status))
class UpgradeThread():
def __init__(self, link):
self.upgradethreadstatus = UpgradeStatus()
self.upgradethreadstatus.status_change("Entered upgrade routine")
for i in range(5):
procoutput = 'very huge logs, mine were 145091 characters'
self.upgradethreadstatus.status_change(procoutput)
time.sleep(1)
self.upgradethreadstatus.status_change("Exiting upgrade routine")
if __name__ == '__main__':
upgradeclass = UpgradeThread(sys.argv[1:])
需要注意的是,如果在并发的两个线程中写入 stdout,输出将会混乱。如果想要避免这种情况,则所有输出都应该由一个拥有队列的单独线程处理。所有希望写入输出的其他线程或进程都应该将字符串或日志记录推送到队列,以便专用的输出线程处理。
然后,输出线程可以使用 for 循环从队列中提取输出:
for message from iter(queue.get, None):
print(message)