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

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:
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:
- Eles não poluem o código, o que significa que você não vai gastar esforço limpando tudo depois (nem commitar um perdido em produção).
- Eles são flexíveis sobre o que registram e quando. Por exemplo, se você só quer amostrar eventos frequentes, é assim que você configura isso.
- Eles permitem inserir logging dentro das suas dependências (vamos fazer isso em instantes).
- Mais importante neste cenário: eles podem poupar redeploys caros. Rodar de novo um container Docker local só para adicionar logging em um projeto de brinquedo pode ser aceitável, mas em projetos grandes do mundo real frequentemente não é.
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:
Mas, depois de olhar o estado do programa e avançar alguns passos, acabamos no caminho de cancelamento:
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 :
Nesse método, podemos reescrever o valor da variável local timeoutNanos logo depois que ela
for atribuída:
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:
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.
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:
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:
- anexamos a um processo remoto
- adicionamos diagnósticos em tempo de execução usando logpoints
- ajustamos o logging conforme nossa hipótese mudava
- testamos a correção sem alterar o código da aplicação
- alteramos o timeout de um servidor em execução
- usamos a skill de agente de IA ij-debugger
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!