例えばディレクトリ構成はこんな感じで、
$ find /home/oracle/scripts/ -type f /home/oracle/scripts/log_rotater.pl /home/oracle/scripts/log/alert.log /home/oracle/scripts/conf/alert.conf
実行してみる。
$ perl log_rotater.pl conf/alert.conf
実行ログを見てみる。
$ cat log/alert.log [2009-05-05 02:50:03] [INFO] log_rotater.pl started. [2009-05-05 02:50:03] [INFO] Copied file: /export/home/oracle/admin/orcl/bdump/alert_orcl.log --> /export/home/oracle/admin/orcl/bdump/alert_orcl.log_20090505-025003. [2009-05-05 02:50:03] [INFO] Truncated file: /export/home/oracle/admin/orcl/bdump/alert_orcl.log. [2009-05-05 02:50:03] [INFO] log_rotater.pl finished.
Oracle を停止してからローテートするなら全然問題ないんだけど、Oracle を停止せずにローテートする場合にちょっと気になることがある。アラートログを copy して、truncate してるんだけど、copy してから trucnate するまでの一瞬にログに書き込みがあったら、その一瞬に書込まれたメッセージが消えてしまう。lock、copy、truncate、unlock ってすれば良いのかな?lock している間にアラートログに書き込みがあったら、待機してくれるのかな?今度検証してみよう。
ソースコードは以下の通り。
- alert.conf
################################################## # # ◆ ログ・メンテナンス・スクリプト設定ファイル # ################################################## # # ◆ メンテナンス対象ログに関する設定 # # 対象ファイル TARGET_FILE => '/export/home/oracle/admin/orcl/bdump/alert_orcl.log', # 対象ファイルの保持期限(日数) TARGET_FILE_RETENTION_POLICY => 31, # # ◆ このスクリプトに関する設定 # # 実行ログのパス SCRIPT_LOG_PATH => '/home/oracle/scripts/log/alert.log', # 実行ログを保持する世代数 SCRIPT_LOG_RETENTION_POLICY => 7,
- log_rotater.pl
#!/usr/bin/perl use strict; use Time::localtime; use File::Copy; use File::Basename; ############################## # # ◆ 設定 # ############################## # 設定ファイルの必須項目 my @conf_mandatory =('TARGET_FILE','TARGET_FILE_RETENTION_POLICY','SCRIPT_LOG_PATH','SCRIPT_LOG_RETENTION_POLICY'); ############################## # # ◆ 初期化処理 # ############################## # 引数をチェックする if ( $#ARGV < 0) { &print_usage; exit(1); } # 設定ファイルを読み込む my $conf = &get_conf($ARGV[0]); # このスクリプトの実行ログをローテートする &rotate($conf->{SCRIPT_LOG_PATH}, $conf->{SCRIPT_LOG_RETENTION_POLICY}); # このスクリプトの実行ログをオープンする open (OUT, "> $conf->{SCRIPT_LOG_PATH}") or print("Failed to create file: $conf->{SCRIPT_LOG_PATH}.") and exit(1); # 処理開始をログに書き込む &write_log('INFO', "$0 started."); ############################## # # ◆ メイン処理 # ############################## # 対象ファイルをローテートする &rotate_files; # 保持期限を過ぎたファイルを削除する &delete_expired_files; ############################## # # ◆ 終了処理 # ############################## # 処理終了をログに書き込む &write_log('INFO', "$0 finished."); # このスクリプトの実行ログをクローズする close (OUT); # 正常終了(戻り値:0)する exit(0); ############################## # # ◆ サブルーチン # ############################## # ◆ 使用方法を表示する sub print_usage { print "$0: missing argument\n"; print "Usage : perl $0 configuration_file\n"; print "Example: perl $0 sample.conf\n"; } # # ◆ 設定情報を取得する # # $_[0]: 設定ファイルのパス # OUT: 設定情報 sub get_conf { my $conf; my $filename = $_[0]; my $str = &read_file($filename); eval ('$conf = { dummy=>1,' . $str . '};1;') or print("Error in conf file. $@.") and exit(1); foreach my $item (@conf_mandatory) { if (!exists($conf->{$item})) { print("$item does not exist in $filename.") and exit(1); } elsif ($conf->{$item} eq '') { print("$item is mandatory in $filename.") and exit(1); } } return $conf; } # # ◆ 実行ログをローテートする # # $_[0]: 実行ログのパス # $_[1]: 実行ログを保持する世代数 sub rotate { my($filename,$max) = @_; my $cur; return if ($max < 0); if (-f "$filename.$max") { unlink ("$filename.$max") or &error_exit("Failed to delete file: $filename.$max."); } for ($cur = $max; $cur > 1; $cur--) { my $prev = $cur - 1; if (-f "$filename.$prev") { rename("$filename.$prev","$filename.$cur") or &error_exit("Failed to rotate files: $filename.$prev --> $filename.$cur."); } } if (-f "$filename") { rename("$filename","$filename.1") or &error_exit("Failed to rotate files: $filename --> $filename.1."); } } # # ◆ ファイルを読込む # # $_[0]: ファイルのパス # OUT: 文字列 sub read_file { my $filename = $_[0]; my $lines = ''; if (-f $filename) { open (IN, "< $filename") or &error_exit("Failed to open file: $filename."); while (my $line = <IN>) { $lines .= $line; } close (IN); } else { &error_exit("File not found: $filename."); } return $lines; } # # ◆ 対象ファイルをローテートする # sub rotate_files { my $tm = localtime; my $dt = sprintf("%04d%02d%02d-%02d%02d%02d", $tm->year+1900, $tm->mon+1, $tm->mday, $tm->hour, $tm->min, $tm->sec); # 対象ファイルをコピーする if (copy($conf->{TARGET_FILE}, "$conf->{TARGET_FILE}_$dt")) { &write_log('INFO', "Copied file: $conf->{TARGET_FILE} --> $conf->{TARGET_FILE}_$dt."); } else { &error_exit("Failed to copy file: $conf->{TARGET_FILE} --> $conf->{TARGET_FILE}_$dt."); } # 対象ファイルをクリアする if (truncate($conf->{TARGET_FILE}, 0)) { &write_log('INFO', "Truncated file: $conf->{TARGET_FILE}."); } else { &error_exit("Failed to truncate file: $conf->{TARGET_FILE}."); } } # # ◆ 保持期限を過ぎたファイルを削除する # sub delete_expired_files { my $dir = dirname($conf->{TARGET_FILE}); my @files = glob("$dir/*"); my $tm = localtime(time - ($conf->{TARGET_FILE_RETENTION_POLICY} * 60 * 60 * 24)); my $expiry_date = sprintf("%04d%02d%02d", $tm->year+1900, $tm->mon+1, $tm->mday); foreach my $filename (@files) { if (my($file_date) = $filename =~ /([0-9]{8})-[0-9]{6}$/g) { if ($file_date < $expiry_date) { if (unlink("$filename")) { &write_log('INFO', "Deleted file: $filename."); } else { &error_exit("Failed to open file: $filename."); } } } } } # # ◆ ログに書き込む # # $_[0]: エラーレベル(INFO/ERROR) # $_[1]: メッセージ sub write_log { my $tm = localtime; my $dt = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $tm->year+1900, $tm->mon+1, $tm->mday, $tm->hour, $tm->min, $tm->sec); print OUT "[$dt] [$_[0]] $_[1]\n"; } # # ◆ 異常終了する # # $_[0]: メッセージ(省略可) sub error_exit { write_log('ERROR', $_[0]) if ($#_ >= 0); exit(1); }