ログのベストプラクティスについて考える

企業で開発をしていると、システムログを出す機会があるかと思います。
とはいえ、初めて業務に関わる方がいざやるぞ！と考えても、「何を出すんだっけ？」となりませんか？
今回は、「ログ　ベストプラクティス」でググったものを起点に、ログのベストプラクティスについて考えます。

## 要約

色々みて回ると、この辺りが多く書かれている印象を受けました。

- 適切なデータを適切に取ろう
  - 特に記録レベル（error や warning など）を分ける
- コンテクストがわからなくても、読んで意味のわかるメッセージにしよう
- 検索しやすいような形（json など）にしよう
- 機密事項など取り扱う情報に気をつけよう
  - 取り扱わないか、マスクすることを忘れずに

## 前提

- claudWatch や DataDog のようなツールに依存したものにはしません。
- 特定の用途に絞ったロギングにフォーカスはしておらず、日々の運用全般がスコープです。
- 不慣れな部分も多いので適宜優しくご指摘いただけると嬉しいです。

## 調査

ここでは「ログ　ベストプラクティス」と調べて出てきたものを列挙します。
検索の結果、主にログのサービスを提供している企業の記事が出てきたので、その記事の内容をまとめます。

### Heroku をみる

[こちら](https://devcenter.heroku.com/ja/articles/writing-best-practices-for-application-logs)を参考にしました。

#### アプリケーションのログ記録のためのベストプラクティス

##### ログに記録するイベントを定義する

なんのログを残しますか？
web アプリケーションなので基本は API リクエストとレスポンスだと思います。
他には…

- アプリケーションエラー
- 入力および出力の検証の失敗
- 認証の成功と失敗
- 承認の失敗、等

##### 関連する詳細を含める

要するにログの項目ですね。

- 実行されたアクション
  - web の API だと path
- アクションを実行したユーザー
- 失敗の発生理由
- 警告とエラーメッセージについては、可能な場合は修正に関する情報

##### 機密情報を除外する

- 機密情報である個人を特定できる情報 (PII) を出力しないでください。
- 暗号化鍵またはシークレットをログに出力しないでください。

##### 構造化ロギングの使用

- 業界のデファクトスタンダートは json らしい。
- Heroku は Heroku で独自のメソッドのようなものを提供していそう。

##### 正しいレベルのログ

- INFO
  - 障害やエラーを表示しない情報メッセージ。
- WARN
  - 潜在的な問題があるが、ユーザーエクスペリエンスに影響しないことを示します。
- ERROR
  - ユーザーエクスペリエンスにいくらか影響する重大な問題であることを示します。
- FATAL
  - ユーザーエクスペリエンスに大きく影響する致命的なエラーであることを示します。
- DEBUG
  - デバッグに使用されます。メッセージは特にアプリの開発者を対象としています。

### AWS をみる

[こちら](https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/logging-monitoring-for-application-owners/logging-best-practices.html)を参考にしました。
※ db のログ

#### ベストプラクティスのログ記録

##### ログ記録レベル

> 過剰量のデータをログしないように注意してください。過剰なログ記録はパフォーマンスに悪影響を及ぼす可能性があるだけでなく、ログ記録のストレージと処理コストも増加する可能性があります。
> HTTP 応答ステータスコードをログに記録すると、特に 200 レベル (成功) と 300 レベル (リダイレクト) のステータスコードなど、大量のログデータが生成される可能性があります。400 レベル (クライアント側のエラー) と 500 レベル (サーバー側のエラー) のステータスコードのみをログ記録するよう検討することを推奨します。
> 開発環境では、開発者を支援するために情報やデバッグなどの詳細なログ記録を使用したいと考えるかもしれません。ただし、過剰なログデータを生成する可能性があるため、本番稼働環境では情報レベルとデバッグレベルは無効にしておくことを推奨します。

##### 注意事項と除外事項

> ログ記録するデータが、特に組織が事業を展開する管轄地域で法的に許可されていることを確認してください。

本家のサイトにはその項目例もありました。

- アプリケーションのソースコード
- セッション ID 値 (セッション固有のイベントを追跡する必要がある場合は、これをハッシュ値に置き換えることを検討してください)
- アクセストークン
- 機密個人データと、健康情報や政府発行の識別子など何らかのかたちで個人を特定できる情報 (PII)
- 認証パスワード
- データベース接続文字列
- 暗号化キーとその他のプライマリシークレット
- 銀行口座または支払いカード名義人のデータ
- ロギングシステムで保存が許可されているよりも高いセキュリティ分類のデータ
- 商業的機密情報
- 関連する管轄地域で収集が違法な情報
- ユーザーが収集をオプトアウトした、または収集に明示的に同意していない情報
- 収集への同意が失効した情報

### DataSet をみる

[こちら](https://www.dataset.com/blog/the-10-commandments-of-logging/)のページを参考にしました。
そしてありがたいことに[日本語訳](https://scrapbox.io/kawasima/%E3%83%AD%E3%82%AE%E3%83%B3%E3%82%B0%E3%83%99%E3%82%B9%E3%83%88%E3%83%97%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%82%B9)も発見。

#### Logging Best Practices: The 13 You Should Know

##### 自分でログの書き出しをしない（車輪の再開発をしない）

- printfをつかったり、ログエントリを自分でファイルに書き出したり、ログローテションを自分でやったりしてはいけない。
- 運用担当者にお願いして、標準ライブラリやシステムAPIコールを使うようにしよう。そうすれば、実行中のアプリケーションが他のシステムコンポーネントと適切に連携して、特別なシステム設定なしに適切な場所またはネットワークサービスにログを記録できるようになる。

##### 正しいレベルでログを出力する

- 正しいログレベルを選ぶのは難しいが、適切なものを使おう。

例
> ACE: 本番環境でこれが使われていたら、コードスメル(不味い兆候)だ。これは開発時にバグを追跡するために使われるべきもので、バージョン管理システムにはコミットしてはならない。
 DEBUG: プログラムで起こることは何でもこのレベルで出力してもよい。ほとんどはデバッグ時に使われるが、本番運用に入る前に必要そうなものだけ残しておいて、障害対応時にアクティブにできるようにしておくのがオススメだ。
 INFO: ユーザ主導またはシステム固有のすべてのアクション(すなわち、定期的にスケジュールされた操作)をこのレベルで記録する。
 NOTICE: 本番運用時にプログラムが実行されるときにこのレベルを使う。エラーと見なされないすべての把握しておきたいイベントを、このレベルで記録する。
 WARN: エラーになる可能性のあるすべてのイベントをこのレベルで記録する。例えば、あるデータベース呼び出しがいつもより長くかかった場合や、メモリー内キャッシュの容量が限界に近づいた場合が該当する。そうすることで、アラートの自動トリガを設定できる。

##### 正しいログカテゴリを採用しよう

そのログが認証やビジネスロジックのエラー、キャッシュ処理のエラーなのかの分別をつける必要がある。
ロギングのライブラリを使っている場合、その設定を変更するだけでいけることが多そうな所感を感じた。

##### 意味のあるログメッセージを出力しよう

何かトラブルが起きた時、そのログメッセージを頼りにトラブルシューティングをすることになる。
ログメッセージを決めるときはそのビジネスロジックなどのコンテクストを意識したものを書くが、一方でそのログを読むときはコンテクストを意識せずに読むことになる。その操作の目的や、可能であればリカバリ内容も記載しておくと良いだろう。

##### ログメッセージは英語にしよう

- 英語はメッセージをASCII文字でログ出力することを意味する。ログメッセージが出力されるまでどこでどう処理されるかがわからないことが多く、最終的に読めないログが出力される…ようなことを防ぐためだ。

##### ログメッセージにコンテクストを付ける

引用元を見ると分かりやすいのでそのまま貼り付けます。

~~~
- 役立たずのログメッセージはこんなものだ。
  - Transaction failed
  - User operation succeeds
  - java.lang.IndexOutOfBoundsException
- 正しいコンテクストがないと、これらのメッセージはノイズでしかない。障害対応時には、なんの価値も産まずただ空間所消費するだけだ。コンテキストを加えれば、より価値のあるメッセージになる。
  - Transaction 236432 failed: cc number checksum incorrect
  - User 54543 successfully registered e-mail user@domain.com
  - IndexOutOfBoundsException: index 12 is greater than collection size 10
~~~

##### 機械でパースしやすいフォーマットでログを書く

人が読みやすいようすることも大切だが、大量のログを見るのに検索することなどがあるだろう。
導入している機会にもよると思うが、基本は json 形式にしておくと良い。

##### でも同時に人が読めるように

読めないと障害対応などができないよね

##### 多すぎず少なすぎず

多すぎると探すことも読むことも大変なので、適切な（欲しい量の）ログを出そう。
ただ、この量を決めるためのルールはないらしく、開発の際にできる限りのログを出し続け、常に本番環境で必要なログを考えることが大事である。

##### 誰がログを読むか考えよう

読む人によってログのレベルが変わる。

- 問題のトラブルシューティングするエンドユーザ(クライアントやデスクトッププログラムを想定)
- システム管理者または運用エンジニアが運用上の問題のトラブルシューティングをする。
- 開発中にデバッグしたり、プロダクトの問題を解決したりする開発者。

##### トラブルシューティング目的だけでログを出力しない

他にも使用される用途は考えられる。

- 監査: これは業務要件の場合がある。その目的は経営層や法務担当者にとって重要なイベントをキャプチャすることだ。これらは通常、システムのユーザが行っていること(誰がログインしたか、誰がそれを編集したかなど…)を記述する。
- プロファイル: ログはその時刻が記録される(時にはミリ秒単位で)ので、プログラムのある部分をプロファイリングする良いツールとなる。例えば処理の開始と終了をログに記録することで、自動的に(ログを解析して)、または障害対応中に、プログラム自体にそれらのメトリックを追加せずに、いくつかの性能メトリックを推測することができる。
- 統計: 特定のイベントが発生するたびに(ある種のエラーまたはイベントのような)ログを記録すると、実行中のプログラム(やユーザの行動)に関する統計を計算できる。また連続して多数のエラーが発生していることを検出してアラートシステムが作動させることも可能になる。

##### ベンダーロックインを避ける

- ベンダー固有の実装しないようにして、いつでも変更可能な状況にしておこう。

##### 機微な情報をログに出力しない

機微な情報はログに記録しないようにしよう。まず明らかな箇所、以下のものは絶対にログに出力してはならない。

- パスワード
- クレジットカード番号
- 社会保障番号

また以下は微妙かもしれないが、ログに記録すべきではないものだ。

- オプトアウトしているユーザのセッション識別子
- 認可トークン
- PII (個人識別情報)

## 総括

- 適切なデータを適切に取ろう
  - 特に記録レベル（error や warning など）を分ける
- コンテクストがわからなくても、読んで意味のわかるメッセージにしよう
- 検索しやすいような形（json など）にしよう
- 機密事項など取り扱う情報に気をつけよう
  - 取り扱わないか、マスクすることを忘れずに

この辺りが被っていそうです。
実際に実装する時に意識してみようと思います。