J'ai un tas de fichiers journaux au format suivant:

[Timestamp1] Text1
Text2
Text3
[Timestamp2] Text4
Text5
...
...

Où le nombre de lignes de texte suivant un horodatage peut varier de 0 à plusieurs. Toutes les lignes suivant un horodatage jusqu'à l'horodatage suivant font partie de l'instruction de journal précédente.

Exemple:

[2016-03-05T23:18:23.672Z] Some log text
[2016-03-05T23:18:23.672Z] Some other log text
[2016-03-05T23:18:23.672Z] Yet another log text
Some text
Some text
Some text
Some text
[2016-03-05T23:18:23.672Z] Log text
Log text

J'essaie de créer un script de fusion de journaux pour ces types de fichiers journaux et je n'ai pas réussi jusqu'à présent.

Si les journaux étaient dans un format standard où chaque ligne est une entrée de journal distincte, il est simple de créer un script de fusion de journaux à l'aide de la saisie et du tri de fichiers.

Je pense que je cherche un moyen de traiter plusieurs lignes comme une seule entité de journal qui est triable sur l'horodatage associé.

Des pointeurs?

0
sumeetkm 10 mars 2016 à 09:00

3 réponses

Meilleure réponse

Vous pouvez écrire un générateur qui agit comme un adaptateur pour votre flux de journaux pour effectuer la segmentation pour vous. Quelque chose comme ça:

def log_chunker(log_lines):
    batch = []
    for line in log_lines:
        if batch and has_timestamp(line):
            # detected a new log statement, so yield the previous one
            yield batch
            batch = []
        batch.append(line)
    yield batch

Cela transformera vos lignes de journal brutes en lots où chacun est une liste de lignes, et la première ligne de chaque liste a l'horodatage. Vous pouvez construire le reste à partir de là. Il peut être plus judicieux de commencer batch comme une chaîne vide et de virer directement sur le reste du message; tout ce qui fonctionne pour vous.

Remarque: si vous fusionnez plusieurs journaux horodatés, vous ne devez pas du tout effectuer de tri global si vous utilisez un tri par fusion en continu.

0
tzaman 10 mars 2016 à 06:12

L'approche suivante devrait bien fonctionner.

from heapq import merge
from itertools import groupby
import re
import glob

re_timestamp = re.compile(r'\[\d{4}-\d{2}-\d{2}')

def get_log_entry(f):
    entry = ''
    for timestamp, g in groupby(f, lambda x: re_timestamp.match(x) is not None):
        entries = [row.strip() + '\n' for row in g]

        if timestamp:
            if len(entries) > 1:
                for entry in entries[:-1]:
                    yield entry
            entry = entries[-1]
        else:   
            yield entry + ''.join(entries)

files = [open(f) for f in glob.glob('*.log')]       # Open all log files

with open('output.txt', 'w') as f_output:     
    for entry in merge(*[get_log_entry(f) for f in files]):
        f_output.write(''.join(entry))

for f in files:
    f.close()

Il utilise les merge fonction pour combiner une liste d'itérables dans l'ordre.

Comme vos horodatages sont naturellement ordonnés, tout ce qui est nécessaire est une fonction pour lire des entrées entières à la fois dans chaque fichier. Cela se fait à l'aide d'une expression régulière pour repérer les lignes commençant dans chaque fichier avec un horodatage, et groupby est utilisé pour lire les lignes correspondantes à la fois.

glob est utilisé pour rechercher d'abord tous les fichiers de votre dossier avec une extension .log.

1
Martin Evans 10 mars 2016 à 10:11

Vous pouvez facilement le diviser en morceaux en utilisant re.split() avec une expression rationnelle de capture:

pieces = re.split(r"(^\[20\d\d-.*?\])", logtext, flags=re.M)

Vous pouvez rendre l'expression rationnelle aussi précise que vous le souhaitez; J'ai juste besoin de [20\d\d- au début d'une ligne. Le résultat contient les parties correspondantes et non correspondantes de logtext, en tant que pièces alternées (en commençant par une partie vide non correspondante).

>>> print(pieces[:5])
['', '[2016-03-05T23:18:23.672Z] ', 'Some log text\n', '[2016-03-05T23:18:23.672Z] ', 'Some other log text\n']

Il reste à remonter les parties du journal, ce que vous pouvez faire avec cette recette de { {X0}}:

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)

log_entries = list( "".join(pair) for pair in pairwise(pieces[1:]) )

Si vous avez plusieurs de ces listes, vous pouvez en effet simplement les combiner et les trier, ou utiliser un tri de fusion plus sophistiqué si vous avez beaucoup de données. Je comprends que votre question concerne la séparation des entrées du journal, donc je ne vais pas entrer dans le détail.

0
alexis 10 mars 2016 à 10:22