如何将subprocess调用程序的标准输出重定向到RotatingFileHandler处理的滚动日志文件?
如何将subprocess调用程序的标准输出重定向到RotatingFileHandler处理的滚动日志文件?
问题分析
你当前的代码无法触发日志滚动的核心原因是:子进程直接绕过了Python logging模块的逻辑,往底层文件描述符写数据。
你通过logger.fileno = logger.handlers[0].stream.fileno把handler的文件描述符暴露给subprocess,相当于让子进程直接往test.log文件写内容——这个操作完全脱离了logging模块的管控。RotatingFileHandler的滚动逻辑只有在通过logger.info()/logger.debug()等日志接口写入时才会触发(此时logging会检查文件大小并判断是否需要滚动),而子进程直接写文件的行为,logging模块根本感知不到,自然不会执行滚动操作。
解决方案:让子进程输出经过logging模块处理
正确的做法是让subprocess把输出写到管道,然后在Python代码中捕获输出内容,再通过logging接口将内容写入日志。这样就能完全利用RotatingFileHandler的滚动机制了。
方案1:一次性捕获输出后写入日志(适合输出量不大的场景)
import subprocess as subp import logging from logging.handlers import RotatingFileHandler # 配置滚动日志 logger = logging.getLogger('My rolling log') logger.setLevel(logging.INFO) # 必须设置日志级别,默认是WARNING,低于级别的日志不会输出 handler = RotatingFileHandler( 'test.log', maxBytes=1000, backupCount=1, mode='a', # 改用追加模式,避免每次启动清空日志(如果需要清空可以在初始化前删除文件) encoding='latin-1' ) logger.addHandler(handler) # 启动子进程,将stdout/stderr指向管道 s = subp.Popen( 'mycode.x', stdin=subp.PIPE, stdout=subp.PIPE, stderr=subp.PIPE, text=False ) # 发送输入并获取所有输出 sortie, err = s.communicate(input=cmd.encode()) # 将子进程stdout内容写入滚动日志 if sortie: # 按行分割输出,逐行写入(也可以直接用logger.info(sortie.decode('latin-1'))) for line in sortie.decode('latin-1').splitlines(): logger.info(line) # 处理stderr内容(可选) if err: for line in err.decode('latin-1').splitlines(): logger.error(line)
方案2:逐行捕获输出并写入日志(适合大输出量场景,避免内存占用过高)
如果子进程输出非常大,一次性读取所有内容会占用过多内存,可以改用逐行读取的方式:
import subprocess as subp import logging from logging.handlers import RotatingFileHandler # 配置滚动日志(同方案1) logger = logging.getLogger('My rolling log') logger.setLevel(logging.INFO) handler = RotatingFileHandler( 'test.log', maxBytes=1000, backupCount=1, mode='a', encoding='latin-1' ) logger.addHandler(handler) # 启动子进程 s = subp.Popen( 'mycode.x', stdin=subp.PIPE, stdout=subp.PIPE, stderr=subp.PIPE, text=False ) # 发送输入并关闭stdin s.stdin.write(cmd.encode()) s.stdin.close() # 逐行读取stdout并写入日志 for line in iter(s.stdout.readline, b''): if line: logger.info(line.decode('latin-1').rstrip('\n')) # 读取并处理stderr err = s.stderr.read() s.wait() if err: for line in err.decode('latin-1').splitlines(): logger.error(line)
额外注意事项
- 日志级别:必须设置
logger.setLevel(logging.INFO),否则默认的WARNING级别会过滤掉INFO级别的日志。 - 文件模式:原代码中的
mode='w+'会每次启动程序时清空日志文件,改用mode='a'更符合滚动日志的追加场景;如果确实需要每次启动清空,可以在初始化handler前执行import os; os.remove('test.log') if os.path.exists('test.log') else None。 - 编码一致性:确保子进程输出的编码和logging handler的编码(
latin-1)一致,避免乱码。
备注:内容来源于stack exchange,提问作者narsonalin
相关产品推荐
相关产品推荐

