Show
Ignore:
Timestamp:
07/16/12 21:21:43 (2 years ago)
Author:
keith
Message:

Updated to latest tail_n_mail version

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/tools/tailnmail/tail_n_mail

    r299 r333  
    66## Greg Sabino Mullane <greg@endpoint.com> 
    77## Selena Deckelmann <selena@chesnok.com> 
     8## See more contributors in the 'Changes' file 
    89## BSD licensed 
    910## For full documentation, please see: http://bucardo.org/wiki/Tail_n_mail 
     
    2526use 5.008003; 
    2627 
    27 our $VERSION = '1.22.1'; 
     28our $VERSION = '1.26.0'; 
    2829 
    2930## Default message subject if not set elsewhere. Keywords replaced: FILE HOST NUMBER UNIQUE 
     
    8182              tempfile         => -1, 
    8283## Allow override of the log file to check 
    83               file             => '', 
     84              file             => [], 
     85## Strip out SQLSTATE codes from the FATAL and ERROR messages 
     86              sqlstate         => 0, 
    8487## Do not send email 
    8588              nomail           => 0, 
     
    98101## Send an email even if 0 matches were found 
    99102              mailzero         => 0, 
     103## Append a signature to the end of mailed messages 
     104              mailsig          => [], 
    100105## Perform some final prettification of queries 
    101106              pretty_query     => 1, 
     
    136141   'duration=i', 
    137142   'tempfile=i', 
    138    'file=s', 
     143   'file=s@', 
     144   'sqlstate', 
    139145   'type=s', 
    140146   'flatten!', 
     
    154160   'mailport=s', 
    155161   'mailzero', 
     162   'mailsig=s@', 
    156163   'smtp', 
    157164   'tsep=s', 
     
    200207 
    201208## Quick expansion of leading tildes for the file argument 
    202 $arg{file} =~ s{^~/?}{$ENV{HOME}/}; 
     209for (@{ $arg{file} }) { 
     210    s{^~/?}{$ENV{HOME}/}; 
     211
    203212 
    204213## Save away our hostname 
    205214my $hostname = qx{hostname}; 
    206215chomp $hostname; 
    207  
    208 ## We use a CURRENT key for future expansion 
    209 my $curr = 'CURRENT'; 
    210216 
    211217## Global option variables 
     
    234240## Process the log_line_prefix and change it into a regex 
    235241my ($havetime,$havepid) = (0,0); 
    236 $llp =~ s/([\-\[\]])/\\$1/g; 
     242 
     243## Escape certain things that may confuse the regex parser 
     244$llp =~ s/([\-\[\]\(\)])/\\$1/g; 
     245 
    237246## This assumes timestamp comes before the pid! 
    238247$llp =~ s/%t/(\\d\\d\\d\\d\-\\d\\d\-\\d\\d \\d\\d:\\d\\d:\\d\\d \\w\\w\\w\\w?)/ and $havetime=1; 
     
    251260$llp =~ s/%r/\\S*/; 
    252261$llp =~ s/%h/\\S*/; 
     262$llp =~ s/%a/\\S*/; 
    253263 
    254264if ($arg{pglog} eq 'syslog') { 
     
    300310 
    301311## Global regex: may change per file 
    302 my ($exclude, $include); 
     312my ($exclude, $include, $exclude_prefix); 
    303313 
    304314## Note if we bumped into maxsize when trying to read a file 
     
    331341## If they requested no lastfile, remove it now 
    332342if ($arg{nolastfile}) { 
    333     delete $opt{$curr}{lastfile}; 
     343    delete $opt{lastfile}; 
    334344} 
    335345 
     346## If they want a mail signature, open the file(s) and read it in now 
     347if (defined $arg{mailsig}->[0]) { 
     348    ## Combine all files in order into a single string 
     349    my $sigstring = ''; 
     350    my $fh; 
     351    for my $sigfile (@{$arg{mailsig}}) { 
     352        $sigfile =~ s{^~/?}{$ENV{HOME}/}; 
     353        if (! open $fh, '<', $sigfile) { 
     354            warn qq{Could not open signature file "$sigfile": $!\n}; 
     355            exit 1; 
     356        } 
     357        { local $/; $sigstring .= <$fh>; } 
     358        close $fh or warn qq{Could not close "$sigfile": $!\n}; 
     359    } 
     360    $arg{mailsignature} = $sigstring; 
     361} 
    336362 
    337363## Parse each file returned by pick_log_file until we start looping 
    338364my $last_logfile = ''; 
    339365my @files_parsed; 
     366my $filenumber = 0; 
     367my $fileinfo = $opt{file}[$filenumber]; 
     368 
    340369{ 
     370 
    341371    ## Generate the next log file to parse 
    342     my $logfile = pick_log_file(); 
    343  
    344     ## If undefined, simply exit the loop 
    345     last if ! defined $logfile; 
    346  
    347     ## If it's the same as the last one we did, we are done 
    348     last if $last_logfile eq $logfile; 
     372    my $logfile = pick_log_file($fileinfo); 
     373 
     374    ## If undefined or same as last time, we are done with this file 
     375    if (! defined $logfile or $logfile eq $last_logfile) { 
     376        ## Grab the next extry 
     377        $fileinfo = $opt{file}[++$filenumber]; 
     378        ## No more file? We are done! 
     379        last if ! defined $fileinfo; 
     380        # Otherwise, loop back with the new fileinfo 
     381        redo; 
     382    } 
    349383 
    350384    $arg{debug} and warn " Parsing file: $logfile\n"; 
    351385 
    352     ## Force to CSV mode if the file ends in csv 
    353     $logfile =~ /\.csv/ and $arg{pglog} = 'csv'; 
    354  
    355     my $count = parse_file($logfile); 
     386    my $count = parse_file($logfile, $fileinfo); 
    356387 
    357388    if ($count >= 0) { 
     
    365396} 
    366397 
    367 $opt{$curr}{filename} = $last_logfile; 
    368  
    369398## We're done parsing the message, send an email if needed 
    370 process_report() if $opt{grand_total} or $arg{mailzero} or $opt{$curr}{mailzero}; 
     399process_report() if $opt{grand_total} or $arg{mailzero} or $opt{mailzero}; 
    371400final_cleanup(); 
    372401 
     
    377406 
    378407    ## Figure out which files we need to parse 
     408    ## Sole argument is a hashref of file information: 
     409    ##   name: logfile to open 
     410    ##   original: original name 
     411    ##   lastfile: we scanned last time this ran. May be an empty string or not exist 
     412 
     413    my $info = shift; 
     414 
     415    my $name = $info->{name} or die 'No name for the file found!'; 
     416    my $orig = $info->{original} or die 'No original file found!'; 
     417    my $lastfile = $info->{lastfile} || ''; 
    379418 
    380419    ## Basic flow: 
     
    382421    ## Then walk forward until we hit the most recent one 
    383422 
    384     ## If a custom file, we always just return the main filename 
    385     ## We also remove the lastfile, as it's not important anymore 
    386     ## Same for a reset - we only want the latest file 
    387     if ($arg{file} or $arg{reset}) { 
    388         delete $opt{$curr}{lastfile}; 
    389         return $arg{file} || $opt{$curr}{filename}; 
    390     } 
    391  
    392     my $lastfile = $opt{$curr}{lastfile}; 
    393     my $orig = $opt{$curr}{original_filename} || ''; 
    394  
    395423    ## Handle the LATEST case right away 
    396424    if ($orig =~ s{([^/\\]*)LATEST([^/\\]*)$}{}o) { 
     
    401429        ## We need all files newer than that one, in order, until we run out 
    402430 
    403         ## Already have the list? Pop off items until we are done 
    404         if (exists $opt{$curr}{middle_filenames}) { 
    405             ## Return the next file, or undef when we run out 
    406             return pop @{$opt{$curr}{middle_filenames}}; 
    407         } 
    408  
    409         my $dir = $orig; 
    410         $dir =~ s{/\z}{}; 
    411         -d $dir or die qq{Cannot open $dir: not a directory!\n}; 
    412         opendir my $dh, $dir or die qq{Could not opendir "$dir": $!\n}; 
    413  
    414         ## We need the modification time of the lastfile 
    415         my $lastfiletime = defined $lastfile ? -M $lastfile : 0; 
    416  
    417         my %fileq; 
    418         while (my $file = readdir($dh)) { 
    419             my $fname = "$dir/$file"; 
    420             my $modtime = -M $fname; 
    421             ## Skip if not a normal file 
    422             next if ! -f _; 
    423             if (length $prefix or length $postfix) { 
    424                 next if $file !~ /\A\Q$prefix\E.*\Q$postfix\E\z/o; 
    425             } 
    426             ## Skip if it's older than the lastfile 
    427             next if $lastfiletime and $modtime > $lastfiletime; 
    428             $fileq{$modtime}{$fname} = 1; 
    429         } 
    430         closedir $dh or warn qq{Could not closedir "$dir": $!\n}; 
    431       TF: for my $time (sort { $a <=> $b } keys %fileq) { 
    432             for my $file (sort keys %{$fileq{$time}}) { 
    433                 push @{$opt{$curr}{middle_filenames}} => $file; 
    434                 ## If we don't have a lastfile, we simply use the most recent file 
    435                 ## and throw away the rest 
    436                 last TF if ! $lastfiletime; 
    437             } 
    438         } 
    439  
    440         return pop @{$opt{$curr}{middle_filenames}}; 
     431        ## If we don't have the list already, build it now 
     432        if (! exists $opt{middle_filenames}) { 
     433 
     434            my $dir = $orig; 
     435            $dir =~ s{/\z}{}; 
     436            -d $dir or die qq{Cannot open $dir: not a directory!\n}; 
     437            opendir my $dh, $dir or die qq{Could not opendir "$dir": $!\n}; 
     438 
     439            ## We need the modification time of the lastfile 
     440            my $lastfiletime = defined $lastfile ? -M $lastfile : 0; 
     441 
     442            my %fileq; 
     443            while (my $file = readdir($dh)) { 
     444                my $fname = "$dir/$file"; 
     445                my $modtime = -M $fname; 
     446                ## Skip if not a normal file 
     447                next if ! -f _; 
     448                if (length $prefix or length $postfix) { 
     449                    next if $file !~ /\A\Q$prefix\E.*\Q$postfix\E\z/o; 
     450                } 
     451                ## Skip if it's older than the lastfile 
     452                next if $lastfiletime and $modtime > $lastfiletime; 
     453                $fileq{$modtime}{$fname} = 1; 
     454            } 
     455            closedir $dh or warn qq{Could not closedir "$dir": $!\n}; 
     456 
     457          TF: for my $time (sort { $a <=> $b } keys %fileq) { 
     458                for my $file (sort keys %{$fileq{$time}}) { 
     459                    push @{$opt{middle_filenames}} => $file; 
     460                    ## If we don't have a lastfile, we simply use the most recent file 
     461                    ## and throw away the rest 
     462                    last TF if ! $lastfiletime; 
     463                } 
     464            } 
     465        } 
     466 
     467        ## Return the next file, or undef when we run out 
     468        my $nextfile = pop @{ $opt{middle_filenames} }; 
     469        ## If we are done, remove this temp hash 
     470        if (! defined $nextfile) { 
     471            delete $opt{middle_filenames}; 
     472        } 
     473        return $nextfile; 
    441474 
    442475    } ## end of LATEST time travel 
    443476 
    444477    ## No lastfile makes it easy 
    445     exists $opt{$curr}{lastfile} or return $opt{$curr}{filename}
     478    return $name if ! $lastfile
    446479 
    447480    ## If we haven't processed the lastfile, do that one first 
    448     exists $find{$lastfile} or return $lastfile
    449  
    450     ## If the last is the same as the current, return 
    451     $lastfile eq $opt{$curr}{filename} and return $lastfile; 
     481    return $lastfile if ! exists $find{$lastfile}
     482 
     483    ## If the last is the same as the current, return the name 
     484    return $name if $lastfile eq $name; 
    452485 
    453486    ## We've processed the last file, are there any files in between the two? 
     
    455488    if ($orig =~ /%/) { 
    456489 
    457         ## Already have the list? Pop off items until we are done 
    458         if (exists $opt{$curr}{middle_filenames}) { 
    459             my $newfile = pop @{$opt{$curr}{middle_filenames}}; 
    460             ## When we run out, return the current file 
    461             return $newfile || $opt{$curr}{filename}; 
    462         } 
    463  
    464         ## We're going to walk backwards, 30 minutes at a time, and gather up 
    465         ## all files between "now" and the "last" 
    466         my $timerewind = 60*30; ## 30 minutes 
    467         my $maxloops = 24*2 * 7 * 30; ## max of 30 days 
    468         my $bail = 0; 
    469         my %seenfile; 
    470         my $lastchecked = ''; 
    471         BACKINTIME: { 
    472  
    473             my @ltime = localtime(time - $timerewind); 
    474             my $newfile = strftime($orig, @ltime); 
    475             if ($newfile ne $lastchecked) { 
    476                 last if $newfile eq $lastfile; 
    477                 $arg{debug} and warn "Checking for file $newfile (last was $lastfile)\n"; 
    478                 if (! exists $seenfile{$newfile}) { 
    479                     $seenfile{$newfile} = 1; 
    480                     push @{$opt{$curr}{middle_filenames}} => $newfile; 
     490        ## Build the list if we don't have it yet 
     491        if (! exists $opt{middle_filenames}) { 
     492 
     493            ## We're going to walk backwards, 30 minutes at a time, and gather up 
     494            ## all files between "now" and the "last" 
     495            my $timerewind = 60*30; ## 30 minutes 
     496            my $maxloops = 24*2 * 7 * 30; ## max of 30 days 
     497            my $bail = 0; 
     498            my %seenfile; 
     499            my $lastchecked = ''; 
     500          BACKINTIME: { 
     501 
     502                my @ltime = localtime(time - $timerewind); 
     503                my $newfile = strftime($orig, @ltime); 
     504                if ($newfile ne $lastchecked) { 
     505                    last if $newfile eq $lastfile; 
     506                    $arg{debug} and warn "Checking for file $newfile (last was $lastfile)\n"; 
     507                    if (! exists $seenfile{$newfile}) { 
     508                        $seenfile{$newfile} = 1; 
     509                        push @{$opt{middle_filenames}} => $newfile; 
     510                    } 
     511                    $lastchecked = $newfile; 
    481512                } 
    482                 $lastchecked = $newfile; 
    483             } 
    484  
    485             $timerewind += 60*30; 
    486             ++$bail > $maxloops and die "Too many loops ($bail): bailing\n"; 
    487             redo; 
    488         } 
    489  
    490         return (keys %seenfile) ? (pop @{$opt{$curr}{middle_filenames}}) : $opt{$curr}{filename}; 
     513 
     514                $timerewind += 60*30; 
     515                ++$bail > $maxloops and die "Too many loops ($bail): bailing\n"; 
     516                redo; 
     517            } 
     518 
     519        } 
     520 
     521        ## If the above loop found nothing, return the current name 
     522        if (! exists $opt{middle_filenames}) { 
     523            return $name; 
     524        } 
     525 
     526        ## Otherwise, pull it off the list until there is nothing left 
     527        my $nextfile = pop @{ $opt{middle_filenames} }; 
     528        ## If we are done, remove this temp hash 
     529        if (! defined $nextfile) { 
     530            delete $opt{middle_filenames}; 
     531        } 
     532        return $nextfile; 
    491533    } 
    492534 
    493535    ## Just return the current file 
    494     return $opt{$curr}{filename}
     536    return $name
    495537 
    496538} ## end of pick_log_file 
     
    521563        while (<$rc>) { 
    522564            next if /^\s*#/; 
    523             next if ! /^\s*(\w+)\s*[=:]\s*(.+?)\s*$/o; 
     565            next if ! /^\s*([\w\_\-]+)\s*[=:]\s*(.+?)\s*$/o; 
    524566            my ($name,$value) = (lc $1,$2); 
    525             $opt{$curr}{$name} = $value; 
     567            $opt{$name} = $value; 
    526568            $arg{$name} = $value; 
    527569            ## If we are disabled, simply exit quietly 
     
    582624 
    583625        ## A non-comment after one or comments allows us to map them to each other 
    584         if (@comment and m{^(\w+):}) { 
     626        if (@comment and m{^([\w\_\-]+):}) { 
    585627            chomp; 
    586628            for my $c (@comment) { 
     
    593635        } 
    594636 
    595         ## What file are we checking on? 
    596         if (/^FILE:\s*(.+?)\s*$/) { 
    597             my $filename = $localopt{original_filename} = $1; 
    598  
     637        ## What file(s) are we checking on? 
     638        if (/^FILE(\d*):\s*(.+?)\s*$/) { 
     639 
     640            my $suffix = $1 || 0; 
     641            my $filename = $2; 
     642 
     643            ## Basic sanity check 
    599644            if ($filename !~ /\w/) { 
    600                 die "No FILE found in the config file! (tried: $filename)\n"; 
    601             } 
    602  
    603             $filename = transform_filename($filename); 
    604  
    605             ## If a custom file was specified, use that instead 
    606             if ($arg{file}) { 
    607                 ## If it contains a path, use it directly 
    608                 if ($arg{file} =~ m{/}) { 
    609                     $filename = $arg{file}; 
     645                die "No valid FILE found in the config file! (tried: $filename)\n"; 
     646            } 
     647 
     648            ## If files were specified on the command line, use those instead 
     649            if ($arg{file}[0]) { 
     650 
     651                ## Have we been here already? Only need to override once 
     652                if (! exists  $localopt{filename}) { 
     653 
     654                    for my $argfile (@{ $arg{file} }) { 
     655 
     656                        ## If it contains a path, use it directly 
     657                        if ($argfile =~ m{/}) { 
     658                            $filename = $argfile; 
     659                        } 
     660                        ## Otherwise, replace the current file name but keep the directory 
     661                        else { 
     662                            my $dir = dirname($filename); 
     663                            $filename = "$dir/$argfile"; 
     664                        } 
     665 
     666                        ## Add it to our local list both as final and original name 
     667                        push @{ $localopt{file} } => { 
     668                            name => $filename, 
     669                            original => $filename, 
     670                            commandline => 1, 
     671                            lastfile => '', 
     672                            offset => 0, 
     673                        }; 
     674                    } 
     675 
     676                    next; 
    610677                } 
    611                 ## Otherwise, replace the current file name but keep the directory 
    612                 else { 
    613                     my $dir = dirname($filename); 
    614                     $filename = "$dir/$arg{file}"; 
    615                 } 
    616             } 
    617  
    618             ## Set some default values 
    619             $localopt{filename} = $filename; 
    620             $localopt{exclude} ||= []; 
    621             $localopt{include} ||= []; 
    622             $localopt{email}   ||= []; 
    623  
     678            } 
     679 
     680            ## If the file contains % escapes, replace with the actual time 
     681            my $newfilename = transform_filename($filename); 
     682 
     683            ## Save to the local list, storing the original filename for config rewriting 
     684            push @{ $localopt{file} } => 
     685                { 
     686                 name => $newfilename, 
     687                 original => $filename, 
     688                 suffix => $suffix, 
     689                 }; 
    624690        } ## end of FILE: 
    625691 
    626692        ## The last filename we used 
    627         elsif (/^LASTFILE:\s*(.+?)\s*$/) { 
    628             $localopt{lastfile} = $1; 
     693        elsif (/^LASTFILE(\d*):\s*(.+?)\s*$/) { 
     694            my $suffix = $1 || 1; 
     695            $localopt{lastfile}{$suffix} = $2; 
    629696        } 
    630697        ## Who to send emails to for this file 
     
    668735            } 
    669736        } 
     737        ## Allow a very local log_line_prefix 
     738        elsif (/^LOG_LINE_PREFIX:\s*(.+)/) { 
     739            $arg{log_line_prefix} = $localopt{log_line_prefix} = $1; 
     740        } 
    670741        ## How to sort the output 
    671742        elsif (/^SORTBY:\s*(\w+)/) { 
     
    684755            push @{$localopt{exclude}}, $1; 
    685756        } 
     757        ## Which prefix lines to exclude from the report 
     758        elsif (/^EXCLUDE_PREFIX:\s*(.+?)\s*$/) { 
     759            push @{$localopt{exclude_prefix}}, $1; 
     760        } 
    686761        ## Which lines to include in the report 
    687762        elsif (/^INCLUDE:\s*(.+)/) { 
    688763            push @{$localopt{include}}, $1; 
    689764        } 
    690         ## The current offset into the file 
    691         elsif (/^OFFSET:\s*(\d+)/) { 
    692             $localopt{offset} = $1; 
    693         } 
    694         ## The custom maxsize for this file 
     765        ## The current offset into a file 
     766        elsif (/^OFFSET(\d*):\s*(\d+)/) { 
     767            my $suffix = $1 || 1; 
     768            $localopt{offset}{$suffix} = $2; 
     769        } 
     770        ## The custom maxsize for all files 
    695771        elsif (/^MAXSIZE:\s*(\d+)/) { 
    696772            $localopt{maxsize} = $1; 
    697773        } 
    698         ## The subject line for this file 
     774        ## The subject line 
    699775        elsif (/^MAILSUBJECT:\s*(.+)/) { ## Trailing whitespace is significant here 
    700776            $localopt{mailsubject} = $1; 
     
    705781            $localopt{mailzero} = $1; 
    706782        } 
     783        ## Allow (possibly multiple) mail signatures 
     784        elsif (/^MAILSIG:\s*(.+)/) { 
     785            push @{$localopt{mailsig}}, $1; 
     786            push @{$arg{mailsig}}, $1; 
     787        } 
    707788        ## Size at which we cutoff long statements 
    708789        elsif (/^STATEMENT_SIZE:\s*(.+)/) { 
     
    711792    } 
    712793    close $c or die qq{Could not close "$configfile": $!\n}; 
     794 
     795    ## Adjust the file suffixes as needed 
     796    ## This allows us to simply add multiple bare 'FILE:' entries before the first rewrite 
     797    ## We also plug in the LASTFILE AND OFFSET values now 
     798    my %numused; 
     799    for my $file (@{ $localopt{file} }) { 
     800        $file->{suffix} ||= 0; 
     801        next if ! $file->{suffix}; 
     802        if ($numused{$file->{suffix}}++) { 
     803            die "The same FILE suffix ($file->{suffix}) was used more than once!\n"; 
     804        } 
     805    } 
     806    for my $file (@{ $localopt{file} }) { 
     807 
     808        ## No need to change anything if we forced via the command line 
     809        next if $file->{commandline}; 
     810 
     811        ## Only need to adjust 0s 
     812        if (! $file->{suffix}) { 
     813 
     814            ## Replace with the first free number 
     815            my $x = 1; 
     816            { 
     817                if (! $numused{$x}++) { 
     818                    $file->{suffix} = $x; 
     819                    last; 
     820                } 
     821                if ($x++ > 999) { 
     822                    die "Something went wrong: 999 iterations to find a FILE suffix!\n"; 
     823                } 
     824                redo; 
     825            } 
     826        } 
     827 
     828        ## Put the lastfile into place if it exists 
     829        $file->{lastfile} = $localopt{lastfile}{$file->{suffix}} || ''; 
     830 
     831        ## Put the offset into place if it exists 
     832        $file->{offset} = $localopt{offset}{$file->{suffix}} || 0; 
     833 
     834    } 
    713835 
    714836    ## Move the local vars into place, also record that we found them here 
     
    722844            } 
    723845        } 
    724         $opt{$curr}{$k} = $localopt{$k}; 
    725     } 
     846        $opt{$k} = $localopt{$k}; 
     847    } 
     848 
    726849    if ($arg{debug}) { 
    727850        local $Data::Dumper::Varname = 'opt'; 
     851        warn Dumper \%opt; 
     852        local $Data::Dumper::Varname = 'arg'; 
    728853        warn Dumper \%arg; 
    729854    } 
     
    738863    ## Call parse_inherit_file on each item in $opt{inherit} 
    739864 
    740     for my $file (@{$opt{$curr}{inherit}}) { 
     865    for my $file (@{$opt{inherit}}) { 
    741866        parse_inherit_file($file); 
    742867    } 
     
    819944        ## How to sort the output 
    820945        elsif (/^SORTBY:\s*(\w+)/) { 
    821             $opt{$curr}{sortby} = $1; 
     946            $opt{sortby} = $1; 
    822947        } 
    823948        ## Which lines to exclude from the report 
    824949        elsif (/^EXCLUDE:\s*(.+?)\s*$/) { 
    825             push @{$opt{$curr}{exclude}}, $1; 
     950            push @{$opt{exclude}}, $1; 
     951        } 
     952        ## Which prefix lines to exclude from the report 
     953        elsif (/^EXCLUDE_PREFIX:\s*(.+?)\s*$/) { 
     954            push @{$opt{exclude_prefix}}, $1; 
    826955        } 
    827956        ## Which lines to include in the report 
    828957        elsif (/^INCLUDE:\s*(.+)/) { 
    829             push @{$opt{$curr}{include}}, $1; 
     958            push @{$opt{include}}, $1; 
    830959        } 
    831960        ## Maximum file size 
    832961        elsif (/^MAXSIZE:\s*(\d+)/) { 
    833             $opt{$curr}{maxsize} = $1; 
     962            $opt{maxsize} = $1; 
    834963        } 
    835964        ## Exclude durations below this number 
     
    859988        ## Who to send emails from 
    860989        elsif (/^FROM:\s*(.+?)\s*$/) { 
    861             $opt{$curr}{from} = $1; 
     990            $opt{from} = $1; 
    862991        } 
    863992        ## Who to send emails to for this file 
    864993        elsif (/^EMAIL:\s*(.+?)\s*$/) { 
    865             push @{$opt{$curr}{email}}, $1; 
     994            push @{$opt{email}}, $1; 
    866995        } 
    867996        ## Force mail to be sent - overrides any other setting 
    868997        elsif (/^MAILZERO:\s*(.+)/) { 
    869             $opt{$curr}{mailzero} = $1; 
     998            $opt{mailzero} = $1; 
    870999        } 
    8711000        ## The file to use 
    872         elsif (/^FILE:\s*(.+)/) { 
    873             $opt{$curr}{original_filename} = $1; 
    874             $opt{$curr}{filename} = transform_filename($1); 
    875             $opt{$curr}{inherited_filename} = 1; 
     1001        elsif (/^FILE(\d*):\s*(.+)/) { 
     1002 
     1003            my $suffix = $1 || 0; 
     1004            my $filename = $2; 
     1005 
     1006            ## Skip entirely if we have a command-line file request 
     1007            ## This is handled in the main config parsing 
     1008            next if $arg{file}[0]; 
     1009 
     1010            ## As with the normal config file, store a temp version 
     1011            ## Save to the local list, storing the original filename for config rewriting 
     1012            push @{ $opt{tempifile} } => 
     1013                { 
     1014                 original => $filename, 
     1015                 suffix => $suffix, 
     1016                 }; 
    8761017        } 
    8771018        ## The mail subject 
    8781019        elsif (/^MAILSUBJECT:\s*(.+)/) { 
    879             $opt{$curr}{mailsubject} = $1; 
    880             $opt{$curr}{customsubject} = 1; 
     1020            $opt{mailsubject} = $1; 
     1021            $opt{customsubject} = 1; 
     1022        } 
     1023        ## The mail signature 
     1024        elsif (/^MAILSIG:\s*(.+)/) { 
     1025            push @{$opt{mailsig}}, $1; 
    8811026        } 
    8821027        ## The log line prefix 
     
    8861031        ## Size at which we cutoff long statements 
    8871032        elsif (/^STATEMENT_SIZE:\s*(.+)/) { 
    888             $opt{$curr}{statement_size} = $1; 
     1033            $opt{statement_size} = $1; 
    8891034        } 
    8901035        else { 
     
    8951040    close $fh or warn qq{Could not close file "$file": $!\n}; 
    8961041 
     1042    ## Merge all the "FILE" entries and adjust suffixes 
     1043    ## We allow overlap between the normal and inherited lists 
     1044 
     1045    if (exists $opt{tempifile}) { 
     1046 
     1047        my %numused; 
     1048        for my $file (@{ $opt{tempifile} }) { 
     1049            $file->{suffix} ||= 0; 
     1050            next if ! $file->{suffix}; 
     1051            if ($numused{$file->{suffix}}++) { 
     1052                die "The same FILE suffix ($file->{suffix}) was used more than once inside $filename!\n"; 
     1053            } 
     1054        } 
     1055 
     1056        for my $file (@{ $opt{tempifile} }) { 
     1057 
     1058            ## Change zero to the first free number 
     1059            if (! $file->{suffix}) { 
     1060                my $x = 1; 
     1061                { 
     1062                    if (! $numused{$x}++) { 
     1063                        $file->{suffix} = $x; 
     1064                        last; 
     1065                    } 
     1066                    if ($x++ > 999) { 
     1067                        die "Something went wrong: 999 iterations to find a FILE suffix inside $filename!\n"; 
     1068                    } 
     1069                    redo; 
     1070                } 
     1071            } 
     1072 
     1073            ## Create our real entry 
     1074            push @{ $opt{file} } => 
     1075                { 
     1076                 name      => transform_filename($file->{original}), 
     1077                 original  => $file->{original}, 
     1078                 suffix    => $file->{suffix}, 
     1079                 inherited => 1, 
     1080                 }; 
     1081        } 
     1082 
     1083        ## Remove our temporary list 
     1084        delete $opt{tempifile}; 
     1085    } 
     1086 
    8971087    return; 
    8981088 
     
    9021092sub parse_file { 
    9031093 
    904     ## Parse the passed in file 
     1094    ## Parse a file - this is the workhorse 
     1095    ## Arguments: two 
     1096    ## 1. Exact filename we are parsing 
     1097    ## 2. Hashref of file information: 
     1098    ##   name: logfile to open 
     1099    ##   original: original name 
     1100    ##   lastfile: we scanned last time this ran. May be an empty string or not exist 
     1101    ##   offset: where in the file we stopped at last time 
    9051102    ## Returns the number of matches 
    9061103 
    9071104    my $filename = shift; 
     1105    my $fileinfo = shift; 
     1106 
     1107    ## The file we scanned last time we ran 
     1108    my $lastfile = $fileinfo->{lastfile} || ''; 
     1109 
     1110    ## Set this as the latest (but not the lastfile) 
     1111    $fileinfo->{latest} = $filename; 
    9081112 
    9091113    ## Touch the hash so we know we've been here 
     
    9231127    my $size = -s $filename; 
    9241128    my $offset = 0; 
    925     my $maxsize = $opt{$curr}{maxsize} ? $opt{$curr}{maxsize} : $arg{maxsize}; 
     1129    my $maxsize = $opt{maxsize} ? $opt{maxsize} : $arg{maxsize}; 
    9261130 
    9271131    ## Is the offset significant? 
    928     ## Usually only is if the stored offset matches the current file 
    929  
    930     if (!$arg{file} 
    931         and (!exists $opt{$curr}{lastfile} or ($opt{$curr}{lastfile} eq $filename)) 
     1132    if (!$arg{file}[0]               ## ...not if we passed in filenames manually 
     1133        and $lastfile eq $filename   ## ...not if this is not the same file we got the offset for last time 
    9321134        ) { 
    9331135        ## Allow the offset to equal the size via --reset 
    9341136        if ($arg{reset}) { 
    935             $offset = $opt{$curr}{newoffset} = $size; 
     1137            $offset = $size; 
    9361138            $arg{verbose} and warn "  Resetting offset to $offset\n"; 
    9371139        } 
     
    9461148            } 
    9471149        } 
    948         elsif (exists $opt{$curr}{offset})
    949             $offset = $opt{$curr}{offset}
     1150        else
     1151            $offset = $fileinfo->{offset} || 0
    9501152        } 
    9511153    } 
     
    9631165 
    9641166    ## If the offset is equal to the size, we're done! 
    965     return 0 if $offset >= $size; 
     1167    ## Store the offset if it is truly new and significant 
     1168    if ($offset >= $size) { 
     1169        $offset = $size; 
     1170        if ($offset and $fileinfo->{offset} != $offset) { 
     1171            $opt{newoffset}{$filename} = $offset; 
     1172        } 
     1173        return 0; 
     1174    } 
    9661175 
    9671176    ## Store the original offset 
     
    10211230 
    10221231        } ## end pos > 1 
     1232 
    10231233    } ## end find_line_number 
    10241234 
    10251235    ## Get exclusion and inclusion regexes for this file 
    1026     ($exclude,$include) = generate_regexes($filename); 
     1236    ($exclude,$include,$exclude_prefix) = generate_regexes($filename); 
    10271237 
    10281238    ## Discard the previous line if needed (we rewound by 10 characters above) 
     
    10351245    my %pidline; 
    10361246 
    1037     if (lc $arg{pglog} eq 'csv') { 
     1247    ## Switch to CSV mode if required of if the file ends in '.csv' 
     1248    if (lc $arg{pglog} eq 'csv' or $filename =~ /\.csv$/) { 
    10381249        if (! defined $csv) { 
    10391250            eval { 
     
    10691280        my $lastline = ''; 
    10701281        my $syslognum = 0; ## used by syslog only 
     1282        my $bailout = 0; ## emergency bail out in case we end up sleep seeking 
    10711283      LOGLINE: while (<$fh>) { 
     1284 
     1285            ## We ran into a truncated line last time, so we are most likely done 
     1286            last if $bailout; 
     1287 
    10721288            ## Easiest to just remove the newline here and now 
    1073             chomp; 
     1289            if (! chomp) { 
     1290                ## There was no newline, so it's possible some other process is in 
     1291                ## the middle of writing this line. Just in case this is so, sleep and 
     1292                ## let it finish, then try again. Because we don't want to turn this 
     1293                ## into a tail -f situation, bail out of the loop once done 
     1294                sleep 1; 
     1295                ## Rewind just far enough to try this line again 
     1296                seek $fh, - (length $_), 1; 
     1297                $_ = <$fh>; 
     1298                if (! chomp) { 
     1299                    ## Still no go! Let's just leave and abandon this line 
     1300                    last LOGLINE; 
     1301                } 
     1302                ## Success! Finish up this line, but then abandon any further slurping 
     1303                $bailout = 1; 
     1304            } 
    10741305            if ($arg{pgmode}) { 
    10751306                ## 1=prefix 2=timestamp 3=PID 4=rest 
    10761307                if ($_ =~ s/$pgpidre/$4/) { 
    10771308 
    1078                     $pgprefix = $1; 
     1309                    ## We want the timestamp and the pid, even if we have to fake it 
     1310                    ($pgprefix,$pgts,$pgpid,$pgnum) = ($1, $2||'', $3||1, 1); 
     1311 
    10791312                    $pgprefix =~ s/\s+$//o; 
    1080                     ## We want the timestamp and the pid, even if we have to fake it 
    1081                     ($pgts,$pgpid,$pgnum) = ($2||'',$3||1, 1); 
    10821313                    if ($arg{pglog} eq 'syslog') { 
    10831314                        if ($pgprefix =~ /\[(\d+)\-\d+/) { 
     
    11171348                    } 
    11181349 
     1350                    ## Optionally strip out SQLSTATE codes 
     1351                    if ($arg{sqlstate}) { 
     1352                        $_ =~ s/^(?:FATAL|ERROR):  ([0-9A-Z]{5}): /ERROR:  /o; 
     1353                    } 
     1354 
    11191355                    ## Assign this string to the current pgnum slot 
    11201356                    $pidline{$pgpid}{string}{$pgnum} = $_; 
     
    11851421    ## Get the new offset and store it 
    11861422    seek $fh, 0, 1; 
    1187     $opt{$curr}{newoffset} = tell $fh; 
     1423    $offset = tell $fh; 
     1424    if ($fileinfo->{offset} != $offset and $offset) { 
     1425        $opt{newoffset}{$filename} = $offset; 
     1426    } 
    11881427 
    11891428    close $fh or die qq{Could not close "$filename": $!\n}; 
     
    12301469    ## Currently, all files get the same regex, so we cache it 
    12311470    if (exists $opt{globalexcluderegex}) { 
    1232         return $opt{globalexcluderegex}, $opt{globalincluderegex}; 
     1471        return $opt{globalexcluderegex}, $opt{globalincluderegex}, $opt{globalexcludeprefixregex}; 
     1472 
    12331473    } 
    12341474 
    12351475    ## Build an exclusion regex 
    12361476    my $lexclude = ''; 
    1237     for my $ex (@{$opt{$curr}{exclude}}) { 
     1477    for my $ex (@{$opt{exclude}}) { 
    12381478        $arg{debug} and warn "  Adding exclusion: $ex\n"; 
    12391479        my $regex = qr{$ex}; 
     
    12431483    $arg{verbose} and $lexclude and warn "  Exclusion: $lexclude\n"; 
    12441484 
     1485    ## Build a prefix exclusion regex 
     1486    my $lexclude_prefix = ''; 
     1487    for my $ex (@{$opt{exclude_prefix}}) { 
     1488        $arg{debug} and warn "  Adding exclusion_prefix: $ex\n"; 
     1489        my $regex = qr{$ex}; 
     1490        $lexclude_prefix .= "$regex|"; 
     1491    } 
     1492    $lexclude_prefix =~ s/\|$//; 
     1493    $arg{verbose} and $lexclude_prefix and warn "  Exclusion_prefix: $lexclude_prefix\n"; 
     1494 
    12451495    ## Build an inclusion regex 
    12461496    my $linclude = ''; 
    1247     for my $in (@{$opt{$curr}{include}}) { 
     1497    for my $in (@{$opt{include}}) { 
    12481498        $arg{debug} and warn "  Adding inclusion: $in\n"; 
    12491499        my $regex = qr{$in}; 
     
    12541504 
    12551505    $opt{globalexcluderegex} = $lexclude; 
     1506    $opt{globalexcludeprefixregex} = $lexclude_prefix; 
    12561507    $opt{globalincluderegex} = $linclude; 
    12571508 
    1258     return $lexclude, $linclude
     1509    return $lexclude, $linclude, $lexclude_prefix
    12591510 
    12601511} ## end of generate_regexes 
     
    13131564        ## Bail if it matches the exclusion regex 
    13141565        return 0 if $exclude and $string =~ $exclude; 
     1566 
     1567        ## Bail if it matches the prefix exclusion regex 
     1568        return 0 if $exclude_prefix and $pgprefix =~ $exclude_prefix; 
    13151569    } 
    13161570 
     
    13181572    if ($arg{type} eq 'duration' and $arg{duration} >= 0) { 
    13191573        return 0 if ($string =~ / duration: (\d+)/o and $1 < $arg{duration}); 
    1320     } 
    1321  
    1322     ## If in tempfile mode, and we have a minimum cutoff, discard smaller ones 
    1323     if ($arg{type} eq 'tempfile' and $arg{tempfile} >= 0) { 
    1324         #return 0 if ($string =~ / temporary file:.+?size (\d+)/o and $1 < $arg{tempfile}); 
    13251574    } 
    13261575 
     
    13801629        }geix; 
    13811630 
    1382         my $thisletter
     1631        my $thisletter = ''
    13831632 
    13841633        $string =~ s{(VALUES|REPLACE)\s*\((.+)\)}{ ## For emacs: ()()() 
     
    17171966 
    17181967    ## Subject with replaced keywords: 
    1719     my $subject = $opt{$curr}{mailsubject} || $DEFAULT_SUBJECT; 
     1968    my $subject = $opt{mailsubject} || $DEFAULT_SUBJECT; 
    17201969    $subject =~ s/FILE/$last_file_parsed/g; 
    17211970    $subject =~ s/HOST/$hostname/g; 
    1722     if ($arg{tsepnosub} or $opt{$curr}{tsepnosub}) { 
     1971    if ($arg{tsepnosub} or $opt{tsepnosub}) { 
    17231972        $subject =~ s/NUMBER/$grand_total/g; 
    17241973        $subject =~ s/UNIQUE/$unique_matches/g; 
     
    17401989 
    17411990    ## Allow no specific email for dryruns 
    1742     if (! @{$opt{$curr}{email}}) { 
     1991    if (! exists $opt{email} or ! @{$opt{email}}) { 
    17431992        if ($arg{dryrun} or $arg{nomail}) { 
    1744             push @{$opt{$curr}{email}} => 'dryrun@example.com'; 
     1993            push @{$opt{email}} => 'dryrun@example.com'; ## no critic (RequireInterpolationOfMetachars) 
    17451994        } 
    17461995    } 
    17471996 
    17481997    ## Fill out the "To:" fields 
    1749     for my $email (@{$opt{$curr}{email}}) { 
     1998    for my $email (@{$opt{email}}) { 
    17501999        push @header => "To: $email"; 
    17512000    } 
    1752     if (! @{$opt{$curr}{email}}) { 
     2001    if (! @{$opt{email}}) { 
    17532002        die "Cannot send email without knowing who to send to!\n"; 
    17542003    } 
    17552004 
    1756     my $mailcom = $opt{$curr}{mailcom} || $arg{mailcom}; 
     2005    my $mailcom = $opt{mailcom} || $arg{mailcom}; 
    17572006 
    17582007    ## Custom From: 
    1759     my $from_addr = $opt{$curr}{from} || ''; 
     2008    my $from_addr = $opt{from} || ''; 
    17602009    if ($from_addr ne '') { 
    17612010        push @header => "From: $from_addr"; 
     
    17882037        push @msg => "Total matches: $pretty_grand_total"; 
    17892038        my $maxcount = 1; 
     2039        my $maxname = 1; 
    17902040        my $maxletter = 1; 
    17912041        for my $file (@files_parsed) { 
     
    17932043            $file->[1] = pretty_number($file->[1]); 
    17942044            $maxcount = length $file->[1] if length $file->[1] > $maxcount; 
     2045            $maxname = length $file->[0] if length $file->[0] > $maxname; 
    17952046            my $name = chr(65+$letter); 
    17962047            if ($letter >= 26) { 
     
    18052056            next if ! $file->[1]; 
    18062057            my $name = $fab{$file->[0]}; 
    1807             push @msg => sprintf 'Matches from %-*s %s: %*s', 
     2058            push @msg => sprintf 'Matches from %-*s %-*s %*s', 
    18082059                $maxletter + 2, 
    18092060                "[$name]", 
    1810                 $file->[0], 
     2061                $maxname+1, 
     2062                "$file->[0]:", 
    18112063                $maxcount, 
    18122064                $file->[1]; 
     
    18452097    } 
    18462098 
    1847     my $emails = join ' ' => @{$opt{$curr}{email}}; 
     2099    my $emails = join ' ' => @{$opt{email}}; 
    18482100 
    18492101    ## Sanity check on number of loops below 
     
    19382190            last if tell $bigfh >= $stop_point; 
    19392191        } 
     2192 
     2193        ## If we have a signature, add it 
     2194        if ($arg{mailsignature}) { 
     2195            ## Caller's responsibility to add a "--" line 
     2196            print {$efh} $arg{mailsignature}; 
     2197        } 
     2198 
    19402199        close $efh or warn qq{Could not close $emailfile: $!\n}; 
    19412200 
     
    19462205        } 
    19472206        else { 
    1948             my $mailmode = $opt{$curr}{mailmode} || $arg{mailmode}; 
     2207            my $mailmode = $opt{mailmode} || $arg{mailmode}; 
    19492208            if ($arg{mailmode} eq 'sendmail') { 
    19502209                system $COM; 
     
    20082267 
    20092268    ## Absorb any values set by rc files, and sanity check things 
    2010     my $mailserver = $opt{$curr}{mailserver} || $arg{mailserver}; 
     2269    my $mailserver = $opt{mailserver} || $arg{mailserver}; 
    20112270    if ($mailserver eq 'example.com') { 
    20122271        die qq{When using smtp mode, you must specify a mailserver!\n}; 
    20132272    } 
    2014     my $mailuser = $opt{$curr}{mailuser} || $arg{mailuser}; 
     2273    my $mailuser = $opt{mailuser} || $arg{mailuser}; 
    20152274    if ($mailuser eq 'example') { 
    20162275        die qq{When using smtp mode, you must specify a mailuser!\n}; 
    20172276    } 
    2018     my $mailpass = $opt{$curr}{mailpass} || $arg{mailpass}; 
     2277    my $mailpass = $opt{mailpass} || $arg{mailpass}; 
    20192278    if ($mailpass eq 'example') { 
    20202279        die qq{When using smtp mode, you must specify a mailpass!\n}; 
    20212280    } 
    2022     my $mailport = $opt{$curr}{mailport} || $arg{mailport}; 
     2281    my $mailport = $opt{mailport} || $arg{mailport}; 
    20232282 
    20242283    ## Attempt to connect to the server 
     
    20692328    sub sortsub { ## no critic (ProhibitNestedSubs) 
    20702329 
    2071         my $sorttype = $opt{$curr}{sortby} || $arg{sortby}; 
     2330        my $sorttype = $opt{sortby} || $arg{sortby}; 
    20722331 
    20732332        if ($arg{type} eq 'duration') { 
     
    21562415            $meancount++; 
    21572416            my $item = sprintf '(item %d, count is %d)', $f->{displaycount}, $f->{count}; 
    2158             push @mean => sprintf "%10s %-22s", pretty_size($f->{mean},1), $item; 
     2417            push @mean => sprintf '%10s %-22s', pretty_size($f->{mean},1), $item; 
    21592418            $maxmean = $f->{displaycount} if $f->{displaycount} > $maxmean; 
    21602419            last if $arg{tempfile_limit} and $meancount >= $arg{tempfile_limit}; 
     
    21692428            $totalcount++; 
    21702429            my $item = sprintf '(item %d, count is %d)', $f->{displaycount}, $f->{count}; 
    2171             push @totaltemp => sprintf "%10s %-22s", pretty_size($f->{total},1), $item; 
     2430            push @totaltemp => sprintf '%10s %-22s', pretty_size($f->{total},1), $item; 
    21722431            $maxtotal = $f->{displaycount} if $f->{displaycount} > $maxtotal; 
    21732432            last if $arg{tempfile_limit} and $totalcount >= $arg{tempfile_limit}; 
     
    21812440        { 
    21822441            last if ! defined $mean[$count] and ! defined $totaltemp[$count]; 
    2183             printf "%-s |", defined $mean[$count] ? $mean[$count] : ''; 
     2442            printf '%-s |', defined $mean[$count] ? $mean[$count] : ''; 
    21842443            printf "%s\n", defined $totaltemp[$count] ? $totaltemp[$count] : ''; 
    21852444            $count++; 
     
    23732632    my $olen = $len; 
    23742633    my $waschopped = 0; 
    2375     my $maxsize = defined $opt{$curr}{statement_size} 
    2376         ? $opt{$curr}{statement_size} 
     2634    my $maxsize = defined $opt{statement_size} 
     2635        ? $opt{statement_size} 
    23772636        : $arg{statement_size}; 
    23782637 
     
    24012660    $arg{debug} and warn "  Performing final cleanup\n"; 
    24022661 
    2403     ## If offset has changed, save it 
    2404     my $newoffset = 0; 
    2405     if (exists $opt{$curr}{newoffset}) { 
    2406         $changes++; 
    2407         $newoffset = $opt{$curr}{newoffset}; 
    2408         $arg{verbose} and warn "  Setting offset to $newoffset\n"; 
    2409     } 
    2410  
    2411     ## Reset always rewrites the file, even in dryrun mode 
    2412     if (($changes and !$arg{dryrun}) or $arg{reset}) { 
    2413         $arg{verbose} and warn "  Saving new config file (changes=$changes)\n"; 
    2414         open my $fh, '>', $configfile or die qq{Could not write "$configfile": $!\n}; 
    2415         my $oldselect = select $fh; 
    2416         my $now = localtime; 
    2417         print qq{## Config file for the tail_n_mail program 
     2662    ## Need to walk through and see if anything has changed so we can rewrite the config 
     2663    ## For the moment, that only means the offset and the lastfile 
     2664 
     2665    ## Have we got new lastfiles or offsets? 
     2666    for my $t (@{ $opt{file} }) { 
     2667        if ($t->{latest} ne $t->{lastfile}) { 
     2668            $changes++; 
     2669            $t->{lastfile} = delete $t->{latest}; 
     2670        } 
     2671        if (exists $opt{newoffset}{$t->{lastfile}}) { 
     2672            my $newoffset = $opt{newoffset}{$t->{lastfile}}; 
     2673            if ($t->{offset} != $newoffset) { 
     2674                $changes++; 
     2675                $t->{offset} = $newoffset; 
     2676            } 
     2677        } 
     2678    } 
     2679 
     2680    ## No rewriting if in dryrun mode, but reset always trumps dryrun 
     2681    ## Otherwise, do nothing if there have been no changes 
     2682    return if (!$changes or $arg{dryrun}) and !$arg{reset}; 
     2683 
     2684    $arg{verbose} and warn "  Saving new config file\n"; 
     2685    open my $fh, '>', $configfile or die qq{Could not write "$configfile": $!\n}; 
     2686    my $oldselect = select $fh; 
     2687    my $now = localtime; 
     2688    print qq{## Config file for the tail_n_mail program 
    24182689## This file is automatically updated 
    24192690## Last updated: $now 
    24202691}; 
    24212692 
    2422         for my $item (qw/ email from type duration tempfile find_line_number sortby duration_limit tempfile_limit/) { 
    2423             next if ! exists $opt{$curr}{$item}; 
    2424  
    2425             next if $item eq 'duration' and $arg{duration} < 0; 
    2426             next if $item eq 'duration_limit' and ! $arg{duration_limit}; 
    2427  
    2428             next if $item eq 'tempfile' and $arg{tempfile} < 0; 
    2429             next if $item eq 'tempfile_limit' and ! $arg{tempfile_limit}; 
    2430  
    2431             ## Only rewrite if it came from this config file, not tailnmailrc or command line 
    2432             next if ! exists $opt{configfile}{$item}; 
    2433             add_comments(uc $item); 
    2434             if (ref $opt{$curr}{$item} eq 'ARRAY') { 
    2435                 for my $itemz (@{$opt{$curr}{$item}}) { 
    2436                     next if ! exists $opt{configfile}{"$item.$itemz"}; 
    2437                     printf "%s: %s\n", uc $item, $itemz; 
    2438                 } 
    2439             } 
    2440             else { 
    2441                 printf "%s: %s\n", uc $item, $opt{$curr}{$item}; 
    2442             } 
    2443         } 
    2444         if ($opt{configfile}{maxsize}) { 
    2445             print "MAXSIZE: $opt{$curr}{maxsize}\n"; 
    2446         } 
    2447         if ($opt{$curr}{customsubject}) { 
    2448             add_comments('MAILSUBJECT'); 
    2449             print "MAILSUBJECT: $opt{$curr}{mailsubject}\n"; 
    2450         } 
    2451  
    2452         print "\n"; 
    2453         add_comments('FILE'); 
    2454         if (! $opt{$curr}{inherited_filename}) { 
    2455             print "FILE: $opt{$curr}{original_filename}\n"; 
    2456         } 
    2457         print "LASTFILE: $opt{$curr}{filename}\n"; 
    2458         print "OFFSET: $newoffset\n"; 
    2459         for my $inherit (@{$opt{$curr}{inherit}}) { 
    2460             add_comments("INHERIT: $inherit"); 
    2461             print "INHERIT: $inherit\n"; 
    2462         } 
    2463         for my $include (@{$opt{$curr}{include}}) { 
    2464             next if ! exists $opt{configfile}{"include.$include"}; 
    2465             add_comments("INCLUDE: $include"); 
    2466             print "INCLUDE: $include\n"; 
    2467         } 
    2468         for my $exclude (@{$opt{$curr}{exclude}}) { 
    2469             next if ! exists $opt{configfile}{"exclude.$exclude"}; 
    2470             add_comments("EXCLUDE: $exclude"); 
    2471             print "EXCLUDE: $exclude\n"; 
    2472         } 
    2473         print "\n"; 
    2474  
    2475         select $oldselect; 
    2476         close $fh or die qq{Could not close "$configfile": $!\n}; 
    2477     } 
     2693    for my $item (qw/ log_line_prefix email from type mailsig duration tempfile find_line_number sortby duration_limit tempfile_limit/) { 
     2694        next if ! exists $opt{$item}; 
     2695 
     2696        next if $item eq 'duration' and $arg{duration} < 0; 
     2697        next if $item eq 'duration_limit' and ! $arg{duration_limit}; 
     2698 
     2699        next if $item eq 'tempfile' and $arg{tempfile} < 0; 
     2700        next if $item eq 'tempfile_limit' and ! $arg{tempfile_limit}; 
     2701 
     2702        ## Only rewrite if it came from this config file, not tailnmailrc or command line 
     2703        next if ! exists $opt{configfile}{$item}; 
     2704        add_comments(uc $item); 
     2705        if (ref $opt{$item} eq 'ARRAY') { 
     2706            for my $itemz (@{$opt{$item}}) { 
     2707                next if ! exists $opt{configfile}{"$item.$itemz"}; 
     2708                printf "%s: %s\n", uc $item, $itemz; 
     2709            } 
     2710        } 
     2711        else { 
     2712            printf "%s: %s\n", uc $item, $opt{$item}; 
     2713        } 
     2714    } 
     2715 
     2716    if ($opt{configfile}{maxsize}) { 
     2717        print "MAXSIZE: $opt{maxsize}\n"; 
     2718    } 
     2719    if ($opt{customsubject}) { 
     2720        add_comments('MAILSUBJECT'); 
     2721        print "MAILSUBJECT: $opt{mailsubject}\n"; 
     2722    } 
     2723 
     2724    print "\n"; 
     2725    for my $inherit (@{$opt{inherit}}) { 
     2726        add_comments("INHERIT: $inherit"); 
     2727        print "INHERIT: $inherit\n"; 
     2728    } 
     2729    for my $include (@{$opt{include}}) { 
     2730        next if ! exists $opt{configfile}{"include.$include"}; 
     2731        add_comments("INCLUDE: $include"); 
     2732        print "INCLUDE: $include\n"; 
     2733    } 
     2734    for my $exclude (@{$opt{exclude}}) { 
     2735        next if ! exists $opt{configfile}{"exclude.$exclude"}; 
     2736        add_comments("EXCLUDE: $exclude"); 
     2737        print "EXCLUDE: $exclude\n"; 
     2738    } 
     2739    for my $exclude_prefix (@{$opt{exclude_prefix}}) { 
     2740        next if ! exists $opt{configfile}{"exclude_prefix.$exclude_prefix"}; 
     2741        add_comments("EXCLUDE_PREFIX: $exclude_prefix"); 
     2742        print "EXCLUDE_PREFIX: $exclude_prefix\n"; 
     2743    } 
     2744 
     2745    print "\n"; 
     2746    add_comments('FILE'); 
     2747    for my $f (sort { $a->{suffix} <=> $b->{suffix} } 
     2748               @{ $opt{file} }) { 
     2749 
     2750        ## Skip inherited files 
     2751        next if exists $f->{inherited}; 
     2752 
     2753        printf "\nFILE%d: %s\n", $f->{suffix}, $f->{original}; 
     2754 
     2755        ## Got any lastfile or offset for these? 
     2756        if ($f->{lastfile}) { 
     2757            printf "LASTFILE%d: %s\n", $f->{suffix}, $f->{lastfile}; 
     2758        } 
     2759        ## The offset may be new, or we may be the same as last time 
     2760        if (exists $opt{newoffset}{$f->{lastfile}}) { 
     2761            printf "OFFSET%d: %d\n", $f->{suffix}, $opt{newoffset}{$f->{lastfile}}; 
     2762        } 
     2763        elsif ($f->{offset}) { 
     2764            printf "OFFSET%d: %d\n", $f->{suffix}, $f->{offset}; 
     2765        } 
     2766    } 
     2767    print "\n"; 
     2768 
     2769    select $oldselect; 
     2770    close $fh or die qq{Could not close "$configfile": $!\n}; 
    24782771 
    24792772    return;