今回は nginx でのログローテーションについて。
logrotate を使う場合
たぶん最も標準的な構成が logrotate を使うものだと思う。
実際、nginx を公式 APT リポジトリからインストールした場合の /etc/logrotate.d/nginx は次の内容になっている。
/var/log/nginx/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 640 nginx adm
sharedscripts
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
postrotate で nginx に USR1 シグナルを送るのが要で、そうしないとファイルハンドルを握ったままの nginx が旧ログに書き込み続けてしまう。
このやり方の場合、monthly にすれば「1ヶ月分のログ」でローテーションされるが、「2018年7月分のログ」という形にはならない。
nginx 自体でできるもの (アクセスログのみ)
Frederic Cambus 氏の記事によると、nginx 0.9.6 以降で $time_iso8601 変数が使えて、アクセスログの指定 (access_log) に流用できるそうだ。
if ($time_iso8601 ~ "^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})") {}
access_log /var/log/nginx/access-$year$month.log
$time_iso8601 には ISO 8601 形式で現在の日付と時刻が入っている。「2018-07-25T20:32:01+09:00」みたいな感じ。
nginx は Perl 互換正規表現 (PCRE) が使えるので名前付きキャプチャでマッチすると、以降で $year、$month、$day 変数に値が入る。これを access_log で使えば nginx が月別ログを出力してくれる、という仕掛け。
ただしこの変数は error_log には使えないので、エラーログには他の手段を使う必要がある。
cronolog を使う場合
Apache で月別ログにする場合、cronolog が広く採用されていると思う。
Apache では普通にパイプが使えるので、下記のようにしておけば月別にログができた。
ErrorLog "| /usr/bin/cronolog --period=months /home/example/log/error-%Y%m.log"
CustomLog "| /usr/bin/cronolog --period=months /home/example/log/access-%Y%m.log" vhost_combined
nginx では素でパイプが使えるわけではないので、mkfifo を使う。
FIFO を作る。
/etc/nginx/mkfifo.sh
#!/usr/bin/env bash
declare -A LOGS=(
["/var/log/nginx/access"]="nginx"
["/var/log/nginx/error"]="nginx"
)
for LOG in ${!LOGS[@]}; do
if ! [ -p $LOG ]; then
rm -f $LOG
mkfifo $LOG
chown ${LOGS[$LOG]} $LOG
fi
done
/etc/nginx/start_cronolog.sh
#!/usr/bin/env bash
declare -A LOGS=(
["/var/log/nginx/access"]="nginx"
["/var/log/nginx/error"]="nginx"
)
for LOG in ${!LOGS[@]}; do
sudo -b -u ${LOGS[$LOG]} sh -c "cat $LOG | cronolog --period=months -S $LOG.log $LOG.%Y%m"
done
ログの指定は [“ログファイル”]=“ファイルオーナー” という形式。
/etc/systemd/system/nginx_cronolog.service
[Unit]
Description=cronolog for nginx
Before=nginx.service
[Service]
Type=forking
ExecStart=/etc/nginx/start_cronolog.sh
[Install]
WantedBy=multi-user.target
nginx_cronolog が開始済みじゃないと nginx は動かない。
レンタルサーバー (共有ホスティング) のように、バーチャルホストのログをユーザーディレクトリ下に出力する場合は、FIFO のオーナーを nginx、グループを当該ユーザーにして、パーミッション 640 にする。
Apache と違い、ログの出力は root 権限ではなく nginx 権限で行われるので、FIFO は nginx で書き込み可能でなくてはならない。
シェルスクリプトで bash を使ったのは連想配列を使いたいからで、別に私は bashism の徒ではない。普段使ってるのは zsh だし。