CODE FARM
Galaxy background

"The wise man speaks because he has something to say, the fool because he has to say something."

- Plato

Raw HTTP Message Logging for .NET HttpClient

1. Introduction

Modern .NET applications frequently communicate with external services over HTTP, making observability of these interactions critical for development and debugging. This article explores a lightweight approach to capturing raw HTTP messages in protocol format, providing developers with immediate visibility into their HTTP traffic without external tooling.

1.1. The HTTP Visibility Problem

Debugging HTTP integrations in .NET applications often requires seeing the exact messages being sent and received over the wire. While IHttpClientFactory provides excellent patterns for managing HttpClient instances, and ASP.NET Core offers built-in HTTP logging middleware, these solutions have limitations for certain debugging scenarios.

The built-in HTTP logging uses structured logging with key-value pairs, which is excellent for telemetry and analytics. However, when troubleshooting API integrations during development, seeing the raw HTTP protocol format - headers and body as they appear on the wire - is often more valuable. This format matches what developers see in tools like Postman, Fiddler, or browser DevTools, making it easier to diagnose issues quickly.

1.2. Why Lightweight Logging Matters

High-level observability tools like OpenTelemetry provide comprehensive telemetry for production systems, but they can be heavyweight for local development troubleshooting. A lightweight solution that outputs raw HTTP messages directly to console or debug logs fills a critical gap:

  • Immediate feedback during development without external tools

  • Easy reproduction by copying HTTP messages to curl or Postman

  • Quick diagnosis of header mismatches, authentication issues, or payload problems

  • Universal format that matches API documentation and can be shared with third-party support teams

2. Solution: DelegatingHandler for HTTP Observability

The Alyio.Extensions.Http.Logging library addresses the HTTP visibility problem by implementing a custom DelegatingHandler that integrates seamlessly with .NET’s IHttpClientFactory infrastructure. This section examines the architectural approach and core components.

2.1. Architecture Overview

The solution leverages the DelegatingHandler pattern within the IHttpClientFactory pipeline to intercept HTTP messages. This approach provides several advantages:

  • Non-invasive integration - works seamlessly with existing HttpClient configurations

  • Pipeline-aware - executes in the correct order with other handlers

  • Lifecycle managed - benefits from `IHttpClientFactory’s connection pooling and handler recycling

  • DI-friendly - fully integrated with .NET dependency injection

The DelegatingHandler pattern allows message handlers to be chained together, with each handler able to inspect and modify requests before they’re sent and responses after they’re received. This makes it ideal for cross-cutting concerns like logging.

2.2. Key Components

The library consists of three primary components:

HttpRawMessageLoggingHandler

A DelegatingHandler implementation that intercepts HTTP messages and logs them in raw protocol format. The handler:

  • Captures request messages before they’re sent

  • Measures request duration with high precision

  • Captures response messages after receipt

  • Handles errors gracefully and logs request context on failure

HttpRawMessageLoggingOptions

Configuration options that control logging behavior:

  • LogLevel - minimum log level for HTTP message logging

  • CategoryName - custom logger category for filtering

  • IgnoreRequestContent/IgnoreResponseContent - toggle body logging

  • IgnoreRequestHeaders/IgnoreResponseHeaders - exclude specific headers

  • RedactRequestHeaders/RedactResponseHeaders - mask sensitive header values

Extension Methods

Clean API surface for registration:

  • AddHttpRawMessageLogging() - extends IHttpClientBuilder for easy integration

  • Works with both global and named client configurations

3. Key Features

The library provides three essential capabilities that distinguish it from existing logging solutions: authentic protocol format capture, security-conscious defaults, and flexible configuration options. Each feature addresses specific challenges in HTTP debugging workflows.

3.1. Raw Request/Response Capture

The library captures HTTP messages in authentic protocol format, not structured logs. This produces output like:

GET /data/2.5/weather?q=London,uk&appid=*** HTTP/1.1
Host: samples.openweathermap.org
Authorization: ***
User-Agent: dotnet-docs

HTTP/1.1 200 OK
Server: openresty/1.9.7.1
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked

{"coord":{"lon":-0.13,"lat":51.51},"weather":[...]...}

This format is immediately recognizable and can be used directly with HTTP testing tools. The raw format shows:

  • Complete HTTP message structure

  • Actual header names and values

  • Request/response body content

  • Protocol version and status codes

3.2. Security-First with Auto-Redaction

Security is built into the default configuration. Sensitive headers are automatically redacted to prevent accidental exposure of credentials in logs:

  • Default redaction - Authorization header is redacted by default

  • Configurable redaction - additional headers like X-Api-Key, Cookie can be added

  • Header filtering - specific headers can be excluded entirely from logs

  • Content control - request and response bodies are excluded by default to prevent PII leakage

Example configuration with security controls:

builder.Services.AddHttpClient("SecureClient")
    .AddHttpRawMessageLogging(options =>
    {
        options.RedactRequestHeaders = new[] {
            "Authorization",
            "X-Api-Key",
            "Cookie"
        };
        options.IgnoreRequestContent = true;  // No request body logging
        options.IgnoreResponseContent = false; // Response body OK to log
    });

3.3. Flexible Configuration

The library supports both global and named client configurations, allowing fine-grained control over logging behavior:

Global Configuration

Apply logging to all HTTP clients in the application:

builder.Services.ConfigureHttpClientDefaults(builder =>
{
    builder.AddHttpRawMessageLogging(options =>
    {
        options.Level = LogLevel.Information;
        options.IgnoreRequestContent = false;
        options.IgnoreResponseContent = false;
    });
});

Named Client Configuration

Target specific clients for detailed logging:

builder.Services
    .AddHttpClient<IWeatherService, WeatherService>(client =>
    {
        client.BaseAddress = new Uri("https://api.openweathermap.org");
    })
    .AddHttpRawMessageLogging(options =>
    {
        options.CategoryName = "WeatherApi";
        options.Level = LogLevel.Debug;
    });

The named client approach is particularly useful when debugging specific API integrations while keeping other HTTP traffic quiet.

4. Getting Started

Integration of the library into existing .NET applications requires minimal setup. The following sections demonstrate installation, basic configuration, and common usage patterns.

4.1. Installation and Basic Usage

Installation from NuGet is straightforward:

dotnet add package Alyio.Extensions.Http.Logging

Minimal setup requires just one line:

builder.Services.AddHttpClient<IApiService, ApiService>()
    .AddHttpRawMessageLogging();

This enables logging with secure defaults:

  • Information log level

  • Request/response headers logged

  • Request/response bodies excluded

  • Authorization header redacted

Configure logging level in appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "System.Net.Http.HttpClient": "Information"
    }
  }
}

4.2. Configuration Options

For more control, configure options during registration:

builder.Services.AddHttpClient("MyApi")
    .AddHttpRawMessageLogging(options =>
    {
        // Custom logger category for filtering
        options.CategoryName = "MyApi.HttpLogs";

        // Log level
        options.Level = LogLevel.Debug;

        // Include message bodies
        options.IgnoreRequestContent = false;
        options.IgnoreResponseContent = false;

        // Exclude noisy headers
        options.IgnoreRequestHeaders = new[] { "User-Agent", "Accept-Encoding" };

        // Redact sensitive headers
        options.RedactRequestHeaders = new[] {
            "Authorization",
            "X-Api-Key"
        };
    });

Example output with full configuration:

info: MyApi.HttpLogs[0]
      Request-Queue: 1
info: MyApi.HttpLogs[0]
      Request-Message:

      POST /api/users HTTP/1.1
      Host: api.example.com
      Content-Type: application/json
      Authorization: ***
      X-Api-Key: ***

      {"name":"John Doe","email":"john@example.com"}

info: MyApi.HttpLogs[0]
      Response-Message: 234ms

      HTTP/1.1 201 Created
      Content-Type: application/json
      Location: /api/users/12345

      {"id":12345,"name":"John Doe","email":"john@example.com"}

5. Real-World Troubleshooting

Raw HTTP logging proves most valuable in practical debugging scenarios. The following examples demonstrate how protocol-level visibility solves common integration problems encountered during development.

5.1. Debugging Third-Party APIs

Missing Headers

When integrating with third-party APIs, header mismatches are common. Raw logging reveals exactly what’s being sent:

POST /api/orders HTTP/1.1
Host: partner-api.example.com
Content-Type: application/json
Authorization: Bearer ***

The API documentation might require an additional X-Correlation-Id header. With raw logging, the absence is immediately visible, unlike structured logs where missing headers might not be obvious.

Payload Mismatches

API integrations often fail due to subtle payload differences. Raw logging shows the exact JSON structure being sent:

{
  "order_date": "2025-12-18T17:02:12+08:00",
  "items": [...]
}

If the API expects orderDate (camelCase) instead of order_date (snake_case), the raw format makes this immediately apparent. The logged request can be copied directly into Postman to verify the issue.

Authentication Problems

OAuth and API key authentication failures are easier to diagnose with raw protocol logs. The complete request shows:

  • Token format and structure (redacted but visible)

  • Correct header name (Authorization vs X-Api-Key)

  • Proper scheme (Bearer vs Basic)

  • Request signing headers if required

5.2. Local Development and Testing

Integration Testing

During development, raw HTTP logs provide valuable feedback without external monitoring tools:

[Fact]
public async Task Should_Send_Correct_Headers()
{
    // Arrange - logging is enabled in test setup
    var client = _factory.CreateClient();

    // Act
    var response = await client.GetAsync("/api/users");

    // Assert - check console output for raw HTTP messages
    response.Should().BeSuccessful();
}

The test output includes complete HTTP exchanges, making it easy to verify behavior without debugger breakpoints.

API Prototyping

When building new API integrations, raw logging accelerates development:

  1. Enable logging for the API client

  2. Run the application

  3. Review console output for the HTTP exchange

  4. Copy the request to curl or Postman for refinement

  5. Iterate quickly without switching tools

Request Queue Monitoring

The library logs the number of active requests, helping identify concurrent request issues:

info: HttpClient.MyApi[0]
      Request-Queue: 3

This simple metric can reveal request throttling or connection pool exhaustion during development.

6. Implementation Insights

Understanding the internal mechanics of the library provides context for its performance characteristics and design decisions. This section explores key implementation details and framework compatibility considerations.

6.1. How It Works

The implementation follows these steps:

  1. Registration - AddHttpRawMessageLogging() registers the handler in the HttpClient pipeline

  2. Interception - When a request is made, SendAsync() is called on the handler

  3. Request Capture - Request message is serialized to raw HTTP format

  4. Timing - Stopwatch measures request duration

  5. Execution - Request proceeds through remaining handlers to actual HTTP call

  6. Response Capture - Response message is serialized to raw HTTP format

  7. Logging - Request and response are logged with duration

  8. Error Handling - Exceptions are caught and logged with request context

Key implementation details:

Conditional Logging

The handler checks log level before processing to avoid performance impact when logging is disabled:

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request,
    CancellationToken cancellationToken)
{
    if (_logger.IsEnabled(_loggingOptions.Level))
    {
        return SendCoreAsync(request, cancellationToken);
    }
    else
    {
        return base.SendAsync(request, cancellationToken);
    }
}

Async Stream Processing

Request and response bodies are read asynchronously without buffering entire content:

public static async Task<string> ReadRawMessageAsync(
    this HttpRequestMessage request,
    bool ignoreContent,
    string[] ignoreHeaders,
    string[] redactHeaders,
    CancellationToken cancellationToken)
{
    // Stream processing implementation
}

Header Redaction

Sensitive headers are replaced with * during serialization:

if (redactHeaders.Contains(header.Key, StringComparer.OrdinalIgnoreCase))
{
    sb.Append("***");
}

6.2. Multi-Framework Support

The library targets multiple .NET versions to support a wide range of applications:

  • .NET 6.0 - LTS support through November 2024

  • .NET 8.0 - LTS support through November 2026

  • .NET 9.0 - STS support through May 2025

  • .NET 10.0 - Latest features and performance improvements

Multi-targeting configuration in the project file:

<TargetFrameworks>net6.0;net8.0;net9.0;net10.0</TargetFrameworks>

This ensures compatibility with:

  • Legacy applications on .NET 6

  • Long-term support deployments on .NET 8

  • Modern applications on .NET 9 and 10

The library uses only stable APIs available across all targeted frameworks, ensuring consistent behavior regardless of runtime version.

7. Conclusion

Raw HTTP message logging bridges the gap between high-level telemetry and low-level network debugging. By providing HTTP protocol format logs through a lightweight DelegatingHandler, developers gain immediate visibility into HTTP traffic during local development and testing.

The library is particularly valuable when:

  • Debugging third-party API integrations

  • Developing new HTTP-based services

  • Troubleshooting authentication or header issues

  • Needing copy-paste-friendly HTTP messages for testing tools

  • Working without access to network inspection tools

Security-first defaults ensure sensitive data is protected, while flexible configuration allows detailed logging when needed. Integration with IHttpClientFactory provides a familiar, .NET-idiomatic API that works seamlessly with existing code.

Project Resources:

Comparison with Other Tools:

Tool Use Case Format

ASP.NET Core HTTP Logging

Server-side structured telemetry

Key-value pairs

Alyio.Extensions.Http.Logging

Client-side development debugging

Raw HTTP protocol

OpenTelemetry

Production observability

Distributed tracing

Fiddler/Postman

External network inspection

Raw HTTP protocol

The library complements existing tools by providing raw HTTP visibility directly in application logs, optimized for the development and troubleshooting workflow.