TL;DR
ポイントは以下です。
- ふつうにRubyのスクリプト書いて systemd でデーモン化する
- logrotate の postscript の中で実行中のスクリプトに対して
USR1
を送出する - スクリプト側では
USR1
シグナルを受け取ったらログファイルをreopen
する
スクリプト本体
デーモンとなるスクリプト本体です。
log_file = File.expand_path("hoge.log", File.dirname(__FILE__)) pid_file = File.expand_path("hoge.pid", File.dirname(__FILE__)) # pid 作成 File.write(pid_file, Process.pid) # logger 初期化 logger = Logger.new(log_file) logger.level = Logger::INFO $reopen_log = false # USR1シグナルでログのreopenフラグを立てる trap("USR1") do $reopen_log = true end # 終了時に pid ファイルを削除 END { File.delete(pid_file) } loop do # フラグ立ってたらログを reopen する if $reopen_log logger.reopen(log_file) $reopen_log = false end logger.info "hogehoge---" sleep 10 end
スクリプトのポイント
いくつかポイントとなる箇所があるので解説。
プロセスIDをファイルに出力する
理由は、logrotate の postrotate のスクリプトで kill -USR1 でプロセスに対してシグナルを送出するためです。
# pid 作成 File.write(pid_file, Process.pid)
# 終了時に pid ファイルを削除 END { File.delete(pid_file) }
シグナルを受け取ってログを再オープンする
普通にログローテートすると、対象ファイルがローテーションした際、既存のファイルがリネームされて新しいファイルが作られるが、reopen
しないとずっと古いファイル(リネームされた方)に出力されてしまう。
そこで、USR1
(たいていUSR1
が使われるらしい)のシグナルを捕捉して、シグナルを受け取ったらreopen
する処理を組み込む。
# USR1シグナルでログのreopenフラグを立てる trap("USR1") do $reopen_log = true end
# フラグ立ってたらログを reopen する if $reopen_log logger.reopen(log_file) $reopen_log = false end
ちなみに、trap()
のブロック内(シグナルハンドラと呼ぶらしい)でreopen
してみましたが、Threadがなんちゃらというエラーが出て落ちてしまいました。
一般的にシグナルハンドラの中では入出力を伴う処理等を行うのはよくないらしく、フラグを立てるくらいしたほうが良いらしいです。
systemd に登録するやつ
特に解説はしません。
Systemdを使ってさくっと自作コマンドをサービス化してみる - Qiita などを参考に。
[Unit] Description = xxxx daemon [Service] ExecStart = /home/xxxx/.rbenv/shims/ruby /path/to/hoge.rb Restart = always Type = simple User = hoge Group = hoge [Install] WantedBy = multi-user.target
このファイル名 hoge.service
がサービス名(hoge
と省略可能)となります。
logrotate
日毎のローテーションで、履歴を10件保持する設定です。
この設定のポイントは、postrotate
のブロックに記述されたスクリプトで、kill -USR1 $(cat $pid)
の部分です。
これによってローテーション完了後、スクリプトにUSR1
シグナルが送出され、それによってスクリプト側ではログファイルがreopen
され、結果としてローテーション後の新しいログファイルにログが出力されるようになるという仕組みになっています。
/path/to/hoge.log { daily rotate 10 missingok su hoge hoge create 0600 hoge hoge postrotate pid=/path/to/hoge.pid test -e $pid && kill -USR1 $(cat $pid) || true endscript }
デーモンの起動/停止
以下のコマンドでデーモンが起動します。
$ sudo systemctl start hoge
停止は以下
$ sudo systemctl stop hoge
さいごに
こんな感じで、意外なほど簡単にRubyで気軽にデーモンが作れるのでbotとかいろいろ作ってみたらどうでしょうか。