¿Cómo duplicar sys.stdout en un archivo de registro?

Resuelto drue asked hace 15 años • 18 respuestas

Editar: dado que parece que no hay solución o que estoy haciendo algo tan no estándar que nadie lo sabe, revisaré mi pregunta para preguntar también: ¿Cuál es la mejor manera de realizar el registro cuando una aplicación Python está creando un muchas llamadas al sistema?

Mi aplicación tiene dos modos. En el modo interactivo, quiero que todos los resultados vayan a la pantalla y a un archivo de registro, incluidos los resultados de cualquier llamada al sistema. En modo demonio, toda la salida va al registro. El modo demonio funciona muy bien usando os.dup2(). No puedo encontrar una manera de "conectar" todos los resultados a un registro en modo interactivo, sin modificar todas y cada una de las llamadas al sistema.


En otras palabras, quiero la funcionalidad de la línea de comando 'tee' para cualquier salida generada por una aplicación Python, incluida la salida de llamada al sistema .

Para aclarar:

Para redirigir todos los resultados, hago algo como esto y funciona muy bien:

# open our log file
so = se = open("%s.log" % self.name, 'w', 0)

# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

Lo bueno de esto es que no requiere llamadas de impresión especiales del resto del código. El código también ejecuta algunos comandos de shell, por lo que es bueno no tener que lidiar también con cada uno de sus resultados individualmente.

Simplemente, quiero hacer lo mismo, excepto duplicar en lugar de redirigir.

Al principio, pensé que simplemente invertir el dup2's' debería funcionar. ¿Por qué no? Aquí está mi prueba:

import os, sys

### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###

print("foo bar")

os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

El archivo "a.log" debe ser idéntico al que se muestra en la pantalla.

drue avatar Mar 06 '09 04:03 drue
Aceptado

Tuve el mismo problema antes y encontré este fragmento muy útil:

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self
    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()
    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)
    def flush(self):
        self.file.flush()

de: http://mail.python.org/pipermail/python-list/2007-May/438106.html

 avatar Mar 05 '2009 21:03

La printdeclaración llamará al write()método de cualquier objeto que asigne a sys.stdout.

Crearía una clase pequeña para escribir en dos lugares a la vez...

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("log.dat", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

sys.stdout = Logger()

Ahora la printdeclaración se repetirá en la pantalla y se agregará a su archivo de registro:

# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)

Obviamente, esto es rápido y sucio. Algunas notas:

  • Probablemente deberías parametrizar el nombre del archivo de registro.
  • Probablemente deberías revertir sys.stdout <stdout>si no vas a iniciar sesión durante la duración del programa.
  • Es posible que desee poder escribir en varios archivos de registro a la vez o manejar diferentes niveles de registro, etc.

Todos estos son lo suficientemente sencillos como para que me sienta cómodo dejándolos como ejercicios para el lector. La idea clave aquí es que printsimplemente llama a un "objeto similar a un archivo" asignado a sys.stdout.

Kenan Banks avatar Mar 05 '2009 21:03 Kenan Banks