¿Cómo duplicar sys.stdout en un archivo de registro?
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.
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
La print
declaració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 print
declaració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 print
simplemente llama a un "objeto similar a un archivo" asignado a sys.stdout
.