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 ):
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:
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.
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:

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:
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:
- Sie vermüllen den Code nicht, was bedeutet, dass du keine Zeit mit Aufräumen verbringst (und nicht versehentlich einen in die Produktion committest).
- Sie sind flexibel darin, was und wann sie protokollieren. Wenn du zum Beispiel häufige Ereignisse nur stichprobenartig erfassen willst, konfigurierst du es so.
- Sie erlauben es, Logging in deine Abhängigkeiten einzufügen (das machen wir gleich).
- Besonders wichtig in diesem Szenario: Sie können teure Redeploys sparen. Einen lokalen Docker-Container nur für mehr Logging neu zu starten mag in einem Spielzeugprojekt akzeptabel sein, in großen echten Projekten ist es das oft nicht.
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:
Aber nachdem wir den Programmzustand angesehen und ein paar Schritte ausgeführt haben, landen wir im Abbruchpfad:
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 :
Darin können wir den Wert der lokalen Variable timeoutNanos direkt nachdem er
zugewiesen wurde, umschreiben:
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:
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.
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:
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:
- uns an einen Remote-Prozess angehängt
- zur Laufzeit Diagnoseausgaben mit Logpoints hinzugefügt
- das Logging angepasst, während sich unsere Hypothese änderte
- den Fix getestet, ohne den Anwendungscode zu ändern
- das Timeout eines laufenden Servers geändert
- den ij-debugger-AI-Agent-Skill verwendet
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!