neilwatson has asked for the wisdom of the Perl Monks concerning the following question:

I have up to two log files. I only know the partial name but I know that each file has been written to in the past 12 hours. I was thinking about use file::find:
sub wanted { if ( (stat $File::Find::name)[9] < time() - (3600 * 12) ) { open LF, ($File::Find::name) || die "cannot open $!"; while (<LF>){

I want to parse the opened files for any entries that look like this:

2005-11-04/08:02:14.011 METRIC 00020036-0800093A log :Monitor: CALLER +Status *PASS* 2005-11-04/08:11:30.924 METRIC 00020036-08000940 log :Monitor: CALLER +Status *PASS* 2005-11-04/08:12:29.830 METRIC 00020036-08000941 log :Monitor: CALLER +Status *PASS* 2005-11-04/09:12:28.790 METRIC 00020036-08000943 log :Monitor: CALLER +Status *PASS* 2005-11-04/09:12:35.869 METRIC 00020036-08000944 log :Monitor: CALLER +Status *PASS*

However, I only want to collect PASS entries if they have occured within the past 12 hours. I think this may involve a look ahead type regex but I can't wrap by thoughts around it. Can anyone offer suggestions?

Neil Watson
watson-wilson.ca

Replies are listed 'Best First'.
Re: Parsing timestamps
by philcrow (Priest) on Nov 04, 2005 at 15:30 UTC
    You could do two tests. First test for PASS, then check the time. I find two tests easier than a look ahead/behind scheme.

    Phil

      Not to mention that just doing the quick qualifying check (next unless /\*PASS\*$/) will prevent you from doing the more intensive parsing work unless it's a line you're really interested in (granted parsing out the date's not that much work, but . . . :).

        ...or the first might be (if *PASS*\s is always last before the newline, as your data suggests):
      while ( <DATA> ) { print "$_ \n"; if ( $_ =~ /(\*PASS\*)\s$/ ) { print "\n \$1 is: $1 \n"; } else { print "Not found\n"; } } __DATA__ 2005-11-04/08:02:14.011 METRIC 00020036-0800093A log :Monitor: CALLER +Status *PASS* 2005-11-04/08:09:34.712 METRIC 0A475-11B Log :monitor: CALLER Status +*FAIL* 2005-11-04/08:11:30.924 METRIC 00020036-08000940 log :Monitor: CALLER +Status *PASS* 2005-11-04/08:12:29.830 METRIC 00020036-08000941 log :Monitor: CALLER +Status *PASS* 2005-11-04/09:12:28.790 METRIC 00020036-08000943 log :Monitor: CALLER +Status *PASS* 2005-11-04/09:12:35.869 METRIC 00020036-08000944 log :Monitor: CALLER +Status *PASS*

      output is:
      2005-11-04/08:02:14.011 METRIC 00020036-0800093A log :Monitor: CALLER +Status *PASS* $1 is: *PASS* 2005-11-04/08:09:34.712 METRIC 0A475-11B Log :monitor: CALLER Status +*FAIL* Not found 2005-11-04/08:11:30.924 METRIC 00020036-08000940 log :Monitor: CALLER +Status *PASS* $1 is: *PASS* 2005-11-04/08:12:29.830 METRIC 00020036-08000941 log :Monitor: CALLER +Status *PASS* $1 is: *PASS* 2005-11-04/09:12:28.790 METRIC 00020036-08000943 log :Monitor: CALLER +Status *PASS* $1 is: *PASS* 2005-11-04/09:12:35.869 METRIC 00020036-08000944 log :Monitor: CALLER +Status *PASS* $1 is: *PASS*
Re: Parsing timestamps
by duff (Parson) on Nov 04, 2005 at 15:44 UTC

    Since the time information inside of your files seems to be in a good enough form that you could compare them directly with a string comparison, you could do something like this:

    use POSIX qw/strftime/; # generate a timestamp of 12 hours ago in the same format as the data +file $twelve_hours_ago = strftime("%Y-%m-%d/%H:%M:%S",localtime(time-12*360 +0)); while (<FH>) { next unless /PASS/; # skip the non-PASS entries my ($date) = /^([^.]+)/; # grab the date up to seconds next unless $date gt $twelve_hours_ago; # process the interesting records. }
    Update: I had a numeric comparator where clearly a string comparator is warranted and fixed the problem with the 12 hour ago timestamp. Thanks neilwatson, japhy

    Update #2: While I'm thinking about it, note that this solution probably breaks at DST boundaries.

      use POSIX qw/strftime/; # generate a timestamp of 12 hours ago in the same format as the data +file $twelve_hours_ago = strftime("%Y-%m-%d/%H:%M:%S",localtime);
      Wouldn't that generate a timestamp of now?

      Neil Watson
      watson-wilson.ca

      Apart from the 12-hours-ago problem stated above me, you also need to use the string comparison operators (gt instead of >) here.

      Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
      How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: Parsing timestamps
by japhy (Canon) on Nov 04, 2005 at 16:14 UTC
    A regex to match a time within the past 12 hours is doable, but it's probably not nearly as efficient as doing two passes, one to collect the time (if it's a PASS entry) and one to compare that time. But because I can't back away from a challenge:
    use Time::Local; while (<$file>) { if (m{ ^ (\d\d\d\d) - (\d\d) - (\d\d) # capture y/m/d to $1,$2,$3 / (\d\d) : (\d\d) : (\d\d) # capture h/m/s to $4,$5,$6 (?(?{ timelocal($6,$5,$4,$3,$2-1,$1-1900) < (time - 12*60*60) })(?!)) # fail if timestamp is older than 12 hours .* \*PASS\* }x) { # it's an ok line! } }
    Untested, but should work fine.

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: Parsing timestamps
by blue_cowdawg (Monsignor) on Nov 04, 2005 at 16:42 UTC
        However, I only want to collect PASS entries if they have occured within the past 12 hours. I think this may involve a look ahead type regex but I can't wrap by thoughts around it. Can anyone offer suggestions?

    Here's one thought I had was something like this:

    #!/usr/bin/perl -w use strict; use Date::Manip; my $now=ParseDate(scalar(localtime())); my $hours_ago = DateCalc("12 hours ago",$now); while (my $line=<DATA>){ chomp $line; my ($timestamp,$metric,$serial,$source,$stuff,$foo,$status,$passfa +il) = split(/[\s\t\n]+/,$line); next unless $passfail eq '*PASS*'; my $date = ParseDate($timestamp); my $flag=Date_Cmp($hours_ago,$date); if ( $flag < 0 ) { printf "%s\n",$line; } } exit(0); __END__ 2005-11-04/08:02:14.011 METRIC 00020036-0800093A log :Monitor: CALLER +Status *PASS* 2005-11-04/08:11:30.924 METRIC 00020036-08000940 log :Monitor: CALLER +Status *PASS* 2005-11-04/08:12:29.830 METRIC 00020036-08000941 log :Monitor: CALLER +Status *PASS* 2005-11-04/09:12:28.790 METRIC 00020036-08000943 log :Monitor: CALLER +Status *PASS* 2005-11-04/09:12:35.869 METRIC 00020036-08000944 log :Monitor: CALLER +Status *PASS*
    Obviously you'd have to tailor this to fit your needs, but it is something you can work with...


    Peter L. Berghold -- Unix Professional
    Peter -at- Berghold -dot- Net; AOL IM redcowdawg Yahoo IM: blue_cowdawg
Re: Parsing timestamps
by neilwatson (Priest) on Nov 04, 2005 at 19:43 UTC
    Thanks for all of your input. Here is the code snipped I used:
    # Timestamp 12 hours ago my $twelve_hours_ago = strftime("%Y-%m-%d/%H:%M:%S", (localtime(time-$ +hours*3600))); # Logfile may have been rotated during the past 12 hours. Thus we mus +t open # any logfile that is less than 12 hours old. find(\&wanted, "$logfile"); sub wanted { # Open loge file if it is less than 12 hours old and the # correct file name if ( (stat $_)[9] > time() - (3600 * $hours) && m/^log_file/ ) { # Debugging lines #print "file: ".$_."\n"; #my $stat = (stat $_)[9]; #print "stat: ".$stat."\n"; #my $twelve = time() - (3600 * $hours); #print "twelve: ".$twelve."\n"; # End of debugging lines. open LF, ($_) || die "Cannot open $logfile $!"; while (<LF>){ # Check for PASS log entry next unless (m/log :Monitor: CALLER Status \*PASS\*/); # Check timestamp $date = $1 if (m/^([^.]+)/); # Grab the date up to secon +ds next unless $date gt $twelve_hours_ago; #print "date: ".$date."\n"; # For debugging. # Count number for passes. $passcount++; # Save log line push @passlogs, $_; } } }

    Neil Watson
    watson-wilson.ca

Re: Parsing timestamps
by ambrus (Abbot) on Nov 04, 2005 at 22:02 UTC

    If you don't need tight performance, I recommend that you use Date::Manip. Here's an example.

    use Date::Manip; $limit = ParseDate("12 hours ago") or die "internal error"; while(<>) { /^(\S+).*\*PASS\*$/ and Date_Cmp($limit, ParseDate($1)) < 0 or nex +t; print "found: $_\n"; }

    Update: I see that blue_cowdawg has dome almost the same as me.