logrotate

logrotate は、放っておけば際限なく肥大してしまう各種ログファイルに対して、世代ローテーションをして何代目かになったら破棄するとか、それらをメールでどこかに送信するなど、様々な処理が行える。世代管理やサイズ制限などの機構を自前で持たないプログラムからのログを管理するのになくてはならない道具だ。

Table of Contents

Logrotate実行の流れ

logrotate 自体はデーモンではないので cron と組み合わせて利用する。実行の流れは、RedHat系ディストリビューションでは以下のようになっている:

つまり、logrotate で管理したい新たなログがあれば /etc/logrotate.d/ ディレクトリにある既存のファイルを編集するか、新たに作ってやればいいわけだ。相手が syslogd を経由してログを吐き出しているプログラムで、特にねじくれた処理を必要としないのなら、既存の /etc/logrotate.d/syslog にログファイル名を書き足すだけでも充分だ。仮に、対象のログが /var/log/some/some.log だとしたら、下記の赤字の箇所を足す:

#/etc/logrotate.d/syslog
/var/log/messages /var/log/secure /var/log/maillog /var/log/spooler /var/log/boot.log /var/log/cron /var/log/some/some.log {
  sharedscripts
  postrotate
    /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
  endscript
}

参考のため、主設定ファイル /etc/logrotate.conf の内容も見ておこう:

#/etc/logrotate.conf
weekly
rotate 4
create
include /etc/logrotate.d
/var/log/wtmp {
  monthly
  create 0644 root utmp
  rotate 1
}

設定の書き方

logrotateの主設定ファイルは /etc/logrotate.conf。通常は、そこにはデフォルトとなるグローバルな設定だけを書き、ログファイルグループ毎の個別の設定は /etc/logrotate.d/ 配下の設定ファイルに分割し、下記に述べる include ディレクティブで読み込ませる。logrotate の設定書式の基本は;

[logfile [logfile ...] {]
 directive
 [directive ...]
[}]
ディレクティブ一覧 ("no"のついたものは同種ディレクティブの否定を意味する)
# # で始まる行はコメントとして無視される
create [mode] [owner] [group] ローテーションを (下記の postrotate も) 行った後、代わりに空の新規ログファイルを作って置いてくる。その属性も指定できる。mode は 0755 のようなオクテット書式。指定しない属性については元のファイルの属性が引き継がれる
nocreate 上記をグローバルな設定にした場合に、個別規定内で create を無効にしたい際に使用
copy/nocopy 元のログファイルはそのままにして、そのコピーを保存する
copytruncate/nocopytruncate copy の動作を行った後、元のログファイルの内容 を消去する。見かけ的には create と同じ結果となる。これはログファイルをリロードする術のないプログラムへの対処法のひとつ。例えば Oracle 10g R1/R2 の alert ログに対しては、これをやらないと以前のファイル (例えば alert_xx.log.1) にログが吐き続けられる (Oracleのバージョンによって異なる気配あり)
rotate num 世代ローテーションのステップ数。例えば元のログファイルが a.log だとして、 num を 2 にしておくと a.log => a.log.1 => a.log.2 => 廃棄 となる。0 だと a.log => 廃棄
start num 初代ローテーションファイルの末尾に付加するナンバー。デフォルトは 1。 num を例えば 5 にすると、a.log => a.log.5 => a.log.6 => ... と推移
extension ext ローテーションした旧ログファイルに拡張子 ext を付ける。ドットも必要。例えば some.log を "extension .bak" の設定でローテーションすると、初代ローテーションログは some.log.1.bak となる。圧縮も行う場合、圧縮による拡張子はさらにその後ろに付く
compress/nocompress ローテーションした後の旧ファイルに圧縮を掛ける。デフォルトは nocompress
compresscmd cmd ログファイルの圧縮に使用するプログラムを指定。デフォルトは gzip
uncompresscmd cmd ログファイルの解凍に使用するプログラムを指定。デフォルトは gunzip
compressoptions opt 圧縮プログラムへ渡すオプション。デフォルトは gzip に渡す "-9" (圧縮率最大)。現在のところ "-9 -s" のようにスペース入りで複数のオプションを指定することは不可能
compressext ext 圧縮後のファイルに付ける拡張子 (ドットも必要)。デフォルトでは、使用する圧縮コマンドに応じたものが付くとされているが、bzip2 を使っても gz になってしまうなど、あまり当てにならない
delaycompress/nodelaycompress 圧縮処理を、その次のローテーションの時まで遅らせる。compress も指定しないと無意味
olddir dir ローテーションした旧ログを dir に移動する。移動先は元と同じデバイス上でなければならない。元のログに対する相対指定も有効
noolddir 上記の否定
mail address 旧ログファイルを address に送信する。どの段階のものを送るかは maillast などのオプションで決まる
nomail 上記の否定
maillast mail に関するオプション。最終世代を経て破棄されるログをメールする (デフォルト)
mailfirst mail に関するオプション。初代ローテーションログをメールする
daily/weekly/monthly ログローテーションを日毎/週毎/月毎に行う。デフォルトは daily。例えば weekly なら、毎日実行を掛けたとしても、その週で最初に必要条件を満たした際にだけローテーションが行われる
size num[K/M] ログのサイズが num バイトを超えていればローテーションを行う。この条件は daily, weekly などの条件より優先される。キロ/メガバイトでの指定も可能
ifempty 元のログファイルが空でもローテーションを行う (デフォルト)
notifempty 元のログファイルが空ならばローテーションしない
missingok 指定のログファイルが実在しなかったとしてもエラーを出さずに処理続行
nomissingok 指定のログファイルが実在しなければエラーを出力 (デフォルト)
firstaction
script
endscript
実際にローテーションの条件に合致するログファイルがひとつでもあった場合に、ローテーションを行う前 (prerotate のアクションよりも前) に一度だけ実行するスクリプト。個別定義内でのみ指定可能
prerotate
script
endscript
実際にローテーションの条件に合致するログファイルがひとつでもあった場合に、ローテーションの前に (firstaction よりは後) に実行するスクリプト。個別定義内でのみ指定可能
postrotate
script
endscript
実際にローテーションが行われた後 (lastaction よりは前) に実行するスクリプト。個別指定内でのみ指定可能
lastaction
script
endscript
実際にローテーションが行われた後 (postrotate よりも後) に一度だけ実行するスクリプト。個別指定内でのみ指定可能
nosharedscripts ローテーションの条件に合致するログが複数あった場合に、prerotate, postrotate のスクリプトを各ログファイル毎に実行する (デフォルト)
sharedscripts ローテーションの条件に合致するログが複数あった場合に、prerotate, postrotate のスクリプトを一度だけ実行する
include file_or_dir 設定ファイル内でこの記述のある位置に別の設定ファイルを読み込む。ディレクトリを指定した場合、その dir 内から、ディレクトリおよび名前付きパイプ以外のレギュラーファイルがアルファベット順に読み込まれる
tabooext [+] ext[,ext,...] include でディレクトリを指定した場合に読み込みから除外するファイルの拡張子を指定。デフォルトで .rpmorig, .rpmsave, .rpmnew, .v, .swp, ~ が設定されている。+ を挟むと追加指定、挟まないと根こそぎ置き換えとなる

上記のディレクティブのうちいくつかは logrotate のバージョンによって挙動が少々異なる (postrotate の実行条件など)。

logrotate プログラム自体にもオプションがある。例えば -d オプションを付けるとデバグモードで走り、行われるはず の動作が詳細に出力されるだけで、実際には何も行われない。これは動作を理解するのにも役に立つ。 '-d 個別設定ファイル名 ' とすると作ったばかりの設定ファイルの試験ができる。動作実験のためのちょっとしたスクリプトを作ったので tar.gz して置いておく

捨てられるログを外部保管させる

ローカルディスク上でのローテーションは数世代に抑えたいが、古いログファイルは廃棄せずに外部媒体や別のディスクに移して長期保存したい、というケースが出てきた。これは logrotate 自体の機能だけでは無理だが、ちょっとしたシェルスクリプトを用意し、firstaction ブロックまたは prerotate ブロックからそれを呼んで処理してやれば実現できる。ローテーション管理下にある最古世代のログファイルを、廃棄する直前にシェルスクリプトで他所へコピーし、あとは何喰わぬ顔で通常のローテーションをさせるという手筈だ。

まず、コピー処理をするシェルスクリプトを作る。これ (prerotacopy) だ。logrotate は、スクリプトブロック内に書かれたスクリプトに対して、対象ログファイルのリストを設定ファイルに書いてあるままの表現で引数として渡してくる。prerotacopy はそれをポジショナルパラメータで全部受け取り、ループで回して所定のディレクトリへコピーする。この時注意しなければならないのは、ログファイルのリストはワイルドカードを含む場合があるわけで、シェルグロブの展開を踏まえて 2次元配列的に処理する必要があるという点。こんな感じだ (スクリプトから抜粋);

EXT=$(date +%Y%m%d)
for V in $*; do
    for F in $V; do
        if [ -f $F.$GEN ]; then
            cp -pf $F.$GEN ${OUTDIR}/$(basename $F).$EXT || :
        fi
    done
done

設定ファイルへの組み込みは下記のようにする;

/var/log/test/*.log {
    daily
    rotate 5
    create
    missingok
    firstaction
        /usr/local/bin/prerotacopy -n 5 -o /mnt/logsave/test ${1+"$@"}
    endscript
}

筆者の書いた prerotacopy スクリプトの仕様として、-n N で最古世代のログファイルの世代番号、-o でコピー先のディレクトリを指定する。ここでもうひとつ注意がある。prerotacopyfirstaction ブロックでなく prerotate で組み込みたい場合、logrotate のバージョンによって -n に指定すべき世代番号が異なるのだ。logrotate 3.7.1 では設定ファイルの rotate X と同じに。3.7.4 では、X +1 の数を指定しなければならないことが分かった。つまり、3.7.4 では、prerotate スクリプトが実行される時点で既にファイルのローテーションが行われている (ただしまだ削除はされていない) ようだ。firstaction ならどちらのバージョンでも N=X でよかった。ふたつのバージョンしか試していないので、それ以外の場合は試してみてから運用していただきたい。