パラメータ化ロギング
効率的にメッセージを整形します。
「パラメータ化ロギング」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
文字列連結の問題
ログメッセージを連結すると、レベルが無効であっても、呼び出しの前に完全な文字列が構築されます。
log.debug("id=" + id + " name=" + name)では、DEBUGが無効でも文字列が構築されるため、CPUを無駄に消費します。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
String name = "Ada"; int id = 7;
// Bad: string is built even if DEBUG is disabled
log.debug("id=" + id + " name=" + name);
}
}{}によるプレースホルダー
SLF4Jでは、{}をプレースホルダーとして使用します。値は追加の引数として渡します。
メッセージはレベルが有効な場合にのみ組み立てられるため、無効なログによるコストはほとんどありません。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
String name = "Ada"; int id = 7;
// Good: no string built unless DEBUG is on
log.debug("id={} name={}", id, name);
}
}複数のプレースホルダー
プレースホルダーには順番に値が入ります。最初の{}には最初の引数が、2番目には2番目の引数が入り、その後も同様です。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
log.info("order {} for user {} totaling {} cents", "A-1", 42, 1599);
}
}効率的な理由
フレームワークはまずレベルを確認します。無効な場合は、どの引数に対してもtoString()を呼び出さず、すぐに戻ります。
コストの高いフォーマット処理は、メッセージが実際に出力される場合にのみ遅延して実行されます。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
static String describe(Object o) { System.out.println("toString called"); return o.toString(); }
public static void main(String[] args) {
// Note: passing describe(...) still evaluates eagerly; pass the raw object instead
log.trace("value={}", new int[]{1, 2, 3});
}
}フォーマット済み文字列ではなくそのままのオブジェクトを渡す
遅延処理の利点を得るには、フォーマットするメソッド呼び出しではなく、そのままのオブジェクトを渡します。
log.debug("{}", buildReport())と記述すると、buildReport()はすぐに実行されてしまいます。ロガー自身がフォーマットできる引数を渡してください。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
Object user = new Object();
// Logger calls user.toString() only if DEBUG is enabled
log.debug("current user: {}", user);
}
}負荷の高い処理に対する遅延サプライヤー
フォーマット処理自体に大きなコストがかかる場合は、Supplierを使うSLF4J 2.xのfluent APIを使用して、処理を遅延させます。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
static String heavyReport() { return "...expensive..."; }
public static void main(String[] args) {
log.atDebug()
.setMessage("report: {}")
.addArgument(() -> heavyReport())
.log();
}
}リテラルの波括弧をエスケープする
メッセージにリテラルの{}を含める必要がある場合は、バックスラッシュでエスケープします:\\{}。
このような場面はまれですが、JSON形式に似たテキストをログに記録するときに役立ちます。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
// Logs a literal {} then the value
log.info("empty object is \\{} and id is {}", 7);
}
}例外とプレースホルダーの組み合わせ
プレースホルダーと末尾の例外を組み合わせることもできます。SLF4Jは最後の引数がThrowableであることを検出し、そのスタックトレースを出力します。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
int id = 99;
try {
throw new RuntimeException("db down");
} catch (Exception e) {
log.error("failed processing record {}", id, e);
}
}
}引数を増やしすぎない
プレースホルダーの数と引数の数が一致しない場合、SLF4Jは可能な限り処理しますが、出力が不自然になることがあります。
数を一致させてください。値が2、3個を超える場合は、代わりに構造化コンテキスト(MDC)の利用を検討してください。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
// Two placeholders, two arguments: correct
log.info("user {} did {}", "ada", "login");
}
}MDCでコンテキストを追加する
リクエストIDのように複数の行で共有する値は、各メッセージに含めるのではなくMDCに格納します。
そうすると、パターンレイアウトによってMDCの値をすべての行に自動的に出力できます。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
MDC.put("requestId", "req-1234");
try {
log.info("handling request");
log.info("request done");
} finally {
MDC.clear();
}
}
}ベストプラクティスのまとめ
効率的なロギングのルールは次のとおりです。
- 常に
{}プレースホルダーを使用し、+による連結は決して使用しません。 - あらかじめ構築した文字列ではなく、そのままのオブジェクトを渡します。
- 本当に負荷の高い処理には、サプライヤーまたは
isXEnabled()を使用します。 - 横断的なコンテキストにはMDCを使用します。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
log.info("processed {} items in {} ms", 1000, 42);
}
}クイックチェック
パラメーター化ロギングについての理解度を確認します。
振り返り
パラメーター化ロギングについて学びました。
- 追加の引数とともに
{}プレースホルダーを使用します。 - メッセージは遅延して、レベルが有効な場合にのみ構築されます。
- そのままのオブジェクトを渡し、負荷の高い処理にはサプライヤーを使用します。
- 共有するコンテキスト値にはMDCを使用します。
次は、Logbackバックエンドの設定です。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
log.info("Parameterized logging recap: {}", "done");
}
}よくある質問
「パラメータ化ロギング」レッスンは無料ですか?
はい。「パラメータ化ロギング」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「パラメータ化ロギング」で何を学びますか?
効率的にメッセージを整形します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「パラメータ化ロギング」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 構造化ログを使う理由
- SLF4J ファサードとロガー
- パラメータ化ロギング
- Logback の設定