My current project has lots of outgoing requests to third parties including HTTP and SOAP requests. I was tasked to log all outgoing requests in their raw form. In this post, I’m going to show my implementation for logging outgoing HTTP requests.
I’m using .Net Core 2.1 and HttpClientFactory for this solution. I could use typed client only for my solution but that means my HTTP client will use the same instance if its used in background processing console app since it gets injected only once and its not per request. Using the same instance is not a recommended way of using HttpClient as shown here, here and here. So I decided to use named client and somewhat typed client at the same time, then create a new client every time we need it.
The interface of the HTTP client
public interface IOutgoingHttpClient { Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequestMessage); }
Its implementation
public class OutgoingHttpClient : IOutgoingHttpClient { private readonly IHttpClientFactory _httpClientFactory; public const string HttpClientName = "OutgoingHttpClient"; public OutgoingHttpClient(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public Task<HttpResponseMessage> SendAsync(HttpRequestMessage httpRequestMessage) { var httpClient = _httpClientFactory.CreateClient(HttpClientName); return httpClient.SendAsync(httpRequestMessage); } }
I then have a delegating handler which will intercept all outgoing requests and log them
public class OutgoingHttpClientLoggingHandler : DelegatingHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var body = request.Content == null ? null : await request.Content.ReadAsStringAsync(); var response = await base.SendAsync(request, cancellationToken); var responseContent = response.Content == null ? null : await response.Content.ReadAsStringAsync(); var log = new { Url = request.RequestUri.AbsoluteUri, Headers = JsonConvert.SerializeObject(request.Headers.ToDictionary(h => h.Key, h => h.Value)), Body = body, Method = request.Method.Method, StatusCode = response.StatusCode, ReasonPhrase = response.ReasonPhrase, Response = responseContent, RequestDate = DateTime.UtcNow }; _ = Task.Run(async () => // Use Task.Run because we want to fire and forget and we don't want logging to make the call takes longer to run { try { // Log request and response here i.e Send message to queue, or log to a remote server } catch (Exception exception) { // Do nothing or log why the request log had an exception } }); return response; } }
Then this is how the delegate handler is registered to the OutgoingHttpClient and how the OutgoingHttpClient is registered to the DI.
public static IServiceCollection AddOutgoingHttpClient(this IServiceCollection services, IConfiguration configuration) { services.AddHttpClient(OutgoingHttpClient.HttpClientName) .AddHttpMessageHandler<OutgoingHttpClientLoggingHandler>(); services.AddTransient<IOutgoingHttpClient, OutgoingHttpClient>(); return services; }
To use this client, you just have to inject IOutgoingHttpClient via the constructor
[Route("api/[controller]")] public class TestController : Controller { private readonly IOutgoingHttpClient _outgoingHttpClient; public TestController(IOutgoingHttpClient outgoingHttpClient) { _outgoingHttpClient = outgoingHttpClient; } [HttpGet] public async Task<ActionResult> Get() { var httpRequestMessage = new HttpRequestMessage { RequestUri = new Uri("https://www.github.com"), Method = HttpMethod.Get }; var result = await _outgoingHttpClient.SendAsync(httpRequestMessage); return Ok(result); } }
Next post, I’ll show my implementation for logging all outgoing SOAP requests.