Introdução

São 3 da manhã. Um alerta crítico te acorda: um serviço em produção está falhando. Você abre os logs, procurando desesperadamente por uma pista, apenas para encontrar um mar de mensagens genéricas como "Ocorreu um erro durante o processamento". A frustração aumenta. Em qual lugar, dentre as milhares de linhas de código, isso aconteceu? Todos nós já passamos por isso. É neste momento que o verdadeiro valor de um log bem estruturado se torna dolorosamente claro.

Escrever bons logs é um ato de empatia — empatia pelo seu futuro eu e pelos colegas que terão que depurar seu código sob pressão. Felizmente, a plataforma .NET nos fornece ferramentas poderosas, porém simples, para transformar nossos logs de anotações ambíguas em informações precisas e acionáveis. Neste artigo, vamos explorar como usar os atributos [CallerFilePath] e [CallerLineNumber] para enriquecer nossos logs, tornando a depuração mais rápida, eficiente e muito menos estressante.

Uma história em quadrinhos ilustrando a experiência típica do desenvolvedor ao depurar com logs vagos versus o alívio de ter logs bem estruturados com informações do chamador.

O Problema dos Logs Vagos

Em sistemas complexos, especialmente aqueles baseados em microsserviços, identificar a origem exata de um erro é o primeiro e mais crucial passo. Uma mensagem de log sem contexto é como uma placa de sinalização sem direção.

Um Exemplo Comum (e Ineficiente) de Log:

public class PaymentService
{
    private readonly ILogger<PaymentService> _logger;

    public PaymentService(ILogger<PaymentService> logger)
    {
        _logger = logger;
    }

    public void ProcessPayment(decimal amount)
    {
        try
        {
            // Lógica complexa aqui...
            if (amount <= 0)
            {
                throw new ArgumentException("Valor de pagamento inválido.");
            }
            // Mais lógica...
        }
        catch (Exception ex)
        {
            // Este log não é muito útil. De onde ele veio?
            _logger.LogError(ex, "Ocorreu um erro ao processar o pagamento.");
        }
    }
}

Quando este log aparece em uma ferramenta centralizada como Seq, Datadog ou Kibana, você sabe o que aconteceu (um erro), mas não sabe a linha exata de código que o disparou. Você precisa confiar no stack trace, que pode ser complexo ou até mesmo estar ausente em alguns cenários assíncronos.

Apresentando os Atributos de Informação do Chamador (Caller Information)

O C# fornece um conjunto de atributos, conhecidos como atributos de Informação do Chamador, que instruem o compilador a injetar informações sobre o código-fonte do método chamador. A melhor parte? Isso é feito em tempo de compilação, o que significa que não há penalidade de desempenho por reflection em tempo de execução.

Os três atributos principais são:

  • [CallerFilePath]: Fornece o caminho completo do arquivo de origem que contém o chamador.
  • [CallerLineNumber]: Fornece o número da linha no arquivo de origem em que o método é chamado.
  • [CallerMemberName]: Fornece o nome do método ou propriedade do chamador.

Esses atributos nos permitem capturar automaticamente a origem exata de uma mensagem de log sem nenhum esforço manual.

Implementação Prática: Criando um Wrapper para o ILogger

Para evitar adicionar esses parâmetros manualmente em cada chamada de log, a melhor prática é criar um método de extensão simples para a interface ILogger. Isso centraliza nossa lógica de logging e mantém o código da nossa aplicação limpo.

Vamos criar uma classe estática para nossas extensões de logger:

// LoggerExtensions.cs
using System.Runtime.CompilerServices;

public static class LoggerExtensions
{
    public static void LogErrorComInfoChamador<T>(
        this ILogger<T> logger,
        Exception exception,
        string message,
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        // Podemos formatar a mensagem para incluir o caminho e a linha
        // Ou, melhor ainda, usar logs estruturados para adicioná-los como propriedades
        logger.LogError(exception, "{Message} [em {SourceFilePath}:{SourceLineNumber}]", 
                       message, sourceFilePath, sourceLineNumber);
    }

    // Você pode criar extensões semelhantes para LogWarning, LogInformation, etc.
    public static void LogInformationComInfoChamador<T>(
        this ILogger<T> logger,
        string message,
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        logger.LogInformation("{Message} [em {SourceFilePath}:{SourceLineNumber}]",
                              message, sourceFilePath, sourceLineNumber);
    }
}

Agora, podemos refatorar nosso PaymentService para usar este novo método de extensão.

O Exemplo “Depois”: Logging Claro e Acionável

public class PaymentService
{
    private readonly ILogger<PaymentService> _logger;

    public PaymentService(ILogger<PaymentService> logger)
    {
        _logger = logger;
    }

    public void ProcessPayment(decimal amount)
    {
        try
        {
            // Lógica complexa aqui...
            if (amount <= 0)
            {
                throw new ArgumentException("Valor de pagamento inválido.");
            }
            // Mais lógica...
        }
        catch (Exception ex)
        {
            // Agora, a chamada é tão simples quanto antes, mas o resultado é muito mais poderoso.
            _logger.LogErrorComInfoChamador(ex, "Ocorreu um erro ao processar o pagamento.");
        }
    }
}

O Resultado no Log

Quando o método LogErrorComInfoChamador é chamado, a saída do log será dramaticamente enriquecida. Se você estiver usando um provedor de log estruturado como o Serilog, a saída em formato JSON pode ser algo assim:

{
  "Timestamp": "2025-10-07T09:15:00.123Z",
  "Level": "Error",
  "MessageTemplate": "{Message} [em {SourceFilePath}:{SourceLineNumber}]",
  "RenderedMessage": "Ocorreu um erro ao processar o pagamento. [em C:\\Users\\Ivaldo\\Projects\\Veetz\\PaymentService.cs:25]",
  "Properties": {
    "Message": "Ocorreu um erro ao processar o pagamento.",
    "SourceFilePath": "C:\\Users\\Ivaldo\\Projects\\Veetz\\PaymentService.cs",
    "SourceLineNumber": 25,
    "Exception": "System.ArgumentException: Valor de pagamento inválido."
  }
}

Instantaneamente, você pode ver o arquivo e o número da linha exatos (PaymentService.cs:25) onde o log foi disparado. A sessão de depuração das 3 da manhã ficou 90% mais fácil.

Benefícios Além do Código

Adotar esta prática oferece benefícios que se estendem do nível técnico ao estratégico:

  1. Precisão Cirúrgica: Elimina a adivinhação. Os desenvolvedores podem navegar diretamente para a origem do problema, reduzindo drasticamente o tempo médio de resolução (MTTR). Para os diretores, isso significa menos tempo de inatividade e um produto mais confiável.

  2. Redução de Estresse e Empoderamento: Fornecer aos desenvolvedores ferramentas precisas para resolver problemas críticos é empoderador. Reduz o estresse associado a falhas de produção e permite que eles se concentrem em soluções, em vez de caçar pistas.

  3. Uma Cultura de Qualidade e Empatia: Quando você escreve logs dessa maneira, está pensando na pessoa que irá lê-los. Isso fomenta uma cultura onde os desenvolvedores constroem ferramentas e código que ajudam uns aos outros a ter sucesso, que é a base de uma equipe de alto desempenho.

  4. Sem Custo de Performance: Como este é um recurso de tempo de compilação, você ganha um poder de diagnóstico imenso sem sacrificar o desempenho em tempo de execução, tornando-se uma escolha segura até mesmo para aplicações de alta performance.

Conclusão

A tecnologia está no seu melhor quando nos ajuda a ser profissionais melhores, mais eficientes e mais empáticos. Os atributos de Informação do Chamador do C# são um exemplo perfeito disso. Eles não são apenas um recurso de linguagem inteligente; são uma ferramenta para construir sistemas mais sustentáveis, resilientes e amigáveis para os desenvolvedores.

Ao dedicar alguns minutos para configurar uma extensão de log simples, você está deixando um rastro de informações claras e acionáveis. Você está demonstrando empatia por sua equipe e por seu futuro eu. Na próxima vez que um alerta disparar no meio da noite, esse pequeno ato de empatia fará toda a diferença.