今回は、Webアプリケーションでアクセスログ、エラーログを出す設定をしました。
よくある設定だと思うのですがきちんと設定したのは初めてだったので手順をまとめます。
要件
- アクセスログとエラーログをそれぞれ別ファイルで保管したい
- 1日1ファイルで日付が変わると新しいファイルにログが書き込まれるようにしたい
- ファイル名はファイルが作られた時の日付
- ログのローテーション(保存期間)は1週間
LoggerとLoggerFileBackend
Logger
はコンソールに出力するのみなので、ファイルにログを書き出したい場合はLoggerFileBackend
とセットで使います。
Loggerとは
- ログツール
- ELixirの標準ライブラリ
- 特別な依存関係を追加することなく利用可能
- 4つのログレベルを提供(:debug、:info、:warn、:error)
Loggerの使用例
以下のようにコード内で出力したい箇所に記述することでログを出力することができます。
require Logger
Logger.debug("This is a debug message.")
Logger.info("This is an info message.")
Logger.warn("This is a warning.")
Logger.error("This is an error message.")
LoggerFileBackendとは
- ログメッセージを処理するためのカスタムバックエンドをサポート
- デフォルトではコンソールへの出力が有効
- ファイルへのログ出力や外部ログ収集システムへの送信など、さまざまな出力先に対応可能
- LoggerFileBackendを使用することでファイルにログを書き込むことができる
LoggerFileBackendの使用例
-
mix.exs
に依存関係を追加defp deps do [ {:logger_file_backend, "~> 0.0.13"} ] end
-
config/config.exs
でバックエンドを設定
ファイルにエラーログとアクセスログを出す設定にします。# Configures Elixir's Logger config :logger, backends: [ :console, {LoggerFileBackend, :error_log}, {LoggerFileBackend, :info_log} ] # :console の設定 config :logger, :console, format: "$time $metadata[$level] $message\n", metadata: [:request_id] # :error_log の設定 config :logger, :error_log, # ログの保存先, ファイル名 path: "log/error.log", # 対象とするレベル level: :error, # ログフォーマット format: "$time $metadata[$level] $message\n", # メタデータの要素 metadata: [:request_id] # :info_log の設定 config :logger, :info_log, path: "log/info.log", # 実際には動的に変更する必要があります level: :info, format: "$time $metadata[$level] $message\n", metadata: [:request_id]
-
依存関係をダウンロード
$ mix deps.get
参考:hexdocs logger
参考:hexdocs LoggerFileBackend
余談
ログのformat形式を時間から日付に変更したい場合は$time
から$date
に変更するとOK
参考:hexdocs Logger.Formatter
logrotateの設定
ローカル環境でのログの確認を行いました。サーバー自体はDockerで構築しています。
config.exs
はサーバー起動時に実行されるため、実行タイミングの時に、ElixirのTimerX
を読み込んでおらずファイル名を動的に変えることは難しいということがわかりました。
そこでlogrotate
というLinux や UNIX 系システムで使用される、ログファイルの管理を自動化するためのツールを使用。
logrotate
を使用することでログファイルの自動的なローテーション、圧縮、削除、およびメール送信を行うことができます。
logrotateの主な機能
- ログファイルのローテーション: 特定のサイズに達したり、特定の期間が経過したりしたログファイルを新しいファイルにローテート(切り替え)
- 圧縮: 古いログファイルを圧縮して、ディスクスペースを節約
- 古いログファイルの削除: 設定した期間より古いログファイルを自動的に削除
- ログファイルのメール送信: ログファイルをメールで送信し、その後ローテー
これらについて必要な機能をピックアップして設定
やりたいことが全部詰まっている...!
設定
/etc/logrotate.d/
に任意の名前でファイルを作成
今回は/etc/logrotate.d/
にsample-project
というファイルを作成。
sudo touch sample-project
ファイル内に設定する
apps/sample-project/log/*.log {
daily # 毎日実行する
rotate 7 # 7日間保持
nocompress # ローテーションしたログを圧縮しない
missingok # ログファイルが存在しなくてもエラーを出さずに処理を続行する
notifempty # ログファイルが空ならスキップ。
copytruncate # ログファイルをコピーし、元ファイルを空にする
dateext # ローテーションしたログのsuffixに番号をつけるのではなく、日付8桁(-YYYYMMDD) をつける。
dateformat %Y-%m-%d
dateyesterday
create 640 root adm
olddir /apps/sample-project/log/old
}
logrotate
コマンドの実行
dockerにログインし、以下のコマンドを実行します
sudo logrotate -f /etc/logrotate.d/sample-project
ファイルが出来あがります!
├── dialyzer.error.log
├── dialyzer.log
├── error.log
├── info.log
└── old
└── info2024-03-25.log
(存在しない場合のみ)logrotateインストールの確認
whici logrote
で確認する。
インストールされていない場合logrote
をインストール
logrote
がインストールされていないと設定ファイルを作っても動きません。
以下のファイルでインストールの設定をします。
/sample-project/apps/docker/elixir_web/Dockerfile
RUN apt-get -y install git vim sudo inotify-tools logrotate cron
今回cron
も合わせてインストールしました。
インストールの実行
docker compose stop
を実行し、docker compose up -d --build
うまくいかないかなかったので、RUN apt upgrade
(OSのアップグレード)をDockerfile
に追加して再度実行します。
RUN apt upgrade
RUN apt-get update
RUN apt-get -y install git vim sudo inotify-tools logrotate cron
ディレクトリやファイルの存在を確認
上記でlogrotate
とcron
をインストールすることで以下2つができ上がります。
/etc/logrotate.d
/etc/crontab
気になったこと調べました
logroteコマンドはどこで実行されている?
etc
の中に以下のようなディレクトリがあります。
ls | grep cron
anacrontab # anacronによって使用されるジョブの設定を保持するファイル
cron.d # 個別のcronジョブ設定ファイルを配置するディレクトリ。システム管理者はここに特定のアプリケーションやサービス用のcronジョブを設定するファイルを置くことができる
cron.daily # 日次で実行されるスクリプトやコマンドを配置するディレクトリ
cron.hourly # 時間ごとに実行されるスクリプトやコマンドを配置するディレクトリ
cron.monthly #月次で実行されるスクリプトやコマンドを配置するディレクトリ
cron.weekly # 週次で実行されるスクリプトやコマンドを配置するディレクトリ
crontab # cronジョブを定義するためのファイルや、crontabコマンドによって編集・管理されるファイル。ユーザーやシステムのcronジョブスケジュールを含む
anacron
は、cron
のようにシステムが稼働している必要がある定時実行スケジューラと異なり、システムが稼動していない時に予定されていたジョブを後で実行できるように設計されています。これは主に、常時稼働しないデスクトップやラップトップのようなシステムで有用です。
anacrontab
ファイルは、ジョブの実行間隔、ジョブを遅延させる日数、ジョブの識別名、そして実行するコマンドやスクリプトのパスを指定するために使用されます。
日時に関してはetc/cron.daily/logrotate
に書かれています(基本ここの中は触らない)
$ cat logrotate
# skip in favour of systemd timer
if [ -d /run/systemd/system ]; then
exit 0
fi
# this cronjob persists removals (but not purges)
if [ ! -x /usr/sbin/logrotate ]; then
exit 0
fi
/usr/sbin/logrotate /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
/usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit $EXITVALUE
/etc/logrotate.conf
コマンドを実行し、システムのログファイルのローテーションを行います。
日時、月次の時間帯の指定はどこで?
etc/crontab
で指定しています。
見つかりづらいのでls | grep cron
で探すと良いです。
SHELL=/bin/sh
# You can also override PATH, but by default, newer versions inherit it from the environment
#PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
5 7 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
59 7 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
12 5 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#
つまり日時は午前7時5分にログファイルができると書かれています。
基本の整理
Loggerはマクロを使用している
以下はコードの実行時ではなく、コンパイル時にLogger
マクロによって評価されます。
これにより、Loggerはアプリケーションの現在のログレベル設定に基づいて、このメッセージを実際にログに記録するかどうかを決定します。
Logger.info("Application started")
モジュールの取り込みuse
とrequire
とimport
の違い
Elixirでのモジュールの取り込み方法にはuse、require、importがあり、それぞれ異なる目的で使用されます。ここで、より簡潔に各用語の違いを説明し、使用例を示します。
use
- 特定のモジュールの機能や振る舞いを現在のモジュールに組み込むために使用
- 指定されたモジュール内の__using__/1マクロを実行し、その結果として関数の定義やマクロの使用が可能に
- フレームワークやDSLの作成時によく使われる
require
- マクロを使用する場合に必要で、関数やマクロを名前空間付きで呼び出す際に使う
- マクロやコンパイル時に実行される関数を使用する前に、その機能を持つモジュールを明示的にコンパイル時のコンテキストに取り込む必要がある
import
- 特定のモジュールから関数やマクロを現在のモジュールのコンテキストに直接取り込み、モジュール名なしでアクセスできるようにする
- コードを簡潔に書くために使われ、特定の関数やマクロだけを選択的に取り込むことも可能
コード例
defmodule MyModule do
defmacro __using__(_) do
quote do
def hello do
IO.puts("Hello, used Module!")
end
end
end
defmacro my_macro do
quote do
IO.puts("Hello, Macro!")
end
end
def my_function do
IO.puts("Hello, Function!")
end
end
# useの例
defmodule UsingModule do
use MyModule
def test do
hello() # MyModuleの__using__/1マクロにより定義された関数
end
end
# requireの例
defmodule RequiringModule do
require MyModule
def test do
MyModule.my_macro() # マクロは名前空間付きで呼び出し
end
end
# importの例
defmodule ImportingModule do
import MyModule, only: [my_function: 0] # 特定の関数のみ取り込み
def test do
my_function() # モジュール名なしで関数を直接呼び出し
end
end
quote
はElixirにおいてAST(抽象構文木)の生成を行う特別な構文です。
ASTとは
Elixirでは、ソースコードはまずASTに変換され、このASTがさらにコンパイルされて実行可能なコードになります。ElixirのASTは、Elixirのデータ構造(主にタプル、リスト、およびリテラル)を使用して表されます。
参考:hexdocs Macro
参考:hexdocs elixir-ast
参考:hexdocs kernel
さいごに
かなり長くなってしまったので、Unixのコマンド確認までにとどめました。日次でのローテーションの確認や設定はまた次回とします!
誤りなどありましたらご指摘お願いします!
また、thewaggleではElixirでの開発にジョインしてくださる仲間を募集しています!
ご興味ある方、お気軽にご連絡ください。