本当にどうでもいいメモなどをたまに書く程度。

nginx で月別ログローテーション

日付:

今回は 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 だし。

comments powered by Disqus