著者: 王蘭
ログの出力は技術であり、ログ情報は開発者がオンラインの問題をトラブルシューティングするための最も重要な手段の 1 つですが、標準のログは開発者によって無視されることがよくあります。ログは保険のようなもので、普段は使いませんが、何かあったときに保険が使えるか確認したいと思っています。良質なログは、外部に対する当社の証拠資料となります。
1. 概要
1.1 ログとは何ですか?
ログは、サーバーによって自動的に作成および維持される 1 つまたは複数のログ ファイルとしてウィキペディアで定義されており、サーバーが実行するアクティビティのリストが含まれています。
適切に出力されたログ ファイルは、開発者に正確なシステム レコードを提供し、開発者がシステム エラーの詳細と根本原因を特定するのに役立ちます。Java アプリケーションでは、通常、ログ ファイルはアプリケーションの動作中に重要なロジック パラメータや異常エラーを記録するために使用され、システム監視システムを構築するためにログ収集システム (ELK、DTM) によって補完されます。
1.2 なぜログを記録するのか?
前述したように、ログは根本原因の分析を容易にする正確なシステム記録を提供します。では、なぜログを記録する必要があるのでしょうか。また、ログを記録する機能は何でしょうか?
-
印刷デバッグ:ログを使用して変数またはロジックの特定の部分を記録し、プログラム動作のプロセス、つまりプログラムが実行するコードを記録して、ロジックの問題のトラブルシューティングを容易にします。
-
問題箇所:プログラムが異常または失敗した場合に、問題箇所をすぐに特定できるため、後で問題を解決するのに便利です。オンラインの本番環境はデバッグができないため、テスト環境で本番環境をシミュレートするには手間がかかります。したがって、ログに記録された情報を頼りに問題を特定することが非常に重要です。
-
監視アラームとユーザー動作監査:ログはフォーマット後、関連する監視システム (AntMonitor) を通じて多次元監視ビューで構成できるため、システムの動作状況を把握したり、ユーザーの動作動作を記録して収集および分析することができます。ビジネス市場を構築するためのログ。
1.3 ログはいつ記録されますか?
ログの重要性は上で述べましたが、どのような場合にログを記録する必要がありますか。
-
コードが初期化されるとき、またはロジック エントリを入力するとき:システムまたはサービスの起動パラメータ。コア モジュールまたはコンポーネントの初期化プロセスは、いくつかの主要な構成に依存することが多く、さまざまなパラメータに従ってさまざまなサービスが提供されます。ここで必ず INFO ログを記録し、起動完了状態のパラメータとサービスの説明を出力してください。
-
プログラミング言語の例外:このタイプのキャッチされた例外は、システムが開発者に注意を払うように通知するものであり、非常に高品質のエラー レポートです。ログは適切に記録され、実際のビジネス状況に応じて WARN または ERROR レベルを使用する必要があります。
-
ビジネス プロセスの期待が一致しない:プロジェクト コードの結果が期待と一致しない場合もログ シナリオの 1 つであり、簡単に言えば、すべてのプロセス ブランチが考慮されます。この状況が許容できるかどうかは開発者が判断します。一般的な適切なシナリオには、不正な外部パラメータ、妥当な範囲外のリターン コードを引き起こすデータ処理の問題などが含まれます。
-
システム/ビジネス コア ロジックの主要なアクション:システム内のコア ロールによってトリガーされるビジネス アクションは、より注意が必要であり、システムの正常な動作を測定するための重要な指標であるため、INFO レベルのログを記録することをお勧めします。
-
サードパーティ サービスのリモート呼び出し:マイクロサービス アーキテクチャ システムの重要な点は、サードパーティが決して信頼できないということです。サードパーティ サービスのリモート呼び出しの場合は、リクエストと応答のパラメーターを出力することをお勧めします。各端末の問題を特定し、サードパーティによって引き起こされるものではないため、サービスログがないことは圧倒的です。
2. 基本仕様
2.1 ロギングの原則
-
隔離:ログ出力はシステムの通常の動作に影響を与えることはできません。
-
セキュリティ:ログの出力自体に、セキュリティ上の問題につながる可能性のある論理的な異常や抜け穴が存在することはできません。
-
データ セキュリティ:ユーザーの連絡先情報、ID 番号、トークンなどの機密情報や機密情報を出力することは許可されていません。
-
監視と分析が可能:分析のための監視および分析システムを監視するためにログを提供できます。
-
ロケーションベースのトラブルシューティング:ログ情報の出力は意味があり、読みやすいものである必要があり、日常の開発学生がオンラインの問題のトラブルシューティングに使用できるようにする必要があります。
2.2 ログレベル設定仕様
私たちの日々の開発では、一般的なログ出力レベルが 4 つあり、異なるレベルは異なる時間にログを出力するのに適しています。
主に以下の4グレードが使用されます。
-
デバッグ
DEUBG レベルは主にデバッグ コンテンツを出力し、このレベルのログは主に開発フェーズおよびテスト フェーズでの出力に使用されます。このレベルのログはできるだけ詳細である必要があります。開発者は、パラメータ情報、デバッグの詳細、戻り値情報などを含む、デバッグに役立つあらゆる種類の詳細情報を DEBUG に記録して、開発とテストの段階で問題や例外が発生した場合は、それを分析します。
-
情報
INFOレベルは、主にシステムの重要な情報を記録するもので、システムの通常運用時の主要な運用指標を保持することを目的としており、開発者は、システムの初期構成やビジネスステータスの変化情報、ユーザーのビジネスプロセスの中核となる処理などを記録することができます。日常の操作に便利な INFO ログ ディメンション作業中のコンテキスト シナリオの再現とエラーのバックトラッキング プロジェクト完了後、テスト環境でログレベルをINFOに設定し、INFOレベルの情報を利用してアプリケーションの動作が理解できるか、問題がないか確認することをお勧めします。これらのログは、有用なトラブルシューティング情報を提供します。
-
警告
WARN レベルの主な出力は、予測可能かつ計画された警告コンテンツです。たとえば、メソッドの入力パラメーターが空である場合、またはパラメーターの値がメソッドの実行条件を満たしていない場合などです。WARN レベルでは、後でログを分析できるように、より詳細な情報が出力される必要があります。
-
エラー
ERROR レベルは主に、エラー、例外などのいくつかの予測不可能な情報を対象としています。たとえば、ネットワーク通信、データベース接続、catch ブロックでキャプチャされたその他の例外などです。例外がプロセス全体にほとんど影響を与えない場合は、システムでは、WARN レベルのログ出力を使用できます。ERRORレベルでログを出力する場合は、メソッドの入力パラメータやメソッド実行時に生成されるオブジェクトなど、可能な限り多くのデータを出力し、エラーのあるデータや異常なオブジェクトがある場合は、それらのオブジェクトもまとめて出力する必要があります。
警告/エラーの選択方法
メソッドや関数でロジック実行異常が発生した場合、WARNレベルまたはERRORレベルのログを出力する必要がありますが、例外発生時にWARNレベルまたはERRORレベルのログを出力するかどうかは、次の2つの側面から分析できます。
一般的な WARN レベルの例外
-
ユーザー入力パラメータエラー
-
非コアコンポーネントの初期化に失敗しました
-
バックエンド タスクの処理は最終的に失敗します (再試行があり、再試行が成功した場合は WARN は必要ありません)。
-
データ挿入は冪等である
一般的な ERROR レベルの例外
-
プログラムの起動に失敗しました
-
コアコンポーネントの初期化に失敗しました
-
データベースに接続できません
-
コアビジネスへのアクセスが依存する外部システムで障害が発生し続ける
-
OOM
ERROR レベルのログを乱用しないでください。一般に、アラームが設定されたシステムでは、WARN レベルでは通常アラームは発生しませんが、ERROR レベルでは監視アラームや電話アラームが設定されます。ERROR レベルのログの出現は、システムに非常に深刻な問題が発生したことを意味します。誰かがすぐに対処しなければなりません。
ほとんどのシステムのアラーム設定は、単位時間あたりの ERROR レベルのログの発生に基づいているため、問題の重要性に関係なく、問題がある限り、ERROR レベルのログを誤って使用することは非常に無責任です。 ERROR ログをランダムに入力する多くのアラームノイズが発生し、重要な問題を見逃す可能性があります。
2.3 一般的なログ形式
サマリーログ
概要ログはフォーマットされた標準ログ ファイルであり、システム構成の監視やオフライン ログ分析に使用できます。一般に、システムによって提供される外部サービスおよび統合されたサードパーティ サービスでは、対応するサービス概要ログを出力する必要があります。概要ログには通常、次の種類の重要な情報が含まれている必要があります。
-
呼び出し時間
-
ログリンク ID (traceId、rpcId)
-
スレッド名
-
インターフェース名
-
メソッド名
-
電話に時間がかかる
-
通話が成功したかどうか (Y/N)
-
エラーコード
-
システムコンテキスト情報 (発信側システム名、発信側システム IP、発信側タイムスタンプ、圧力テスト (Y/N))
2022-12-12 06:05:05,129 [0b26053315407142451016402xxxxx 0.3 - /// - ] INFO [SofaBizProcessor-4-thread-333] - [(interfaceName,methodName,1ms,Y,SUCCESS)(appName,ip地址,时间戳,Y)
詳細ログ
詳細ログは、トラブルシューティングのために概要ログ内の一部のビジネス パラメータを補足するために使用されるログ ファイルです。詳細ログには通常、次の種類の情報が含まれます。
-
呼び出し時間
-
ログリンク ID (traceId、rpcId)
-
スレッド名
-
インターフェース名
-
メソッド名
-
電話に時間がかかる
-
通話が成功したかどうか (Y/N)
-
システムコンテキスト情報 (発信側システム名、発信側システム IP、発信側タイムスタンプ、圧力テスト (Y/N))
-
エントリーをリクエストする
-
参照のリクエスト
2022-12-12 06:05:05,129 [0b26053315407142451016402xxxxx 0.3 - /// - ] INFO [SofaBizProcessor-4-thread-333] - [(interfaceName,methodName,1ms,Y,SUCCESS)(appName,ip地址,时间戳,Y)(参数1,参数2)(xxxx)
業務執行ログ
ビジネス実行ログは、システムの実行プロセス中に出力されるログです。通常、特定の形式はありません。コードの実行ロジックを追跡するために開発者によって出力されるログです。個人的には、概要ログ、詳細なログを出力するときに出力する必要があります。ログ、エラー ログは完全ですが、システムがログを実行する場所は比較的少ないです。業務実行ログを印刷する必要がある場合は、以下の点に注意する必要があります。
このログは印刷する必要がありますか? 出力されない場合、その後のトラブルシューティングに影響しますか? ログが出力されると、その後の出力頻度が高くなりすぎて、オンライン ログが過剰に出力されますか?
ログ形式は認識性が高いですか? 後からログを監視したりクリーニングした場合、他のログと区別できなくなったり、毎回出力されるログの形式が統一されなかったりする問題はありませんか?
現在の実行の主要なステップと説明を出力し、後続の保守担当者が読むのに便利なこのログを印刷する機能を明確に表現します。
ログには、現在の実行ステップの明確な印刷意味と主要なパラメータが含まれている必要があります。
推奨される形式: [ログシーン] [ログの意味] ビジネスパラメータを含む特定の情報
[scene_bind_feature][feature_exists]功能已经存在[tagSource='MIF_TAG',tagValue='123']
3. ロギングのベストプラクティス
3.1 必須
1. ログを出力するコードが失敗してプロセスをブロックすることは許されません。
log print ステートメントによってスローされた例外によってビジネス プロセスが中断されないようにする必要があります。次の図に示すように、ショップが null の場合は NPE がスローされます。
public void doSth(){
log.info("do sth and print log: {}", shop.getId());
// 业务逻辑
...
}
2. ログを出力するための System.out.println() の使用を無効にする
反例:
public void doSth(){
System.out.println("doSth...");
// 业务逻辑
...
}
分析します:
-
System.out.println のソース コードを分析すると、System.out.println は同期メソッドであることがわかり、同時実行性が高い場合、多数の println メソッドを実行するとパフォーマンスに重大な影響を及ぼします。
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
-
レベル別のログ出力はできません。具体的には、デバッグ、情報、エラーなど、ログ フレームワークと同じレベルの制御を行うことはできません。
-
System.outおよびSystem.errorで出力されるログはログファイルには出力されず、端末上に直接出力されるため、ログを収集できません。
正例:
日常の開発またはデバッグのプロセスでは、標準のログ システム log4j2 または logback を使用して (ただし、API を直接使用しないでください) 非同期でログを収集してみてください。
public void doSth(){
log.info("doSth...");
// 业务逻辑
...
}
3. ログシステム(Log4j、Logback)のAPIを直接使用することは禁止されています。
アプリケーションはログ システム (Log4j、Logback) の API を直接使用できませんが、ログ フレームワーク (SLF4J、JCL--Jakarta Commons Logging) の API に依存する必要があります。
分析します:
Log4j または Logback で API を直接使用すると、システム コード内で強結合ログ システムが実装されることになりますが、後からログ実装を切り替える必要がある場合、比較的大きな変換コストがかかります ログ フレームワークの APIファサードモードを利用したログであるSLF4JやJCLなどを一律に利用することで、特定のログ実装を分離する機能をフレームワークで実現することで、その後のメンテナンスに役立てるとともに、クラスごとに統一したログ処理方法を確保します。
正例:
// 使用 SLF4J:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(xxx.class);
// 使用 JCL:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log log = LogFactory.getLog(xxx.class);
4. ロギング ツール オブジェクトを宣言します。 Logger は private static Final として宣言する必要があります。
分析します:
-
ロガー オブジェクトが他のクラスによって不正に使用されるのを防ぐために、プライベートとして宣言します。
-
静的として宣言する目的は、新しいロガー オブジェクトが繰り返されることを防ぐこと、ロガーがシリアル化されてセキュリティ リスクが生じることを防ぐことです。リソースを考慮して、ロガーの構築メソッドのパラメーターは Class であり、ロガーがログを区別するために使用されることを決定します。クラス構造に基づいているため、クラスにはロガーが 1 つだけ必要なので、静的です。
-
プログラムの実行中にロガーが変更されるのを避けるために、クラスのライフサイクル中にロガーを変更する必要がないため、final として宣言されます。
正例:
private static final Logger LOGGER = LoggerFactory.getLogger(xxx.class);
5.trace/debug/infoレベルのログ出力の場合、ログレベルの切り替え判定を行う必要があります。
反例:
public void doSth(){
String name = "xxx";
logger.trace("print debug log" + name);
logger.debug("print debug log" + name);
logger.info("print info log" + name);
// 业务逻辑
...
}
分析します:
設定されたログ レベルが警告の場合、上記のログは出力されませんが、文字列連結操作が実行されます。名前がオブジェクトの場合は、toString() メソッドも実行され、システム リソースが浪費されます。上記の操作を行った場合、最終的なログは出力されませんので、判定するログスイッチを追加することをお勧めします。
正例:
トレース、デバッグ、情報レベルのログを出力する前に、対応するレベルのログ切り替え判定を追加します 通常、切り替え判定ロジックはログツールクラスにパッケージ化して統一実装できます。
public void doSth(){
if (logger.isTraceEnabled()) {
logger.trace("print trace log {}", name);
}
if (logger.isDebugEnabled()) {
logger.debug("print debug log {}", name);
}
if (logger.isInfoEnabled()) {
logger.info("print info log {}", name;
}
// 业务逻辑
...
}
6. 例外をキャッチした後にログを出力するために e.printStackTrace() を使用しないでください。
反例:
public void doSth(){
try{
// 业务逻辑
...
} catch (Exception e){
e.printStackTrace();
}
}
分析します:
-
e.printStackTrace() によって出力されるスタック ログはビジネス コード ログと交互に配置されるため、通常、例外ログを確認するのは不便です。
-
e.printStackTrace() ステートメントによって生成された文字列には、スタック情報が記録されます。情報が長すぎる場合、文字列定数プールが配置されているメモリ ブロックにスペースがなくなり、メモリがいっぱいになり、システム リクエストが実行されなくなります。ブロックされる。
正例:
public void doSth(){
try{
// 业务逻辑
...
} catch (Exception e){
log.error("execute failed", e);
}
}
7. 例外ログを出力するときは、すべてのエラー メッセージを出力する必要があります。
反例:
-
例外は出力されず、どのタイプの例外が発生したかを特定することは不可能です
public void doSth(){
try{
// 业务逻辑
...
} catch (Exception e){
log.error("execute failed");
}
}
-
詳細なスタック例外情報は記録されず、基本的なエラー説明情報のみが記録されるため、トラブルシューティングには役立ちません。
public void doSth(){
try{
// 业务逻辑
...
} catch (Exception e){
log.error("execute failed", e.getMessage());
}
}
正例:
一般に、ログ フレームワークの警告レベルとエラー レベルには、Throwable 例外タイプを渡す API があり、スローされた例外はログ API に直接渡すことができます。
void error(String var1, Throwable var2);
public void doSth(){
try{
// 业务逻辑
...
} catch (Exception e){
log.error("execute failed", e);
}
}
8. ログを出力する場合、JSON ツールを使用してオブジェクトを文字列に直接変換することは禁止されています。
反例:
public void doSth(){
log.info("do sth and print log, data={}", JSON.toJSONString(data));
// 业务逻辑
...
}
分析します:
-
fastjson などのシリアル化コンポーネントは、オブジェクトの get メソッドを呼び出してオブジェクトをシリアル化します。オブジェクト内の一部の get メソッドがオーバーライドされ、例外がスローされた場合、ログの出力によって通常のビジネス プロセスの実行が影響を受ける可能性があります。
-
ロギングプロセス中の一部のオブジェクトのシリアル化プロセスも、比較的パフォーマンスを消費します。まず第一に、シリアル化プロセス自体は CPU を消費する計算集約的なプロセスです。第 2 に、このプロセスでは大量の中間オブジェクトが生成されるため、メモリにはあまり優しくありません。
正例:
オブジェクトの toString() メソッドを使用してオブジェクト情報を出力できます。コードに toString() のカスタム ロジックがない場合は、Apache ToStringBulider ツールを使用できます。
public void doSth(){
log.info("do sth and print log, data={}", data.toString());
log.info("do sth and print log, data={}", ToStringBuilder.reflectionToString(data, ToStringStyle.SHORT_PREFIX_STYLE));
}
9. 意味のない(ビジネスコンテキストや関連するログリンクIDがない)ログを出力しないでください。
反例:
-
ビジネス情報が含まれていないログは、トラブルシューティングには意味がありません。
public void doSth(){
log.info("do sth and print log");
// 业务逻辑
...
}
-
異常分岐のないコードはログが出力されますが、通常の処理では異常分岐はログが出力され、異常がなければ正常に実行されたことになります。
public void doSth(){
doIt1();
log.info("do sth 111");
doIt2();
log.info("do sth 222");
}
正例:
-
ログには、トラブルシューティングや原因の迅速な特定に役立つ、関連するビジネス情報が含まれている必要があります。
public void doSth(){
log.info("do sth and print log, id={}", id);
// 业务逻辑
...
}
-
大量に出力される意味のないログについては、直接強制終了するか、デバッグ レベルで出力できます。
10. INFO レベルのログをループで出力しないでください。
反例:
public void doSth(){
for(String s : strList) {
log.info("do sth and print log: {}", s);
// 业务逻辑
...
}
}
11. 重複したログを印刷しない
反例:
public void doSth(String s){
log.info("do sth and print log: {}", s);
doStep(s);
}
private void doStep(String s){
log.info("do sth and print log: {}", s);
// 业务逻辑
...
}
public void doSth(String s) {
try {
doStep(s);
} catch (Exception e){
log.error("something wrong", e);
}
}
private void doStep(String s){
try {
// 业务逻辑
} catch (Exception e){
log.error("something wrong", e);
throw e;
}
}
分析します:
-
重複したログは、ネストされたリンクごとに出力されます。
-
ロギング後に例外をスローしないでください。スローされた例外は通常、外側の層によって処理されます。そうでない場合、なぜそれを捨てるのでしょうか?原則として、例外が発生したかどうかに関係なく、同じイベントのログ メッセージを異なる場所に繰り返し記録しないでください。
正例:
直接強制終了するか、ログをデバッグ レベルのログにダウングレードします。
12. 機密情報のエクスポートを避ける
13. 単一ログ行のサイズは 200K を超えてはなりません
3.2 推奨事項
1. ログ言語として英語を使用してみてください
提案: 中国語のコードと端末間の不一致による印刷時の文字化けを防ぐために、ログを印刷するときは英語で出力するようにしてください。障害の特定とトラブルシューティングにある程度の支障が生じます。
2. 重要なメソッドは通話ログを記録することができます
重要なメソッドの入口でメソッド呼び出しログを記録し、出口でパラメータを出力することをお勧めします。これは、トラブルシューティングに非常に役立ちます。
public String doSth(String id, String type){
log.info("start: {}, {}", id, type);
String res = process(id, type);
log.info("end: {}, {}, {}", id, type, res};
}
3. コア ビジネス ロジックで if...else などの条件が発生した場合、各ブランチの最初の行にログを出力しようとします。
コア ビジネス ロジック コードを作成するときに、 if...else... や switch などの条件が発生した場合、ブランチの最初の行にログを出力できるため、トラブルシューティングの際にログを使用して次のことを判断できます。どのブランチを入力したかに応じて、コードのロジックが明確になり、問題のトラブルシューティングが容易になります。
提案:
public void doSth(){
if(user.isVip()){
log.info("该用户是会员,Id:{},开始处理会员逻辑",user,getUserId());
//会员逻辑
}else{
log.info("该用户是非会员,Id:{},开始处理非会员逻辑",user,getUserId())
//非会员逻辑
}
}
4. オブジェクト全体ではなく、必要なパラメータのみを出力することをお勧めします。
反例:
public void doSth(){
log.info("print log, data={}", data.toString());
// 业务逻辑
...
}
分析します:
まず、すべてのオブジェクトのフィールドを出力する必要があるかどうかを分析します。オブジェクトに 50 個のフィールドがあるが、特定の原因を特定するために必要なパラメータが 2 つだけである場合、すべてのフィールドを印刷するとコンテンツ スペースが無駄になり、フィールドが多すぎるため根本原因の調査に影響を及ぼします。
正例:
public void doSth(){
log.info("print log, id={}, type={}", data.getId(), data.getType());
// 业务逻辑
...
}
この方法を使用する場合は、NPE を時間内に防止し、それがコア シナリオであるかどうかを検討する必要があります。オンラインでの問題の特定とトラブルシューティングに影響を与える通話の欠落や通話の減少を避けるために、すべてのコア シナリオをプレイすることをお勧めします。