A principal dificuldade ao diagnosticar problemas em ssitemas distribuídos está na identificação de logs relacionados a uma única requisição. Mesmo com ferramentas como ELK para coleta centralizada, a fragmentação de dados em ambientes com alta carga torna difícil correlacionar eventos entre serviços e threads.
A abordagem proposta utiliza identificadores únicos para rastrear fluxos de execução. A implementação se baseia no mecanismo MDC (Mapped Diagnostic Context) do Logback, que permite armazenar informações contextuais associadas a threads. O valor é inserido no template de logs via %X{traceId}, mantendo a compatibilidade com a infraestrutura existente.
O desafio principal reside na propagação do contexto entre threads e serviços. Para resolver isso, foram implementadas soluções em diferentes camadas:
- Filtro de gateway: Gera um identificador único e o repassa para serviços downstream via cabeçalho HTTP
- Interceptores Spring: Capturam o identificador nas requisições e o asosciam ao contexto da thread atual
- Interceptores Feign: Transmitem o identificador entre serviços remotos
- Executor de thread customizado: Garante a propagação do contexto MDC para threads criadas dinamicamente
Exemplo de implementação de filtro de gateway:
@Component
public class TraceFilter extends ZuulFilter {
@Autowired
private ConfiguracaoRastreamento configuracao;
@Override
public String getType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int getOrder() {
return FORM_BODY_WRAPPER_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return configuracao.isAtivo();
}
@Override
public Object run() {
String idRastreamento = GeradorId.gerar();
MDC.put("request_id", idRastreamento);
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("X-Trace-ID", idRastreamento);
return null;
}
}
Para serviços Spring, o interceptor associa o identificador à thread:
public class RastreamentoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String id = request.getHeader("X-Trace-ID");
if (id != null && !id.isEmpty()) {
MDC.put("request_id", id);
}
return true;
}
}
No caso de chamadas assíncronas, o executor personalizado preserva o contexto:
public class ExecutorPersonalizado extends ThreadPoolTaskExecutor {
@Override
public void execute(Runnable task) {
Map<String, String> contexto = MDC.getContextMap();
super.execute(() -> executarComContexto(task, contexto));
}
private void executarComContexto(Runnable task, Map<String, String> contexto) {
if (contexto != null) {
MDC.setContextMap(contexto);
}
try {
task.run();
} finally {
MDC.clear();
}
}
}
Com essa abordagem, é possível agrupar todos os logs relacionados a uma requisição específica utilizando o identificaodr único, facilitando a identificação de falhas em sistemas distribuídos.