Rastreamento de logs em sistemas distribuídos com identificadores únicos

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:

  1. Filtro de gateway: Gera um identificador único e o repassa para serviços downstream via cabeçalho HTTP
  2. Interceptores Spring: Capturam o identificador nas requisições e o asosciam ao contexto da thread atual
  3. Interceptores Feign: Transmitem o identificador entre serviços remotos
  4. 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.

Tags: logback MDC Distributed Logging Spring Framework Thread Pool

Publicado em 9-1 03:31