DEV Community

Cover image for Pare de usar Logs como MULETA. Voce está fazendo ERRADO!!!
Milton Camara
Milton Camara

Posted on

Pare de usar Logs como MULETA. Voce está fazendo ERRADO!!!

Se você já abriu um arquivo de log de 2GB no meio da madrugada tentando descobrir por que o pagamento do cliente falhou, esse post é pra você. E não, a culpa não é da ferramenta. A culpa é sua.

A real é dura: a maioria dos devs trata log como se fosse Console.WriteLine com esteroides. Espalha LogInformation em cada método, acha que está "instrumentando" a aplicação, e quando o sistema quebra em produção fica horas "grepando" arquivo atrás de pista.

Logs não foram feitos pra explicar fluxos. Eles servem pra registrar eventos pontuais. Quem te entrega o fluxo completo de uma operação são os traces. E se você ainda não sabe a diferença, esse é o problema que você precisa resolver hoje.


O pecado capital: log como narrador de novela

Olha se você nunca escreveu algo parecido com isso:

logger.LogInformation("Entrou no método ProcessarPagamento");
logger.LogInformation("Validando dados do cliente {ClienteId}", clienteId);
logger.LogInformation("Chamando API de Pagamento com valor {Valor}", valor);
logger.LogInformation("Pagamento retornou sucesso");
logger.LogInformation("Saiu do método ProcessarPagamento");
Enter fullscreen mode Exit fullscreen mode

Parece responsável. Parece "boa prática". Não é.

Esse código tem 4 problemas graves que ninguém te conta:

1. Zero correlação entre as linhas

Em produção, com 500 requests por segundo, essas 5 linhas vão estar embaralhadas com outras 50.000 de outros usuários. Boa sorte tentando montar a sequência de um pedido específico sem um TraceId.

2. Você está pagando caro por lixo

Datadog, New Relic, CloudWatch, Splunk: todos cobram por volume ingerido. Cada LogInformation("Entrou no método X") é dinheiro real saindo da sua conta. Já vi empresa gastar R$ 80k/mês em log que ninguém nunca leu.

3. PII e vazamento de dados (LGPD chegou em todo mundo)

Quando o dev coloca logger.LogInformation("Request: {@Request}", request) achando que está sendo esperto, ele acabou de logar CPF, cartão, token JWT e senha em texto puro. Auditoria vai te encontrar. A LGPD também.

4. Logs não contam história, contam fofoca

Você sabe que algo aconteceu, mas não sabe quando, quanto tempo demorou, o que veio antes nem o que veio depois. É como tentar entender um filme só pelas legendas, fora de ordem.


A virada de chave: traces

Trace é uma árvore. Cada operação (uma chamada HTTP, um query no banco, uma publicação no Kafka) é um span, e os spans se conectam formando o fluxo completo de uma requisição, mesmo quando ela atravessa 8 microsserviços diferentes.

Com trace você responde perguntas que log nunca vai conseguir responder:

  • "Por que esse pedido demorou 4 segundos?" → você vê exatamente qual span travou.
  • "O bug está no meu serviço ou no fornecedor?" → o trace mostra a cadeia inteira.
  • "Quantos % das requests estão lentas por causa do banco?" → métricas derivadas do span do EF Core.

E o melhor: você instrumenta uma vez, com OpenTelemetry, e exporta pra qualquer backend (Jaeger, Tempo, Datadog, Honeycomb, Azure Monitor). Sem vendor lock-in.


Hands-on: o mesmo cenário, agora bem feito (.NET 8 + OpenTelemetry)

Setup mínimo no Program.cs

using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

builder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("MinhaApp.Pagamento"))
    .WithTracing(tracing => tracing
        .AddSource("MinhaApp.Pagamento")
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation()
        .AddOtlpExporter()); // exporta pro coletor que você quiser
Enter fullscreen mode Exit fullscreen mode

Pronto. Toda chamada HTTP de entrada, toda chamada HTTP de saída e toda query no EF Core já estão sendo rastreadas automaticamente. Você nem precisa escrever código pra isso.

Instrumentação manual onde importa

using System.Diagnostics;

public static class Telemetry
{
    public static readonly ActivitySource Source = new("MinhaApp.Pagamento");
}

public class PagamentoService
{
    private readonly HttpClient _http;
    private readonly ILogger<PagamentoService> _logger;

    public PagamentoService(HttpClient http, ILogger<PagamentoService> logger)
    {
        _http = http;
        _logger = logger;
    }

    public async Task<bool> ProcessarAsync(Guid pedidoId, int clienteId, decimal valor)
    {
        using var activity = Telemetry.Source.StartActivity(
            "processar-pagamento", 
            ActivityKind.Internal);

        // tags = metadados consultáveis no backend de observabilidade
        activity?.SetTag("pedido.id", pedidoId);
        activity?.SetTag("cliente.id", clienteId);
        activity?.SetTag("valor.total", valor);
        activity?.SetTag("moeda", "BRL");

        try
        {
            activity?.AddEvent(new ActivityEvent("validando-cliente"));
            await ValidarClienteAsync(clienteId);

            activity?.AddEvent(new ActivityEvent(
                "chamando-gateway",
                tags: new ActivityTagsCollection 
                { 
                    { "gateway", "Stripe" },
                    { "endpoint", "POST /v1/charges" }
                }));

            var response = await _http.PostAsJsonAsync("/v1/charges", new { valor });

            activity?.SetTag("http.status_code", (int)response.StatusCode);
            activity?.SetTag("response.size", response.Content.Headers.ContentLength);

            if (!response.IsSuccessStatusCode)
            {
                activity?.SetStatus(ActivityStatusCode.Error, "Gateway recusou pagamento");
                // log SÓ aqui, no erro, e SEM payload sensível
                _logger.LogWarning("Pagamento recusado para pedido {PedidoId}", pedidoId);
                return false;
            }

            activity?.SetStatus(ActivityStatusCode.Ok);
            return true;
        }
        catch (Exception ex)
        {
            activity?.RecordException(ex);
            activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
            throw; // deixa o ASP.NET Core lidar
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

O que esse código faz que o anterior não fazia

Antes (logs) Depois (traces)
5 linhas soltas no arquivo 1 span estruturado com início, fim e duração
Nenhuma correlação entre serviços TraceId propaga automaticamente via HTTP headers
Busca textual com grep Query estruturada por pedido.id, cliente.id, etc.
Sem noção de tempo Latência precisa de cada etapa, em microssegundos
Logger genérico vazando PII Tags controladas, sem campos sensíveis
Custo proporcional ao volume bruto Custo proporcional a operações + sampling configurável

"E os logs, jogo fora?"

Não. Logs continuam sendo úteis. O que muda é quando você os usa:

  • Use log pra: eventos de negócio importantes ("Pedido X cancelado pelo cliente"), erros inesperados, auditoria de ações sensíveis.
  • Não use log pra: rastrear fluxo, medir performance, debugar requisição específica, "marcar que passou aqui".

A regra que eu sigo: se a informação faz parte da história de uma requisição, é trace. Se é um fato isolado que importa fora do contexto da requisição, é log.

E quando você precisar dos dois juntos? Correlacione pelo TraceId:

_logger.LogWarning(
    "Pedido {PedidoId} cancelado. TraceId: {TraceId}",
    pedidoId,
    Activity.Current?.TraceId);
Enter fullscreen mode Exit fullscreen mode

Agora, do dashboard de logs, você clica no TraceId e cai direto no trace. Fim do grep eterno.


A pirâmide da observabilidade (decora isso)

        🔺 Logs        ← eventos pontuais, raros, com contexto
       🔺🔺 Métricas    ← agregações, dashboards, alertas
      🔺🔺🔺 Traces      ← a base de tudo, o fluxo das requests
Enter fullscreen mode Exit fullscreen mode

Quem começa pela base debuga em segundos. Quem começa pelo topo debuga em horas.


Conclusão sem rodeios

Se você ainda está enchendo seu código de LogInformation achando que está fazendo observabilidade, você está uma década atrás do estado da arte. Trace não é luxo de Big Tech, é o mínimo pra qualquer aplicação séria em 2026.

Instala o OpenTelemetry hoje. Roda um Jaeger ou Tempo local. Vê seu sistema do jeito que ele realmente é: uma árvore de operações com latências, erros e dependências. Você nunca mais vai voltar pro tail -f.

E aí, vai continuar caçando agulha no palheiro ou vai aprender a usar a ferramenta certa?


💬 Se esse post fez sentido, deixa um comentário com o pior cenário de "debug por log" que você já viveu.

Top comments (0)