¿Cómo puedo seguir un archivo de registro en Python?

Me gustaría que la salida de tail -F o algo similar esté disponible para mí en Python sin bloquear o bloquear. He encontrado un código muy antiguo para hacer eso aquí , pero estoy pensando que debe haber una mejor manera o una biblioteca para hacer lo mismo a estas alturas. Alguien sabe de uno?

Idealmente, tendría algo como tail.getNewData() que podría llamar cada vez que quisiera más datos.

No locking

Si está en Linux (ya que Windows no admite la selección de archivos en las llamadas) puede utilizar el módulo de subproceso junto con el módulo de selección.

 import time import subprocess import select f = subprocess.Popen(['tail','-F',filename],\ stdout=subprocess.PIPE,stderr=subprocess.PIPE) p = select.poll() p.register(f.stdout) while True: if p.poll(1): print f.stdout.readline() time.sleep(1) 

Esto sondea la tubería de salida en busca de nuevos datos y los imprime cuando está disponible. Normalmente, el time.sleep(1) y print f.stdout.readline() serían reemplazados por un código útil.

Bloqueo

Puede utilizar el módulo de subproceso sin las llamadas extra del módulo de selección.

 import subprocess f = subprocess.Popen(['tail','-F',filename],\ stdout=subprocess.PIPE,stderr=subprocess.PIPE) while True: line = f.stdout.readline() print line 

Esto también imprimirá nuevas líneas a medida que se agreguen, pero se bloqueará hasta que se cierre el progtwig de cola, probablemente con f.kill() .

Usando el módulo sh (pip install sh):

 from sh import tail # runs forever for line in tail("-f", "/var/log/some_log_file.log", _iter=True): print(line) 

[actualizar]

Como sh.tail with _iter = True es un generador, puedes:

 import sh tail = sh.tail("-f", "/var/log/some_log_file.log", _iter=True) 

Entonces puedes “obtenerNewData” con:

 new_data = tail.next() 

Tenga en cuenta que si el buffer de cola está vacío, se bloqueará hasta que haya más datos (de su pregunta no queda claro qué desea hacer en este caso).

[actualizar]

Esto funciona si reemplaza -f con -F, pero en Python estaría bloqueado. Estaría más interesado en tener una función a la que pueda llamar para obtener datos nuevos cuando lo desee, si eso es posible. – Eli

Un generador de contenedor que coloca la llamada de cola dentro de un bucle True y captura eventuales excepciones de E / S tendrá casi el mismo efecto de -F.

 def tail_F(some_file): while True: try: for line in sh.tail("-f", some_file, _iter=True): yield line except sh.ErrorReturnCode_1: yield None 

Si el archivo se vuelve inaccesible, el generador devolverá Ninguno. Sin embargo, aún se bloquea hasta que haya nuevos datos si el archivo es accesible. No queda claro para mí qué quiere hacer en este caso.

El enfoque de Raymond Hettinger parece bastante bueno:

 def tail_F(some_file): first_call = True while True: try: with open(some_file) as input: if first_call: input.seek(0, 2) first_call = False latest_data = input.read() while True: if '\n' not in latest_data: latest_data += input.read() if '\n' not in latest_data: yield '' if not os.path.isfile(some_file): break continue latest_lines = latest_data.split('\n') if latest_data[-1] != '\n': latest_data = latest_lines[-1] else: latest_data = input.read() for line in latest_lines[:-1]: yield line + '\n' except IOError: yield '' 

Este generador devolverá ” si el archivo se vuelve inaccesible o si no hay datos nuevos.

[actualizar]

La segunda respuesta final gira alrededor de la parte superior del archivo que parece que se queda sin datos. – Eli

Creo que la segunda dará salida a las últimas diez líneas cada vez que finalice el proceso de cola, que con -f es cuando hay un error de E / S. El comportamiento de tail --follow --retry no está lejos de esto para la mayoría de los casos que se me ocurren en entornos similares a Unix.

Tal vez si actualiza su pregunta para explicar cuál es su objective real (la razón por la que desea imitar la cola –retry), obtendrá una mejor respuesta.

La última respuesta no sigue realmente la cola y simplemente lee lo que está disponible en el tiempo de ejecución. – Eli

Por supuesto, la cola mostrará las últimas 10 líneas de forma predeterminada … Puede colocar el puntero del archivo al final del archivo usando file.seek, dejaré una implementación adecuada como un ejercicio para el lector.

En mi opinión, el enfoque de file.read () es mucho más elegante que una solución basada en subprocesos.

De hecho, la única forma portátil de seguir con la tail -f un archivo puede leerse y volver a intentarlo (después de la sleep ) si la read devuelve 0. Las utilidades de la tail en varias plataformas utilizan trucos específicos de la plataforma (por ejemplo, kqueue en BSD ) para seguir de manera eficiente un archivo para siempre sin necesidad de sleep .

Por lo tanto, implementar una buena tail -f puramente en Python probablemente no sea una buena idea, ya que tendrías que usar la implementación del mínimo común denominador (sin recurrir a hacks específicos de la plataforma). Usando un subprocess simple para abrir tail -f e iterando a través de las líneas en un hilo separado, puede implementar fácilmente una operación de tail no bloqueante en Python.

Ejemplo de implementación:

 import threading, Queue, subprocess tailq = Queue.Queue(maxsize=10) # buffer at most 100 lines def tail_forever(fn): p = subprocess.Popen(["tail", "-f", fn], stdout=subprocess.PIPE) while 1: line = p.stdout.readline() tailq.put(line) if not line: break threading.Thread(target=tail_forever, args=(fn,)).start() print tailq.get() # blocks print tailq.get_nowait() # throws Queue.Empty if there are no lines to read 

Entonces, esto está llegando bastante tarde, pero me encontré nuevamente con el mismo problema, y ​​ahora hay una solución mucho mejor. Solo usa pygtail :

Pygtail lee las líneas del archivo de registro que no se han leído. Incluso manejará archivos de registro que han sido rotados. Basado en logcheck’s logtail2 ( http://logcheck.org )

Idealmente, tendría algo como tail.getNewData () que podría llamar cada vez que quisiera más datos

Ya tenemos uno y es muy bonito. Simplemente llame a f.read () cuando desee más datos. Comenzará a leer donde se detuvo la lectura anterior y leerá hasta el final de la secuencia de datos:

 f = open('somefile.log') p = 0 while True: f.seek(p) latest_data = f.read() p = f.tell() if latest_data: print latest_data print str(p).center(10).center(80, '=') 

Para leer línea por línea, use f.readline () . A veces, el archivo que se está leyendo terminará con una línea de lectura parcial. Maneja ese caso con f.tell () encontrando la posición actual del archivo y usando f.seek () para mover el puntero del archivo al principio de la línea incompleta. Vea esta receta de ActiveState para el código de trabajo.

Puede usar la biblioteca ‘tailer’: https://pypi.python.org/pypi/tailer/

Tiene una opción para obtener las últimas líneas:

 # Get the last 3 lines of the file tailer.tail(open('test.txt'), 3) # ['Line 9', 'Line 10', 'Line 11'] 

Y también puede seguir un archivo:

 # Follow the file as it grows for line in tailer.follow(open('test.txt')): print line 

Si uno quiere un comportamiento similar al de una cola, esa parece ser una buena opción.

Todas las respuestas que usan tail -f no son pythonic.

Aquí está la forma pythonica: (sin utilizar ninguna herramienta o biblioteca externa)

 def follow(thefile): while True: line = thefile.readline() if not line or not line.endswith('\n'): time.sleep(0.1) continue yield line if __name__ == '__main__': logfile = open("run/foo/access-log","r") loglines = follow(logfile) for line in loglines: print(line, end='') 

Otra opción es la biblioteca de tailhead que proporciona las versiones de Python de tail utilidades tail y head y la API que puede usar en su propio módulo.

Originalmente basado en el módulo de tailer , su principal ventaja es la capacidad de seguir archivos por ruta, es decir, puede manejar la situación cuando se recrea el archivo. Además, tiene algunas correcciones de errores para varios casos de borde.

La adaptación de la respuesta de Ijaz Ahmad Khan a las líneas de rendimiento solo cuando están completamente escritas (las líneas terminan con caracteres de nueva línea) ofrece una solución pythonica sin dependencias externas:

 def follow(file) -> Iterator[str]: """ Yield each line from a file as they are written. """ line = '' while True: tmp = file.readline() if tmp is not None: line += tmp if line.endswith("\n"): yield line line = '' else: time.sleep(0.1) if __name__ == '__main__': for line in follow(open("test.txt", 'r')): print(line, end='') 

También puede utilizar el comando ‘AWK’.
Ver más en: http://www.unix.com/shell-programming-scripting/41734-how-print-specific-lines-awk.html
awk se puede usar para seguir la última línea, las últimas líneas o cualquier línea de un archivo.
Esto se puede llamar desde python.

Si está en Linux, implementa una implementación sin locking en Python de la siguiente manera.

 import subprocess subprocess.call('xterm -title log -hold -e \"tail -f filename\"&', shell=True, executable='/bin/csh') print "Done" 

Python es “baterías incluidas”, tiene una buena solución para él: https://pypi.python.org/pypi/pygtail

Lee las líneas del archivo de registro que no se han leído. Recuerda dónde terminó la última vez, y continúa desde allí.

 import sys from pygtail import Pygtail for line in Pygtail("some.log"): sys.stdout.write(line)