LoginSignup
4
2

Phoenixでアクセスログとエラーログを出力する①~Loggerとlogrotate~

Last updated at Posted at 2024-03-26

今回は、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

ディレクトリやファイルの存在を確認

上記でlogrotatecronをインストールすることで以下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")

モジュールの取り込みuserequireimportの違い


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での開発にジョインしてくださる仲間を募集しています!
ご興味ある方、お気軽にご連絡ください。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2