ログの追記ぶんだけを取り出す

一定時間ごとにログを集計、監視をするときに前回からの差分だけ取り出すツールが欲しかったので作りました。

使い方

実行するごとに、指定したファイルの前回実行からの追記分を出力します

$ ./logtail.pl /tmp/test.log
1
2
3
$ ./logtail.pl /tmp/test.log
4
5

ログローテート時にデータ抜けがないようにローテートされてるかもファイルを指定できます。

$ ./logtail.pl messages -r messages.1
$ ./logtail.pl access_log.20101028 -r access_log.20101027

デフォルトではログファイルと同じディレクトリの<ファイル名>.offsetというファイルに前回実行時の情報(inode番号とポジション)を保存します。
パスはオプションで変えられます。

$ ./logtail.pl /var/log/messages -o /tmp/messages.offset

ソースコード

Perlが入ってれば使えます。

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;

my $rotated_log;   # ローテートされてるかもファイル
my $offset_file;   # 前回どこまでtailしたか保存するファイル
GetOptions(
  "rotated=s" => \$rotated_log,
  "offset=s"  => \$offset_file
);

my $log = shift
  or die "Usage: $0 log [-r rotated_log] [-o offset_file]\n";
$offset_file ||= "$log.offset";

# 途中終了による重複出力防止のため事前に権限をチェック
check_log($log);
if($rotated_log && -e $rotated_log){
  check_log($rotated_log);
}
check_offset_file($offset_file);

my $offset = load_offset($offset_file);
my $new_offset;

if($rotated_log && -e $rotated_log){
  $new_offset = tail_rotated_log($rotated_log, $offset);
  save_offset($offset_file, $new_offset) if $new_offset;
}

$new_offset = tail_log($log, $offset);
save_offset($offset_file, $new_offset) if $new_offset;

exit 0;


sub check_log {
  my $log = shift;

  -f $log && -r $log
    or die "Can't access $log";
}

sub check_offset_file {
  my $offset_file = shift;

  if(-e $offset_file){
    -f $offset_file && -w $offset_file
      or die "Can't write offset_file:$offset_file";
  }else{
    open my $fh, ">", $offset_file
      or die "Can't create offset_file:$offset_file";
    close $fh;
  }
}

sub load_offset {
  my $offset_file = shift;

  open my $fh, '<', $offset_file or die $!;
  my $line = <$fh>;
  close $fh;

  my $offset = {ino => 0, pos => 0};
  if($line && $line =~ /^(\d+) (\d+)$/){
    $offset->{ino} = $1;
    $offset->{pos} = $2;
  }

  return $offset;
}

sub save_offset {
  my ($offset_file, $offset) = @_;

  open my $fh, '>', $offset_file or die $!;
  print $fh $offset->{ino} .' '.  $offset->{pos};
  close $fh;
}

sub tail_log {
  my ($log, $offset) = @_;
  my $ret_offset;

  my($ino, $size) = (stat $log)[1,7];

  if($offset->{ino} == $ino){
    if($offset->{pos} < $size){
      my $end_pos = tail_print($log, $offset->{pos});
      $ret_offset = {ino => $ino, pos => $end_pos};
    }
  }else{
    my $end_pos = tail_print($log, 0);
    $ret_offset = {ino => $ino, pos => $end_pos};
  }

  return $ret_offset;
}

sub tail_rotated_log {
  my ($log, $offset) = @_;
  my $ret_offset;

  my ($ino, $size) = (stat $log)[1,7];

  if($offset->{ino} == $ino && $offset->{pos} < $size){
    my $end_pos = tail_print($log, $offset->{pos});
    $ret_offset = {ino => $ino, pos => $end_pos};
  }

  return $ret_offset;
}

sub tail_print {
  my ($log, $start_pos) = @_;
  $start_pos ||= 0;

  open my $fh, "<", $log or die "$!";

  seek $fh, $start_pos, 0;
  print while <$fh>;
  my $end_pos = tell $fh;

  close $fh;

  return $end_pos;
}


githubにアップしました
https://github.com/mikeda/tools/blob/master/logtail.pl