93 votes

Comment empiler un fichier journal en Python ?

J'aimerais mettre à ma disposition la sortie de tail -F ou quelque chose de similaire dans Python sans blocage ni verrouillage. J'ai trouvé un très vieux code pour faire cela aquí mais je pense qu'il doit y avoir un meilleur moyen ou une bibliothèque pour faire la même chose maintenant. Quelqu'un en connaît-il un ?

Idéalement, j'aurais quelque chose comme tail.getNewData() que je pouvais appeler chaque fois que je voulais plus de données.

78voto

Matt Points 1751

Non bloquant

Si vous êtes sous linux (car Windows ne prend pas en charge l'appel de select sur les fichiers), vous pouvez utiliser le module subprocess en même temps que le module select.

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)

Ceci interroge le tuyau de sortie pour de nouvelles données et les imprime lorsqu'elles sont disponibles. Normalement, la commande time.sleep(1) y print f.stdout.readline() seraient remplacés par du code utile.

Blocage de

Vous pouvez utiliser le module subprocess sans les appels supplémentaires du module select.

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

Cela imprimera également les nouvelles lignes au fur et à mesure qu'elles sont ajoutées, mais cela bloquera jusqu'à ce que le programme tail soit fermé, probablement avec f.kill() .

52voto

Paulo Scardine Points 17518

Utilisation de la module 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)

[mise à jour]

Puisque sh.tail avec _iter =True est un générateur, vous pouvez :

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

Ensuite, vous pouvez "getNewData" avec :

new_data = tail.next()

Notez que si le tampon de queue est vide, il se bloquera jusqu'à ce qu'il y ait plus de données (d'après votre question, ce que vous voulez faire dans ce cas n'est pas clair).

[mise à jour]

Cela fonctionne si vous remplacez -f par -F, mais en Python ce serait un verrouillage. Je serais plus intéressé par une fonction que je pourrais appeler pour obtenir de nouvelles données quand je le veux, si c'est possible. - Eli

Un générateur de conteneur plaçant l'appel de la queue à l'intérieur d'une boucle while True et récupérant les éventuelles exceptions d'E/S aura presque le même effet que -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 le fichier devient inaccessible, le générateur renvoie None. Cependant, il bloque toujours jusqu'à ce qu'il y ait de nouvelles données si le fichier est accessible. Ce que vous voulez faire dans ce cas n'est pas clair pour moi.

L'approche de Raymond Hettinger semble assez bonne :

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 ''

Ce générateur retournera '' si le fichier devient inaccessible ou s'il n'y a pas de nouvelles données.

[mise à jour]

L'avant-dernière réponse tourne autour du haut du fichier, il semble que chaque fois qu'il manque de données. - Eli

Je pense que la seconde sortira les dix dernières lignes lorsque le processus de queue se terminera, ce qui avec -f c'est chaque fois qu'il y a une erreur d'E/S. Le site tail --follow --retry n'est pas loin de cela dans la plupart des cas auxquels je peux penser dans les environnements de type unix.

Peut-être que si vous mettez à jour votre question pour expliquer quel est votre véritable objectif (la raison pour laquelle vous voulez imiter tail --retry), vous obtiendrez une meilleure réponse.

La dernière réponse ne suit pas réellement la queue et lit simplement ce qui est disponible au moment de l'exécution. - Eli

Bien sûr, tail affichera les 10 dernières lignes par défaut... Vous pouvez positionner le pointeur de fichier à la fin du fichier en utilisant file.seek, je laisserai une implémentation correcte comme exercice au lecteur.

À mon avis, l'approche file.read() est bien plus élégante qu'une solution basée sur un sous-processus.

27voto

nneonneo Points 56821

Le seul portable moyen de tail -f un fichier semble être, en fait, de le lire et de réessayer (après une sleep ) si le read renvoie 0. tail sur diverses plates-formes utilisent des astuces spécifiques à la plate-forme (par ex. kqueue sur BSD) pour suivre efficacement un fichier pour toujours sans avoir besoin de sleep .

Par conséquent, la mise en œuvre d'un bon tail -f purement en Python n'est probablement pas une bonne idée, car il faudrait utiliser l'implémentation la moins commune (sans recourir à des bidouillages spécifiques à la plate-forme). L'utilisation d'un simple subprocess pour ouvrir tail -f et d'itérer à travers les lignes dans un thread séparé, vous pouvez facilement implémenter un système non-bloquant tail en Python.

Exemple de mise en œuvre :

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

27voto

Isaac Turner Points 101

Solution purement pythonique utilisant readline() non bloquant

Adaptation de l'œuvre d'Ijaz Ahmad Khan réponse pour ne donner des lignes que lorsqu'elles sont complètement écrites (les lignes se terminent par un caractère de nouvelle ligne) donne une solution pythonique sans dépendances externes :

def follow(file, sleep_sec=0.1) -> Iterator[str]:
    """ Yield each line from a file as they are written.
    `sleep_sec` is the time to sleep after empty reads. """
    line = ''
    while True:
        tmp = file.readline()
        if tmp is not None:
            line += tmp
            if line.endswith("\n"):
                yield line
                line = ''
        else if sleep_sec:
            time.sleep(sleep_sec)

if __name__ == '__main__':
    with open("test.txt", 'r') as file:
        for line in follow(file):
            print(line, end='')

15voto

Eli Points 5047

J'arrive un peu tard, mais j'ai rencontré à nouveau le même problème et il y a maintenant une bien meilleure solution. Il suffit d'utiliser queue d'aronde :

Pygtail lit les lignes du fichier journal qui n'ont pas été lues. Il peut même même les fichiers journaux qui ont fait l'objet d'une rotation. Basé sur logtail2 de logcheck ( http://logcheck.org )

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X