【问题标题】:Filter from large log file从大日志文件中过滤
【发布时间】:2022-11-17 04:37:57
【问题描述】:

我想在不更改 Windows 格式的大型日志文件的情况下执行以下操作

  1. 删除所有 CRLF 字符
  2. 在日志文件最后一行的“CLG...”“TRC...”之间插入一个空行
  3. 以段落模式读取结果后,如果存在特定字符串则打印段落

    下面的代码不起作用。

    use strict;
    use warnings;
    
    my $ID = "D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2";
    my $SDP;
    
    open (LOG, "file.log") || die $!;
    
    my $line;
    
    while(<LOG>) {
            $line .= $_;
            $line =~s/\r//g;
    }
    
    local $/ = '';
    
    while (<>) {
        if ( /Call-ID:\s+(.+)/ and $ID ) {
            $SDP = 1;
            print;        
            next;
        }
    
        print if $SDP && /\brtpmap\b/;
    
        $SDP = 0;
    }
    
    close(LOG);
    

    Jan 28 11:39:37.525 CET: //1393628/D5CC0586A87B/SIP/Msg/ccsipDisplayMsg:^M
    Received:^M 
    SIP/2.0 200 OK^M
    Via: SIP/2.0/UDP 10.218.16.2:5060;branch=z9hG4bKB22001ED5^M
    From: "Frankeerapparaat Secretariaat" <sip:089653717@10.210.2.49>;tag=E7E0EF64-192F^M
    To: <sip:022046187@10.210.2.49>;tag=25079324~19cc0abf-61d9-407f-a138-96eaffee1467-27521338^M
    Date: Mon, 28 Jan 2013 10:39:32 GMT^M
    Call-ID: D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2^M
    CSeq: 102 INVITE^M
    Allow: INVITE, OPTIONS, INFO, BYE, CANCEL, ACK, PRACK, UPDATE, REFER, SUBSCRIBE, NOTIFY^M
    Allow-Events: presence^M
    Supported: replaces^M
    Supported: X-cisco-srtp-fallback^M
    Supported: Geolocation^M
    Session-Expires:  1800;refresher=uas^M
    Require:  timer^M
    P-Preferred-Identity: <sip:022046187@10.210.2.49>^M
    Remote-Party-ID: <sip:022046187@10.210.2.49>;party=called;screen=no;privacy=off^M
    Contact: <sip:022046187@10.210.2.49:5060>^M
    Content-Type: application/sdp^M
    Content-Length: 209^M
    ^M
    v=0^M
    o=CiscoSystemsCCM-SIP 2000 1 IN IP4 10.210.2.49^M
    s=SIP Call^M
    c=IN IP4 10.210.2.1^M
    t=0 0^M
    m=audio 16844 RTP/AVP 8 101^M
    a=rtpmap:8 PCMA/8000^M
    a=ptime:20^M
    a=rtpmap:101 telephone-event/8000^M
    a=fmtp:101 0-15^M
    ^M
    Jan 28 11:39:37.529 CET: //1393628/D5CC0586A87B/SIP/Msg/ccsipDisplayMsg:^M
    Sent:^M
    ACK sip:022046187@10.210.2.49:5060 SIP/2.0^M
    Via: SIP/2.0/UDP 10.218.16.2:5060;branch=z9hG4bKB2247150A^M
    From: "Frankeerapparaat Secretariaat" <sip:089653717@10.210.2.49>;tag=E7E0EF64-192F^M
    To: <sip:022046187@10.210.2.49>;tag=25079324~19cc0abf-61d9-407f-a138-96eaffee1467-27521338^M
    Date: Mon, 28 Jan 2013 10:39:36 GMT^M
    Call-ID: D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2^M
    Max-Forwards: 70^M
    CSeq: 102 ACK^M
    Authorization: Digest username="Genk_AC_1",realm="infraxnet.be",uri="sip:022046187@10.210.2.49:5060",response="9546733290a96d1470cfe29a7500c488",nonce="5V/Jt8FHd5I8uaoahshiaUud8O6UujJJ",algorithm=MD5^M
    Allow-Events: telephone-event^M
    Content-Length: 0^M
    ^M
    ^M
    Jan 28 11:39:37.529 CET: //1393627/D5CC0586A87B/SIP/Msg/ccsipDisplayMsg:^M
    Sent:^M
    SIP/2.0 200 OK^M
    Via: SIP/2.0/UDP 192.168.8.11:5060;branch=z9hG4bK24ecaaaa6dbd3^M
    From: "Frankeerapparaat Secretariaat" <sip:3717@192.168.8.11>;tag=e206cc93-1791-457a-aaac-1541296cf17c-29093746^M
    To: <sip:022046187@192.168.8.28>;tag=E7E0F8A4-EA3^M
    Date: Mon, 28 Jan 2013 10:39:32 GMT^M
    Call-ID: fedc8f80-10615564-45df0-b08a8c0@192.168.8.11^M
    CSeq: 101 INVITE^M
    Allow: INVITE, OPTIONS, BYE, CANCEL, ACK, PRACK, UPDATE, REFER, SUBSCRIBE, NOTIFY, INFO, REGISTER^M
    Allow-Events: telephone-event^M
    Remote-Party-ID: <sip:022046187@192.168.8.28>;party=called;screen=no;privacy=off^M
    Contact: <sip:022046187@192.168.8.28:5060>^M
    Supported: replaces^M
    Supported: sdp-anat^M
    Server: Cisco-SIPGateway/IOS-15.3.1.T^M
    Session-Expires:  1800;refresher=uas^M
    Require: timer^M
    Supported: timer^M
    Content-Type: application/sdp^M
    Content-Disposition: session;handling=required^M
    Content-Length: 247^M
    ^M
    v=0^M
    o=CiscoSystemsSIP-GW-UserAgent 7276 9141 IN IP4 192.168.8.28^M
    s=SIP Call^M
    c=IN IP4 192.168.8.28^M
    t=0 0^M
    m=audio 30134 RTP/AVP 8 101^M
    c=IN IP4 192.168.8.28^M
    a=rtpmap:8 PCMA/8000^M
    a=rtpmap:101 telephone-event/8000^M
    a=fmtp:101 0-15^M
    a=ptime:20^M
    ^M
    CLG(2022-11-07 00:09:06.444)| Call(Terminate) | 302A330B040C73070A021806021C0200 | ^M
    TRC(2022-11-15 00:00:38.012)| SIP( OUT : Response ) Trying( 100 INVITE ) | 2 |  | 0 | 332C30050A0F750A00011A06021C0200 | SIP/2.0 100 Trying^M
    

【问题讨论】:

  • 1. 显示的代码仅删除回车符(而不是换行符)。无论如何,更改为 UNIX 换行符有什么意义? 2.“CLG...”“TRC...”是不是在最后一行,正如文本所暗示的那样;它们在不同的最后两行。您还想在它们之间添加额外的空白行吗?那会很容易,但是为什么您发现它比更改换行符慢,需要在每一个线?恐怕我不确定我是否了解需要做什么。
  • 最后,我根本没有得到代码和那两个 ​​while 循环——首先遍历该文件,第二个读取命令行中给出的名称的文件。为什么?我以为这都是一个日志文件?
  • 1.只需要去掉回车符即可。只有这样你才能以段落模式阅读。 2. 以“CLG...”“TRC...”开头的行也应被识别为一个段落。日志很大而且被截断了,但是“TRC”行之后有很多日志。
  • 打印日志文件中包含特定字符串的所有段落。例如,您应该查找并打印包含字符串“D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2”的段落。但是,每一行都包含一个回车符,它将一个日志文件识别为一个段落。一个日志文件是正确的。我不知道如何在一个while循环中解决它,所以我强迫自己去做。

标签: perl


【解决方案1】:

这里有很多事情阻碍了你。我想我会接近你想要做的事情,但我必须做出一些猜测。

首先,$ID 中的裸露的 @ 是插值的,你在你的字符串中丢失了 @10,而它应该是一个文字。您没有收到警告可能是因为该标识符是一个 Perl 特殊变量而不是用户定义的变量。

其次,你在那里有一些奇怪的文件处理。

您正在将修改后的日志文件构建到$line 中的单个大字符串中。你说你有大文件,这对不同的人意味着不同的事情。但是有些人在“大”的环境中工作是数十或数百场演出。不要那样做。

第三,在构建之后,您不对$line 做任何事情。我认为您希望通过&lt;&gt; 再次阅读它。

我会以不同的方式处理这个问题。我不太关心蝙蝠的行尾。如果我要浏览数百万条记录,我不想花时间转换每一行,因为我要忽略其中的大部分。当我有东西要输出时,我可以转换它。这也有点取决于您期望的点击率。如果您几乎要打印每条记录,那么打印其中的 1% 就没有那么重要了。

首先,我知道文件格式看起来很像电子邮件 mbox 格式。带有日期的第一行破坏了这一切,因为它没有固定的字符串,您可以像 mbox 信封一样使用它来查看记录的开头。这也意味着,由于整个记录(标题和主体)由 CRLFCRLF 分隔,而记录本身由 CRLFCRLF 分隔,因此在段落模式下获取完整记录有点棘手。

因此,让我们读取由 CRLFCRLF 分隔的块。第一个块应该是标题,第二个块应该是主体。有机会在这里摆脱困境,您可以做一些事情来从中恢复过来,但我会在这里跳过。基本上,检查块并查看它是否符合您的预期(以日期等开头)。如果您对这类事情感到好奇,那么 UTF-8 的设计很有趣,因为它的出发点是事情可能会出现乱码,但您可以回到正轨。

这是我们目前所拥有的。单引号$ID获取真实值,并设置ARGV(空&lt;&gt;的文件句柄)使用CRLFCRLF作为输入记录分隔符($/)。这是一个适用于当前选定(默认)文件句柄的每个文件句柄变量,所以我selectARGV,设置值,然后重新选择以前的默认值。这很奇怪,但让我们就此打住吧。然后,我的程序的核心是 while 循环:

use v5.10;
use strict;
use warnings;

my $ID = 'D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2';

my $old = select(ARGV);
$/ = "
" x 2;
select($old);

while( <> ) {
    ...
    }

外部的 &lt;&gt; 获取标头 ($_),即使我想跳过整条记录,我也总是必须检查正文。那只会使阅读保持同步(而您的其他要求预示着其他一些不同步的方法)。诀窍是我必须查看 Content-length 标头以查看是否有正文。不过,我并不特别相信该长度值,因为我还没有完成工作来查看它是来自 LF 输出还是 CRLF 输出(也就是说,记录器添加了八位字节而不更改 Content-Length 标头)。

有很多方法可以做到这一点,但这很简单:检查内容长度是否大于零:

while( <> ) {
    my $body = '';
    $body = <> if( /Content-Length:s+([0-9]+)/i and $1 > 0 );

    ... filter goes here ...

    print $_, $body
    }

现在我需要决定是否要这张唱片。我认为你有两个要求:

  • Call-ID 的值为 $ID
  • 正文有rtpmap

$ID 值开始。我希望它成为标头值,所以我希望它在模式中。为此,我使用 quotemeta 准备要插入到模式中的字符串(. 是一个特殊的字符)。你有一行 /Call-ID:s+(.+)/ and $ID,我认为你认为来自 (.+) 的捕获值将与 $ID 进行比较,但事实并非如此。

my $ID = quotemeta('D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2');
while( <> ) {
    my $body = '';
    $body = <> if( /Content-Length:s+([0-9]+)/i and $1 > 0 );
    next unless /Call-ID:s+$ID/;
    print $_, $body
    }

这是一个有趣的注释。我不能使用 ^ 行锚点开头,因为我的行结尾是 CRLFCRLF,但内部行由 CRLF 分隔。我知道标题前面会有垂直空格,所以我添加了一个 来锚定它。没有大碍。

现在,我的模式不会改变,所以我可以用 qr// 预编译它。稍后我可以在 m// 中使用该权限:

my $ID = quotemeta('D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2');
my $header_pattern = qr/Call-ID:s+$ID/;

while( <> ) {
    my $body = '';
    $body = <> if( /Content-Length:s+([0-9]+)/i and $1 > 0 );
    next unless /$header_pattern/;
    next unless $body =~ /rtpmap/;

    print $header, $body
    }

我可能会满足于这样离开它。输出仍然会有 CRLF 提要,但谁在乎呢?如果它真的很重要,我可以在另一个程序中修复它,例如dos2unix

在您的示例中,我认为行尾无关紧要。看起来您想提取几个值。我认为您想打印 Call-IDrtpmap 值的值。由于各种原因,您的代码无法正常工作,因为您试图记住自己的位置并且在整个状态下踩踏。相反,我现在有了标题和正文,我使用捕获来获取值,然后我将它们与换行符一起输出。我从来没有转换过行尾,因为我从来不需要。

while( <> ) {
    my $body = '';
    $body = <> if( /Content-Length:s+([0-9]+)/i and $1 > 0 );
    next unless m/$header_pattern/;
    my $this_id = $1;
    next unless $body =~ /(rtpmap:[^]+)/g;

    print join "
", $this_id, $;
    }

但是还有一个问题。主体有多个 rtpmap 行。如果我想要所有这些,我需要进行调整。我可以在全局匹配中匹配主体并检查我得到了多少结果:

    my @rtpmaps = $body =~ /(rtpmap:[^]+)/g;
    next unless @rtpmaps > 0;
    print join "
", $this_id, @rtpmaps;

这是全部:

#perl
use v5.10;
use warnings;

my $ID = quotemeta('D5CCA1AE-686D11E2-A881ED01-8DFA6D70@10.218.16.2');
my $header_pattern = qr/Call-ID:s+($ID)/;

my $old = select(ARGV);
$/ = "
" x 2;
select($old);

chdir '/Users/brian/Desktop';
@ARGV = 'test.log';

while( <> ) {
    my $body = '';
    $body = <> if( /Content-Length:s+([0-9]+)/i and $1 > 0 );
    next unless m/$header_pattern/;
    next unless length $body;
    my $this_id = $1;
    my @rtpmaps = $body =~ /(rtpmap:[^]+)/g;
    next unless @rtpmaps > 0;

    print join "
", $this_id, @rtpmaps;
    }

您对以CTGTRC 开头的行有额外要求。你可以在寻找尸体之前检查这条线,然后决定你想用它们做什么。

【讨论】:

    【解决方案2】:

    我假设您正在使用 Unix 样式行结尾的系统上运行,否则文件的 Windows 行结尾将不是问题。在 Unix 下处理 Windows 文件的关键是让 Perl 在打开文件时使用 :crlf I/O 层来完成脏活。为此,您需要使用open() 的三参数版本。在您的情况下,这是 open LOG, '&lt;:crlf', 'file.log' or die $!。请注意,我不需要 open() 中的括号,因为我使用的是松散绑定的 or 而不是紧密绑定的 ||

    假设我了解您的要求,以下是我将如何实现您的代码:

    #!/usr/bin/env perl
    
    use 5.010;        # for K
    
    use strict;
    use warnings;
    
    open my $log, '<:crlf', 'file.log'
        or die "Failed to open file.log: $!
    ";
    
    local $/ = '';
    my $state = &state_1;
    while ( <$log> ) {
        if ( eof $log ) {
            s/ ^ CLG .*? 
     K (?= TRC ) /
    /smx;
        }
        $state = $state->();
    }
    
    sub state_1 {
        if ( m/ Call-ID: s+ /smx ) {
            print;
            return &state_2;
        }
        return &state_1;
    }
    
    sub state_2 {
        if ( m/  rtpmap  /smx ) {
            print;
        }
        return &state_1;
    }
    
    # ex: set ts=8 sts=4 sw=4 tw=72 ft=perl expandtab shiftround :
    

    我没有对标志变量(你的$SDP)做逻辑,而是实现了一个状态机。

    我的逻辑没有提到$ID,因为你给出的值永远是真的。如果$ID 为假,我相信根本不应该产生任何输出。

    严格来说,$/ 应该被本地化以防止 Spooky Action at a Distance,但在像这样的小脚本中它不太可能导致问题。

    if ( eof $log ) ... 实现了您的要求,即在最后一段的两行之间插入一个空行。如果您打算将其分成两段,您将需要不同的实现。

    【讨论】:

    • 即使没有包含 rtpmap 的行,您也需要能够再次进入 state_1。查看样本输入:一个记录的有效长度为 0,因此您将以错误的状态跨越记录边界,并最终得到来自不同记录的数据。
    猜你喜欢
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 2015-10-20
    • 2015-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-26
    相关资源
    最近更新 更多