应用服务器通常对第三方服务具有网络依赖性。Http 可能是用于此类服务器到服务器交互的最常见协议。
当然,对进出应用服务器的数据进行遥测对于诊断、故障排除和审计目的至关重要。
由于日志记录代码通常是一个跨领域的关注点,因此最好引入面向方面的编程,以避免在整个代码库中编织嘈杂的日志记录代码。本文探讨了如何设置Serilog以自动记录所有 http 调用及其相应的响应。
1. 安装软件包
Serilog — 主要日志记录框架
Serilog.Formatting.Compact — 用于日志的简单 json 格式化程序
Serilog.Sinks.Console — 用于 serilog 将日志输出到控制台的接收器
Serilog.Sinks.AzureEventHub — 用于 serilog 将日志输出到 AzureEventHub 实例的接收器
Flurl.Http — 流行的 http 客户端库
*关于 serilog 接收器的说明。在此示例中,我们将配置 Serilog 以使用控制台接收器作为本地开发环境,以及用于本地开发之上的环境 AzureEventHub。
有各种各样的接收器可用。您最终使用哪个接收器可能会受到更广泛的解决方案架构和支持目标接收器的资源可用性的影响。
2. ISerilogFactory
Serilog.ILogger 是用于记录事件的主要接口。我们将引入 ISerilogFactory 接口来配置和实例化 ILogger 对象。
using Serilog;
using Serilog.Formatting.Compact;
using Serilog.Sinks.SystemConsole.Themes;
public interface ISerilogFactory
{
ILogger Build();
}
ISerilogFactory 的实现与使用哪些 Serilog 接收器以及您希望如何配置 Serilog密切相关。因此,您的 SerilogFactory.cs 版本可能与此示例不同。
using Serilog;
using Serilog.Formatting.Compact;
using Serilog.Sinks.SystemConsole.Themes;
public class SerilogFactory : ISerilogFactory
{
private readonly bool _writeToConsole;
private readonly string _eventHubConnectionString;
private readonly string _eventHubEntity;
private readonly ILogger _logger;
public SerilogFactory(
bool writeToConsole,
string eventHubConnString,
string eventHubEntity)
{
this._writeToConsole = writeToConsole;
this._eventHubConnectionString = eventHubConnString;
this._eventHubEntity = eventHubEntity;
var loggerConfig = new LoggerConfiguration();
// modify the behaviour of your logger in this region
// in accordance to your sinks and log strateg
#region configuration
if (this._writeToConsole)
{
loggerConfig.WriteTo.Console(
theme: AnsiConsoleTheme.Code);
}
if (!this._writeToConsole &&
!string.IsNullOrEmpty(this._eventHubConnectionString) &&
!string.IsNullOrEmpty(this._eventHubEntity))
{
loggerConfig.WriteTo.AzureEventHub(
new CompactJsonFormatter(),
this._eventHubConnectionString,
this._eventHubEntity);
}
#endregion
// singleton object assignment
this._logger = loggerConfig.CreateLogger();
}
public ILogger Build()
{
return this._logger;
}
}
3.IFurlTelemetryLogger
Flurl是一个方便的库,它建立在 .NET 的HttpClient 和相关类之上。Flurl 提供各种级别的抽象,同时仍提供对其行为的良好控制。HttpCall是 Flurl 用来封装其进行的每个 http 调用的详细信息的类。
HttpTelemetry.cs
在开始实现之前,请先创建自己的遥测类,以捕获我们感兴趣的 http 调用的各个方面。为简洁起见,此示例仅捕获一些基本细节。您可能希望根据自己的要求修改此类。
public class HttpTelemetry
{
public string AspNetCoreEnvironment { get; set; }
public string Endpoint { get; set; }
public bool Succeeded { get; set; }
public string RequestBody { get; set; }
public int? HttpStatusCode { get; set; }
public string ResponseBody { get; set; }
}
FlurlClientTelemetryLogger 的构造函数接受 ISerilogFactory 并调用 .Build() 来为我们提供单例 ILogger 对象。
它还接受一个额外的“env”参数,用于标识特定日志源自哪个环境。
您可能希望使用不同的参数修改构造函数,以使用不是从 HttpCall 派生的自定义详细信息来扩充日志。
using Flurl.Http;
using Newtonsoft.Json;
using Serilog;
using System;
using System.Linq;
using System.Threading.Tasks;
internal class FlurlTelemetryLogger : IFlurlTelemetryLogger
{
private readonly ILogger _logger;
private readonly string _aspNetCoreEnvironment;
internal FlurlClientTelemetryLogger(
ISerilogFactory factory,
string env)
{
this._logger = factory.Build();
this._aspNetCoreEnvironment = env;
}
public async Task Log(HttpCall call)
{
var flurlTelemetry = new HttpTelemetry
{
AspNetCoreEnvironment = this._aspNetCoreEnvironment,
Endpoint = call.ToString(),
Succeeded = call.Succeeded,
RequestBody = call.RequestBody,
HttpStatusCode = call.HttpStatus.HasValue ?
(int)call.HttpStatus : default(int?)
};
if (call.Response != null &&
call.Response.Content != null)
{
var ct = call.Response.Content.Headers.ContentType.ToString();
// avoid logging file downloads
if (ct.ToLower().Contains("application/json") ||
ct.ToLower().Contains("text/plain"))
{
flurlTelemetry.ResponseBody = await call.Response.Content.ReadAsStringAsync();
}
}
this._logger.Information("{@FlurlTelemetry}",
JsonConvert.SerializeObject(flurlTelemetry));
}
}
4. 连接一切
从这里开始,注册类以将日志记录框架放置到位就相当简单了。创建一个名为AddFlurlTelemetry的扩展方法并从Startup类中调用它。
public static IServiceCollection AddFlurlTelemetry(
this IServiceCollection services,
IHostingEnvironment env,
string eventHubConnectionString,
string eventHubEntity)
{
var writeToConsole = env.IsDevelopment();
services.AddSingleton<ISerilogFactory>(sp =>
new SerilogFactory(
writeToConsole,
eventHubConnectionString,
eventHubEntity));
services.AddSingleton<IFlurlTelemetryLogger>(sp =>
new FlurlClientTelemetryLogger(
sp.GetRequiredService<ISerilogFactory>(),
env));
services.AddSingleton<IFlurlClient>(sp =>
{
var client = new FlurlClient("https://exampleapi.com");
var logger = sp.GetRequiredService<IFlurlTelemetryLogger>();
client.Configure(settings =>
{
settings.AfterCallAsync = (httpCall) =>
{
logger.log(httpCall);
}
});
return client;
});
}
5. 查看遥测的实际效果
要查看日志,请按常规将 flurl 客户端注入到控制器中,然后在操作方法中使用客户端进行调用
public class TestController : ControllerBase
{
private readonly IFlurlClient _client;
public TestController(IFlurlClient client)
{
this._client = client;
}
public async Task TestActionAsync()
{
await this._client.GetJsonAsync("/dummy");
}
}
在本地运行您的应用程序,您应该会看到 Serilog 自动将您的 flurl 遥测数据转储到命令提示符中。显然,控制台接收器在您的实际应用程序服务器中没有用处。这就是您需要修改 SerilogFactory 以使用不同的接收器的地方。
6. 后续步骤
本文介绍了设置 .NET Core Web 应用程序以自动记录通过Flurl客户端进行的 http 遥测的基本方面。
设置实际的日志记录框架以捕获和查询遥测数据超出了本文的范围。
无论您最终使用哪种解决方案,都请考虑日志记录堆栈需要处理的数量、频率和查询支持。
最后,Serilog 和 Flurl 都是灵活的库,它们的优点是可以抽象出较低级别的问题,同时仍保留大量可扩展点供您插入应用程序代码。
此示例的目的是演示将日志记录逻辑合并到 .NET Core 应用程序中的技术,而不会干扰您的常规应用程序逻辑。
此技术有许多调整和变化,您对 Serilog 和 Flurl 库越熟悉,您就越能够采用此技术以最适合您的应用程序。
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。