在 Python 中合并和排序多行日志

Merging and sorting multiline logs in Python

我有一堆格式如下的日志文件:

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

时间戳后的文本行数可以从 0 到很多不等。时间戳之后直到下一个时间戳的所有行都是前一个日志语句的一部分。

示例:

[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

我正在尝试为此类日志文件创建一个日志合并脚本,但到目前为止还没有成功。

如果日志采用标准格式,其中每一行都是一个单独的日志条目,则可以直接使用文件输入和排序创建日志合并脚本。

我认为我正在寻找一种方法来将多行视为可根据相关时间戳进行排序的单个日志实体。

有什么指点吗?

您可以编写一个生成器作为日志流的适配器来为您进行分块。像这样:

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

这会将您的原始日志行变成批次,其中每行都是一个行列表,每个列表中的第一行都有时间戳。您可以从那里构建其余部分。将 batch 作为空字符串开始并直接添加消息的其余部分可能更有意义;任何适合你的东西。

旁注,如果您要合并多个带时间戳的日志,如果您使用流式合并排序,则根本不需要执行全局排序。

以下方法应该很有效。

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()

它使用merge 函数按顺序组合可迭代列表。

由于您的时间戳是自然排序的,因此所需要的只是一个从每个文件中一次读取整个条目的函数。这是通过使用正则表达式来识别每个文件中带有时间戳的行来完成的,并且 groupby 用于一次读取匹配的行。

glob 用于首先查找文件夹中扩展名为 .log 的所有文件。

您可以使用 re.split() 和捕获正则表达式轻松地将其分成块:

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

您可以根据需要使正则表达式精确;我只需要 [20\d\d- 在一行的开头。结果包含 logtext 的匹配和不匹配部分,作为交替部分(从空的非匹配部分开始)。

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

仍然需要重新组装日志部分,您可以使用 itertools:

中的配方来完成
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:]) )

如果您有多个这样的列表,您确实可以将它们合并并排序,或者如果您有大量数据,则使用更高级的合并排序。我理解你的问题是关于拆分日志条目,所以我不会讨论这个。