按日期合并多个日志文件,其中也包括未注明日期的行(例如堆栈跟踪)

按日期合并多个日志文件,其中也包括未注明日期的行(例如堆栈跟踪)

如何合并日志文件,即按时间排序但也有多行的文件,其中只有第一行有时间,其余行没有。

日志1

01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

日志2

01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3

预期结果

01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

如果不是以数字开头的非时间戳行,那么简单的sort -nm log1 log2会做。

在 unix/linux cmd 行上有没有简单的方法来完成这项工作?

编辑由于这些日志文件通常以 GB 为单位,因此合并时不应重新排序(已排序的)日志文件,也不应将文件完全加载到内存中。

答案1

有点棘手。虽然可以使用datebash 数组,但这确实是一种可以从真正的编程语言中受益的东西。例如在 Perl 中:

$ perl -ne '$d=$1 if /(.+?),/; $k{$d}.=$_; END{print $k{$_} for sort keys(%k);}' log*
01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

以下是未浓缩为注释脚本的相同内容:

#!/usr/bin/env perl

## Read each input line, saving it 
## as $_. This while loop is equivalent
## to perl -ne 
while (<>) {
    ## If this line has a comma
    if (/(.+?),/) {
        ## Save everything up to the 1st 
        ## comma as $date
        $date=$1;
    }
    ## Add the current line to the %k hash.
    ## The hash's keys are the dates and the 
    ## contents are the lines.
    $k{$date}.=$_;
}

## Get the sorted list of hash keys
@dates=sort(keys(%k));
## Now that we have them sorted, 
## print each set of lines.
foreach $date (@dates) {
    print "$k{$date}";
}

请注意,这假设所有日期变更线和仅有的日期行包含逗号。如果不是这种情况,你可以改用以下格式:

perl -ne '$d=$1 if /^(\d+:\d+:\d+\.\d+),/; $k{$d}.=$_; END{print $k{$_} for sort keys(%k);}' log*

上述方法需要将文件的全部内容保存在内存中。如果这是个问题,这里有一个不需要的方法:

$ perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/' log* | 
    sort -n | perl -lne 's/\0/\n/g; printf'
01:02:03.6497,2224,0022 foo
foo1
2foo
foo3    
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3    
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

这个命令只是将连续时间戳之间的所有行放到一行上,方法是用 替换换行符\0(如果这可以在您的日志文件中,请使用任何您知道永远不会出现的字符序列)。这传递给sort,然后tr取回行。


正如 OP 非常正确地指出的那样,上述所有解决方案都需要重新考虑,并且没有考虑到文件可以合并。这里有一个可以合并的文件,但与其他文件不同,它只能在两个文件上工作:

$ sort -m <(perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/' log1) \
            <(perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/' log2) | 
    perl -lne 's/[\0\r]/\n/g; printf'

如果将 perl 命令保存为别名,则可以得到:

$ alias a="perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/'"
$ sort -m <(a log1) <(a log2) | perl -lne 's/[\0\r]/\n/g; printf'

答案2

有一种方法可以做到(感谢@terdon 提出的换行替换想法):

  1. 在每个输入文件中,将所有多行替换为单行,例如用 NUL 替换
  2. sort -m对替换的文件执行
  3. 将 NUL 替换回换行符

例子

由于多行连接使用多次,因此我们将alias其除去:

alias a="awk '{ if (match(\$0, /^[0-9]{2}:[0-9]{2}:[0-9]{2}\\./, _))\
    { if (NR == 1) printf \"%s\", \$0; else printf \"\\n%s\", \$0 }\
    else printf \"\\0%s\", \$0 } END { print \"\" }'"

这是使用上述别名的合并命令:

sort -m <(a log1) <(a log2) | tr '\0' '\n'

作为 Shell 脚本

为了像这样使用它

merge-logs log1 log2

我将其放入一个 shell 脚本中:

x=""
for f in "$@";
do
 x="$x <(awk '{ if (match(\$0, /^[0-9]{2}:[0-9]{2}:[0-9]{2}\\./, _)) { if (NR == 1) printf \"%s\", \$0; else printf \"\\n%s\", \$0 } else printf \"\\0%s\", \$0 } END { print \"\" }' $f)"
done

eval "sort -m $x | tr '\0' '\n'"

不确定我是否可以提供可变数量的日志文件,而无需诉诸邪恶eval

答案3

如果您可以选择使用 Java,请尝试日志合并

java -jar log-merger-0.0.3-jar-with-dependencies.jar -f 1 -tf "HH:MM:ss.SSS" -d "," -i log1,log2
01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

答案4

尝试超快速系统日志搜索器

(假设你有安装有锈迹

cargo install super_speedy_syslog_searcher

然后

s4 log1 log2

然而,超快速系统日志搜索器希望找到日期时间戳。如果您可以将日志记录格式更改为该格式,则s4可以对其进行排序。

相关内容