読者です 読者をやめる 読者になる 読者になる

ログ監視

webアプリケーションを実際に運用していく上で必須なのがログの監視です。
エラーが出ているのを知らずに放っておくなんて危険です。

アプリ内で400番、500番台のエラーが出ていた場合にメールでお知らせしてくれるスクリプトを紹介します。
専用のツールもあるんですが今回は自作します。

実際のソースです。

#!/usr/bin/perl

use strict;
use warnings;
use utf8;

use FindBin;
use lib "$FindBin::Bin/../extlib/lib/perl5";
use local::lib "$FindBin::Bin/../extlib";

use Email::Sender::Simple 'try_to_sendmail';
use Email::MIME;

use File::Tail;
use Time::TAI64 qw/:tai64n/;
use Carp;
use Data::Dumper;

my $config = do "$FindBin::Bin/config.pl";
my $match  = $config->{match};
my $mail   = $config->{mail};

my $timeout;
my @files = map { File::Tail->new( name => $_ ) } keys %$match;

MAIN:
while (1) {
    my ( $nfound, $timeleft, @pending ) =
            File::Tail::select( undef, undef, undef, $timeout, @files );

    next MAIN unless $nfound;

    my @elogs;
    my @title_signs;
PENDING:
    foreach my $p (@pending) {
        my @reads_org = $p->read;
        next PENDING if scalar @reads_org <= 0;

        my @reads;
        my @include = @{ $match->{ $p->{input} }->{include} };
        my @exclude = @{ $match->{ $p->{input} }->{exclude} };

        for my $regexp ( @include ) {
            push @reads, (grep /$regexp/, @reads_org);
        }
        for my $regexp ( @exclude ) {
            @reads = grep !/$regexp/, @reads;
        }

        next PENDING if scalar @reads <= 0;

        if ( $match->{ $p->{input} }->{tai64} ) {
            my @reads_buff;
            for (@reads) {
                my($tai, $mes) = split(' ', $_, 2);
                push @reads_buff, sprintf("%s %s",tai64nlocal($tai), $mes);
            }
            @reads = @reads_buff;
        }

        # title_sign
        foreach (@reads) {
            if ($_ =~ /\" ([5|4]\d{2}) /) {
                push @title_signs, $1;
            }
            if ($_ =~ / \[(error|warn|fatal)\] /) {
                push @title_signs, 'E';
            }
        }

        push @elogs, {
            name => $p->{input},
            log  => join('', @reads),
        }
    }

    next MAIN unless @elogs;

    # 簡単に整形
    my $body_string = '';
    foreach (@elogs) {
        $body_string .= "■" . $_->{name} . "\n";
        $body_string .= $_->{log} . "\n\n";
    }

    my $mail_subject = $mail->{subject}
                        . ( (scalar(@title_signs) >= 1) ? " ... ".join(" ",@title_signs) : "" );

    my $email = Email::MIME->create(
      header_str => [
        From    => $mail->{from},
        To      => $mail->{to},
        Subject => $mail_subject,
      ],
      attributes => {
        content_type => 'text/plain',
        charset      => 'UTF-8',
        encoding     => 'base64',
      },
      body_str  => $body_string,
    );

    if ( try_to_sendmail($email) ) {
        carp "[info] try_to_sendmail success.";
    }
    else {
        carp "[error] try_to_sendmail error!";
    }
}

1;

ざっくりと説明すると
1.設定ファイルとしてconfig.plを取得
2.File::Tailを使い、ファイルを後ろから取得
3.ファイルの中身にconfig.plで設定した条件に一致したらメールを送信
こういう構成になってます。

config.pl 設定ファイルを作っていきます。
下記は1例です。

# vim config.pl
{
    match => {
        "/var/log/nginx/kojinbaibai.bundlebox.jp/access.log" => {
            include  => [
                qr/" 5\d{2} /,
            ],
            exclude  => [
            ],
        },
        "/var/log/nginx/account.bundlebox.jp/access.log" => {
            include  => [
                qr/" 5\d{2} /,
            ],
            exclude  => [
            ],
        }
    },
    mail => {
        from    => 'sample@sample.com',
        to      => 'sample@sample.com',
        subject => "error mail.",

	}
};

これを設定して自分の環境にカスタマイズしてください。

監視するログの条件を設定します。

{
    match = {
        "(監視するログファイル)" => {
            include  => [
                (監視対象にするパターンの正規表現), (...), (...)
            ],
            exclude  => [
                (includeの中から対象外にするパターンの正規表現), (...), (...)
            ],
        },
    }
};

今回書いた例で言うと
/var/log/nginx/kojinbaibai.bundlebox.jp/access.log
/var/log/nginx/account.bundlebox.jp/access.log
この2つのログファイルの500番台のエラーを検知するように設定しています。
これを自分の環境に合わせて、監視したい条件を書きます。
最初にエラーを知らせてくれると書きましたが、正規表現なのでなんでもありです。

次にメールの設定。
自分のメールアドレスに送信するようにします。

{
    mail =>{
        from => (メールの送信元アドレス),
        to => (メールの送信先アドレス),
        subject => (メールのタイトル),
    }
}

これで設定完了です。
実際動かしてみて問題ないようならデーモン化して終わりです。
エラーが出る度にメールが飛んでくるはずです。

デーモン化するのは下記を参考にしてください。
daemontoolsによるwebアプリケーション管理 - 城好きwebプログラマーの生態メモ - 仕事編

広告を非表示にする