registre los errores de syntax y las excepciones no detectadas para un subproceso python e imprímalas en el terminal

El problema

He estado intentando escribir un progtwig que registra las excepciones no detectadas y los errores de syntax de un subproceso. Fácil, ¿verdad? Sólo tubo stderr al lugar correcto.

Sin embargo , el subproceso es otro progtwig de Python, lo llamaré test.py , que debe ejecutarse como si sus resultados / errores no se capturaran. Es decir, ejecutar el progtwig logger debe parecer que el usuario acaba de ejecutar python test.py manera normal.

Para complicar aún más el problema, está el problema de que raw_input realmente se envía a stderr si no se utiliza readline . Desafortunadamente, no puedo import readline , ya que no tengo control sobre los archivos que se ejecutan utilizando mi registrador de errores.

Notas:

  • Estoy bastante restringido en las máquinas en las que este código se ejecutará. No puedo instalar pexpect o editar los archivos *customize.py (ya que el progtwig será ejecutado por muchos usuarios diferentes). Realmente siento que debería haber una solución estándar de todas formas …
  • Esto solo tiene que funcionar en macs.
  • La motivación para esto es que soy parte de un equipo que investiga los errores que los nuevos progtwigdores cometen.

Lo que he intentado

He intentado los siguientes métodos, sin éxito:

  • simplemente usando tee como en la pregunta ¿Cómo escribo stderr en un archivo mientras uso “tee” con una tubería? (no se raw_input producir mensajes de entrada sin raw_input ); implementaciones de python de tee que encontré en varias preguntas de SO tenían problemas similares
  • sobrescribiendo sys.excepthook (no se pudo hacer funcionar para un subproceso)
  • La respuesta principal de esta pregunta parecía prometedora, pero no raw_input correctamente los mensajes de raw_input .
  • El módulo de registro parece útil para escribir realmente en un archivo de registro, pero no parece llegar al punto crucial del problema.
  • lectores stderr personalizados
  • googlear sin fin

La respuesta basada en el tee que ha vinculado no es muy adecuada para su tarea. Aunque podría solucionar el problema ” raw_input() ” usando la opción -u para desactivar el almacenamiento en búfer:

 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() 

Una solución más adecuada podría basarse en pexpect o pty , por ejemplo .

ejecutar el progtwig logger debe parecer que el usuario acaba de ejecutar python test.py de forma normal.

 #!/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() 

No es necesario que instale pexpect que es Python puro, puede ponerlo junto a su código.

Aquí hay un análogo basado en tee ( test.py se ejecuta de forma no interactiva):

 #!/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) 

Es necesario fusionar stdout / stderr debido a que no está claro dónde raw_input() , getpass.getpass() pueden imprimir sus mensajes.

En este caso los hilos tampoco son necesarios:

 #!/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() 

Nota: el último ejemplo y la solución basada en tee no capturan el getpass.getpass() , pero la pexpect y 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) 

No sé si pty.spawn() funciona en macs.

Basado en el consejo de @nneonneo en los comentarios de la pregunta, hice este progtwig que parece hacer el trabajo. (Tenga en cuenta que en la actualidad, el nombre del archivo del registrador debe ser “pylog” para que los errores se impriman correctamente al usuario final.)

 #!/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()