Registro em log HTTP no ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

O registro em log HTTP é um middleware que registra informações sobre a entrada de solicitações e respostas HTTP. O registro em log HTTP fornece logs de:

  • Informações de solicitação HTTP
  • Propriedades comuns
  • Cabeçalhos
  • Corpo
  • Informações de resposta HTTP

O registro em log HTTP pode:

  • Registre todas as solicitações e respostas ou apenas as solicitações e respostas que atendam a determinados critérios.
  • Selecione quais partes da solicitação e da resposta são registradas em log.
  • Permite que você exclua informações confidenciais dos logs.

O registro em log HTTP pode reduzir o desempenho de um aplicativo, especialmente ao registrar em log os corpos de solicitação e resposta. Considere o impacto sobre o desempenho ao selecionar campos para registrar. Teste o impacto sobre o desempenho das propriedades de log selecionadas.

Aviso

O registro em log HTTP pode registrar PII (informações de identificação pessoal). Considere o risco e evite registrar informações confidenciais.

Habilitar o registro em log HTTP

O registro em log HTTP é habilitado chamando AddHttpLogging e UseHttpLogging, conforme mostrado no exemplo a seguir:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(o => { });

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

O lambda vazio no exemplo anterior de chamada AddHttpLogging adiciona o middleware com a configuração padrão. Por padrão, o registro em log HTTP registra propriedades comuns, como caminho, código de status e cabeçalhos para solicitações e respostas.

Adicione a seguinte linha ao arquivo appsettings.Development.json no nível "LogLevel": { para que os logs HTTP sejam exibidos:

 "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

Com a configuração padrão, uma solicitação e resposta é registrada como um par de mensagens semelhante ao exemplo a seguir:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Protocol: HTTP/2
      Method: GET
      Scheme: https
      PathBase:
      Path: /
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
      Host: localhost:52941
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61
      Accept-Encoding: gzip, deflate, br
      Accept-Language: en-US,en;q=0.9
      Upgrade-Insecure-Requests: [Redacted]
      sec-ch-ua: [Redacted]
      sec-ch-ua-mobile: [Redacted]
      sec-ch-ua-platform: [Redacted]
      sec-fetch-site: [Redacted]
      sec-fetch-mode: [Redacted]
      sec-fetch-user: [Redacted]
      sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8
      Date: Tue, 24 Oct 2023 02:03:53 GMT
      Server: Kestrel

Opções de registro em log HTTP

Para configurar opções globais para o middleware de registro em log HTTP, chame AddHttpLogging em Program.cs, usando o lambda para configurar HttpLoggingOptions.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Observação

No exemplo anterior e nos exemplos a seguir, UseHttpLogging é chamado após UseStaticFiles, portanto, o registro em log HTTP não está habilitado para arquivos estáticos. Para habilitar o log HTTP do arquivo estático, chame UseHttpLogging antes de UseStaticFiles.

LoggingFields

HttpLoggingOptions.LoggingFields é um sinalizador de enumeração que configura partes específicas da solicitação e da resposta para registrar. HttpLoggingOptions.LoggingFields usa como padrão RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders.

RequestHeaders e ResponseHeaders

RequestHeaders e ResponseHeaders são conjuntos de cabeçalhos HTTP registrados. Os valores de cabeçalho são registrados apenas para nomes de cabeçalho que estão nestas coleções. O código a seguir adiciona sec-ch-ua ao RequestHeaders, para que o valor do cabeçalho sec-ch-ua seja registrado. O código a seguir adiciona MyResponseHeader ao ResponseHeaders, para que o valor do cabeçalho MyResponseHeader seja registrado. Se essas linhas forem removidas, os valores desses cabeçalhos serão [Redacted].

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions fornece configuração para selecionar qual codificação usar para um tipo de mídia específico.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Essa abordagem também pode ser usada para habilitar o registro em log de dados que não são registrados por padrão (por exemplo, dados de formulário, que podem ter um tipo de mídia como application/x-www-form-urlencoded ou multipart/form-data).

Métodos MediaTypeOptions

RequestBodyLogLimit e ResponseBodyLogLimit

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

CombineLogs

Definir CombineLogs como true configura o middleware para consolidar todos os seus logs habilitados para uma solicitação e resposta em um único log no final. Isso inclui a solicitação, o corpo da solicitação, a resposta, o corpo da resposta e a duração.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Configuração específica do ponto de extremidade

Para configuração específica de ponto de extremidade em aplicativos de API mínimos, um método de extensão WithHttpLogging está disponível. O exemplo a seguir mostra como configurar o registro em log HTTP para um ponto de extremidade:

app.MapGet("/response", () => "Hello World! (logging response)")
    .WithHttpLogging(HttpLoggingFields.ResponsePropertiesAndHeaders);

Para a configuração específica de ponto de extremidade em aplicativos que usam controladores, o atributo [HttpLogging] está disponível. O atributo também pode ser usado em aplicativos de API mínimos, conforme mostrado no exemplo a seguir:

app.MapGet("/duration", [HttpLogging(loggingFields: HttpLoggingFields.Duration)]
    () => "Hello World! (logging duration)");

IHttpLoggingInterceptor

IHttpLoggingInterceptor é a interface de um serviço que pode ser implementado para lidar com retornos de chamada por solicitação e por resposta para personalizar quais detalhes são registrados. As configurações de log específicas do ponto de extremidade são aplicadas primeiro e podem ser substituídas nesses retornos de chamada. Uma implementação pode:

  • Inspecione uma solicitação ou resposta.
  • Habilite ou desabilite qualquer HttpLoggingFields.
  • Ajuste a quantidade de logs do corpo de solicitação ou resposta.
  • Adicione campos personalizados aos logs.

Registre uma implementação IHttpLoggingInterceptor chamando AddHttpLoggingInterceptor<T> em Program.cs. Se várias instâncias IHttpLoggingInterceptor forem registradas, elas serão executadas na ordem registrada.

O exemplo a seguir mostra como registrar uma implementação IHttpLoggingInterceptor:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.Duration;
});
builder.Services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();

O exemplo a seguir é uma implementação IHttpLoggingInterceptor que:

  • Inspeciona o método de solicitação e desabilita o registro em log para solicitações POST.
  • Para solicitações não POST:
    • Reduz o caminho da solicitação, os cabeçalhos da solicitação e os cabeçalhos da resposta.
    • Adiciona campos personalizados e valores de campo aos logs de solicitação e resposta.
using Microsoft.AspNetCore.HttpLogging;

namespace HttpLoggingSample;

internal sealed class SampleHttpLoggingInterceptor : IHttpLoggingInterceptor
{
    public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext)
    {
        if (logContext.HttpContext.Request.Method == "POST")
        {
            // Don't log anything if the request is a POST.
            logContext.LoggingFields = HttpLoggingFields.None;
        }

        // Don't enrich if we're not going to log any part of the request.
        if (!logContext.IsAnyEnabled(HttpLoggingFields.Request))
        {
            return default;
        }

        if (logContext.TryDisable(HttpLoggingFields.RequestPath))
        {
            RedactPath(logContext);
        }

        if (logContext.TryDisable(HttpLoggingFields.RequestHeaders))
        {
            RedactRequestHeaders(logContext);
        }

        EnrichRequest(logContext);

        return default;
    }

    public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext)
    {
        // Don't enrich if we're not going to log any part of the response
        if (!logContext.IsAnyEnabled(HttpLoggingFields.Response))
        {
            return default;
        }

        if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders))
        {
            RedactResponseHeaders(logContext);
        }

        EnrichResponse(logContext);

        return default;
    }

    private void RedactPath(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter(nameof(logContext.HttpContext.Request.Path), "RedactedPath");
    }

    private void RedactRequestHeaders(HttpLoggingInterceptorContext logContext)
    {
        foreach (var header in logContext.HttpContext.Request.Headers)
        {
            logContext.AddParameter(header.Key, "RedactedHeader");
        }
    }

    private void EnrichRequest(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter("RequestEnrichment", "Stuff");
    }

    private void RedactResponseHeaders(HttpLoggingInterceptorContext logContext)
    {
        foreach (var header in logContext.HttpContext.Response.Headers)
        {
            logContext.AddParameter(header.Key, "RedactedHeader");
        }
    }

    private void EnrichResponse(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter("ResponseEnrichment", "Stuff");
    }
}

Com esse interceptador, uma solicitação POST não gera nenhum log, mesmo que o registro em log HTTP esteja configurado para registrar HttpLoggingFields.All. Uma solicitação GET gera registros semelhantes ao exemplo a seguir:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Path: RedactedPath
      Accept: RedactedHeader
      Host: RedactedHeader
      User-Agent: RedactedHeader
      Accept-Encoding: RedactedHeader
      Accept-Language: RedactedHeader
      Upgrade-Insecure-Requests: RedactedHeader
      sec-ch-ua: RedactedHeader
      sec-ch-ua-mobile: RedactedHeader
      sec-ch-ua-platform: RedactedHeader
      sec-fetch-site: RedactedHeader
      sec-fetch-mode: RedactedHeader
      sec-fetch-user: RedactedHeader
      sec-fetch-dest: RedactedHeader
      RequestEnrichment: Stuff
      Protocol: HTTP/2
      Method: GET
      Scheme: https
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      Content-Type: RedactedHeader
      MyResponseHeader: RedactedHeader
      ResponseEnrichment: Stuff
      StatusCode: 200
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
      ResponseBody: Hello World!
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[8]
      Duration: 2.2778ms

Ordem de precedência da configuração de registro em log

A lista a seguir mostra a ordem de precedência da configuração de registro em log:

  1. Configuração global de HttpLoggingOptions, definida pela chamada de AddHttpLogging.
  2. A configuração específica do ponto de extremidade do atributo [HttpLogging] ou do método de extensão WithHttpLogging substitui a configuração global.
  3. IHttpLoggingInterceptor é chamado com os resultados e pode modificar ainda mais a configuração por solicitação.

O Registro em Log HTTP é um middleware que registra informações sobre a entrada de solicitações HTTP e respostas HTTP. O registro em log HTTP fornece logs de:

  • Informações de solicitação HTTP
  • Propriedades comuns
  • Cabeçalhos
  • Corpo
  • Informações de resposta HTTP

O registro em log HTTP é útil em vários cenários para:

  • Registrar informações sobre solicitações e respostas de entrada.
  • Filtrar quais partes da solicitação e da resposta são registradas.
  • Filtrar quais cabeçalhos registrar.

O registro em log HTTP pode reduzir o desempenho de um aplicativo, especialmente ao registrar em log os corpos de solicitação e resposta. Considere o impacto sobre o desempenho ao selecionar campos para registrar. Teste o impacto sobre o desempenho das propriedades de log selecionadas.

Aviso

O registro em log HTTP pode registrar PII (informações de identificação pessoal). Considere o risco e evite registrar informações confidenciais.

Habilitando o registro em log HTTP

O registro em log HTTP é habilitado com UseHttpLogging, que adiciona o middleware de registro em log HTTP.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

Por padrão, o registro em log HTTP registra propriedades comuns, como caminho, código de status e cabeçalhos, para solicitações e respostas. Adicione a seguinte linha ao arquivo appsettings.Development.json no nível "LogLevel": { para que os logs HTTP sejam exibidos:

 "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

A saída é registrada como uma mensagem em LogLevel.Information.

Exemplo de saída de solicitação

Opções de registro em log HTTP

Para configurar o middleware de registro em log HTTP, chame AddHttpLogging em Program.cs.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Observação

No exemplo anterior e nos exemplos a seguir, UseHttpLogging é chamado após UseStaticFiles, portanto, o registro em log HTTP não está habilitado para arquivo estático. Para habilitar o log HTTP do arquivo estático, chame UseHttpLogging antes de UseStaticFiles.

LoggingFields

HttpLoggingOptions.LoggingFields é um sinalizador de enumeração que configura partes específicas da solicitação e da resposta para registrar. HttpLoggingOptions.LoggingFields usa como padrão RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders.

RequestHeaders

Headers são um conjunto de cabeçalhos de solicitação HTTP que têm permissão para ser registrados. Os valores de cabeçalho são registrados apenas para nomes de cabeçalho que estão nesta coleção. O código a seguir registra o cabeçalho de solicitação "sec-ch-ua". Se logging.RequestHeaders.Add("sec-ch-ua"); for removido, o valor do cabeçalho da solicitação "sec-ch-ua" será redigido. O seguinte código realçado chama HttpLoggingOptions.RequestHeaders e HttpLoggingOptions.ResponseHeaders:

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions fornece configuração para selecionar qual codificação usar para um tipo de mídia específico.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Essa abordagem também pode ser usada para habilitar o registro em log de dados que não são registrados por padrão (por exemplo, dados de formulário, que podem ter um tipo de mídia como application/x-www-form-urlencoded ou multipart/form-data).

Métodos MediaTypeOptions

RequestBodyLogLimit e ResponseBodyLogLimit

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();