Exploring Serilog v2 - Using the HTTP Client Factory
This is the next post in a series re-discovering Serilog v2.
Recently, the new HTTP Client Factory was announced as apart of the ASP.NET Core 2.1 release. There have been a number of posts relating to how it can be hooked with ASP.Net.
- ASP.NET Core 2.1-preview1: Introducing HTTPClient factory
- HttpClientFactory for typed HttpClient instances in ASP.NET Core 2.1
In this post, I wanted to illustrate how you can use this new package whilst swapping in Serilog’s superior logging capabilites. The new HTTP Client Factory aids in the lifecycle and scope when using HTTP Client.
First create a new console app:
dotnet new console -n example-console
cd example-console
- Edit the
.csproj
file to get the latest preview packages. (This will change when ASP.NET Core 2.1 is RTM)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0-preview1-34029" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0-preview1-34029" />
<PackageReference Include="Microsoft.Extensions.Http" Version="2.2.0-preview1-34029" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
</ItemGroup>
<PropertyGroup Label="RestoreSources">
<RestoreSources>$(DotNetRestoreSources)</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true' AND '$(AspNetUniverseBuildOffline)' != 'true' ">
$(RestoreSources);
https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
</RestoreSources>
<RestoreSources Condition="'$(DotNetBuildOffline)' != 'true'">
$(RestoreSources);
https://api.nuget.org/v3/index.json;
</RestoreSources>
</PropertyGroup>
</Project>
Update the Program.cs
as follows to setup Serilog with the HTTP Client Factory.
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
namespace httpclientfactory
{
class Program
{
// Sweet C# 7.1... Thanks @dennisroche
static async Task Main()
{
await Go();
}
static async Task Go()
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
var services = new ServiceCollection()
.AddLogging(builder =>
{
// Add Serilog
builder.AddSerilog();
});
// Register a HTTP Client
services.AddHttpClient<GoogleClient>();
var serviceProvider = services.BuildServiceProvider();
// This is the Microsoft Logging interface
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
logger.LogInformation("We are using Serilog!");
// Get a HTTP Client and make a request
var google = serviceProvider.GetRequiredService<GoogleClient>();
await google.Get();
}
}
class GoogleClient
{
HttpClient HttpClient { get; }
public GoogleClient(HttpClient client)
{
HttpClient = client;
HttpClient.BaseAddress = new Uri("https://google.com.au");
}
public async Task<HttpResponseMessage> Get()
{
var request = new HttpRequestMessage(HttpMethod.Get, "/");
var response = await HttpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return response;
}
}
}
When we run the app, the default middleware will take effect and push logs through to Serilog.
[11:40:40 INF] We are using Serilog!
[11:40:40 INF] Start processing HTTP request GET https://google.com.au/
[11:40:40 INF] Sending HTTP request GET https://google.com.au/
[11:40:43 INF] Received HTTP response after 2469.583ms - OK
[11:40:43 INF] End processing HTTP request after 2499.582ms - OK
As previously with HttpClient
a handler can be used to extend behaviour. Below is a simple handler, that drops down to the core Serilog.ILogger
interface.
public class SerilogHandler : DelegatingHandler
{
private Serilog.ILogger _logger;
public SerilogHandler(Serilog.ILogger logger)
{
_logger = logger;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
_logger.Debug("Request completed: {route} {method} {code} {headers}", request.RequestUri.Host, request.Method, response.StatusCode, request.Headers);
return response;
}
}
Now if Serilog is already referenced via dependency injection, you can extend a HTTP Client as follows by registering the handler.
services.AddTransient<SerilogHandler>();
services.AddHttpClient<GoogleClient>()
.AddHttpMessageHandler<SerilogHandler>();
Then when the app is run, we can see both the default and Serilog specfic handling.
[12:43:40 INF] We are using Serilog!
[12:43:40 INF] Start processing HTTP request GET https://google.com.au/
[12:43:40 INF] Sending HTTP request GET https://google.com.au/
[12:43:42 DBG] Received HTTP response after 1877.623ms - OK
[12:43:42 INF] Request completed: www.google.com.au GET OK []
[12:43:42 INF] End processing HTTP request after 1916.045ms - OK