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

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

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

IntelliJ IDEA editor showing logpoints inside the discountBpsFor method

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.

Info icon

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:

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

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:

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

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:

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:

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

Pero después de mirar el estado del programa y avanzar un par de pasos, acabamos en la ruta de cancelación:

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

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 :

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

Dentro de ese método, podemos reescribir el valor de la variable local timeoutNanos justo después de que se haya asignado:

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

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:

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

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.


Info icon

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:

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

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:

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!

all posts ->