web-dev-qa-db-fra.com

Comment puis-je suivre un fichier journal en Python?

J'aimerais que la sortie de tail -F ou de quelque chose de similaire soit disponible en Python sans blocage ni verrouillage. J'ai trouvé un très vieux code pour le faire ici , mais je pense qu'il doit y avoir une meilleure façon ou une bibliothèque de faire la même chose maintenant. Quelqu'un en sait un?

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

60
Eli

Donc, cela arrive assez tard, mais je me suis retrouvé face au même problème et il existe maintenant une solution bien meilleure. Il suffit d'utiliser pygtail :

Pygtail lit les lignes du fichier journal qui n'ont pas été lues. Il va même gérer les fichiers journaux qui ont été pivotés. Basé sur logtail2 de logcheck ( http://logcheck.org )

12
Eli

Non bloquant

Si vous êtes sur linux (Windows ne prenant pas en charge l’appel de sélection sur des fichiers), vous pouvez utiliser le module de sous-processus avec le module de sélection.

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)

Cela interroge le canal de sortie pour les nouvelles données et l’imprime lorsqu’il est disponible. Normalement, time.sleep(1) et print f.stdout.readline() seraient remplacés par du code utile.

Blocage

Vous pouvez utiliser le module de sous-processus sans les appels de module de sélection supplémentaires.

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

Cela va aussi imprimer les nouvelles lignes à mesure qu’elles sont ajoutées, mais cela restera bloqué jusqu’à la fermeture du programme tail, probablement avec f.kill().

57
Matt

Utilisation du 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)

[mettre à 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 bloquera jusqu'à ce qu'il y ait plus de données (votre question ne dit pas clairement ce que vous voulez faire dans ce cas).

[mettre à jour]

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

Un générateur de conteneur plaçant l'appel final dans une boucle while tout en capturant 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 renverra Aucun. Cependant, il bloque toujours jusqu'à ce qu'il y ait de nouvelles données si le fichier est accessible. Ce que je veux faire dans ce cas n’est pas clair pour moi.

L'approche de Raymond Hettinger semble plutôt 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.

[mettre à jour]

L'avant-dernière réponse tourne en rond vers le haut du fichier, semble-t-il chaque fois qu'il manque de données. - Eli 

Je pense que le second affichera les dix dernières lignes à la fin du processus final, ce qui correspond à -f dès qu’une erreur d’entrée/sortie se produit. Le comportement de tail --follow --retry n’est pas éloigné de cela dans la plupart des cas auxquels je peux penser dans des 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 la réplique), vous obtiendrez une meilleure réponse.

La dernière réponse ne suit pas réellement la queue et se contente de lire 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 au lecteur une implémentation appropriée.

IMHO, l'approche file.read () est beaucoup plus élégante qu'une solution basée sur un sous-processus.

32
Paulo Scardine

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

Par conséquent, implémenter un bon tail -f purement en Python n'est probablement pas une bonne idée, car vous devriez utiliser l'implémentation avec le plus petit dénominateur commun (sans recourir à des hacks spécifiques à la plate-forme). En utilisant une simple subprocess pour ouvrir tail -f et en parcourant les lignes dans un thread séparé, vous pouvez facilement implémenter une opération tail non bloquante en Python.

Exemple d'implémentation:

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
22
nneonneo

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

Nous en avons déjà un et c'est très gentil. Appelez simplement f.read () quand vous voulez plus de données. Il commencera à lire où la lecture précédente a été laissée et lira jusqu'à la fin du flux de données:

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, '=')

Pour lire ligne par ligne, utilisez f.readline (). Parfois, le fichier en cours de lecture se termine par une ligne partiellement lue. Traitez ce cas avec f.tell () pour trouver la position actuelle du fichier et utilisez f.seek () pour ramener le pointeur de fichier au début de la ligne incomplète. Voir cette recette ActiveState pour le code de travail.

9
Raymond Hettinger

Vous pouvez utiliser la bibliothèque 'tailer': https://pypi.python.org/pypi/tailer/

Il a une option pour obtenir les dernières lignes:

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

Et il peut aussi suivre un fichier:

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

Si on veut un comportement semblable à celui de la queue, celui-ci semble être une bonne option.

4
Haroldo_OK

Une autre option est la bibliothèque tailhead qui fournit à la fois les versions Python des utilitaires tail et head et de l'API pouvant être utilisées dans votre propre module.

Initialement basé sur le module tailer, son principal avantage est la possibilité de suivre les fichiers par chemin, c’est-à-dire qu’il peut gérer la situation lorsque le fichier est recréé. En outre, il propose des corrections de bugs pour différents cas Edge.

2
Kentzo

Toutes les réponses qui utilisent tail -f ne sont pas Pythonic.

Voici la méthode Pythonic: (sans outil ni bibliothèque externe)

def follow(thefile):
     while True:
        line = thefile.readline()
        if not line or not line.endswith('\n'):
            time.sleep(0.1)
            continue
        yield line



if __== '__main__':
    logfile = open("run/foo/access-log","r")
    loglines = follow(logfile)
    for line in loglines:
        print(line, end='')
2
Ijaz Ahmad Khan

En adaptant la réponse answer d'Ijaz Ahmad Khan aux seules lignes de sortie lorsqu'elles sont complètement écrites (les lignes se terminent par un caractère newline), on obtient une solution Pythonic sans dépendance externe:

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 __== '__main__':
    for line in follow(open("test.txt", 'r')):
        print(line, end='')
2
Isaac Turner

Vous pouvez également utiliser la commande 'AWK'.
Voir plus sur: http://www.unix.com/Shell-programming-scripting/41734-how-print-specific-lines-awk.html
awk peut être utilisé pour aligner la dernière ligne, les dernières lignes ou n’importe quelle ligne d’un fichier.
.__ Ceci peut être appelé à partir de python.

0
Madhusoodan

Si vous êtes sur Linux, implémentez une implémentation non bloquante en python de la manière suivante.

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