ASP.NET Core 中的 HTTP 日志记录
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
HTTP 日志记录是一种中间件,用于记录传入 HTTP 请求和 HTTP 响应的相关信息。 HTTP 日志记录可以记录:
- HTTP 请求信息
- 公共属性
- 标头
- 正文
- HTTP 响应信息
HTTP 日志记录可以:
- 记录所有请求和响应,或者仅记录满足特定条件的请求和响应。
- 选择要记录请求和响应的哪些部分。
- 支持修订日志中的敏感信息。
HTTP 日志记录可能会降低应用的性能,尤其是在记录请求和响应正文时。 在选择要记录的字段时请考虑性能影响。 测试所选日志记录属性的性能影响。
警告
HTTP 日志记录可能会记录个人身份信息 (PII)。 请考虑风险,并避免记录敏感信息。
启用 HTTP 日志记录
HTTP 日志记录通过调用 AddHttpLogging 和 UseHttpLogging 来调用,如以下示例所示:
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();
注意
在上述示例和下面的示例中,UseHttpLogging
在 UseStaticFiles
之后调用,因此没有为静态文件启用 HTTP 日志记录。 要启用静态文件 HTTP 日志记录,请在 UseStaticFiles
之前调用 UseHttpLogging
。
LoggingFields
HttpLoggingOptions.LoggingFields
是一个枚举标志,用于配置要记录的请求和响应的特定部分。 HttpLoggingOptions.LoggingFields
默认为 RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders。
RequestHeaders
和 ResponseHeaders
RequestHeaders 和 ResponseHeaders 是记录的 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-urlencoded
或 multipart/form-data
等媒体类型)。
MediaTypeOptions
方法
RequestBodyLogLimit
和 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
将 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
日志记录配置优先级顺序
以下列表显示了日志记录配置的优先级顺序:
- HttpLoggingOptions 中的全局配置,通过调用 AddHttpLogging 来设置。
[HttpLogging]
属性或 WithHttpLogging 扩展方法中特定于终结点的配置会替代全局配置。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();
注意
在前面的示例和以下示例中,UseHttpLogging
在 UseStaticFiles
之后调用,因此没有为静态文件启用 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.RequestHeaders
和 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 提供了选择要用于特定媒体类型的编码的配置。
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-urlencoded
或 multipart/form-data
的媒体类型。
MediaTypeOptions
方法
RequestBodyLogLimit
和 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();