Depurar pese a los timeouts con logpoints
Otros idiomas: EnglishFrançaisDeutsch日本語한국어Português中文
Las herramientas de desarrollo modernas, especialmente IntelliJ IDEA, tienen tantas funciones de depuración que lleva un tiempo incluso recordar todo lo que está disponible. Para casi cualquier caso de uso de nicho, hay una herramienta adecuada para el trabajo.
En este artículo quiero abordar la depuración desde el otro extremo y mirar lo básico. Si estás empezando con las herramientas de depuración y quieres el mayor retorno por el tiempo que inviertas en aprender, la función por la que deberías empezar son los logpoints.
Los logpoints son mis favoritos porque, aunque son tan simples como depurar con
sentencias println corrientes
(y posiblemente más simples),
amplían enormemente el rango de problemas que puedes depurar: para algunos problemas, los logpoints son el único enfoque práctico.
Y para el resto, aportan una comodidad que ahorra mucho tiempo y esfuerzo.
Además, en IntelliJ IDEA 2026.2, los logpoints recibieron algunas mejoras muy interesantes, así que es un momento perfecto para repasarlos.
Planteamiento del problema
Aquí hay un mini cliente/servidor que usa gRPC para la comunicación. El servidor tiene un bug que hace que devuelva valores de descuento incorrectos para algunos tenants.
Así que seguiremos el flujo habitual de depuración: reproducir el problema, crear visibilidad sobre el funcionamiento interno del servidor, enviar una solicitud problemática y observar exactamente cómo produce el resultado equivocado.
Reproducir el bug
Para simular la ejecución en otro entorno, el proyecto incluye un Dockerfile
con los puertos de escucha y depuración expuestos.
Puedes lanzarlo usando la configuración de ejecución GrpcQuoteServer in Docker incluida, o directamente desde la línea de comandos:
docker build -t grpc-timeout .
docker run --rm -p 50051:50051 -p 5005:5005 grpc-timeout
Luego, para una solicitud problemática, usa la configuración de ejecución GrpcQuoteClientLoop , que consultará
periódicamente al servidor. Esto nos permite olvidarnos de enviar solicitudes a mano y concentrarnos mejor en lo que ocurre en el servidor.
Cuando tanto el servidor como el bucle del cliente están en ejecución, la consola muestra lo siguiente:
tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=100.00 USD source=live detail=region=emea, discount_bps=0
… en lugar de lo esperado:
tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=80.00 USD source=live detail=region=emea, discount_bps=2000
Adjuntarse al servidor
El servidor no se lanza desde una sesión local de depuración de IntelliJ IDEA, pero escucha conexiones del depurador, así que
aún podemos adjuntarnos a él usando la configuración de ejecución GrpcQuoteServer attach incluida.
Hay algo que mucha gente entiende mal y que vale la pena mencionar aquí: para el depurador, no hay diferencia entre que el proceso se ejecute localmente, en un entorno separado o en un host remoto. En cualquier caso, la comunicación ocurre a través de un socket, así que este ejercicio sirve para depurar cualquier proceso Java sin importar dónde se ejecute.
Logpoints
Los logpoints son parecidos a depurar con sentencias println porque no suspenden el programa
y solo registran en la consola los detalles necesarios. A diferencia de las sentencias println ,
se pueden cambiar sin recompilar ni volver a desplegar la aplicación.
Puede que ya sepas cómo crear un logpoint, pero desde IntelliJ IDEA 2026.2 hay una forma nueva y más rápida.
Haz clic en el gutter entre dos líneas ejecutables cualquiera e introduce la expresión que quieres registrar.
Como punto de partida, podemos usar el comienzo del método que maneja la consulta
( QuoteEndpoint:12 ):
Cuidado con los cálculos pesados en rutas calientes. Se ejecutan en la misma VM y no son mágicamente gratis. Desde 2026.2, IntelliJ IDEA elimina mediante instrumentación el overhead introducido por el depurador, pero las expresiones de logging pesadas pueden seguir tardando en ejecutarse.
Para cada solicitud entrante, la consola ahora imprime:
EMEA JetBrains
Ahora, con el bucle de solicitudes en ejecución, podemos cambiar y añadir logpoints progresivamente hasta que la salida apunte al bug. Simplemente añade más logpoints o actualiza los existentes y observa los nuevos mensajes en la consola a medida que llegan nuevas solicitudes.
Después de seguir la cadena de llamadas y descartar nuestras sospechas iniciales,
llegamos al método discountBpsFor() :
La consola señala que el nombre del tenant no se está normalizando correctamente:
tenant = JetBrains expected: jetbrains
Además, la ausencia de discount applied nos dice que nunca se entra en el bloque con el descuento correcto.
Normalizar el nombre del tenant debería corregir el bug.
Consejo: cuando tengas dudas sobre qué produjo una salida concreta en la consola, haz clic en esa línea y IntelliJ IDEA te llevará al fragmento de código o logpoint correspondiente:

Incluso si usas printlns para registrar información, la navegación funcionará siempre que estés ejecutando
el proceso con el depurador de IntelliJ IDEA.
Probar la corrección
Probemos la corrección mientras estamos aquí. Los logpoints están pensados para registrar información, no para modificar el programa, pero en realidad nada nos impide probar cómo se comportaría una corrección concreta:
Funciona como se esperaba:
EMEA JetBrains
jetbrains
discount applied
Por qué no printlns
Probablemente estés pensando que los logpoints se parecen mucho a unos println más cómodos. Eso es correcto, en cierto sentido, porque la técnica central es la misma: añadir sondas de una forma sencilla que no afecte a cómo se ejecuta el programa.
Hay varias razones por las que los logpoints pueden ser una mejor opción:
- No ensucian el código, lo que significa que no gastarás esfuerzo limpiándolo (y no commitearás uno perdido a producción).
- Son flexibles sobre qué registran y cuándo. Por ejemplo, si solo quieres muestrear eventos frecuentes, así es como lo configuras.
- Te permiten insertar logging dentro de tus dependencias (lo haremos enseguida).
- Lo más importante para este escenario: pueden ahorrarte redespliegues costosos. Volver a ejecutar un contenedor Docker local solo para añadir logging en un proyecto de juguete puede ser aceptable, pero en proyectos grandes del mundo real a menudo no lo es.
En ese punto, los logpoints dejan de parecer printlns y empiezan a sentirse como una herramienta de depuración profesional.
Por qué no breakpoints normales
Al usar el depurador, la mayoría de los desarrolladores recurren a breakpoints. Pero este escenario concreto es exactamente donde los logpoints encajan mejor, y no es solo una cuestión de preferencia.
Veamos qué pasa si usamos breakpoints normales.
Después de adjuntarnos al servidor, establece un breakpoint de línea en GrpcQuoteServer.java:55 .
La siguiente solicitud del bucle suspende el servidor:
Pero después de mirar el estado del programa y avanzar un par de pasos, acabamos en la ruta de cancelación:
Una vez que estamos ahí, el código ha tomado una ruta de ejecución distinta. Puedes ver que IntelliJ IDEA ha atenuado las partes del código que no se van a ejecutar. Para recuperar el estado problemático, tenemos que enviar solicitudes una tras otra y encajar nuestro trabajo de depuración dentro de la ventana del timeout.
Esto ocurre porque nuestro cliente establece una deadline para la llamada remota. A diferencia del timeout típico de un cliente HTTP/REST, donde el timeout solo señala el fallo en el cliente, gRPC puede propagar la deadline del cliente al servidor. Como resultado, el cliente puede cancelar realmente el trabajo del lado del servidor, no solo dejar de esperar una respuesta.
Por otro lado, los logpoints nos dan la misma información que obtendríamos en la UI del depurador, excepto que la observamos en la consola. Lo importante es que esto no suspende el servidor, así que podemos extraer la información que necesitamos sin provocar el timeout.
Bonus: eliminar el timeout
Si prefieres un enfoque alternativo para este escenario, aquí tienes otra forma de depurarlo. En nuestro ejemplo de gRPC, la parte problemática era el timeout, y podemos eliminarlo en tiempo de ejecución usando… logpoints.
Como acabamos de ver, las expresiones de los logpoints pueden modificar el programa en ejecución mediante efectos secundarios. Aquí podemos usar esta técnica para ajustar la solicitud entrante.
Primero, encuentra el método de la biblioteca que establece el timeout. Hay varios lugares donde podemos hacerlo.
Uno de ellos es io.grpc.internal.ServerImpl.createContext :
Dentro de ese método, podemos reescribir el valor de la variable local timeoutNanos justo después de que
se haya asignado:
Con este logpoint activo, cada vez que el valor de timeout de gRPC se lee desde los headers de la solicitud, se reemplaza inmediatamente por una deadline de cinco minutos. Esto significa que podemos volver a suspender el servidor.
Si quieres extender el timeout solo para las solicitudes del reproducer y mantener el servidor funcionando como siempre, por ejemplo en una instancia compartida de staging, puedes usar lógica multilínea dentro de un logpoint:
Aquí está el código para copiar en el 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";
}
La expresión multilínea analiza los headers de la solicitud y extiende el timeout solo para las solicitudes con
el header Debug (que nuestros clientes de prueba añaden). Las demás solicitudes conservan la deadline normal.
La rama if devuelve "Timeout reset", confirmando cuándo se visitó la rama.
Puedes combinar los logpoints con funciones más avanzadas de IntelliJ IDEA, como Mark Object. Si esto te parece interesante, quizá quieras ver este artículo.
Por supuesto, este método requiere familiaridad con la biblioteca o tiempo para explorarla. Si no tienes ninguna de las dos cosas y solo quieres cambiar rápidamente el comportamiento en tiempo de ejecución, puedes delegar la tarea a un agente de IA usando la skill de agente de IA incluida:
Conclusión
En este artículo, vimos un caso de uso en el que los logpoints ofrecen una alternativa más simple y elegante
a los breakpoints o al logging con println . Hicimos lo siguiente:
- nos adjuntamos a un proceso remoto
- añadimos diagnósticos en tiempo de ejecución usando logpoints
- ajustamos el logging a medida que cambiaba nuestra hipótesis
- probamos la corrección sin cambiar el código de la aplicación
- cambiamos el timeout de un servidor en ejecución
- usamos la skill de agente de IA ij-debugger
Espero que hayas aprendido algo nuevo y que ahora tengas una mejor opción para la próxima vez que
las sentencias println o los breakpoints se interpongan.
En el siguiente post de la serie, veremos cómo funciona la instrumentación del depurador,
que es el mecanismo subyacente que hace que los nuevos logpoints sean tan rápidos.
¡Feliz depuración!