【図解あり】Phoenixでアクセスログとエラーログを出力する②~Loggerとlogrotate~あとcronも。


こちらの記事は前回の続きからです。

https://qiita.com/mito_mito/items/b5c34311455773d91533

前回までで以下の部分まで解説しました。
- `Logger`と`LoggerFileBackend`を使いlogファイルに出力
- ログ管理を自動化するツール`logrotate`を使い、実行タイミングやデータ保持の期間などのローテーションを設定
- ひとまずコマンド即時実行でログファイルが出来上がることを確認

要件を改めて書きます。

# 要件

- アクセスログとエラーログをそれぞれ別ファイルで保管したい
- 1日1ファイルで日付が変わると新しいファイルにログが書き込まれるようにしたい
- 元々書き込まれていたログは新しいファイルに書き込まれたタイミングで消える
- ファイル名はファイルが作られた時の日付
- ログのローテーション(保存期間)は1週間
 
今回は、指定した時間に自動でログが出力される仕組みを作っていきます。
つまり「`logrotate`を指定した時間に実行したい」がゴールです。

# cronジョブが動いているかの確認
```
service cron status
```
動作していない場合は以下。
```
sudo service cron start
```

今回、システム全体のジョブとしてログを出力する方法とユーザー個別ジョブとしてログを出力する方法の両方を試したので両方のやり方を書きます。

ちなみに今回は前者を採用しました。
理由はユーザー個別ジョブの場合実行時に`53 2 * * * sudo logrotate -f /etc/logrotate.d/sample-project`とユーザー個別にしてアクセスを制限しているはずなのですが、`sudo`権限を使わないと実行できないので考え方としておかしいのでは...という結論です。


# 方法1: システム全体のジョブとしてログを出力方法

## `/etc/cron.d`にファイルを作成
- `/etc/cron.d`まで移動
    ```
    cd etc
    ```
    ```
    ls | grep cron
    ```
    ```
    cd cron.d
    ```
    この配下におく(元ファイルがあったのでコピーない場合は作ってくれる)
    ```
    sudo cp test_cron sample-project
    ```
    ファイルを開く
    ```
    sudo vi sample-project
    ```
---


- 特定の時間で自動実行されるように`sample-project`に書き込みを行う
    ```
    i
    ```
    ```
    # /etc/crontab: system-wide crontab
    # Unlike any other crontab you don't have to run the `crontab'
    # command to install the new version when you edit this file
    # and files in /etc/cron.d. These files also have username fields,
    # that none of the other crontabs do.
    
    SHELL=/bin/sh
    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
    0 0  *  *  * root logrotate -f /etc/logrotate.d/sample-project || echo "exit status : $? | $(date)" >> /home/devel_server/error.log

    ```
    
 
    ```
    :wq
    ```
    ```
    cat sample-project
    ```
    insertした内容が反映されていたらOK。

## 細かな解説


`0 0 * * *`は毎日0時0分に実行されることを意味します。`root logrotate -f /etc/logrotate.d/sample-project`は、root権限で`logrotate`コマンドを使用して`/etc/logrotate.d/sample-project`の設定に従って特定のログファイルを強制的にローテートします。`||`は左側のコマンドが失敗した場合（終了ステータスが0以外の場合）にのみ、右側のコマンドを実行することを意味します。

`echo "exit status : $? | $(date)" >> /home/devel_server/error.log`は、`logrotate`の実行が失敗した場合（終了ステータスが0以外の場合）、エラーメッセージとその時の日時を`/home/devel_server/error.log`に追記します。

## `/etc/logrotate.d/sample-project`の書き換え

以下のようにユーザーをデフォルトの`root`から`devel_server`に切り替えます。

```elixir
/apps/sample-project/log/*.log {
    daily
    rotate 7
    nocompress
    missingok
    notifempty
    copytruncate
    dateext
    dateformat %Y-%m-%d
    dateyesterday
    extension .log
    create 640 devel_server devel_server 
    olddir /apps/sample-project/log/old
    createolddir 755 devel_server devel_server
    su devel_server devel_server
}

```
## 検証
- cronの再実行
    ```
    sudo service cron restart
    ```
    以下コマンドでcronが起動していることを確認
    ```
    service cron status
    ```
---
- 実行結果を確認
  指定した時間に実行され、自動でファイルが追加される
  <pre>
    
    ├── dialyzer.error.log
    ├── dialyzer.log
    ├── error.log
    ├── info.log
    └── old
        ├── error2024-03-30.log
        └── info2024-03-30.log
  </pre>

## `logrotate`が2回実行される場合の挙動

`cron`のジョブの設定は以下の3つあります。
- ユーザーの`crontab`
- `/etc/crontab`
- `/etc/cron.d`

今回、`/etc/crontab`で`/etc/cron.daily`を呼び出しておりそのディレクトリ配下に`logrotate`があります。そして`/etc/cron.d`でも`logrotate`の呼び出し処理があるため`logrotate`が2回呼び出されることになります。

上記のような場合、2回目つまり、すでにローテーションが実行された場合は、出来上がっているログファイルに上書きされたりすることなくスキップされることがわかりました。
つまり2回実行されても結果最初の1回のみの実行となります。


  
# 方法2: ユーザー個別のジョブとしてログを出力する方法

以下コマンドを叩くと現在のユーザーのcronジョブを編集するためのデフォルトエディタが開きます
今回は`devel_server`ユーザーだったので`/var/spool/cron/crontabs/devel_server`のcrontab設定エディタが開きます。
```
# crontab -e
```
インサートモードへ
```
i
```
以下を追加(時間は現在の時間の少し後に設定するとすぐに実行を確認できます)
```
53 2 * * * sudo logrotate -f /etc/logrotate.d/sample-project
```
escキーを押して保存して閉じる

```
:wq
```
いきなり`logrotate`を実行し、上手く動かない時はまずはログが出るかの確認で1分ごとにログを定期的に出すような以下を書いても良いと思います。

```
*/1 * * * * date >> /tmp/date.log
```

cronの再実行

```
sudo service cron restart
```
新たに指定したフォルダ配下に`info$date.log`ファイルができていることが確認できました

<pre>
.
├── dialyzer.error.log
├── dialyzer.log
├── error.log
├── info.log
└── old
    ├── error2024-03-30.log
    ├── info2024-03-30.log
    └── info2024-03-31.log

</pre>


# 基本の確認
以下については初学者向けなので、分からない用語があったり、もう少し深く内容を理解したい方のみご覧ください。

## Unix便利なコマンド

### Undo
---
Escでコマンドモードにして、`U`キー

### Redo
---
Escでコマンドモードにして`Ctrl+ R`キー

### ユーザーを一時的に`root`に切り替える
---
```
devel_server@cd311092b662:/etc$ sudo su
```
すると、`root`に切り替わって実行できます。またr`root`から抜ける時は`exit`と打ちます。

```
root@cd311092b662:/etc# 
```
### Dockerのデフォルトのエディタをvimにする
---
ユーザーのホームの`.bashrc`に`vim`の実行ファイルの場所を環境変数に設定


- `.bashrc`の場所と中身を確認
    ```
    cd
    ```
    ```
    ls -a
    ```
    ```
    cat .bashrc
    ```
---    
- 実行ファイルの場所を確認

    ```
    which vim
    ```
    私の場合は`/usr/bin/vim`でした。
---
- `vim`で開く

    ```
    vi .bashrc
    ```
    インサートモードに
    ```
    i
    ```
    追記
    ```
    /usr/bin/vim
    ```
    保存は`esc`タブを押した後に以下コマンド
    ```
    :wq
    ```
    記述内容の確認
    ```
    cat .bashrc
    ```
---
- `.bashrc`ファイルの変更を現在のシェルセッションに適用
    ```
    source ~/.bashrc
    ```
    適応を確認
    ```
    echo $EDITOR
    ```
    `/usr/bin/vim`になっていればOKです！

### 時間を確認する際に、vim実行中に一時的にターミナルでコマンドを実行する
    `esc`押したあと以下コマンド実行
    ```
    :!date
    ```
    Enterで抜ける。
#
## 用語の確認
### パーミッションって何？
---

パーミッション（権限）は、ファイルやディレクトリに対するアクセス権を制御する仕組みです。UNIX系オペレーティングシステム（Linuxを含む）では、このパーミッションを使って、特定のファイルやディレクトリを誰が読み込み、書き込み、実行できるかを管理します。これにより、システムのセキュリティを強化し、ユーザー間でのデータの共有とプライバシーを適切に管理できます。

以下で`cron`と名前がつくディレクトリorファイルをgrepしパーミッション情報を調べます。
<pre>
root@cd311092b662:/etc# ls -l | grep cron
drwxr-xr-x 1 root root    4096 Apr  1 07:57 cron.d
drwxr-xr-x 1 root root    4096 Apr  1 07:23 cron.daily
drwxr-xr-x 2 root root    4096 Apr  1 07:23 cron.default
drwxr-xr-x 2 root root    4096 Mar 27 05:42 cron.hourly
drwxr-xr-x 2 root root    4096 Mar 27 05:42 cron.monthly
drwxr-xr-x 2 root root    4096 Mar 27 05:42 cron.weekly
-rw-r--r-- 1 root root    1042 Apr  1 07:55 crontab

</pre>

### パーミッション情報の読み方1
---
順番に分解して読んでいきます。まずは`drwxr-xr-x`の部分から。

![IMG_0188.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3743559/06183101-0963-19e9-e41a-f4aa8e264fe5.jpeg)


### パーミッション情報の読み方２
---
その他の部分を読んでいきます。

![IMG_0189.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3743559/dedd6a0b-630d-803c-bbba-8fae7e1c79bf.jpeg)

### `logrotate`と`cron`の関係性
---
触るファイルが多く、関係性が複雑で何がどうなっているかわからなかったので関係性を図解しました。

イメージとして`cron`というバックグラウンドで実行されるプログラム群の中の一つとして`logrotate`があるイメージです。

![スクリーンショット 2024-04-03 13.22.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3743559/8d719484-a649-ced8-71ab-c1dd4b776a45.png)

### ユーザーの`crontab`、`/etc/crontab`、`/etc/cron.d`内のファイルの関係性

一言で言うと並列な関係です。

全て`cron`デーモンによって管理され、それぞれ独立したスケジュールとして扱われます。

- ユーザーの`crontab`: 個々のユーザーが自分自身のために設定したジョブ。これらは各ユーザーに固有で、crontabコマンドを使用して管理する

- `/etc/crontab`: システム全体のジョブを設定するためのファイル。システム管理者がシステムレベルのタスクをスケジュールするために使用する

- `/etc/cron.d`: アプリケーションやパッケージが自身のジョブをスケジュールするために使用するファイルを格納するディレクトリ。これにより、パッケージ固有のジョブを`/etc/crontab`や個々のユーザーの`crontab`とは独立して管理できる

### `crontab`の処理の流れの要点を抑える

- `crontab`が呼び出され以下を実行される
`25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )`
- `cron.daily`の`logrotate`が呼び出され以下をさらに呼びだす
    `/etc/logrotate.conf` 
- `logrotate.conf`の以下を実行
    　`include /etc/logrotate.d`
- `/etc/logrotate.d`ディレクトリは以下の`sample-project`の以下を実行
    ```elixir
    /apps/sample-project/log/*.log {
        daily
        rotate 7
        nocompress
        missingok
        notifempty
        copytruncate
        dateext
        dateformat %Y-%m-%d
        dateyesterday
        extension .log
        create 640 devel_server devel_server 
        olddir /apps/sample-project/log/old
        createolddir 755 devel_server devel_server
        su devel_server devel_server
    }

    ```

### 今回の対応の図解

![スクリーンショット 2024-04-06 23.06.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3743559/442a7cf9-2967-c3e1-2ddd-631199729628.png)



# 以下の記事を参考にさせていただきました

https://qiita.com/mk34/items/d55e08a5c657e2e022ee#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%B7%A8%E9%9B%86

https://qiita.com/yuzo_____/items/1b8af6e04ffa2baa1cab

# さいごに

今回もさらに長くなってしまいました。
ローカルのdockerでの設定までとなったのでまた次回に本番サーバーへの設定について書きます。
誤りなどありましたらご指摘お願いします！
また、thewaggleではElixirでの開発にジョインしてくださる仲間を募集しています！
ご興味ある方、お気軽にご連絡ください。