[Linux] SystemdのUnit定義ファイルで日時入りのログ出力を実装する方法
はじめに
Raspberry Pi内に入っているsystemdを使って、Raspberry Piを起動したらスクリプトを自動起動させるという設定を行う機会がありました。
当初はshファイルをただ実行するコマンドだけを設定していましたが、このshファイルのログを何時でも見られるようにする必要がでてきたため、ログ出力を実装することになりました。
今回は、Unit定義ファイル内でログ出力を実装する方法をまとめていきます。
環境
- Raspberry Pi 4 Model B
- OS: Raspbian 32bit(64bit版でも実装可能です)
- Python 3.11.6
実装
事前準備
serviceで使用するshファイルを作成しておきます。
今回は~/test_server
ディレクトリに置かれているソースコードを用いて、Pythonの仮想環境(venv)を立ち上げてFastAPIを立ち上げるという前提でshファイルを作成します(FastAPIの実装方法はこちらの記事では紹介しません)
cd ~/test_server
source .venv/bin/activate
uvicorn main:app --host=0.0.0.0 --port=8000
また、Unit定義ファイルからshファイルを実行できるように実行権限を以下のコマンドでつけておきます。
sudo chmod +x start_test_server.sh
最後に、ログを保管しておくためのディレクトリを作成します。
mkdir -p ~/logs/test_server/
serviceファイルを作成
/etc/systemd/service/
ディレクトリにtest.service
というUnit定義ファイルを作成します。
[Unit]
Description=Start up Server.
After=network.target
[Service]
WorkingDirectory=/home/USERNAME/Desktop/test_server/
ExecStart=/bin/bash -c 'stdbuf -oL -eL /home/USERNAME/Desktop/test_server/start_test_server.sh 2>&1 | while IFS= read -r line; do echo "$(date "+%%Y-%%m-%%d %%H:%%M:%%S.%%3N") $line"; done >> /home/USERNAME/logs/test_server/test_server.log'
User=USERNAME
[Install]
WantedBy=multi-user.target
※USERNAMEは適宜書き換えてください。
ExectStartで設定しているコマンドを要素ごとに分けると以下のようになります。
-
/bin/bash -c
: bashシェルを起動し、次に続くコマンドを実行します。 -
stdbuf -oL -eL /home/USERNAME/Desktop/test_server/start_test_server.sh
: start_test_server.shスクリプトを実行し、その標準出力と標準エラー出力をリアルタイムで処理します。 -
2>&1
: 標準エラー出力を標準出力にリダイレクトします。 -
while IFS= read -r line; do echo "$(date "+%%Y-%%m-%%d %%H:%%M:%%S.%%3N") $line"; done
: start_test_server.shからの出力を1行ずつ処理し、時刻とともにログファイルに書き込むための処理を行います。(serviceファイルに# -
>> /home/USERNAME/logs/test_server/test_server.log
: 処理したログをtest_server.logファイルに追記します。
Unit定義ファイルでログの保管場所を指定したため、ディレクトリを作成します。
mkdir -p ~/logs/test_server/
Unit定義ファイルが新たに追加されたためdaemon-reloadを実行します。
sudo systemctl daemon-reload
サーバーを起動します。
sudo systemctl start test.service
test.serviceが起動しているか確認します。
sudo systemctl status test.service
スクリプトを自動起動したいため、enableを指定して自動起動を設定します
sudo systemctl enable test.service
ログファイルの中身を確認すると、以下のようにログの前に日時が登録されていることを確認できます。
2024-05-21 17:26:02.789 INFO: Started server process [705]
2024-05-21 17:26:02.793 INFO: Waiting for application startup.
2024-05-21 17:26:02.797 INFO: Application startup complete.
2024-05-21 17:26:02.800 INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
ログローテーションを設定
このままでも良いのですが、ログが溜まりすぎてしまうとストレージの容量を圧迫してしまうため、ログローテーションも設定しておきます。
/etc/logrotate.d/logrotate_test_server
を作成します。
sudo nano /etc/logrotate.d/logrotate_test_server
logrotate_test_server
の中身を以下のように書き換えます
/home/USERNAME/logs/test_server/test_server.log {
daily
rotate 7
copytruncate
dateext
dateformat -%Y%m%d%H%s
notifempty
compress
missingok
}
以下、ログローテーションの中身は以下の通りです。
- ローテーション対象: 1行目に書かれているログファイル
-
daily
: 毎日実行する -
rotate
: 7つ(7日分)のローテーションファイルを保持する -
copytruncate
: コピーを作成した後に元のログファイルを空にする -
dateext
: 旧バージョンのログファイルに、YYYYMMDD のように日付を付加してアーカイブする -
dateformat -%Y%m%d%H%s
: ローテーションファイルに付けられる日付のフォーマットを指定する -
notifempty
: ログファイルが空の場合、ローテーションを行わない -
compress
: 旧バージョンのログファイルがgzipで圧縮される -
missingok
: ログファイルが存在しない場合でも、エラーメッセージを出力せずに次の対象のファイルへ移る(ログファイルがなくても処理を止めない)
例えば5/9~5/16の8日間、サーバーが立ち上がりっぱなしで何かしらのログを一日必ず出力する場合、以下のようにログファイルが保存されていきます。ログローテーションが実行されると、test_server.logのコピーが作成され、ログローテーションされる前まで使われていたtest_server.logはgzipで圧縮されます。7バージョン前までローテーションファイルは保管され、8バージョンより前のものは削除されるようになっています。また、各ローテーションファイルには圧縮された日時が記載されています。
$ ls ~/logs/test_server/ -la
合計 40
drwxr-xr-x 2 pi pi 4096 5月 14 12:10 .
drwxr-xr-x 4 pi pi 4096 5月 9 11:13 ..
-rw-r--r-- 1 pi pi 2310 5月 16 11:33 test_server.log
-rw-r--r-- 1 pi pi 20 5月 9 13:38 test_server.log-20240510001715315923.gz
-rw-r--r-- 1 pi pi 20 5月 10 13:38 test_server.log-20240511001715315935.gz
-rw-r--r-- 1 pi pi 20 5月 11 13:38 test_server.log-20240512001715315942.gz
-rw-r--r-- 1 pi pi 425 5月 12 14:37 test_server.log-20240513001715322207.gz
-rw-r--r-- 1 pi pi 114 5月 13 15:24 test_server.log-20240514001715322276.gz
-rw-r--r-- 1 pi pi 815 5月 14 18:00 test_server.log-20240515001715556165.gz
-rw-r--r-- 1 pi pi 559 5月 15 19:32 test_server.log-20240516001715612419.gz
最後に
今回はSystemdのUnit定義ファイルで日時入りのログ出力を実装する方法について説明しました。ログ出力の方法は様々ありますが、Raspberry Pi起動時に自動実行するアプリやサーバーのログを保存しておく1つの手法として参考になるかと思います。
参考記事
systemdを使ってスクリプト自動起動
systemdの*.serviceファイルの書き方 #Linux - Qiita
【日本語man】logrotateの全オプション解説 – Hacker's High
Discussion