Logpoint でタイムアウトを避けてデバッグ
他の言語: EnglishEspañolFrançaisDeutsch한국어Português中文
現代の開発ツール、特に IntelliJ IDEA には非常に多くのデバッグ機能があり、 何が使えるのかを思い出すだけでも少し時間がかかります。かなり特殊なユースケースでも、たいていは それに合ったツールがあります。
この記事では、デバッグを反対側から眺めて、基本に戻ってみたいと思います。 デバッグツールを学び始めたばかりで、学習投資から最大のリターンを得たいなら、 まず見るべき機能は logpoint です。
私が logpoint を気に入っているのは、
普通の println 文でデバッグするのと同じくらい単純で
(むしろそれより単純とも言えます)、
それでいてデバッグできる問題の範囲を大きく広げてくれるからです。問題によっては、logpoint が唯一の実用的な方法です。
それ以外の場合でも、logpoint は多くの時間と手間を省いてくれる便利さを提供します。
さらに IntelliJ IDEA 2026.2 では、logpoint に とても良い改善がいくつか入りました。 そのため、今は振り返るのにちょうどよいタイミングです。
問題の説明
ここに ミニクライアント/サーバーがあります。 通信には gRPC を使っています。サーバーにはバグがあり、一部の tenant に対して 誤った割引値を返します。
そこで通常のデバッグフローに従います。問題を再現し、サーバー内部の動作を観察できるようにし、 問題のあるリクエストを送り、どのように誤った結果が作られるのかを正確に見ていきます。
バグを再現する
別の環境で実行している状況をシミュレートするため、プロジェクトには
リスニングポートとデバッグポートを公開した Dockerfile が含まれています。
付属の GrpcQuoteServer in Docker 実行構成を使って起動することも、コマンドラインから直接起動することもできます。
docker build -t grpc-timeout .
docker run --rm -p 50051:50051 -p 5005:5005 grpc-timeout
次に、問題のあるリクエストには GrpcQuoteClientLoop 実行構成を使います。これはサーバーに
定期的に問い合わせます。これにより、手動でリクエストを送ることを忘れて、サーバー側で何が起きているかに集中できます。
サーバーとクライアントループの両方が実行されると、コンソールには次のように表示されます。
tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=100.00 USD source=live detail=region=emea, discount_bps=0
… 期待される結果は次のはずです。
tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=80.00 USD source=live detail=region=emea, discount_bps=2000
サーバーにアタッチする
サーバーはローカルの IntelliJ IDEA デバッグセッションから起動されているわけではありませんが、デバッガー接続を待ち受けているため、
付属の GrpcQuoteServer attach 実行構成を使ってアタッチできます。
ここで触れておく価値がある、よく誤解される点があります。 デバッガーにとっては、プロセスがローカルで動いているか、別の環境で動いているか、 リモートホスト上で動いているかに違いはありません。 どの場合でも通信は socket 経由で行われます。そのため、この演習は場所に関係なく 任意の Java プロセスのデバッグに有効です。
Logpoints
Logpoint は、 println 文でデバッグする場合と同じように、プログラムを停止せず、
必要な詳細だけをコンソールに記録します。 println 文とは違い、
アプリケーションを再ビルドしたり再デプロイしたりせずに変更できます。
すでに logpoint の設定方法を知っているかもしれませんが、IntelliJ IDEA 2026.2 からは新しく、より速い方法があります。
任意の 2 つの実行可能行の間にある gutter をクリックし、記録したい式を入力します。
出発点として、クエリ処理メソッドの先頭を使えます
( QuoteEndpoint:12 ):
hot path での重い計算には注意してください。それらは同じ VM で実行され、 魔法のように無料になるわけではありません。 2026.2 以降、IntelliJ IDEA は instrumentation によってデバッガー由来の overhead を取り除きますが、 重い logging 式の実行にはそれでも時間がかかることがあります。
受信するすべてのリクエストに対して、コンソールは次のように出力します。
EMEA JetBrains
リクエストループを動かしたまま、出力がバグを指し示すまで logpoint を少しずつ変更したり追加したりできます。 logpoint をさらに追加するか既存のものを更新し、新しいリクエストが来るたびにコンソールの新しいメッセージを見ます。
呼び出しチェーンをたどり、最初の疑いを消していくと、
discountBpsFor() メソッドにたどり着きます。
コンソールは、tenant 名が適切に正規化されていないことを示しています。
tenant = JetBrains expected: jetbrains
また、discount applied がないことから、正しい割引を適用するブロックに入っていないことも分かります。
tenant 名を正規化すればバグは直るはずです。
プロ向けの小技: あるコンソール出力が何によって生成されたのか分からないときは、 コンソール内のその行をクリックしてください。IntelliJ IDEA が関連するコードまたは logpoint に移動してくれます。

logging に println を使っている場合でも、IntelliJ IDEA のデバッガーでプロセスを実行していれば
このナビゲーションは機能します。
修正をテストする
ここまで来たので修正をテストしてみましょう。Logpoint はログ出力のためのもので、 プログラムを変更するためのものではありませんが、特定の修正がどう振る舞うかを試すこと自体は妨げられていません。
期待どおりに動きます。
EMEA JetBrains
jetbrains
discount applied
println ではだめなのか
おそらく、logpoint は少し便利な println 文のように見えると思います。 ある意味では正しいです。中核となるテクニックは同じだからです。 プログラムの動作に影響を与えない簡単な方法で probe を追加するということです。
Logpoint の方がよい選択になり得る理由はいくつかあります。
- コードを散らかしません。つまり後で片付ける手間がなく、うっかり production に混入させることもありません。
- 何をいつ記録するかを柔軟に指定できます。たとえば頻繁に起きるイベントだけをサンプリングしたい場合は、 このように設定します。
- 依存関係の内部に logging を差し込めます(この後すぐにやります)。
- このシナリオで特に重要なのは、高コストな redeploy を避けられることです。logging を追加するためだけにローカル Docker コンテナを再実行するのは、 小さな練習用プロジェクトなら許容できるかもしれませんが、大規模な実プロジェクトではそうはいかないことが多いです。
すると logpoint は println のようなものではなく、プロフェッショナルなデバッグツールのように感じられるようになります。
通常の breakpoint ではだめなのか
デバッガーを使うとき、多くの開発者は breakpoint に手を伸ばします。 しかしこのシナリオこそ、logpoint の方が向いている場面です。 これは単なる好みの問題ではありません。
通常の breakpoint を使うと何が起きるかを見てみましょう。
サーバーにアタッチしたら、 GrpcQuoteServer.java:55 に行 breakpoint を設定します。
ループからの次のリクエストでサーバーが停止します。
しかしプログラム状態を見て数ステップ進めると、キャンセル経路に入ってしまいます。
そこまで来ると、コードは別の実行経路を取った後です。 IntelliJ IDEA が、これから実行されないコード部分をグレー表示しているのが分かります。 問題のある状態を戻すには、リクエストを何度も送り直し、 timeout の時間内にデバッグ作業を収める必要があります。
これはクライアントがリモート呼び出しに deadline を設定しているためです。 一般的な HTTP/REST クライアントの timeout では、timeout はクライアント側の失敗を示すだけですが、 gRPC はクライアントの deadline をサーバーへ伝播できます。その結果、クライアントは応答待ちをやめるだけでなく、 サーバー側の作業そのものを実際にキャンセルできます。
一方、logpoint ならデバッガー UI で得られるのと同じ情報を、コンソールで観察できます。 重要なのは、これがサーバーを停止しないことです。そのため timeout を引き起こさずに必要な情報を取り出せます。
おまけ: timeout を取り除く
このシナリオに別のアプローチで取り組みたい場合は、次のようなデバッグ方法もあります。 この gRPC の例で問題だったのは timeout であり、 logpoint を使えば実行時にそれを取り除けます。
先ほど見たように、 logpoint の式は副作用を通じて実行中のプログラムを変更できます。 ここではこのテクニックを使って、受信リクエストを調整できます。
まず、timeout を設定しているライブラリメソッドを探します。これを行える場所はいくつかあります。
その一つが io.grpc.internal.ServerImpl.createContext です。
そのメソッドの中で、ローカル変数 timeoutNanos が代入された直後に
その値を書き換えられます。
この logpoint があると、gRPC timeout 値がリクエスト headers から読み取られるたびに、 すぐに 5 分の deadline に置き換えられます。つまり、サーバーを再び停止できるようになります。
reproducer のリクエストだけ timeout を延長し、それ以外ではサーバーを通常どおり動かしたい場合、 たとえば共有 staging インスタンスで動いている場合は、logpoint の中で複数行のロジックを使えます。
logpoint にコピーするコードは次のとおりです。
Metadata.Key<String> DEBUG_HEADER =
Metadata.Key.of("Debug", Metadata.ASCII_STRING_MARSHALLER);
String debugHeader = headers.get(DEBUG_HEADER);
if ("Debug".equals(debugHeader)) {
timeoutNanos = java.util.concurrent.TimeUnit.MINUTES.toNanos(5L);
return "Timeout reset";
}
この複数行の式はリクエスト headers を解析し、
Debug header を持つリクエスト(テストクライアントが追加します)だけ timeout を延長します。
他のリクエストは通常の deadline のままです。
if 分岐は "Timeout reset" を返し、その分岐を通ったことを確認できます。
Logpoint は オブジェクトのマーク (Mark Object) など、IntelliJ IDEA のより高度な機能と組み合わせることもできます。 興味があれば、この記事も見てみてください。
もちろん、この方法にはライブラリへの理解、またはそれを調べる時間が必要です。 どちらもなく、実行時の振る舞いをすばやく変えたいだけなら、付属の AI agent skill を使って AI agent に作業を任せることもできます。
まとめ
この記事では、logpoint が breakpoint や println logging よりも
シンプルで洗練された代替手段になるユースケースを見ました。ここでは次のことを行いました。
- リモートプロセスにアタッチする
- logpoint を使って実行時に診断情報を追加する
- 仮説の変化に合わせて logging を調整する
- アプリケーションコードを変えずに修正をテストする
- 実行中のサーバーの timeout を変更する
- ij-debugger AI agent skill を使う
何か新しいことを学び、次に println 文や breakpoint が邪魔になるときの
より良い選択肢を持てたなら幸いです。
シリーズの次の記事では、新しい logpoint をこれほど高速にしている基盤の仕組みである、
debugger instrumentation の動作を見ていきます。
よいデバッグを!