HttpClient
Logging
RequestResponse
HTTP
Programming

Logging request/response messages when using HttpClient

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The clean way to log HttpClient traffic in .NET is to place logging in a DelegatingHandler. That gives you one central point for requests, responses, timings, and failures instead of scattering logging code through every service method that makes an HTTP call.

Why a Handler Is the Right Place

HttpClient sends requests through a handler pipeline. A logging handler can:

  • inspect the outgoing request
  • call the inner handler
  • inspect the response
  • measure elapsed time
  • log exceptions

That is much cleaner than adding ad hoc logging in every repository or service class that uses the client.

A Basic Logging Handler

Here is a minimal handler using ILogger:

csharp
1using System;
2using System.Diagnostics;
3using System.Net.Http;
4using System.Threading;
5using System.Threading.Tasks;
6using Microsoft.Extensions.Logging;
7
8public sealed class LoggingHandler : DelegatingHandler
9{
10    private readonly ILogger<LoggingHandler> _logger;
11
12    public LoggingHandler(ILogger<LoggingHandler> logger)
13    {
14        _logger = logger;
15    }
16
17    protected override async Task<HttpResponseMessage> SendAsync(
18        HttpRequestMessage request,
19        CancellationToken cancellationToken)
20    {
21        var stopwatch = Stopwatch.StartNew();
22
23        _logger.LogInformation("HTTP {Method} {Uri}", request.Method, request.RequestUri);
24
25        try
26        {
27            var response = await base.SendAsync(request, cancellationToken);
28            stopwatch.Stop();
29
30            _logger.LogInformation(
31                "HTTP {Method} {Uri} returned {StatusCode} in {ElapsedMs} ms",
32                request.Method,
33                request.RequestUri,
34                (int)response.StatusCode,
35                stopwatch.ElapsedMilliseconds
36            );
37
38            return response;
39        }
40        catch (Exception ex)
41        {
42            stopwatch.Stop();
43            _logger.LogError(
44                ex,
45                "HTTP {Method} {Uri} failed after {ElapsedMs} ms",
46                request.Method,
47                request.RequestUri,
48                stopwatch.ElapsedMilliseconds
49            );
50            throw;
51        }
52    }
53}

This already captures the most useful operational data: method, URI, status code, duration, and failure details.

Register It With IHttpClientFactory

The handler is most useful when attached through IHttpClientFactory.

csharp
1builder.Services.AddTransient<LoggingHandler>();
2
3builder.Services.AddHttpClient("WeatherApi", client =>
4{
5    client.BaseAddress = new Uri("https://api.example.com/");
6})
7.AddHttpMessageHandler<LoggingHandler>();

This keeps logging behavior consistent and avoids duplicating setup across manually created HttpClient instances.

Log Bodies Carefully

Request and response bodies can be useful during development, but they also create risk and overhead.

csharp
1if (request.Content is not null)
2{
3    var body = await request.Content.ReadAsStringAsync(cancellationToken);
4    _logger.LogDebug("Request body: {Body}", body);
5}

Only do this when it is truly needed. Logging bodies can:

  • increase memory use
  • slow the request path
  • expose sensitive data
  • cause trouble with very large payloads

In many production systems, body logging should be disabled or heavily constrained.

Redact Sensitive Data

HTTP logging becomes dangerous quickly if secrets are written to logs. Headers such as Authorization, cookies, tokens, or personal data in JSON payloads should be redacted.

csharp
1foreach (var header in request.Headers)
2{
3    if (string.Equals(header.Key, "Authorization", StringComparison.OrdinalIgnoreCase))
4    {
5        _logger.LogDebug("Header {Header}: <redacted>", header.Key);
6    }
7    else
8    {
9        _logger.LogDebug("Header {Header}: {Value}", header.Key, string.Join(",", header.Value));
10    }
11}

Safe logging is as important as complete logging.

Distinguish Response Errors From Transport Failures

A 500 response is not the same as a timeout or DNS failure. Good logging keeps those cases separate:

  • non-success HTTP responses still return an HttpResponseMessage
  • transport or connection failures throw exceptions

That distinction matters when debugging service issues and when building alerts from logs.

Add Correlation Data

Outbound HTTP logs become much more useful when they carry correlation information such as:

  • request ID
  • target service name
  • logical operation name
  • elapsed time

This is especially helpful when one incoming request triggers several outbound HTTP calls. Without correlation, the logs are much harder to reconstruct into a useful timeline.

Common Pitfalls

The biggest mistake is logging in every API method instead of once in a handler. That duplicates code and usually misses cross-cutting behavior in the pipeline.

Another common issue is logging request and response bodies indiscriminately. That creates performance cost and privacy risk very quickly.

People also forget to redact secrets, especially authorization headers and tokens in payloads.

Finally, do not rely only on exceptions. Non-success status codes often carry the most important diagnostic information even when no exception is thrown.

Summary

  • Use a DelegatingHandler to centralize HttpClient request and response logging.
  • Register the handler through IHttpClientFactory for consistent behavior.
  • Capture method, URI, status code, elapsed time, and failures as a baseline.
  • Log bodies only when necessary, and redact sensitive data aggressively.
  • Keep transport failures and HTTP response errors distinct in your logs.

Course illustration
Course illustration

All Rights Reserved.