Depurar apesar dos timeouts com logpoints

Outras línguas: EnglishEspañolFrançaisDeutsch日本語한국어中文

As ferramentas modernas de desenvolvimento, especialmente o IntelliJ IDEA, têm tantos recursos de depuração que leva um tempo até para lembrar o que está disponível. Para praticamente qualquer caso de uso específico, existe uma ferramenta adequada para o trabalho.

Neste artigo, quero abordar a depuração pelo outro lado e olhar para o básico. Se você está começando com ferramentas de depuração e quer o maior retorno pelo tempo investido em aprendizado, o recurso pelo qual vale começar são os logpoints.

Logpoints são os meus favoritos porque são tão simples quanto depurar com instruções println comuns (e talvez até mais simples), mas ampliam muito o conjunto de problemas que você consegue depurar: para alguns problemas, logpoints são a única abordagem prática. E, para o restante, eles oferecem uma conveniência que economiza bastante tempo e esforço.

Além disso, no IntelliJ IDEA 2026.2, os logpoints receberam algumas melhorias muito interessantes, então este é um ótimo momento para recapitular.

Definição do problema

Aqui está um mini cliente/servidor que usa gRPC para comunicação. O servidor tem um bug que faz com que ele retorne valores de desconto incorretos para alguns tenants.

Então vamos seguir o fluxo habitual de depuração: reproduzir o problema, criar visibilidade sobre o funcionamento interno do servidor, enviar uma requisição problemática e observar exatamente como ele produz o resultado errado.

Reproduzir o bug

Para simular a execução em outro ambiente, o projeto inclui um Dockerfile com as portas de escuta e de depuração expostas. Você pode iniciá-lo usando a configuração de execução GrpcQuoteServer in Docker fornecida ou diretamente pela linha de comando:

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

Depois, para uma requisição problemática, use a configuração de execução GrpcQuoteClientLoop , que vai consultar o servidor periodicamente. Isso nos permite esquecer o envio manual de requisições e focar melhor no que acontece no servidor.

Quando tanto o servidor quanto o loop do cliente estão em execução, o console mostra o seguinte:

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

… em vez do esperado:

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

Anexar ao servidor

O servidor não é iniciado a partir de uma sessão local de depuração do IntelliJ IDEA, mas ele escuta conexões do depurador, então ainda podemos anexar a ele usando a configuração de execução GrpcQuoteServer attach fornecida.

Uma coisa que muita gente entende errado, e que vale mencionar aqui: para o depurador, não há diferença entre o processo rodar localmente, em um ambiente separado ou em um host remoto. De qualquer forma, a comunicação acontece por um socket, então nosso exercício é válido para depurar qualquer processo Java, independentemente de onde ele esteja rodando.

Logpoints

Logpoints são parecidos com depurar usando instruções println porque não suspendem o programa e apenas registram os detalhes necessários no console. Ao contrário de instruções println , eles podem ser alterados sem recompilar ou reimplantar a aplicação.

Talvez você já saiba como definir um logpoint, mas desde o IntelliJ IDEA 2026.2 existe uma forma nova e mais rápida. Clique no gutter entre quaisquer duas linhas executáveis e informe a expressão que você quer registrar. Como ponto de partida, podemos usar o início do método que trata a consulta ( QuoteEndpoint:12 ):

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

Cuidado com computações pesadas em caminhos quentes. Elas são executadas na mesma VM e não são magicamente gratuitas. Desde a versão 2026.2, o IntelliJ IDEA remove por instrumentação o overhead introduzido pelo depurador, mas expressões de logging pesadas ainda podem levar tempo para executar.

Para cada requisição recebida, o console agora imprime:

EMEA JetBrains

Agora, com o loop de requisições rodando, podemos alterar e adicionar logpoints progressivamente até que a saída aponte para o bug. Basta adicionar mais logpoints ou atualizar os existentes e observar as novas mensagens no console conforme novas requisições chegam.

Depois de seguir a cadeia de chamadas e descartar nossas suspeitas iniciais, chegamos ao método discountBpsFor() :

IntelliJ IDEA editor showing logpoints inside the discountBpsFor method

O console mostra que o nome do tenant não está sendo normalizado corretamente:

tenant = JetBrains expected: jetbrains

Além disso, a ausência de discount applied nos diz que o bloco com o desconto correto nunca é executado. Normalizar o nome do tenant deve corrigir o bug.

Info icon

Dica profissional: quando estiver em dúvida sobre o que produziu uma saída específica no console, clique na linha e o IntelliJ IDEA levará você ao trecho de código ou logpoint correspondente:

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

Mesmo se você estiver usando printlns para logging, a navegação funcionará desde que o processo esteja rodando com o depurador do IntelliJ IDEA.

Testar a correção

Vamos testar a correção enquanto estamos aqui. Logpoints foram feitos para registrar informações, não para modificar o programa, mas nada realmente nos impede de testar como uma correção específica se comportaria:

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

Funciona como esperado:

EMEA JetBrains
jetbrains
discount applied

Por que não printlns

Você provavelmente está pensando que logpoints parecem muito com printlns mais elegantes. Isso está correto, em certo sentido, porque a técnica central é a mesma: adicionar sondas de forma simples, sem afetar como o programa executa.

Há vários motivos pelos quais logpoints podem ser uma escolha melhor:

Nesse ponto, logpoints deixam de parecer printlns e passam a parecer uma ferramenta profissional de depuração.

Por que não breakpoints comuns

Ao usar o depurador, a maioria dos desenvolvedores recorre a breakpoints. Mas este cenário específico é exatamente onde logpoints se encaixam melhor, e isso não é apenas uma questão de preferência.

Vamos ver o que acontece se usarmos breakpoints comuns. Depois de anexar ao servidor, defina um breakpoint de linha em GrpcQuoteServer.java:55 . A próxima requisição do loop suspende o servidor:

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

Mas, depois de olhar o estado do programa e avançar alguns passos, acabamos no caminho de cancelamento:

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

Quando chegamos ali, o código já tomou outro caminho de execução. Você pode ver que o IntelliJ IDEA deixou em cinza partes do código que não serão executadas. Para trazer o estado problemático de volta, precisamos enviar uma requisição depois da outra e encaixar nosso trabalho de depuração dentro da janela de timeout.

Isso acontece porque nosso cliente define um deadline para a chamada remota. Ao contrário de um timeout típico de cliente HTTP/REST, em que o timeout apenas sinaliza falha no cliente, o gRPC pode propagar o deadline do cliente para o servidor. Como resultado, o cliente pode realmente cancelar o trabalho no lado do servidor, e não apenas parar de esperar pela resposta.

Por outro lado, logpoints nos dão a mesma informação que obteríamos na UI do depurador, exceto que a observamos no console. O ponto importante é que isso não suspende o servidor, então podemos extrair a informação de que precisamos sem acionar o timeout.

Bônus: remover o timeout

Se você preferir uma abordagem alternativa para este cenário, aqui vai outra forma de depurá-lo. No nosso exemplo com gRPC, a parte problemática era o timeout, e podemos removê-lo em tempo de execução usando… logpoints.

Como você acabou de ver, expressões de logpoint podem modificar o programa em execução por meio de efeitos colaterais. Aqui, podemos usar essa técnica para ajustar a requisição recebida.

Primeiro, encontre o método da biblioteca que define o timeout. Há vários lugares onde podemos fazer isso. Um deles é io.grpc.internal.ServerImpl.createContext :

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

Nesse método, podemos reescrever o valor da variável local timeoutNanos logo depois que ela for atribuída:

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

Com esse logpoint em vigor, toda vez que o valor de timeout do gRPC é lido dos headers da requisição, ele é imediatamente substituído por um deadline de cinco minutos. Isso significa que podemos suspender o servidor novamente.

Se você quiser estender o timeout apenas para as requisições do reproducer e manter o servidor funcionando normalmente, por exemplo em uma instância de staging compartilhada, pode usar lógica multilinha dentro de um logpoint:

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

Aqui está o código para copiar para o 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";
}

A expressão multilinha analisa os headers da requisição e estende o timeout apenas para requisições com o header Debug (que nossos clientes de teste adicionam). As outras requisições mantêm o deadline normal. O ramo if retorna "Timeout reset", confirmando quando esse ramo foi visitado.


Info icon

Você pode combinar logpoints com recursos mais avançados do IntelliJ IDEA, como Mark Object. Se isso parecer interessante, talvez você queira ler este artigo.

Claro, esse método exige familiaridade com a biblioteca ou tempo para explorá-la. Se você não tiver nenhum dos dois e só quiser alterar rapidamente o comportamento em tempo de execução, pode delegar a tarefa a um agente de IA usando a skill de agente de IA incluída:

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

Conclusão

Neste artigo, vimos um caso de uso em que logpoints oferecem uma alternativa mais simples e elegante a breakpoints ou ao logging com println . Nós:

Espero que você tenha aprendido algo novo e agora tenha uma opção melhor para a próxima vez que instruções println ou breakpoints atrapalharem. No próximo post da série, veremos como funciona a instrumentação do depurador, que é o mecanismo subjacente que torna os novos logpoints tão rápidos.

Feliz depuração!

all posts ->