Skip to Main Menu

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.

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

Get Amongst It!!

comments powered by Disqus