ASP.NET Core 中的 HTTP 日志记录

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

HTTP 日志记录是一种中间件,用于记录传入 HTTP 请求和 HTTP 响应的相关信息。 HTTP 日志记录可以记录:

  • HTTP 请求信息
  • 公共属性
  • 标头
  • 正文
  • HTTP 响应信息

HTTP 日志记录可以:

  • 记录所有请求和响应,或者仅记录满足特定条件的请求和响应。
  • 选择要记录请求和响应的哪些部分。
  • 支持修订日志中的敏感信息。

HTTP 日志记录可能会降低应用的性能,尤其是在记录请求和响应正文时。 在选择要记录的字段时请考虑性能影响。 测试所选日志记录属性的性能影响。

警告

HTTP 日志记录可能会记录个人身份信息 (PII)。 请考虑风险,并避免记录敏感信息。

启用 HTTP 日志记录

HTTP 日志记录通过调用 AddHttpLoggingUseHttpLogging 来调用,如以下示例所示:

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();

上述调用 AddHttpLogging 的示例中的空 Lambda 添加具有默认配置的中间件。 默认情况下,HTTP 日志记录会记录路径、状态代码、请求头和响应头等常用属性。

将以下行添加到 "LogLevel": { 级别的 appsettings.Development.json 文件,以便显示 HTTP 日志:

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

如果使用默认配置,请求和响应将记录为一对消息,与以下示例类似:

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

HTTP 日志记录选项

若要配置 HTTP 日志记录中间件的全局选项,请调用 Program.cs 中的 AddHttpLogging,使用 Lambda 来配置 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();

注意

在上述示例和下面的示例中,UseHttpLoggingUseStaticFiles 之后调用,因此没有为静态文件启用 HTTP 日志记录。 要启用静态文件 HTTP 日志记录,请在 UseStaticFiles 之前调用 UseHttpLogging

LoggingFields

HttpLoggingOptions.LoggingFields 是一个枚举标志,用于配置要记录的请求和响应的特定部分。 HttpLoggingOptions.LoggingFields 默认为 RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders

RequestHeadersResponseHeaders

RequestHeadersResponseHeaders 是记录的 HTTP 标头集。 只记录这些集合中的标头名称的标头值。 以下代码将 sec-ch-ua 添加到 RequestHeaders 中,因此会记录 sec-ch-ua 标头的值。 并且它将 MyResponseHeader 添加到 ResponseHeaders 中,因此会记录 MyResponseHeader 标头的值。 如果删除了这些行,则这些标头的值是 [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 提供了选择要用于特定媒体类型的编码的配置。

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();

此方法也可用于为在默认情况下未记录的数据启用日志记录(例如表单数据,其可能具有 application/x-www-form-urlencodedmultipart/form-data 等媒体类型)。

MediaTypeOptions 方法

RequestBodyLogLimitResponseBodyLogLimit

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

CombineLogs 设置为 true 会配置中间件,使其在最后将请求和响应所有已启用的日志合并到一个日志中。 这包括请求、请求正文、响应、响应正文和持续时间。

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();

特定于终结点的配置

对于最小 API 应用中特定于终结点的配置,可使用 WithHttpLogging 扩展方法。 以下示例演示如何为一个终结点配置 HTTP 日志记录:

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

对于使用控制器的应用中特定于终结点的配置,可使用 [HttpLogging] 属性。 还可在最小 API 应用中使用该属性,如以下示例所示:

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

IHttpLoggingInterceptor

IHttpLoggingInterceptor 是服务的接口,可实现它来处理每请求和每响应回调,从而自定义记录的详细信息。 首先应用任何特定于终结点的日志设置,然后可在这些回调中替代它们。 实现可以:

  • 检查请求或响应。
  • 启用或禁用任何 HttpLoggingFields
  • 调整记录的请求或响应正文量。
  • 将自定义字段添加到日志。

通过调用 Program.cs 中的 AddHttpLoggingInterceptor<T> 来注册 IHttpLoggingInterceptor 实现。 如果注册了多个 IHttpLoggingInterceptor 实例,会按注册的顺序运行这些实例。

以下示例显示如何注册 IHttpLoggingInterceptor 实现:

var builder = WebApplication.CreateBuilder(args);

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

以下示例是一个 IHttpLoggingInterceptor 实现,它执行以下操作:

  • 检查请求方法并禁用 POST 请求的日志记录。
  • 对于非 POST 请求:
    • 修订请求路径、请求头和响应头。
    • 将自定义字段和字段值添加到请求和响应日志。
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");
    }
}

使用此侦听器时,即使 HTTP 日志记录配置为记录 HttpLoggingFields.All,POST 请求也不会生成任何日志。 GET 请求生成类似于以下示例的日志:

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

日志记录配置优先级顺序

以下列表显示了日志记录配置的优先级顺序:

  1. HttpLoggingOptions 中的全局配置,通过调用 AddHttpLogging 来设置。
  2. [HttpLogging] 属性或 WithHttpLogging 扩展方法中特定于终结点的配置会替代全局配置。
  3. IHttpLoggingInterceptor 使用结果进行调用,并且可进一步修改每个请求的配置。

HTTP 日志记录是一种中间件,用于记录传入 HTTP 请求和 HTTP 响应的相关信息。 HTTP 日志记录可以记录:

  • HTTP 请求信息
  • 公共属性
  • 标头
  • 正文
  • HTTP 响应信息

在以下几种方案中,HTTP 日志记录很有价值:

  • 记录传入请求和响应的相关信息。
  • 筛选请求和响应的哪些部分被记录。
  • 筛选要记录的头。

HTTP 日志记录可能会降低应用的性能,尤其是在记录请求和响应正文时。 在选择要记录的字段时请考虑性能影响。 测试所选日志记录属性的性能影响。

警告

HTTP 日志记录可能会记录个人身份信息 (PII)。 请考虑风险,并避免记录敏感信息。

启用 HTTP 日志记录

HTTP 日志记录是通过 UseHttpLogging 启用,它添加了 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();

HTTP 日志记录默认记录路径、状态代码以及请求头和响应头等常用属性。 将以下行添加到 "LogLevel": { 级别的 appsettings.Development.json 文件,以便显示 HTTP 日志:

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

输出作为单条消息记录在 LogLevel.Information 上。

示例请求输出

HTTP 日志记录选项

若要配置 HTTP 日志记录中间件,请在 Program.cs 中调用 AddHttpLogging

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();

注意

在前面的示例和以下示例中,UseHttpLoggingUseStaticFiles 之后调用,因此没有为静态文件启用 HTTP 日志记录。 要启用静态文件 HTTP 日志记录,请在 UseStaticFiles 之前调用 UseHttpLogging

LoggingFields

HttpLoggingOptions.LoggingFields 是一个枚举标志,用于配置要记录的请求和响应的特定部分。 HttpLoggingOptions.LoggingFields 默认为 RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders

RequestHeaders

Headers 是一组可以记录的 HTTP 请求头。 只记录此集合中的头名称的头值。 以下代码记录请求头 "sec-ch-ua"。 如果删除 logging.RequestHeaders.Add("sec-ch-ua");,则修订请求头 "sec-ch-ua" 的值。 以下突出显示的代码调用 HttpLoggingOptions.RequestHeadersHttpLoggingOptions.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 提供了选择要用于特定媒体类型的编码的配置。

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();

此方法也可用于为默认未记录的数据启用日志记录(例如表单数据,其媒体类型可能为 application/x-www-form-urlencodedmultipart/form-data)。

MediaTypeOptions 方法

RequestBodyLogLimitResponseBodyLogLimit

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();