使用 awk 获取日志文件的特定条目

使用 awk 获取日志文件的特定条目

我目前正在尝试使用 awk 从大量日志文件中获取符合特定条件的项目。本质上,我需要能够根据命令中包含的信息(通常可以位于命令的不同位置)提取由事务 ID 标记的整个命令。下面的示例日志(高度浓缩)。请注意,发送的命令可以是单行,也可以分布在多行中(如 00001 和 00002),并且命令不一定组合在一起,它们之间可以散布其他 ID:

(NAME, 486, 00001) <xml><command:name>target</command:name></xml>
(NAME, 486, 00001)   <response>
(NAME, 486, 00001)     <result code="200">
(NAME, 486, 00001)       <msg>Command failed</msg>
(NAME, 486, 00001)     </result>
(NAME, 486, 00001)  </response>
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002)   <response>
(FOO, 486, 00002)     <result code="400">
(FOO, 486, 00002)       <msg>Command completed successfully</msg>
(FOO, 486, 00002)     </result>
(FOO, 486, 00002)  </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

本质上,我想返回整个命令:名称,包括响应(括号中的 5 位数字是事务 ID),但仅限于成功的地方(结果代码 =“400”)。

这是我到目前为止所拥有的:

BEGIN { FS="[(,)]"; }
$4 ~ "<command:name" { id[$3] = $3 }

{ for (i in id) {
        if ($3 == i) {
                if ($5 ~ "Command completed success")
                        success[i] = i;
                }
        }
}

$4 in success { print $0 }

但显然这不会再回去了向上一旦发现成功,即可获取条目的其余部分。它只返回:

(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

我尝试在 BEGIN 语句中放置一个循环,但这需要很长时间,并且在尝试使用其大小的数组时遇到内存问题(这些文件超过 1 GB)。

我希望返回的是:

(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

我想知道我正在尝试的事情是否可以在 awk 中实现?一段时间以来,我一直在尝试找出使用哪个工具来完成这项任务,据我所知,awk 无疑是最好的(除了必须使用 Python 之外)。速度是我主要关心的问题,只有今天的文件以纯文本格式提供(所以这些文件足够快),但其余的文件都是 gzip 压缩的(所以我正在这样做zcat filename | awk -f test.awk)-我试图避免多次读取文件,而且它们太大而无法存储在内存中。

答案1

您可以用作</response>记录结束标记。例如:

$ awk -F'[ )]' '{record[$3] = record[$3] "\n" $0};

                /<\/response>/ {
                  if (record[$3] ~ /completed successfully/) {
                    # optional: remove leading newline if you don't want
                    # a blank line before each output record:
                    # sub(/\n/,"",record[$3])

                    print record[$3]
                  };
                  delete record[$3]
                }' input.log 

(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002)   <response>
(FOO, 486, 00002)     <result code="400">
(FOO, 486, 00002)       <msg>Command completed successfully</msg>
(FOO, 486, 00002)     </result>
(FOO, 486, 00002)  </response>

(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>

(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

这与下面的 sed+perl 和 sed+awk 版本类似,但它通过将每行(前面有换行符)附加到名为 的数组的适当元素(即 id 号)来构造每条记录本身record。当它看到一行时</response>,如果它匹配“成功完成”,它就会打印该元素,然后删除该元素。

这将比 sed + awk 或 sed + perl 版本慢一点(因为它必须将每个输入行附加到数组的一个元素sed- 它比仅仅经常插入一个空行使用更多的CPU资源),并且使用更多的内存(因为每条记录都保留在内存中,直到有</response>一行),但并不过分......它保持每条记录仅在需要时保留在内存中,然后将其删除。

但是,即使任何给定 id 的记录与其他 id 的记录交错,此版本也应该可以工作。

这是一个 Perl 等效项:

perl -F'[\h)]' -e '
  $record{$F[2]} .= $_;

  if (/<\/response>/) {
    if ($record{$F[2]} =~ /completed successfully/) {
      # print blank line between records
      print "\n" if $not_first_record++;

      print $record{$F[2]}
    }
    delete $record{$F[2]};
  }' input.log

我的测试(使用包含 100,000 个示例数据副本的 120 MB 输入文件)表明 awk 版本的速度几乎是前者的两倍。 awk 版本在我的测试系统(古老的 AMD Phenom II 1090T)上运行大约 4.6 秒,而 perl 版本大约需要 7.4 秒。

更新

这是一个优化的 perl 版本:

它不使用匹配水平空白或右括号 ( [\h)]) 作为字段分隔符的正则表达式,而是使用 perl 默认的空白分隔。它从第三个字段中提取每个记录的键,然后截掉最后一个字符())。

该版本的运行时间约为 3.9 秒,几乎是快的两倍 - 表明使用正则表达式进行-F自动分割模式时会产生巨大的性能损失。

顺便说一句,我尝试使用索引数组来记录而不是关联数组(即@record使用数字索引而不是%record字符串键),但它在性能上没有明显的差异。我还尝试使用该index()函数而不是正则表达式匹配(index($record{$key},"completed successfully")而不是$record{$F[2]} =~ /completed successfully/),但这也没有产生明显的性能差异。

perl -ane '
  chop($key = $F[2]);
  $record{$key} .= $_;

  if (/<\/response>/) {
    if ($record{$key} =~ /completed successfully/) {
      print "\n" if $not_first_record++;
      print $key, $record{$key};
    }
    delete $record{$key}
  }' input.log

同样的优化也提高了 awk 的性能,尽管没有那么显着。

awk 没有chop()函数,但substr()可以用来做同样的事情。

awk '{
       key = substr($3, 1, length($3)-1);
       record[key] = record[key] "\n" $0
     };

     /<\/response>/ {
       if (record[key] ~ /completed successfully/) {
         sub(/^\n/,"",record[key])
         print record[key]
       };
       delete record[key]
     }' input.log

该版本的运行时间约为 3.5 秒(比之前的 awk 版本的 4.6 秒快约 30%)。

总体而言,更新后的 awk 和 perl 版本在性能上更加接近,但 awk 的速度仍快 12% 左右。

对代码的微小更改可能会导致性能上的巨大差异。


或者:

您的日志条目是否总是像这样用 id 整齐地分隔,或者它们是否与其他 id 交错?

如果它们被整齐地分开,最简单的方法之一是使用sed通过插入空行将其分成“段落”(即由一个或多个空行分隔)<xml>行。

sed然后将 的输出通过管道输入awk或 ,perl以“段落模式”读取日志。对于 awk,RS=""在 BEGIN 块中设置(或使用-v选项),在 perl 中使用-00命令行选项。然后你的 awk 或 perl 脚本只需要检查记录是否包含“成功完成”。如果是,则打印记录:

这应该比上面的仅 awk 版本运行得明显更快并且使用更少的内存......但只有在记录时才能正确工作不是与其他记录交错。

$ sed '/) <xml>/i\\n' input.log |
    perl -00 -ne 'print if /completed successfully/m'
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002)   <response>
(FOO, 486, 00002)     <result code="400">
(FOO, 486, 00002)       <msg>Command completed successfully</msg>
(FOO, 486, 00002)     </result>
(FOO, 486, 00002)  </response>

(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>

(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

或者使用 awk:

sed '/) <xml>/i\\n' input.log | awk -v RS='' '/completed successfully/'

此版本的输出几乎相同,但每个输出记录之间没有空行。

就我个人而言,我发现每个输出记录之间的空行很有用,因为它可以更轻松地在必要时进一步处理输出,因为它已经处于“段落模式”。当然这只是主观偏好。


相关内容