Mit Logpoints trotz Timeouts debuggen

Andere Sprachen: EnglishEspañolFrançais日本語한국어Português中文

Moderne Entwicklungstools, besonders IntelliJ IDEA, haben so viele Debugging-Funktionen, dass man schon eine Weile braucht, um sich nur daran zu erinnern, was alles verfügbar ist. Für praktisch jeden Nischenfall gibt es ein passendes Werkzeug.

In diesem Artikel möchte ich Debugging einmal von der anderen Seite angehen und auf die Grundlagen schauen. Wenn du Debugging-Tools gerade erst kennenlernst und den größten Nutzen aus deiner Lernzeit ziehen willst, dann sind Logpoints die Funktion, mit der du starten solltest.

Logpoints sind meine Favoriten, weil sie so einfach sind wie Debugging mit normalen println -Anweisungen (und wohl sogar einfacher), aber den Bereich der Probleme, die man debuggen kann, enorm erweitern: Für manche Probleme sind Logpoints der einzige praktische Ansatz. Und für den Rest bieten sie einen Komfort, der viel Zeit und Aufwand spart.

Außerdem haben Logpoints in IntelliJ IDEA 2026.2 einige sehr coole Verbesserungen bekommen, also ist es der perfekte Zeitpunkt für eine Zusammenfassung.

Problemstellung

Hier ist ein Mini-Client/Server, der gRPC für die Kommunikation verwendet. Der Server hat einen Bug, durch den er für einige Tenants falsche Rabattwerte zurückgibt.

Wir folgen also dem üblichen Debugging-Ablauf: das Problem reproduzieren, Einblick in die interne Arbeitsweise des Servers schaffen, eine problematische Anfrage senden und genau beobachten, wie das falsche Ergebnis entsteht.

Den Bug reproduzieren

Um die Ausführung in einer anderen Umgebung zu simulieren, enthält das Projekt ein Dockerfile mit freigegebenen Listening- und Debug-Ports. Du kannst es mit der mitgelieferten Run-Konfiguration GrpcQuoteServer in Docker starten oder direkt über die Kommandozeile:

docker build -t grpc-timeout .
docker run --rm -p 50051:50051 -p 5005:5005 grpc-timeout

Für eine problematische Anfrage verwendest du dann die Run-Konfiguration GrpcQuoteClientLoop , die den Server regelmäßig abfragt. So müssen wir keine Anfragen manuell senden und können uns stärker darauf konzentrieren, was auf dem Server passiert.

Wenn sowohl der Server als auch die Client-Schleife laufen, zeigt die Konsole Folgendes:

tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=100.00 USD source=live detail=region=emea, discount_bps=0

… statt des erwarteten Ergebnisses:

tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=80.00 USD source=live detail=region=emea, discount_bps=2000

An den Server anhängen

Der Server wird nicht aus einer lokalen IntelliJ-IDEA-Debug-Session gestartet, wartet aber auf Debugger-Verbindungen. Deshalb können wir uns trotzdem mit der bereitgestellten Run-Konfiguration GrpcQuoteServer attach an ihn anhängen.

Eine Sache verstehen viele falsch, und sie ist hier erwähnenswert: Für den Debugger gibt es keinen Unterschied, ob der Prozess lokal, in einer separaten Umgebung oder auf einem entfernten Host läuft. In jedem Fall läuft die Kommunikation über einen Socket. Die Übung ist also gültig für das Debuggen jedes Java-Prozesses, unabhängig davon, wo er ausgeführt wird.

Logpoints

Logpoints ähneln dem Debuggen mit println -Anweisungen darin, dass sie das Programm nicht anhalten, sondern nur die notwendigen Details in die Konsole schreiben. Im Gegensatz zu println -Anweisungen lassen sie sich ändern, ohne die Anwendung neu zu bauen oder neu bereitzustellen.

Vielleicht weißt du schon, wie man einen Logpoint setzt, aber seit IntelliJ IDEA 2026.2 gibt es einen neuen, schnelleren Weg. Klicke im Gutter zwischen zwei beliebigen ausführbaren Zeilen und gib den Ausdruck ein, den du protokollieren möchtest. Als Ausgangspunkt können wir den Anfang der Methode verwenden, die die Anfrage verarbeitet ( QuoteEndpoint:12 ):

IntelliJ IDEA editor showing an inline Log field for a logpoint expression between two Java statements
Info icon

Vorsicht mit schweren Berechnungen in Hot Paths. Sie werden in derselben VM ausgeführt und sind nicht magisch kostenlos. Seit 2026.2 entfernt IntelliJ IDEA den vom Debugger eingeführten Overhead durch Instrumentierung, aber schwere Logging-Ausdrücke können trotzdem Zeit kosten.

Für jede eingehende Anfrage gibt die Konsole nun Folgendes aus:

EMEA JetBrains

Jetzt, während die Anfrage-Schleife läuft, können wir Logpoints schrittweise ändern und hinzufügen, bis die Ausgabe auf den Bug zeigt. Füge einfach weitere Logpoints hinzu oder aktualisiere die vorhandenen und beobachte die neuen Nachrichten in der Konsole, während neue Anfragen eingehen.

Nachdem wir der Aufrufkette gefolgt sind und unsere ersten Vermutungen ausgeschlossen haben, kommen wir bei der Methode discountBpsFor() an:

IntelliJ IDEA editor showing logpoints inside the discountBpsFor method

Die Konsole zeigt, dass der Tenant-Name nicht richtig normalisiert wird:

tenant = JetBrains expected: jetbrains

Außerdem sagt uns das Fehlen von discount applied, dass der Block mit dem korrekten Rabatt nie betreten wird. Den Tenant-Namen zu normalisieren sollte den Bug beheben.

Info icon

Profi-Tipp: Wenn du dir nicht sicher bist, was eine bestimmte Konsolenausgabe erzeugt hat, klicke auf die Zeile in der Konsole. IntelliJ IDEA bringt dich dann zur relevanten Code-Stelle oder zum entsprechenden Logpoint:

IntelliJ IDEA debug console showing logpoint output with an Open popup that navigates back to the code

Auch wenn du printlns zum Logging verwendest, funktioniert die Navigation, solange der Prozess mit dem Debugger von IntelliJ IDEA läuft.

Den Fix testen

Testen wir den Fix, solange wir hier sind. Logpoints sind zum Protokollieren gedacht, nicht zum Verändern des Programms, aber eigentlich hindert uns nichts daran zu testen, wie sich ein bestimmter Fix verhalten würde:

IntelliJ IDEA editor showing a logpoint that normalizes the tenant name before the discount check

Funktioniert wie erwartet:

EMEA JetBrains
jetbrains
discount applied

Warum nicht printlns

Du denkst wahrscheinlich, dass Logpoints wie angenehmere println-Anweisungen aussehen. Das stimmt in gewisser Weise, denn die Kerntechnik ist dieselbe: Sonden auf einfache Weise hinzufügen, ohne zu verändern, wie das Programm läuft.

Es gibt mehrere Gründe, warum Logpoints die bessere Wahl sein können:

An diesem Punkt fühlen sich Logpoints nicht mehr wie printlns an, sondern wie ein professionelles Debugging-Werkzeug.

Warum nicht reguläre Breakpoints

Beim Debuggen greifen die meisten Entwickler zu Breakpoints. Aber genau in diesem Szenario sind Logpoints die bessere Wahl, und das ist nicht nur Geschmackssache.

Schauen wir uns an, was passiert, wenn wir normale Breakpoints verwenden. Nachdem wir uns an den Server angehängt haben, setzen wir einen Line-Breakpoint bei GrpcQuoteServer.java:55 . Die nächste Anfrage aus der Schleife suspendiert den Server:

IntelliJ IDEA debugger paused at a breakpoint in the getQuote method of GrpcQuoteServer.java

Aber nachdem wir den Programmzustand angesehen und ein paar Schritte ausgeführt haben, landen wir im Abbruchpfad:

IntelliJ IDEA debugger paused on a Status.CANCELLED exception while the remaining discount calculation code is greyed out

Sobald wir dort sind, hat der Code einen anderen Ausführungspfad genommen. Man sieht, dass IntelliJ IDEA Codebereiche ausgegraut hat, die nicht mehr ausgeführt werden. Um den problematischen Zustand zurückzubekommen, müssen wir eine Anfrage nach der anderen senden und unsere Debugging-Arbeit in das Timeout-Fenster pressen.

Das passiert, weil unser Client für den entfernten Aufruf eine Deadline setzt. Anders als bei einem typischen HTTP/REST-Client-Timeout, bei dem das Timeout nur einen Fehler auf dem Client signalisiert, kann gRPC die Deadline des Clients an den Server weitergeben. Dadurch kann der Client die serverseitige Arbeit tatsächlich abbrechen, anstatt nur nicht mehr auf eine Antwort zu warten.

Andererseits liefern Logpoints uns dieselben Informationen, die wir in der Debugger-UI sehen würden, nur dass wir sie in der Konsole beobachten. Wichtig ist: Der Server wird dabei nicht suspendiert, also können wir die nötigen Informationen gewinnen, ohne das Timeout auszulösen.

Bonus: Timeout entfernen

Wenn du für dieses Szenario lieber einen alternativen Ansatz möchtest, gibt es noch eine weitere Möglichkeit. In unserem gRPC-Beispiel war der problematische Teil das Timeout, und wir können es zur Laufzeit mit … Logpoints entfernen!

Wie du gerade gesehen hast, können Logpoint-Ausdrücke das laufende Programm über Seiteneffekte verändern. Hier können wir diese Technik nutzen, um die eingehende Anfrage anzupassen.

Zuerst suchen wir die Bibliotheksmethode, die das Timeout setzt. Es gibt mehrere Stellen, an denen das möglich ist. Eine davon ist io.grpc.internal.ServerImpl.createContext :

IntelliJ IDEA Search Everywhere dialog showing the io.grpc.internal.ServerImpl.createContext symbol

Darin können wir den Wert der lokalen Variable timeoutNanos direkt nachdem er zugewiesen wurde, umschreiben:

IntelliJ IDEA editor showing a logpoint that changes timeoutNanos in ServerImpl.createContext

Mit diesem Logpoint wird jedes Mal, wenn der gRPC-Timeout-Wert aus den Request-Headers gelesen wird, sofort eine Deadline von fünf Minuten eingesetzt. Das bedeutet, dass wir den Server wieder suspendieren können.

Wenn du das Timeout nur für die Reproducer-Anfragen verlängern und den Server ansonsten normal weiterlaufen lassen willst, zum Beispiel auf einer gemeinsam genutzten Staging-Instanz, kannst du mehrzeilige Logik in einem Logpoint verwenden:

IntelliJ IDEA editor showing a multiline logpoint that extends timeoutNanos only for requests with a Debug header

Hier ist der Code, den du in den Logpoint kopieren kannst:

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";
}

Der mehrzeilige Ausdruck parst die Request-Headers und verlängert das Timeout nur für Anfragen mit dem Debug-Header (den unsere Test-Clients hinzufügen). Andere Anfragen behalten ihre normale Deadline. Der if -Zweig gibt "Timeout reset" zurück und bestätigt damit, dass der Zweig besucht wurde.


Info icon

Du kannst Logpoints mit fortgeschritteneren IntelliJ-IDEA-Funktionen kombinieren, zum Beispiel mit Mark Object. Wenn das interessant klingt, schau dir diesen Artikel an.

Natürlich setzt diese Methode Vertrautheit mit der Bibliothek voraus oder Zeit, sie zu erkunden. Wenn du beides nicht hast und nur schnell das Laufzeitverhalten ändern willst, kannst du die Aufgabe mit dem mitgelieferten AI-Agent-Skill an einen KI-Agenten delegieren:

OpenAI Codex terminal showing a prompt to add a logpoint that resets incoming request timeouts to five minutes IntelliJ IDEA AI Agents terminal showing a non-suspending logpoint added to GrpcQuoteServer.java

Fazit

In diesem Artikel haben wir uns einen Fall angesehen, in dem Logpoints eine einfachere und elegantere Alternative zu Breakpoints oder println -Logging bieten. Wir haben:

Ich hoffe, du hast etwas Neues gelernt und hast jetzt eine bessere Option für das nächste Mal, wenn println -Anweisungen oder Breakpoints im Weg stehen. Im nächsten Post der Serie sehen wir uns an, wie Debugger-Instrumentierung funktioniert, also den Mechanismus, der die neuen Logpoints so schnell macht.

Viel Erfolg beim Debugging!

all posts ->