繁体   English   中英

记录python子进程的语法错误和未捕获的异常,并将它们打印到终端

[英]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解决方案,但是...
  • 这只需要在mac上工作。
  • 这样做的动机是我是一个研究新程序员得到的错误的团队的一员。

我试过的

我尝试了以下方法,没有成功:

  • 只是在问题中使用tee 如何在使用带管道的“tee”时将stderr写入文件? (未能产生raw_input提示); 我在几个SO问题中发现的tee python实现有类似的问题
  • 覆盖sys.excepthook (无法使其适用于子sys.excepthook
  • 这个问题的最佳答案似乎很有希望,但它无法正确显示raw_input提示。
  • 日志记录模块似乎对实际写入日志文件很有用,但似乎并没有解决问题的关键
  • 自定义stderr读者
  • 无休止的谷歌搜索

您链接的基于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()

更合适的解决方案可能基于pexpectpty例如

运行记录器程序需要看起来像用户正常运行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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM