バックエンド

APIログのベストプラクティスを考える

API logging best practice
koma

こんにちは!
先日結婚したこまっちゃんです。とはいえ、当社はニックネームで呼びあう文化なので、何とも実感を持てないまま日々を過ごしております笑

さて、普段はバックエンドを実装しているのですが、APIを作る上でのログのベストプラクティスを検討したいな〜と思い、調べたことをまとめてみます。

どんなログが求められるのか

そもそもなぜログを出すのかというと、

  • エラーの検証・デバッグなどのトラブルシューティング
  • アクセス記録を残し、アラートを発出するようなセキュリティ / 監査目的
  • サーバーメンテナンス中であることをクライアントに提示

などが目的として挙げられると思います。

これらの目的を達成するためには、

  • 適切なステータスコード
  • エラー場合、詳細な内容
  • どのアクション(APIのpath / method等)が実行されたか
  • 誰が実行したか
  • ログが発生した正確な日時(特にエラーの場合、エラーが起きたタイミングで発出)

など、必要十分な情報をログに残すことが必須となります。

ステータスコードの返し方

HTTPレスポンスの先頭行に付けられる3桁の数字、それがステータスコードです。
参考までに当社HPを $ curl -I https://mmmcorp.co.jp/ で叩いてみると、

HTTP/2 200
content-type: text/html
content-length: 186130
date: Wed, 15 Feb 2023 20:57:23 GMT
last-modified: Tue, 07 Feb 2023 09:00:28 GMT
:

こんな感じで返ってきます。
1行目の末尾に記載されている「200」の部分がステータスコードですね。

ステータスコードは、大まかに

ステータスコード 意味
100番台 情報
200番台 成功
300番台 リダイレクト
400番台 クライアントエラー
500番台 サーバーエラー

に分かれています。(詳細は RFC 7231 にてどうぞ)

ここで重要なのは「00」番を使うのは最終手段ということです。例えば、

  • 200 OK
  • 400 BadRequest
  • 500 InternalServerError

では具体的に何が成功したのか、どんなエラーが起きているのかを読み取ることができません。

  • データの作成が完了したのであれば 201 Created
  • データが見つからないのであれば 404 Not Found
  • サーバーが一時的に停止しているのであれば 503 Service Unavailable

という形で、ステータスコードからログの概要を読み取ることができるようにする必要があります。

APIを設計する際も、500以外は200で全て返すのではなく、APIの機能に応じた適切なステータスコードを返すように設定することが大切ですね。

とはいえ、204 No Content のように、少々使いにくいコードもあります。

例としてDeleteメソッドが成功した場合、204を返すことでデータが無い = 削除が成功した、と指し示すこともできますが、内容が漠然としていて本当に削除されたか解釈が難しいという印象も受けます。
この辺りは、フロントをはじめ他のチームメンバーとしっかりコミュニケーションを取ってコードを検討すると良いです。

ログとして残すべき詳細

特にエラーログにとって、ログの詳細情報は非常に重要です。
例として404 Not Foundは、本当に欲しいデータがないのか、探すべき場所を誤っているためデータが見つからないのかの区別をすることができません。

「DBにアクセスしたけれどデータが見つからない」、「DBにアクセスするために必要な情報がそもそも見つからない」など、わかる文章を簡潔に英語で記載する必要があります。

上記のような「どんな」に加え、

  • いつ(エラーが起きたタイミングやログの時刻)
  • 誰が(クライアントIPアドレスなど)
  • どこで(プロトコルのAPIのパス、メソッドなど)

なども必須の要素ですね。

Open Web Application Security Project(通称OWASP)が出しているチートシートをご覧いただくと、大変参考になると思います。

⚠️注意⚠️
機密性の高い情報をログに残すことは推奨しません。ユーザーの個人情報や暗号化キーなどは決して残さないよう、機密性を侵害していないかはチェックが必要ですね。

ログの構造

さて、今までログの詳細についてざっくりですと見てまいりましたが、必要な情報が載っていても、把握できなければ意味がありませんよね。

  • 全く改行されていない
  • 文章がただ羅列されていて欲しい情報がどこにあるかわからない

というログは、クライアントサイドにとってもデバッグする開発者にとっても非常にやっかいです。
JSONで構造化するなどして、できるだけフラットかつ見やすいログ構造にするとよさそうです。解析する上でもキー:バリュー形式の方がより高速かつ容易、正確に実行可能となります。

● Before

2023-02-16T12:09:26.628+09:00 { Error : status404, User a1's data does not exist or have already deleted. }

● After

2023-02-16T12:09:26.628+09:00
{
    "level": "error",
    "status": 404,
    "user-id": "a1",
    "description": "user data does not exist or have already deleted.",
}

ログのレベル

Afterにてしれっと「level」を使用していますが、私が参画しているプロジェクトでは以下のようにログレベルの切り分けをしています。

level 説明
info エラー以外のログに残しておくべき情報
warn 潜在的リスクや、400番台エラー(クライアントサイド)の情報
error ユーザーに影響するエラーや、500番台エラー(サーバーサイド)の情報

この他、さらに致命的なエラーの場合使用する FATALや、デバッグに使用する DEBUG などがあります。

エラー番号によってレベルを分けてからは、開発者がいち早く対処しなければならないサーバーエラーを見つけやすくなったのでよかったです!

最後に

いかがでしたでしょうか。

私の場合、API設計はまだまだ新米でこれからなので、基本的な内容ばかりだったと思いますが、基本をしっかり積み重ね頼り甲斐のあるエンジニアに成長していきたいです。

引き続き頑張ります!

AUTHOR
koma
koma
エンジニア
元々製薬業界で働いており、スクールを経てDWSに入社。主にgolangを使用したバックエンド業務に携わっているが、触ったことのない技術にも楽しく挑戦していきたいと考えている。趣味はスキューバダイビング。
記事URLをコピーしました