记录python子进程的语法错误和未捕获异常,并将它们打印到终端 问题
我一直在尝试编写一个程序,记录子进程的未捕获异常和语法错误。简单,对吗?只需将管道记录python子进程的语法错误和未捕获异常,并将它们打印到终端 问题,python,subprocess,stderr,Python,Subprocess,Stderr,我一直在尝试编写一个程序,记录子进程的未捕获异常和语法错误。简单,对吗?只需将管道stderr输送到正确的位置 但是,子流程是另一个python程序,我将其称为test.py,它需要像未捕获其输出/错误一样运行。也就是说,运行logger程序需要让用户看起来就像正常运行了python test.py 使问题进一步复杂化的是,如果未使用readline,则raw\u输入实际上会被发送到stderr。不幸的是,我不能只导入readline,因为我无法控制使用错误记录器运行的文件 注意事项: 我在运
stderr
输送到正确的位置
但是,子流程是另一个python程序,我将其称为test.py
,它需要像未捕获其输出/错误一样运行。也就是说,运行logger程序需要让用户看起来就像正常运行了python test.py
使问题进一步复杂化的是,如果未使用readline
,则raw\u输入实际上会被发送到stderr
。不幸的是,我不能只导入readline,因为我无法控制使用错误记录器运行的文件
注意事项:
- 我在运行此代码的机器上受到相当大的限制。我无法安装
pexpect
或编辑*customize.py
文件(因为程序将由许多不同的用户运行)。我真的觉得无论如何都应该有一个stdlib解决方案
- 这只适用于Mac电脑李>
- 这样做的动机是,我是一个研究新程序员所犯错误的团队的一员李>
我试过的
我尝试了以下方法,但没有成功:
- 仅使用问题中的
tee
(未能生成raw\u输入
提示);我在几个SO问题中发现的tee
的python实现也有类似的问题
- 覆盖
sys.excepthook
(无法使其适用于子流程)
- 最上面的答案似乎很有希望,但它未能正确显示
raw\u输入
提示
- 对于实际写入日志文件来说,这似乎很有用,但似乎并没有抓住问题的关键
- 自定义标准读取器
- 无休止的谷歌搜索
不太适合您的任务。尽管您可以通过使用
-u
选项禁用缓冲来解决“原始输入()
提示”问题:
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()
更合适的解决方案可能基于或pty
运行记录器程序需要让用户看起来像正常运行python test.py一样
您不需要安装pexpect
这是纯Python,您可以将其与代码一起安装
这是一个基于tee的模拟(test.py
以非交互方式运行):
有必要合并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()
是否能在Mac电脑上工作。根据@nneonneo在问题评论中的建议,我制作了这个程序,似乎可以完成任务。(请注意,当前,记录器文件的名称必须使用“pylog”才能正确打印到最终用户。)
什么使它不合适?那些其他模块在幕后不会做类似的事情吗?(我现在正在阅读它们。)而且,-u
标志似乎无法解决问题。(我正在使用另一个答案和其中的代码中的call
函数。)@Matthewaddams:当程序检测到它以交互方式运行(使用tty)时,除了I/O缓冲行为外,它还可能改变其行为的其他方面。我已经用test.py
:print raw_input('input string to reverse')[::-1]
对它进行了测试,它可以正常工作。顺便说一句,raw_input()
将其提示符打印到stdout(如文档所示),而不是stderr。因此,如果我将import readline
添加到test.py
,它将按预期工作。我显然不想这样做,因为我不写输入文件。(我仍然很困惑,但你得到了我的支持。)@Matthewaddams:(Python 3.2)和(Python 2.7)如果有tty,则在C级别使用PyOS\u Readline()
,并在非交互式情况下打印到标准输出。如果未启用readline
,则确实PyOS\u readline()
会打印其内容。如果问题得到解决,您可以将import readline
添加到sitecustomize.py
,usercustomize.py
。您需要如何分发程序?您是否允许使用,a来打包您的程序pexpect
是纯Python,如果不允许安装任何东西,可以将其与程序打包。子进程实际上必须是子进程吗?您可以使用execfile
运行用户提供的代码吗?@nneo是的,它必须是子流程,因为用户的代码必须具有类似于sys.argv
的内容,才能按预期运行。sys.argv
完全没有问题。您可以替换sys.argv
。您目前是如何围绕用户代码运行记录器的?他们是否明确地调用它?如果没有,你的钩子是什么?我以前做过这种事情,信不信由你——我曾经在Python自动签名系统上工作过。您可以让脚本在经过消毒的环境中执行,而无需跳转,它们的代码执行起来就像在单独的解释器中调用一样。试一试——将自己的代码放入目标环境的灵活性让您可以做比其他情况下更复杂的事情(如自省堆栈跟踪和捕获代码和执行环境)。为什么要捕获SystemExit
?这就是sys.exit()
的工作方式。对于1,您可以使用traceback.print\u exception
。
很容易故意或无意地从子程序内部弄乱基于exec的解决方案,例如,import\u main\uuuuuuuuu;删除主回溯
或#!/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()
#!/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)
#!/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()