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
HttpClientconfigurations -
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()- extendsIHttpClientBuilderfor 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 -
Authorizationheader is redacted by default -
Configurable redaction - additional headers like
X-Api-Key,Cookiecan 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:
-
Informationlog level -
Request/response headers logged
-
Request/response bodies excluded
-
Authorizationheader 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 (
AuthorizationvsX-Api-Key) -
Proper scheme (
BearervsBasic) -
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:
-
Enable logging for the API client
-
Run the application
-
Review console output for the HTTP exchange
-
Copy the request to curl or Postman for refinement
-
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:
-
Registration -
AddHttpRawMessageLogging()registers the handler in theHttpClientpipeline -
Interception - When a request is made,
SendAsync()is called on the handler -
Request Capture - Request message is serialized to raw HTTP format
-
Timing -
Stopwatchmeasures request duration -
Execution - Request proceeds through remaining handlers to actual HTTP call
-
Response Capture - Response message is serialized to raw HTTP format
-
Logging - Request and response are logged with duration
-
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:
-
GitHub Repository: https://github.com/alyiox/Alyio.Extensions.Http.Logging
-
NuGet Package: https://www.nuget.org/packages/Alyio.Extensions.Http.Logging
-
License: MIT
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.