Déboguer malgré les timeouts avec des logpoints
Autres langues : EnglishEspañolDeutsch日本語한국어Português中文
Les outils de développement modernes, en particulier IntelliJ IDEA, ont tellement de fonctionnalités de débogage qu’il faut un moment rien que pour se rappeler ce qui existe. Pour presque n’importe quel cas d’usage un peu particulier, il y a un outil adapté.
Dans cet article, je veux aborder le débogage par l’autre bout et revenir aux bases. Si vous découvrez les outils de débogage et voulez le meilleur retour sur votre temps d’apprentissage, le meilleur point de départ, ce sont les logpoints.
Les logpoints sont mes préférés parce qu’ils sont aussi simples que le débogage avec
des instructions println ordinaires
(et même plus simples, selon le point de vue),
mais ils élargissent énormément le type de problèmes que l’on peut déboguer : pour certains problèmes, les logpoints sont la seule approche pratique.
Et pour le reste, ils apportent un confort qui économise beaucoup de temps et d’efforts.
En plus, dans IntelliJ IDEA 2026.2, les logpoints ont reçu quelques améliorations très intéressantes, c’est donc le bon moment pour faire le point.
Énoncé du problème
Voici un mini client/serveur qui utilise gRPC pour communiquer. Le serveur contient un bug qui lui fait renvoyer des valeurs de remise incorrectes pour certains tenants.
Nous allons donc suivre le flux de débogage habituel : reproduire le problème, rendre visibles les mécanismes internes du serveur, envoyer une requête problématique et observer exactement comment elle produit le mauvais résultat.
Reproduire le bug
Pour simuler une exécution dans un autre environnement, le projet fournit un Dockerfile
avec les ports d’écoute et de débogage exposés.
Vous pouvez le lancer avec la configuration d’exécution GrpcQuoteServer in Docker fournie, ou directement en ligne de commande :
docker build -t grpc-timeout .
docker run --rm -p 50051:50051 -p 5005:5005 grpc-timeout
Ensuite, pour une requête problématique, utilisez la configuration d’exécution GrpcQuoteClientLoop , qui interrogera
périodiquement le serveur. Cela nous évite d’envoyer les requêtes à la main et nous permet de mieux nous concentrer sur ce qui se passe côté serveur.
Quand le serveur et la boucle cliente sont tous les deux en cours d’exécution, la console affiche ceci :
tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=100.00 USD source=live detail=region=emea, discount_bps=0
… au lieu du résultat attendu :
tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=80.00 USD source=live detail=region=emea, discount_bps=2000
S’attacher au serveur
Le serveur n’est pas lancé depuis une session de débogage locale d’IntelliJ IDEA, mais il écoute les connexions du débogueur, donc
nous pouvons quand même nous y attacher avec la configuration d’exécution GrpcQuoteServer attach fournie.
Une chose que beaucoup de gens comprennent mal, et qui mérite d’être mentionnée ici : pour le débogueur, il n’y a aucune différence entre un processus qui tourne localement, dans un environnement séparé ou sur un hôte distant. Dans tous les cas, la communication passe par un socket. Cet exercice est donc valable pour déboguer n’importe quel processus Java, quel que soit l’endroit où il s’exécute.
Logpoints
Les logpoints ressemblent au débogage avec des instructions println dans le sens où ils ne suspendent pas le programme
et écrivent seulement les détails nécessaires dans la console. Contrairement aux instructions println ,
ils peuvent être modifiés sans reconstruire ni redéployer l’application.
Vous savez peut-être déjà définir un logpoint, mais depuis IntelliJ IDEA 2026.2 il existe une nouvelle façon plus rapide.
Cliquez dans le gutter entre deux lignes exécutables quelconques et saisissez l’expression à journaliser.
Comme point de départ, nous pouvons utiliser le début de la méthode qui traite la requête
( QuoteEndpoint:12 ) :
Attention aux calculs lourds dans les chemins chauds. Ils s’exécutent dans la même VM et ne deviennent pas gratuits par magie. Depuis 2026.2, IntelliJ IDEA supprime par instrumentation l’overhead introduit par le débogueur, mais les expressions de logging lourdes peuvent toujours prendre du temps à s’exécuter.
Pour chaque requête entrante, la console affiche maintenant :
EMEA JetBrains
Maintenant, avec la boucle de requêtes en cours, nous pouvons modifier et ajouter progressivement des logpoints jusqu’à ce que la sortie pointe vers le bug. Ajoutez simplement d’autres logpoints, ou mettez à jour ceux qui existent, puis observez les nouveaux messages dans la console au fil des nouvelles requêtes.
Après avoir suivi la chaîne d’appels et écarté nos premières hypothèses,
nous arrivons à la méthode discountBpsFor() :
La console indique que le nom du tenant n’est pas correctement normalisé :
tenant = JetBrains expected: jetbrains
De plus, l’absence de discount applied nous dit que le bloc avec la bonne remise n’est jamais atteint.
Normaliser le nom du tenant devrait corriger le bug.
Astuce : en cas de doute sur ce qui a produit une sortie précise dans la console, cliquez sur la ligne et IntelliJ IDEA vous mènera au morceau de code ou au logpoint correspondant :

Même si vous utilisez des println pour journaliser, la navigation fonctionnera tant que le processus
est lancé avec le débogueur d’IntelliJ IDEA.
Tester le correctif
Testons le correctif tant que nous sommes ici. Les logpoints sont faits pour journaliser, pas pour modifier le programme, mais rien ne nous empêche vraiment de tester le comportement d’un correctif précis :
Cela fonctionne comme prévu :
EMEA JetBrains
jetbrains
discount applied
Pourquoi pas des println
Vous pensez probablement que les logpoints ressemblent beaucoup à des instructions println plus agréables. C’est vrai, dans un sens, parce que la technique de base est la même : ajouter des sondes simplement, sans changer la façon dont le programme s’exécute.
Il y a plusieurs raisons pour lesquelles les logpoints peuvent être un meilleur choix :
- Ils n’encombrent pas le code, ce qui évite de passer du temps à nettoyer (et d’en laisser passer un en production par accident).
- Ils sont flexibles sur ce qu’ils journalisent et quand. Par exemple, si vous voulez seulement échantillonner des événements fréquents, voici comment le configurer.
- Ils permettent d’insérer du logging dans vos dépendances (nous allons le faire dans un instant).
- Surtout dans ce scénario, ils peuvent vous éviter des redéploiements coûteux. Relancer un conteneur Docker local juste pour ajouter du logging dans un projet jouet peut passer, mais dans de gros projets réels, ce n’est souvent pas le cas.
À ce stade, les logpoints cessent de ressembler à des println et commencent à ressembler à un outil de débogage professionnel.
Pourquoi pas les breakpoints ordinaires
Quand ils utilisent le débogueur, la plupart des développeurs se tournent vers les breakpoints. Mais ce scénario précis est exactement le genre de cas où les logpoints conviennent mieux, et ce n’est pas seulement une question de préférence.
Voyons ce qui se passe si nous utilisons des breakpoints ordinaires.
Après nous être attachés au serveur, définissez un breakpoint de ligne sur GrpcQuoteServer.java:55 .
La requête suivante de la boucle suspend le serveur :
Mais après avoir regardé l’état du programme et avancé de quelques pas, nous nous retrouvons dans le chemin d’annulation :
Une fois arrivés là, le code a pris un autre chemin d’exécution. On voit qu’IntelliJ IDEA a grisé les parties du code qui ne seront pas exécutées. Pour retrouver l’état problématique, il faut renvoyer des requêtes les unes après les autres et faire tenir notre travail de débogage dans la fenêtre du timeout.
Cela arrive parce que notre client définit une deadline pour l’appel distant. Contrairement au timeout typique d’un client HTTP/REST, où le timeout signale seulement un échec côté client, gRPC peut propager la deadline du client jusqu’au serveur. Résultat : le client peut vraiment annuler le travail côté serveur, et pas seulement arrêter d’attendre une réponse.
À l’inverse, les logpoints nous donnent la même information que dans l’interface du débogueur, sauf que nous l’observons dans la console. Surtout, cela ne suspend pas le serveur, nous pouvons donc extraire l’information nécessaire sans déclencher le timeout.
Bonus : supprimer le timeout
Si vous préférez une autre approche pour ce scénario, voici une autre façon de le déboguer. Dans notre exemple gRPC, la partie problématique était le timeout, et nous pouvons le supprimer à l’exécution avec… des logpoints.
Comme vous venez de le voir, les expressions de logpoint peuvent modifier le programme en cours d’exécution au moyen d’effets de bord. Ici, nous pouvons utiliser cette technique pour ajuster la requête entrante.
Commencez par trouver la méthode de bibliothèque qui définit le timeout. Il y a plusieurs endroits où le faire.
L’un d’eux est io.grpc.internal.ServerImpl.createContext :
Dans cette méthode, nous pouvons réécrire la valeur de la variable locale timeoutNanos juste après
son affectation :
Avec ce logpoint en place, chaque fois que la valeur de timeout gRPC est lue depuis les headers de la requête, elle est immédiatement remplacée par une deadline de cinq minutes. Cela veut dire que nous pouvons de nouveau suspendre le serveur.
Si vous voulez étendre le timeout seulement pour les requêtes de reproduction et laisser le serveur fonctionner normalement, par exemple sur une instance de staging partagée, vous pouvez utiliser une logique multilignes dans un logpoint :
Voici le code à copier dans le 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";
}
L’expression multilignes analyse les headers de la requête et étend le timeout seulement pour les requêtes avec
le header Debug (que nos clients de test ajoutent). Les autres requêtes gardent leur deadline normale.
La branche if renvoie "Timeout reset", ce qui confirme quand cette branche a été visitée.
Vous pouvez combiner les logpoints avec des fonctionnalités plus avancées d’IntelliJ IDEA, comme Mark Object. Si cela vous intéresse, vous pouvez lire cet article.
Bien sûr, cette méthode demande de connaître la bibliothèque ou de prendre le temps de l’explorer. Si vous n’avez ni l’un ni l’autre et voulez simplement changer rapidement le comportement à l’exécution, vous pouvez déléguer la tâche à un agent IA avec le skill d’agent IA fourni :
Conclusion
Dans cet article, nous avons examiné un cas où les logpoints offrent une alternative plus simple et plus élégante
aux breakpoints ou au logging avec println . Nous avons :
- attaché le débogueur à un processus distant
- ajouté des diagnostics à l’exécution avec des logpoints
- ajusté le logging à mesure que notre hypothèse changeait
- testé le correctif sans modifier le code de l’application
- changé le timeout d’un serveur en cours d’exécution
- utilisé le skill d’agent IA ij-debugger
J’espère que vous avez appris quelque chose de nouveau et que vous avez maintenant une meilleure option pour la prochaine fois
où les instructions println ou les breakpoints vous gêneront.
Dans le prochain article de la série, nous verrons comment fonctionne l’instrumentation du débogueur,
le mécanisme sous-jacent qui rend les nouveaux logpoints aussi rapides.
Bon débogage !