今さら聞けないログの基本と設計指針

# はじめに
皆さんのログに対する理解はどんなものでしょうか？仕組みから設計方法まで完璧に理解しているエンジニアもいれば、なんとなく使用しているエンジニアも多いことでしょう。

ログとは、システムに着いてエラーや障害の発生、利用者による操作や設定の変更、外部との通信などを時系列に記録したものです。ログに関する理解を深めることで、複雑なシステム開発や運用が可能となります。また、AWS、Azure、GCPなどのクラウドサービスを利用している場合はシステムの開発が可能になるだけでなく、経費削減に繋がる可能性も考えられます。

本記事では、ログの基本を押さえるためにその設計方法について解説します。少しでも自信がない方は、ご一読ください。

# ログを出力する理由は？
ログの基本や、ログの設計について解説する前にそもそもログを出力する理由を押さえましょう。大きく4つの理由が考えられます。

・問題が発生した時に調査するため
・問題の発生を防ぐため
・データ分析を通してソフトウェアを改善するため
・監査ログとして利用するため

問題の調査や防止だけでなく、ログを出力することでユーザーの行動までも分析可能です。現時点で、ログの出力に対する必要性をあまり感じていなくても将来的に必ず必要になります。

# ログの設計を行う理由は？
では、なぜログを設計する必要があるのでしょうか？

そもそもログの設計とは、ログとして出力する情報と形式を決定することです。ログの利用目的から逆算して設計を考えることが基本的な方針になります。

ログの設計を行う理由は、目的に合わせたログを正しく取捨選択し、適切な表示を行うことにあります。上記で説明した通り、ログには様々な種類があります。様々な種類が存在しているため、その取捨選択と表示はかなり複雑です。

例えば、AWSなどのクラウドを活用したシステム開発における事例を紹介します。クラウドサービスでは、スケールアウト、スケールインを容易に行うことができます。非常に便利な機能ですが、容易にスケールが可能ということはいつ生まれ、いつ無くなるか分からないサーバーが無数に存在しているということです。それを理由として、クラウドシステムにおけるログ出力では様々なサーバーのログが非同期に書き込まれます。仮に、ミリ秒単位で絞り込んだとしても特定のログを探し出すことは非常に困難です。

![スクリーンショット 2023-08-24 18.18.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486778/bdbc8031-f52a-b01e-bcd7-9ea9b9d3886b.png)

そこで、重要になってくるのがログの設計です。あるイベントのリクエストからレスポンスに対して一意に判別可能なIDを付与することで対象のイベントに関するログを探し出すことが可能となります。ログの設計を予め行っておくことで、ログの検索時間を大幅に減少させることが可能です。

上記はあくまで一例ですが、複雑化したシステムの開発・運用においてログの設計は必ず行うことが推奨されています。

# ログに関する基礎知識
ログの設計に関する解説を行う前に、本パートではログの基本的な知識について解説します！以下の内容について、簡単に説明しているので自信のない部分だけ読んでいただければ幸いです。

・ログのレベル
・ログの出力場所
・ログのフォーマット

## ログのレベルと意味
ログは、アクセス解析、障害調査、セキュリティ・監査など様々な用途に使用されています。ログの重要度を表すレベルと種類を以下の表にまとめました。使用している開発言語や、ログ管理システム、設計に応じて内容が変化する場合もありますが、基本はこんなところです。

| 種類 | 意味 | 
|:-----------|:------------|
|EMERG|システムの利用が不可能|
|ALERT|アクションをすぐに起こさなければならない|
|CRIT|致命的な欠落がある状態|
|ERROR|エラーが起こっている状態|
|WARN|警告されている状態|
|NOTICE|正常ではあるが重要な状態|
|INFO|情報メッセージ|
|DEBUG|システムの動作に関する情報|

## ログの出力場所
ログは、開発者や運用担当者が見つけやすい箇所に出力することを原則としましょう。ファイルに出力する場合は、```log```ディレクトリなどを作成しておくことをお勧めします。基本的に、出力先は以下の4つが想定されます。

<dl>
<dt>・ファイルに出力する</dt>
<dd>コンソール外で起動するアプリケーションに使用される方法です。</dd>
<dt>・標準出力</dt>
<dd>コンソールから起動するアプリケーションで使用されます。途中経過などを出力するための出力方法です。</dd>
<dt>・外部ログ管理ツールのファイルに出力</dt>
<dd>外部のログ管理ツールを用いることが可能な場合は、専用のログ記録場所に出力することを推奨しています。</dd>
<dt>・外部システムへ出力</dt>
<dd>開発者・運用者の作業やコミュニケーションを円滑に行うために、Slackなどのチャットツールに出力するケースもあります。ただし、稼働率に注意する必要があり過度なログの出力は控えるようにしましょう。</dd>
</dl>

基本的に、外部ログ管理システムを利用している場合やクラウド環境で開発を行っている場合はそれら専用のツールが出力先として適切です。クラウド環境の例を挙げると、Azure Application InsightsやAmazon CloudWatch Logsなどです。

加えて注意点としては、不特定多数のユーザーがアクセス可能な場所に出力しないことです。システム内の重要な情報をログ出力することは一般的にタブーとされています。

## ログのフォーマット
ログのフォーマットとは、ログがどのような内容を出力するかを決定するためのものです。例えば、以下の表にある項目を出力可能です。

| 項目 | 内容 | 備考 |
|:-----------|:------------|:------------|
|時間|ログを記録した時間|年月日時分秒ミリ秒。最低でもミリ秒単位で出力する必要がある|
|イベントID|イベントのID|一連のイベントをトレースするために必要|
|ログレベル|ログのレベル|INFO、ERRORなど|
|リクエスト対象|どこに対してのリクエストか|URLなど|
|ユーザー情報|リクエストしたユーザーの情報|ユーザーIDやIPアドレスなど|
|処理対象|何に対する処理を行ったか|処理を行ったリソースのIDなど|
|処理内容|どのような処理を行ったか|DELETE、PUT、GETなど|
|処理結果|処理を行った結果どうなったか|成功、失敗、処理件数など|
|メッセージ|その他、出力したい内容|ログレベルがエラー以上の時にメッセージがあると望ましい|

ログの出力は基本的に5W1Hに基づいて出力することが利用とされおり、必要な情報を過不足なく出力することが求められます。以下のような内容が取れるように意識すると良いでしょう。

When　→ いつその処理が実行されたか
Who → 誰がその処理を呼び出したか
Where → どこで実行されているのか
What → 何をするためにその処理が呼び出されたか
Why → なぜその処理が実行されたのか

例えば、以下のフォーマットは理想的なものの一例です。

```
(ログレベル)　(時間)　（IPアドレス）　（処理対象）　（処理結果）　（メッセージ）
```
このフォーマットで出力されるログは以下のようになります。

```
[Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server
```

出力可能な情報を全て出力すれば良いわけではない点が非常に難しいため、ログの設計を行う前に使用用途を明確にしましょう。詳しくは、後半で解説します。

上記の内容を参考に、以下パートでログの設計についての理解を深めましょう！

# ログの設計方法の解説

ここから、ログの設計方法について解説を行います。

ログの設計では、画期的に素晴らしい設計を構築するよりもNGを犯さないことの方が大切です。そのため、確認事項とコツや注意点について解説します。

まずは、設計時の確認事項とコツについて解説します！

## ログ設計時の確認事項とコツ

次に、ログの設計を行う際に必ず確認しなければならないポイントと設計のコツの解説を行います。これらを押さえておくことで、効果的なログ解析が実施可能になります。一方で、これらの点が踏まえられていなければ全く使えないログになってしまいます。

まずは、確認するべきことから解説します。

### ・ログの使用用途は何か？
ログの使用用途は多岐に渡ります。本記事の最初に添付した表「ログの種類と使用用途」でも紹介した通りです。様々な使い方があるからこそ、利用用途を明確にする必要があります。開発者だけでなく、運用者、企画や法務部など関わるすべてのステークホルダーにヒアリングしましょう。

例えば、ログの使用用途は以下の3つが考えられます。

・外部からの不正アクセスの把握
・情報漏洩防止など内部統制
・システムなどの利用状況把握

この中のどれが当てはまるのかを明らかにしてください。

### ・ログの読者は誰か？
利用目的を決定した後、ログの読者を想定しましょう。開発者、運用担当者、顧客企業の社員など様々な読者が想定可能です。読者によって出力方法や出力物が変化するはずです。使用用途も踏まえて考えられると理想的ですね。

例えば、使用用途に合わせて読者を想定すると次のように考えられます。

| 使用用途 | 読者 |
|:-----------|:------------|
|外部からの不正アクセスの把握|運用担当者、顧客企業の社員、法務担当者、システムの責任者など|
|情報漏洩防止など内部統制|開発者、運用担当者、顧客企業の社員、法務担当者など|
|システムなどの利用状況把握|開発者、運用担当者など|


これらの確認事項を把握したのであればログ設計の準備はほとんど整っていると言えるでしょう。
次に、設計を行う際のポイントを解説します。

### ・どのようなフォーマットにするかを考える
ログを実際に設計する際に、ログのフォーマットを決めましょう。ログのフォーマットとは、ログにどのような情報が含まれているかを決定します。対象とするログの読者と話し合って、決定しましょう。

次のようなフォーマットを基準にしてください。

```
(ログレベル)　(時間)　（IPアドレス）　（処理対象）　（処理結果）　（メッセージ）
```
このフォーマットで出力されるログは以下のようになります。

```
[Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server
```

ここに、ログの読者の要望を追加しましょう。例えば法務担当者を対象とする場合、IPアドレスや処理の対象リソース載せていても意味がないかもしれません。

そのため、メッセージをログの前半に持ってくるなど工夫すると親切ですね。

```
(ログレベル)　(時間)　（メッセージ） （IPアドレス）　（処理対象）　（処理結果）
```

```
[Error] Feb 21 12:32:23, client denied by server, 193.121.123, https-8080, Failed
```

### ・どのようなメッセージを入れるかを考える
ログのフォーマットには、メッセージという項目があります。ログは、システムの状況を分かりやすく伝える役割を持ちます。メッセージを有効活用することで、その効能を最大限発揮可能です。エラーログが出力される場合は、想定されるエラーの原因を必ず記述しましょう。

また、想定可能な読者のITリテラシーに適したメッセージを考えましょう。仮に、「外部からの不正アクセスの把握」がログの使用用途であれば技術者以外が閲覧する可能性があります。専門的な知識がなくても、何が問題でエラーが起きているのかを把握出来るように、簡単な言葉で説明しましょう。

### ・どのレベルを選択するか吟味する
本記事の前半で添付した「ログのレベルと意味」という表にあるようにログにはレベルという概念があります。レベルを定義することで、一定レベル以上のメッセージのみ担当者に通知が行くようにする処理なども可能です。逆に、担当者が知る必要のないログが通知されても邪魔なだけです。

具体的には、「システムなどの利用状況把握」以外の目的でログレベルDEBUGは必要ないですよね。他にも「情報漏洩防止など内部統制」をログの使用目的にする場合、システムの利用が不可能なEMERGログが流れてきても何も出来ないため不要ですよね。

これらの内容を参考に、ログの設計を行ってください。

## ログを扱う際の注意点

次に、ログを扱う上での注意点を紹介します。対象となるログを探す際に困らないようにするための内容が主になります。

### ログファイルを分ける
ログの調査を行う際は、対象となるログを少なくすることで調査時間を短縮可能です。そのために、ログファイルを分けることが推奨されています。例えば、時間ごとにログファイルを分割、サイトごとに分割など作業のしやすさを考慮して分割しましょう。

logrotateを利用していてログファイルを分けたいと考えた場合、`etc/logrotate.d`ディレクトリの中に配置されているはずのログローテーションの設定ファイルを変更しましょう。

例えば,`sample-service`用のログ設定ファイルを`etc/logrotate.d/sample-service`の中身はこんな感じです。

```
/var/log/sample-service/sample.log { # 対象のログファイル
    ifempty            # ログファイルが空でもローテーションする
    missingok          # ログファイルがなくてもエラーを出さない
    compress           # 圧縮する
    daily              # 毎日ローテートする
    rotate 10          # 10世代分古いログを残す
    create             # 更新後に新しいログファイルを作成する
    endscript
}
```

`create`というコマンドで、ログファイルを分けることが可能であるため、ファイルの中にこのコマンドがあるかどうか確認してください。

また、新しく作成されるログファイルの更新を行なった日程を含めたい場合は以下のようにしてください。

```
/var/log/sample-service/sample.log { # 対象のログファイル
    ifempty            # ログファイルが空でもローテーションする
    missingok          # ログファイルがなくてもエラーを出さない
    compress           # 圧縮する
    daily              # 毎日ローテートする
    rotate 10          # 10世代分古いログを残す
    dateext            # 更新を行った日付でファイルを作成する
    endscript
}
```

`dateext`を追加することで、ファイル名が`元のファイル名+ -YYYYMMDD`に変更されます。

### 構造化ロギングを導入する
可能であれば、必ず構造化ロギングを導入しましょう。ログの出力フォーマットを統一することで、単なるテキストではなく構造化したデータセットとして扱うことを構造化ロギングと言います。フォーマットとしてはJson形式が一般的です。構造化ロギングは、他ツールとの連携を容易にするだけでなく、検索性能が高いなどのメリットがあります。

以下の例を参考にしてください。


```
[Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server
```

上記のように表示されたログが、

```
jsonPayload: {
    "level": "[Error]",
    "timestamp": "Feb 21 12:32:23",
    "host": "193.121.123",
    "port-number": "https-8080",
    "result": "Failed",
    "message": "client denied by server",
}
```

このように構造化できます。

人間からは若干読みにくいかもしれませんが、機械からすれば構造化されたログの方が読みやすいため構造化を導入しましょう。

### 時刻同期とタイムゾーンの設定
ログを調査する際に、ログが出力された時間を把握したいことがあると思います。サーバとローカルの時間の同期を完了していない、同じタイムゾーンを設定していないなどのケースで、時間を上手く扱えません。また設定を変更した場合は、反映させるためにサーバーを再起動しましょう！

ローカルの時間を変更するには、`/etc/localtime`で以下のコマンドをターミナルに入力してください。

```
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

その後、`date`とコマンドを入力し、次のような表示（JSTになっているか）が出力されるかを確認してください。

```
Wed Dec  12 13:15:11 JST 2015 
```

また、タイムゾーンの確認は`/etc/timezone`を確認しましょう。`$ cat /etc/timezone
`を入力した後、以下のような結果が出力されるか確認してください。

```
Asia/Tokyo
```

もし、このように出力されない場合は中身を書き換えましょう。

### ログローテーションの基本設定を確認する
ログローテーションとは、ログが際限なく増えることを防ぐために一定の容量や期間ごとに古いログを削除したりファイルの切り分けを行える機能です。ログローテーションを行う際に、利用するコマンドのデフォルト設定の確認をしましょう。ローテーションが行われる時間が必ずしも0時とは限らず、日付ごとにファイルの切り分けを行えない場合もあります。

ログローテーション時間を変更するには、以下の2ステップを踏みましょう（logrotateを用いている場合の解説です）。

まず、logrotateを実行している`etc/cron.daily`をanacron設定から削除します。以下のような行をコメントアウト、もしくは削除してください。

```
[hour]     [minutes]     cron.daily     nice run-parts /etc/cron.daily
```

次に、`/etc/cron.daily`をcrontabに実行したい時間で登録します。具体的には、`etc/crontab`を開き次の行を追記してください。

```
23     59     cron.daily     root run-parts /etc/cron.daily
```

これで、ログローテーションが23:59に行われるようになります。

### Dokcerを利用する場合に注意
Dockerを利用している場合は、Docker内のコンテナファイルにログが出力されてしまう場合があります。その状態で、Dokcerコンテナを作り直してしまうとログの出力ファイルごと削除されてしまいます。出力先を必ず変更しましょう。

出力先を変更するには、Dockerを起動するタイミングで以下のような入力を行いましょう。

```
docker run --log-driver=<ドライバ名>
```

Dockerのログ出力に関する詳しい解説は、記事の後半で行なっています。特にドライバで悩んでいる方は、そちらもご一読ください。

### CDNやリバースプロキシを利用する場合に注意
CDNやリバースプロキシを利用している場合、そのIPアドレスがログとして出力されてしまいます。しかし、クライアントのIPアドレスは別のヘッダーフィールドにきちんと記録されているため、該当箇所を記録するように設定を変更する必要があります。

・Nginx
・Varnish
・Apache Traffic Server
・HAProxy

設定の変更方法は、リバースプロキシによって様々です。上記は、リバースプロキシの一例ですが皆さんが使っているもので設定の変更方法を調べていただければと思います。

# 実際のログ出力について解説
ここまでの内容では、汎用的なログ設計の基本について解説を行いました。最後のパートでは、様々なケースにおけるログ出力を一連の流れに沿って解説します。

実際に、ログを出力することを考えると次の３つのフェーズが存在します。

<dl>
<dt>・ログ出力</dt>
<dd>どのようなフォーマットでいつ出力されるか</dt>
<dt>・ログローテーション</dt>
<dd>増え続けるログに対してどのように対処するか</dd>
<dt>・ログ集約</dt>
<dd>どのようにしてログを長期間保存するか</dd>
</dl>

それぞれのフェーズにおいて、留意点が異なることが分かると思います。以下、様々なケースにおける注意点を解説します。

## VMベースのログ出力
まずは、 VM（仮想機械）ベースでログを出力する場合の流れを解説します。例えば、AWSのEC2で動いているようなアプリケーションでのログ出力などが該当します。上記で説明した３つのフェーズ（ログ出力、ログローテーション、ログ集約）の中で特に気をつけるべきなのは「ログ出力」です。

### ログ出力
最初に思いつく出力方法として、アプリケーションが直接ログファイルに出力するパターンです。多くのフレームワークの初期設定で使われているパターンですね。

一方で、アプリケーション開発のベストプラクティスとして知られる The Twelve-factor App では、アプリケーション開発で出力されるログはログファイルで管理するべきではないと言われています。

代替案として、以下の方法を取るべきだと言われています。

・標準出力でログを出力した後にログ管理システムを用いてファイルに出力する

このような方法を取ることで、ステージング、デプロイ環境のアプリケーションからはログを見ることも設定することも出来ず、代わりに実行環境によって完全に管理することが可能となります。

## Dockerベースのログ出力
「ログを扱う際の注意点」でも説明した通り、Dockerコンテナでは、コンテナ内に書き込まれたデータはコンテナを削除する際に全て一緒に破棄されてしまいます。つまり、Dockerベースのログ出力で困難になるのはログの集約です。正しくログの集約を行うために、２つの方法が考えられます。

・volume先に指定して永久保存
・log driverを使って転送

これらをそれぞれ説明していきます。

### volume先に指定して永久保存
volumeを用いることで、ホストを永続的に保存することが可能です。参照を切らせないために、`--volumes-from <container_name>`でデータ用コンテナに保持するか、`-v <host_path>:<container_path>`としてください。

一方で、オートスケール等の機能でインスタンス自体も頻繁に削除される場合は、別の箇所にログを転送して保持しておく必要があります。その場合は、収集したデータを一時的にバッファに蓄積できる機構を持っているfluentdコンテナなどを用意し、そのvolumeをマウントして転送するなど対応が必須です。

### logging driverを使って転送する
Dockerに用意されている、ログを外部のログ記録ソフトウェアに転送するLogging Driver機構を用いることで、複雑なコンテナ構造をしている場合もログの集約が可能になります。

Logging Driverでは、コンテナの起動時に出力先を指定することで指定したログ管理システムにログを出力することが出来ます。通常は「json-file」ドライバが使用されますが、Docker1.12時点では以下のようなドライバが利用可能です。

| ドライバ | 出力先 | 
|:-----------|:------------|
|json-file|Json形式でファイルに保存（Dockerのデフォルト）|
|none|ログを記録しない|
|syslog|syslog|
|journald|journald|
|gelf|gelfをサポートするログ管理システム|
|fluentd|ログ管理ツール「fluentd」|
|awslogs|AWSの提供するログ管理システム「Amazon CloudWatch Logs」|
|splunk|ログ管理システム「Splunk」|
|etwlogs|WindowsのEvent Tracing for Windows|
|gcplogs|GCPの提供するログ管理システム「Google Cloud Logging」|

実際にドライバを指定するには、コンテナ起動時に`docker run --log-driver=<ドライバ名>`と入力します。もし仮に、`docker run`だけ入力した場合はデフォルトの「json-file」ドライバが使用されます。

## ログ設計時にやってはいけないこと
最後に、ログの設計を行う際にやってはいけない事を2つ紹介します。

### 機密情報は出力しない
情報漏洩の原因の一つにログによる機密情報の出力があります。ログは開発者や担当者しか知り得ないと安心していませんか？実際は、不正なアクセスなどによってログが見られてしまう可能性もあります。念には念を入れて、大切な機密情報はログとして出力しないように徹底しましょう。

例えば、

・氏名
・住所
・電話番号
・メールアドレス
・パスワード
・アクセストークン
・クレジットカード番号

などです。

### 不必要な情報は出力しない
不必要な情報は出力しないようにしましょう。その理由は以下の3つです。

・ログのサイズが大きくなりサービス利用料が増加する
・調査を行う際に邪魔になってしまう
・システムのパフォーマンス低下に繋がる

# まとめ

本記事では、ログの設計方法について紹介しました！開発を行うのであれば必ず利用するであろうログですので、明日からの開発に活かしていただければ幸いです！

::: note
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています！興味をお持ちいただける方は、[こちら](https://www.recruit.nuco.co.jp/?qiita_item_id=19c774898c68add6185e)まで。
:::
https://www.recruit.nuco.co.jp/?qiita_item_id=19c774898c68add6185e