Practice of Programming

プログラム とか Linuxとかの話題

ログを監視して怪しげな文字列を見つけたら何かする

Parallel::ForkManager訳しました(nekokakさんの0.7.5の翻訳からの差分だけなので楽でした)。


で、複数のエラーログファイルがあって、そのログファイルを tail(File::Tailを使って)しつつ、Errorを見つけたら、何かするっていうスクリプト
例として、それどうよって感じですね!(ぉ


(一般的には、定期的なデータ更新とかで結構デカ目だけど、複数サーバで分散とかいうほど大げさじゃない(ていうか、予算上そんなサーバないし…)、素直にやると5〜6hくらいかかっちゃうんだけども、どーすっかなーみたいな処理を1hくらいで済ましたいなーみたいな処理に使えば良いと思います -- Web/FTPからデータ(複数ファイル)取ってきて、パースして、DBに突っ込む的なアレ)。

#!/usr/bin/perl

use strict;
use warnings;
use File::Tail;
use Proc::PID::File;
use Parallel::ForkManager;

main();

sub main {
  my $name = $0;
  $name =~s{/}{-}g;
  my $pidfile = Proc::PID::File->new(name => $name, dir => "/tmp/");
  # Proc::PID::Fileで複数プロセス動かないようにチェック
  if (not $pidfile->alive) {
    $pidfile->touch; tail_error_log($pidfile);
  }
}

sub tail_error_log {
  my $pidfile = shift;
  my %error;
  my @files = </var/log/httpd/*error_log>;

  my $parent_pid = $$;
  local(@SIG{qw/INT TERM HUP/});
  $SIG{TERM} = $SIG{INT} = sub {$pidfile->release; exit(1)};
  # cronでたまに動かすようにしてるので、HUPでもexitさせた
  $SIG{HUP}  = sub { $pidfile->release; exit;  };

  # 子プロセスは終わらないので、ファイルの数だけプロセスが必要
  my $pm = Parallel::ForkManager->new(scalar @files);
  foreach my $file (@files) {
    my $past_log_ctime = (stat $file . '.1')[10]; # .1 log's epoch time;
    print "start tail -f $file\n";
    my $pid = $pm->start and next;

    my $tail = File::Tail->new($file) or die "cannot tail log $file";
    # -1 渡すと、最初に全体読むそうな
    $tail->tail(-1);

    # alarmでlogrotateをチェックする
    local $SIG{ALRM} = sub {
      my $past_log_ctime_test = (stat $file . '.1')[10];
      if ($past_log_ctime_test != $past_log_ctime) {
        # ログがローテートされたもよう
        print "file is changed";
        $past_log_ctime = $past_log_ctime_test;

        # ログファイルがローテートされたので、ファイルを読み直す
        $tail->resetafter(0);
        %error = ();
      }
      # 親が $pidfile を解放したら(HUP/INT/TERM時)、子プロセスも終了する
      $pm->finish if not $pidfile->alive;

      # もっかい、alarmを仕込む
      alarm(300);
    };
    alarm(300);
    while (defined(my $error = $tail->read)) {
      my $error_string = $error;
      $error_string =~s{^.+?\] Error in}{Error in} or next;
      $error_string =~s{, referer.+?$}{};
      # 初めてのエラー内容のみ処理(ローテートの時にはクリアしてます。ALRMんとこ参照)
      if (not $error{$error_string}++) {
        do_something($error);
      }
    }
    $pm->finish;
  }
  $pm->wait_all_children;
}

sub do_something {
  # 何かする
}