[英]log syntax errors and uncaught exceptions for a python subprocess and print them to the terminal
我一直在尝试编写一个程序来记录子进程的未捕获的异常和语法错误。 容易,对吗? 把stderr
管道送到正确的地方。
但是 ,子进程是另一个python程序 - 我将其称为test.py
- 需要运行,就像它的输出/错误没有被捕获一样。 也就是说,运行记录器程序需要看起来像用户正常运行python test.py
更复杂的问题是, 这个问题是raw_input
实际上被发送到stderr
,如果readline
不被使用。 不幸的是,我不能只import readline
,因为我无法控制使用我的错误记录器运行的文件。
笔记:
pexpect
或编辑*customize.py
文件(因为该程序将由许多不同的用户运行)。 我真的觉得应该有一个stdlib解决方案,但是... 我尝试了以下方法,没有成功:
tee
如何在使用带管道的“tee”时将stderr写入文件? (未能产生raw_input
提示); 我在几个SO问题中发现的tee
python实现有类似的问题 sys.excepthook
(无法使其适用于子sys.excepthook
) raw_input
提示。 您链接的基于T恤的答案不太适合您的任务。 虽然你可以通过使用-u
选项来修复“ raw_input()
提示”问题来禁用缓冲:
errf = open('err.txt', 'wb') # any object with .write() method
rc = call([sys.executable, '-u', 'test.py'], stderr=errf,
bufsize=0, close_fds=True)
errf.close()
更合适的解决方案可能基于pexpect
或pty
, 例如 。
运行记录器程序需要看起来像用户正常运行python test.py。
#!/usr/bin/env python
import sys
import pexpect
with open('log', 'ab') as fout:
p = pexpect.spawn("python test.py")
p.logfile = fout
p.interact()
你不需要安装pexpect
它是纯Python你可以把它放在你的代码旁边。
这是一个基于tee的模拟( test.py
以非交互方式运行):
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE, STDOUT
from threading import Thread
def tee(infile, *files):
"""Print `infile` to `files` in a separate thread."""
def fanout(infile, *files):
flushable = [f for f in files if hasattr(f, 'flush')]
for c in iter(lambda: infile.read(1), ''):
for f in files:
f.write(c)
for f in flushable:
f.flush()
infile.close()
t = Thread(target=fanout, args=(infile,)+files)
t.daemon = True
t.start()
return t
def call(cmd_args, **kwargs):
stdout, stderr = [kwargs.pop(s, None) for s in 'stdout', 'stderr']
p = Popen(cmd_args,
stdout=None if stdout is None else PIPE,
stderr=None if stderr is None else (
STDOUT if stderr is STDOUT else PIPE),
**kwargs)
threads = []
if stdout is not None:
threads.append(tee(p.stdout, stdout, sys.stdout))
if stderr is not None and stderr is not STDOUT:
threads.append(tee(p.stderr, stderr, sys.stderr))
for t in threads: t.join() # wait for IO completion
return p.wait()
with open('log','ab') as file:
rc = call([sys.executable, '-u', 'test.py'], stdout=file, stderr=STDOUT,
bufsize=0, close_fds=True)
有必要合并stdout / stderr,因为不清楚raw_input()
, getpass.getpass()
可能会打印其提示。
在这种情况下,线程也不是必需的:
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE, STDOUT
with open('log','ab') as file:
p = Popen([sys.executable, '-u', 'test.py'],
stdout=PIPE, stderr=STDOUT,
close_fds=True,
bufsize=0)
for c in iter(lambda: p.stdout.read(1), ''):
for f in [sys.stdout, file]:
f.write(c)
f.flush()
p.stdout.close()
rc = p.wait()
注意:最后一个示例和基于tee的解决方案不捕获getpass.getpass()
提示符,但pexpect
和基于pty
的解决方案执行:
#!/usr/bin/env python
import os
import pty
import sys
with open('log', 'ab') as file:
def read(fd):
data = os.read(fd, 1024)
file.write(data)
file.flush()
return data
pty.spawn([sys.executable, "test.py"], read)
我不知道pty.spawn()
是否适用于macs。
根据@nneonneo在问题评论中的建议,我制作了这个程序似乎完成了工作。 (请注意,目前,记录器文件的名称必须通过“pylog”才能将错误正确打印到最终用户。)
#!/usr/bin/python
'''
This module logs python errors.
'''
import socket, os, sys, traceback
def sendError(err):
# log the error (in my actual implementation, this sends the error to a database)
with open('log','w') as f:
f.write(err)
def exceptHandler(etype, value, tb):
"""An additional wrapper around our custom exception handler, to prevent errors in
this program from being seen by end users."""
try:
subProgExceptHandler(etype, value, tb)
except:
sys.stderr.write('Sorry, but there seems to have been an error in pylog itself. Please run your program using regular python.\n')
def subProgExceptHandler(etype, value, tb):
"""A custom exception handler that both prints error and traceback information in the standard
Python format, as well as logs it."""
import linecache
errorVerbatim = ''
# The following code mimics a traceback.print_exception(etype, value, tb) call.
if tb:
msg = "Traceback (most recent call last):\n"
sys.stderr.write(msg)
errorVerbatim += msg
# The following code is a modified version of the trackeback.print_tb implementation from
# cypthon 2.7.3
while tb is not None:
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
# Filter out exceptions from pylog itself (eg. execfile).
if not "pylog" in filename:
msg = ' File "%s", line %d, in %s\n' % (filename, lineno, name)
sys.stderr.write(msg)
errorVerbatim += msg
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals)
if line:
msg = ' ' + line.strip() + '\n'
sys.stderr.write(msg)
errorVerbatim += msg
tb = tb.tb_next
lines = traceback.format_exception_only(etype, value)
for line in lines:
sys.stderr.write(line)
errorVerbatim += line
# Send the error data to our database handler via sendError.
sendError(errorVerbatim)
def main():
"""Executes the program specified by the user in its own sandbox, then sends
the error to our database for logging and analysis."""
# Get the user's (sub)program to run.
try:
subProgName = sys.argv[1]
subProgArgs = sys.argv[3:]
except:
print 'USAGE: ./pylog FILENAME.py *ARGS'
sys.exit()
# Catch exceptions by overriding the system excepthook.
sys.excepthook = exceptHandler
# Sandbox user code exeuction to its own global namespace to prevent malicious code injection.
execfile(subProgName, {'__builtins__': __builtins__, '__name__': '__main__', '__file__': subProgName, '__doc__': None, '__package__': None})
if __name__ == '__main__':
main()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.