【问题标题】:Merging and sorting multiline logs in Python在 Python 中合并和排序多行日志
【发布时间】:2016-06-24 20:11:03
【问题描述】:

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

[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

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

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

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

任何指针?

【问题讨论】:

    标签: python sorting merge multiline


    【解决方案1】:

    您可以使用带有捕获正则表达式的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:]) )
    

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

    【讨论】:

      【解决方案2】:

      下面的方法应该很好用。

      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 扩展名的文件。

      【讨论】:

        【解决方案3】:

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

        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 作为空字符串开始并直接附加消息的其余部分可能更有意义;任何适合你的。

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

        【讨论】:

        • 我最终采用了一种非常相似的技术——将日志条目边界周围的各个行分块,然后将它们合并/排序。感谢您的意见。
        猜你喜欢
        • 2011-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-29
        • 1970-01-01
        相关资源
        最近更新 更多