记录 ASP .NET Core http 请求和响应是几乎每个 .NET 开发人员迟早都会面临的常见任务。在很长一段时间里,开发团队选择的最流行的方法似乎是编写自定义中间件。但是由于我们内置了一个库。所以,亲爱的 .NET 海狸,让我们看看Microsoft给我们的日志!
我们将从 .NET 提供给我们的最小设置开始,通过运行以下控制台命令
dotnet new web
检查 http 请求的每个部分的日志记录非常重要,例如请求正文、查询字符串、错误和路径参数。因此,默认终结点将不起作用。让我们创建一个带有二十一点查询和路由参数的派对:
app.MapPost("/parties/{partyId}/guests", (string partyId, [FromQuery] bool? loungeAccess, Guest visitor) => {
if (loungeAccess == true && !visitor.Vip)
throw new NotEnoughLevelException();
return new Ticket(
PartyId: partyId,
Receiver: visitor.Name,
LoungeAccess: loungeAccess ?? false,
Code: Guid.NewGuid().ToString()
);
});
app.Run();
public record Guest(string Name, bool Vip);
public record Ticket(string PartyId, string Receiver, bool LoungeAccess, string Code);
public class NotEnoughLevelException : Exception;
默认情况下,对于 ASP,.NET Core 将大致中断请求并返回 。让我们返回一个错误对象。实现它的最简单方法可能是使用 nuget 包。要安装它,我们将使用以下命令:
dotnet add package Nist.Errors
并将我们的异常映射到相应的错误:
app.UseErrorBody<Error>(ex => ex switch {
NotEnoughLevelException _ => new (HttpStatusCode.BadRequest, "NotEnoughLevel"),
_ => new (HttpStatusCode.InternalServerError, "Unknown")
}, showException: false);
现在,让我们来看看请求和响应日志记录。启用它的最简约方法包括 4 个步骤:
1. 注册 http 日志服务
builder.Services.AddHttpLogging(o => {});
2. 附加 http 日志中间件
app.UseHttpLogging();
3. 删除以最大程度地减少我们的配置开销appsettings.Development.json
4. 由于默认情况下,日志级别为 http 日志中间件指定专用日志级别:
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
在所有这些更改之后,我们的完整内容将如下所示:Program.cs
using Microsoft.AspNetCore.Mvc;
using Nist.Errors;
using System.Net;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(o => {});
var app = builder.Build();
app.UseHttpLogging();
app.UseErrorBody<Error>(ex => ex switch {
NotEnoughLevelException _ => new (HttpStatusCode.BadRequest, "NotEnoughLevel"),
_ => new (HttpStatusCode.InternalServerError, "Unknown")
}, showException: false);
app.MapPost("/parties/{partyId}/guests", (string partyId, \[FromQuery\] bool? loungeAccess, Guest visitor) => {
if (loungeAccess == true && !visitor.Vip)
throw new NotEnoughLevelException();
return new Ticket(
PartyId: partyId,
Receiver: visitor.Name,
LoungeAccess: loungeAccess ?? false,
Code: Guid.NewGuid().ToString()
);
});
app.Run();
public record Guest(string Name, bool Vip);
public record Ticket(string PartyId, string Receiver, bool LoungeAccess, string Code);
public class NotEnoughLevelException : Exception;
让我们测试一下,从一个普通的请求开始:
POST https://localhost:5244/parties/new-year/guests?loungeAccess=true
{
"name": "Paul",
"vip" : true
}
以下是我们得到的日志:
现在让我们转向一个“坏”请求。
POST https://localhost:5244/parties/halloween/guests?loungeAccess=true
{
"name": "John",
"vip" : false
}
我们将得到:
正如你所看到的,虽然我们记录了很多东西,但我们不能真正说太多关于请求的信息。我们可能会弄清楚命中了哪个端点以及我们的响应是否成功(如果我们使用 http 状态代码),但仅此而已。此外,在高负载环境中,匹配请求和响应可能是一个挑战,因为它们是单独记录的。因此,让我们看看我们可以记录什么。代码如下:CombineLogs
builder.Services.AddHttpLogging(o => {
o.CombineLogs = true;
o.LoggingFields = HttpLoggingFields.All | HttpLoggingFields.RequestQuery;
});
HttpLoggingFields.All是一种谎言,它在日志中指定:“HttpRequest.QueryString 不包含在此标志中,因为它可能包含私人信息”。因此,我们需要手动附加 RequestQuery
这就是我们现在得到的:
这要好得多,现在我们可以看到到底收到了什么,响应了什么,以及处理花费了多少时间。然而,日志仍然让人感到不知所措——我们记录了很多标头,这似乎没有任何价值。让我们只留下我们需要的东西:
builder.Services.AddHttpLogging(o => {
o.CombineLogs = true;
o.LoggingFields = HttpLoggingFields.RequestQuery
| HttpLoggingFields.RequestMethod
| HttpLoggingFields.RequestPath
| HttpLoggingFields.RequestBody
| HttpLoggingFields.ResponseStatusCode
| HttpLoggingFields.ResponseBody
| HttpLoggingFields.Duration;
});
现在,我们将以更紧凑的格式获得几乎相同数量的有用信息:
但是,我们所拥有的仍然存在一些问题:
幸运的是,我们有一个 nuget 包,它涵盖了内置库中的盲点。让我们来看看。首先,我们需要安装它:
dotnet add package Nist.Logging
然后只更改 3 行:
using Nist.Logs;
// remove builder.Services.AddHttpLogging
app.UseHttpIOLogging(); // instead of app.UseHttpLogging();
我们得到了最少但功能齐全的日志:
虽然, 仍然记录发生的异常,但我们不再需要依赖它,因为我们在日志记录中间件中记录了异常。您甚至可以 将 with 静音ExceptionHandlerMiddlewareExceptionHandlerMiddleware"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None"appsettings.json
总而言之,让我们看一下我们能够获得的最佳请求-响应日志记录:
尽管由于 .NET 6 提供了开箱即用的广泛 http 日志记录功能,但仍缺少一些功能。因此,我们改用了 nuget 包。下表总结了库的功能列表:Microsoft.AspNetCoreNist.Logs
| Feature | Microsoft.AspNetCore.HttpLogging | Nist.Logs |
|--------------------|--------------------------------------|-----------|
| Full Uri | ❌ - Only Path and Query Separately | ✅ |
| Minimalism | ❌ - Few unremovable redundant fields | ✅ |
| Exception attached | ❌ | ✅ |
| Endpoint id | ❌ | ✅ |
| Single Line | ✅ | ✅ |
| Http Method | ✅ | ✅ |
| Request Body | ✅ | ✅ |
| Response Body | ✅ | ✅ |
| Response Code | ✅ | ✅ |
| Request Duration | ✅ | ✅ |
为了快速参考,这是最后一张:Program.cs
using Microsoft.AspNetCore.Mvc;
using Nist.Logs;
using Nist.Errors;
using System.Net;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpIOLogging();
app.UseErrorBody\<Error>(ex => ex switch {
NotEnoughLevelException _ => new (HttpStatusCode.BadRequest, "NotEnoughLevel"),
_ => new (HttpStatusCode.InternalServerError, "Unknown")
}, showException: false);
app.MapPost("/parties/{partyId}/guests", (string partyId, [FromQuery] bool? loungeAccess, Guest visitor) => {
if (loungeAccess == true && !visitor.Vip)
throw new NotEnoughLevelException();
return new Ticket(
PartyId: partyId,
Receiver: visitor.Name,
LoungeAccess: loungeAccess ?? false,
Code: Guid.NewGuid().ToString()
);
});
app.Run();
public record Guest(string Name, bool Vip);
public record Ticket(string PartyId, string Receiver, bool LoungeAccess, string Code);
public class NotEnoughLevelException : Exception;