このガイドの目的は、サービスをより効果的に監視およびトラブルシューティングできるようにするための基本的な洞察と実践を提供することです。
アプリケーション開発では、ログ記録は見落とされがちですが、堅牢で監視可能なシステムを構築する上で重要な要素です。適切なログ記録を実践することで、アプリケーションの可視性を高め、内部の仕組みに対する理解を深め、アプリケーション全体の健全性を向上させることができます。
アプリケーションのエントリ ポイントにデフォルトのログ記録メカニズムを組み込むことは、非常に有益です。この自動ログ記録により、重要なやり取りをキャプチャし、エントリ ポイントの引数を含めることができます。ただし、パスワードなどの機密情報をログに記録すると、プライバシーやセキュリティ上のリスクが生じる可能性があるため、注意が必要です。
アプリケーションが実行するすべての重要なアクション、特に状態を変更するアクションは、ログ エントリを生成する必要があります。この徹底的なログ記録アプローチは、問題が発生したときに迅速に特定して対処するための鍵であり、アプリケーションの健全性と機能性を透過的に把握できます。ログ記録をこのように入念に行うことで、診断とメンテナンスが容易になります。
アプリケーションによって生成される膨大な量のデータを管理および解釈するには、適切なログ レベルを採用することが重要です。ログを重大度と関連性に基づいて分類することで、重要な問題を迅速に特定して対処できると同時に、監視作業に負担をかけずに、それほど緊急でない情報にもアクセスできるようになります。
以下はログ レベルを効果的に活用するためのガイドラインです。
レベル | 説明と例 | 承認された使用 | 受け入れられません |
---|---|---|---|
| システム操作を停止させる致命的なイベント。例: データベース接続の喪失 | 重大なシステムエラー | ユーザーのログイン試行の失敗などの重大でないエラー |
| 問題はありますが、システムは実行を続行し、要求された操作を完了できます。 | 問題につながる潜在的な問題 | 定期的な状態の変化 |
| ユーザーアカウントの作成やデータの書き込みなど、通常のアプリケーション機能に関する洞察 | 状態の変化 | 変更なしの読み取り専用操作 |
| プロセスの開始/終了などの詳細な診断情報 | プロセスステップのログ記録はシステム状態を変更しない | 定期的な状態変更または高頻度の操作 |
| メソッドのエントリ/終了を含む最も詳細なレベル | プロセスの流れと詳細を理解する | 機密情報の記録 |
アプリケーションでアクションをログに記録する場合、直接関係するエンティティの ID を含めることは、ログ情報をデータベース データにリンクするために重要です。階層的なアプローチでは、項目を親グループまたはカテゴリにリンクすることで、アプリケーションの特定の部分に関連付けられているすべてのログをすばやく見つけることができます。
たとえば、メッセージの送信に失敗したときにチャットの ID のみを記録するのではなく、チャット ルームの ID とそれが属する会社の ID も記録する必要があります。こうすることで、より多くのコンテキストが得られ、問題のより広範な影響を把握できます。
Failed to send the message - chat=$roomId, chatRoomId=chatRoomId, company=$companyId
以下は、階層的アプローチを使用した場合の運用ログの例です。
すべてのチームでログ形式を標準化すると、ログの読みやすさと理解しやすさが大幅に向上します。検討すべき標準化されたプレフィックスをいくつか示します。
変数名と値をログ メッセージの本文から分離すると、次のようないくつかの利点があります。
Log message - valueName=value
以下に、説明したベスト プラクティスに従って適切に構造化されたログ エントリの例を示します。
2023-10-05 14:32:01 [INFO] Successful login attempt - userId=24543, teamId=1321312 2023-10-05 14:33:17 [WARN] Failed login attempt - userId=536435, teamId=1321312
これらの例は以下を示しています。
以下は、提案されたプラクティスを使用した場合の運用ログの例です。
ログを特定のユーザー アクションに効果的に関連付けるには、ログにtraceId
( correlationId
とも呼ばれます) を含めることが重要です。ID は、そのエントリ ポイントによってトリガーされるロジックによって生成されるすべてのログで一貫している必要があり、イベントのシーケンスを明確に把握できます。
Datadog などの一部の監視サービスでは、ログのグループ化がすぐに使用できますが、手動で実装することもできます。Spring を使用する Kotlin アプリケーションでは、HandlerInterceptor を使用して REST リクエストのトレース ID を実装できます。
@Component class TraceIdInterceptor : HandlerInterceptor { companion object { private const val TRACE_ID = "traceId" } override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { val traceId = UUID.randomUUID().toString() MDC.put(TRACE_ID, traceId) return true } override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) { MDC.remove(TRACE_ID) } }
このインターセプターは、リクエストごとに一意のtraceId
を生成し、リクエストの開始時にそれを MDC に追加し、リクエストの完了後にそれを削除します。
このようなログ集約を実装すると、以下の例のようにログをフィルタリングできるようになります。
多くのシステムでは、エンティティはUUID
またはLong
ID のいずれかを主要な識別子として使用しますが、一部のシステムでは、異なる目的で両方のタイプの ID を使用する場合があります。ログ記録の目的で各タイプが及ぼす影響を理解することは、情報に基づいた選択を行うために重要です。
考慮すべき事項の内訳は次のとおりです。
読みやすさ: Long
ID は、特にLong
範囲の上限にない場合は、読みやすく、かなり短くなります。
一意の値: UUID
ID はシステム全体で一意性を提供し、ID の衝突の問題に直面することなく ID を使用してログを検索できるようにします。ここでの衝突とは、関連のない DB テーブルの 2 つのエンティティが同じLong
ID を持つ可能性があることを意味します。
システムの制限: 長い主キーをエンティティ ID として使用するシステムでは、ランダムなUUID
ID を追加するのは通常簡単ですが、 UUID
エンティティ ID を持つ分散システムでは、ログ記録専用のLong
ID を持つことは困難またはコストがかかる可能性があります。
既存のログ:ログで使用される ID の種類の一貫性は、少なくともエンティティごとに重要です。システムがすでに一部のエンティティのログを生成していて、それらすべてを変更することを検討していない場合は、エンティティを識別するためにすでに使用されている種類を維持する方がよいでしょう。移行期間中は両方の ID をログに記録することを検討できますが、複数の ID を永続的に保持すると、ログが不必要に乱雑になります。
適切なログ記録方法は、効果的なサービス監視に不可欠です。包括的なログ記録、適切なログ レベル、トレース ID、標準化されたログ形式を組み込むことで、アプリケーションの監視とトラブルシューティングの能力を大幅に強化できます。これらの方法により、ログの明瞭性と一貫性が向上し、問題を迅速に診断して解決しやすくなります。
この投稿を読んでいただきありがとうございます。