ASP.NET Core  

Building Custom Middleware for API Request and Response Logging in ASP.NET Core

Introduction

In modern web applications, monitoring API requests and responses is essential for debugging, auditing, and performance optimization. ASP.NET Core provides a powerful middleware pipeline, making it easy to create custom middleware that can intercept HTTP traffic.

In this article, we’ll walk through building a custom middleware for request and response logging — from basic setup to production-ready enhancements like exception handling and structured logging.

What is Middleware?

Middleware in ASP.NET Core is software that sits in the request-response pipeline. Each middleware component:

  • Processes incoming requests,

  • Optionally modifies or logs them,

  • And can pass them along to the next middleware in the pipeline.

By placing custom logic here, you can centrally handle tasks like logging, authentication, error handling, or response modification.

Step 1: Setting Up the Middleware Class

Let’s create a middleware called RequestResponseLoggingMiddleware.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text;
using System.Threading.Tasks;

public class RequestResponseLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestResponseLoggingMiddleware> _logger;

    public RequestResponseLoggingMiddleware(RequestDelegate next, ILogger<RequestResponseLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Log Request
        context.Request.EnableBuffering();
        var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
        context.Request.Body.Position = 0;

        _logger.LogInformation("Incoming Request: {method} {url} \nBody: {body}",
            context.Request.Method, context.Request.Path, requestBody);

        // Capture Response
        var originalBodyStream = context.Response.Body;
        using var responseBody = new MemoryStream();
        context.Response.Body = responseBody;

        await _next(context); // Call next middleware

        // Log Response
        context.Response.Body.Seek(0, SeekOrigin.Begin);
        var responseText = await new StreamReader(context.Response.Body).ReadToEndAsync();
        context.Response.Body.Seek(0, SeekOrigin.Begin);

        _logger.LogInformation("Response: {statusCode}\nBody: {body}",
            context.Response.StatusCode, responseText);

        await responseBody.CopyToAsync(originalBodyStream);
    }
}

Step 2: Registering Middleware in Program.cs

Add the custom middleware in the HTTP pipeline:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Register the middleware
app.UseMiddleware<RequestResponseLoggingMiddleware>();

app.MapControllers();
app.Run();

By adding it before app.MapControllers(), all API requests will pass through the logging middleware.

Step 3: Output Example

Request Log Example:

Incoming Request: POST /api/orders
Body: {"customerId":123,"amount":250.75}

Response Log Example:

Response: 200
Body: {"orderId":987,"status":"Confirmed"}

Step 4: Enhancing the Middleware

a. Log Only JSON or Specific Endpoints

You can add conditions to reduce noise in logs:

if (!context.Request.Path.StartsWithSegments("/api"))
    return;

b. Add Correlation ID

Add a unique request ID for tracing distributed logs:

var correlationId = Guid.NewGuid().ToString();
context.Response.Headers.Add("X-Correlation-ID", correlationId);
_logger.LogInformation("Correlation ID: {id}", correlationId);

c. Use Structured Logging (Serilog / Seq)

Integrate Serilog for advanced visualization and filtering:

_logger.LogInformation("Request {@Request}", new {
    context.Request.Method,
    context.Request.Path,
    Body = requestBody
});

Step 5: Security and Performance Considerations

  • Avoid logging sensitive data such as passwords or tokens.

  • Use configuration-based logging levels — for instance, log full body only in Development mode.

  • Optimize large responses — log only partial data for large payloads.

Step 6: Example of Production-Ready Middleware Registration

if (app.Environment.IsDevelopment())
{
    app.UseMiddleware<RequestResponseLoggingMiddleware>();
}
else
{
    app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), 
        appBuilder => appBuilder.UseMiddleware<RequestResponseLoggingMiddleware>());
}

This ensures that detailed logs are only captured in development or for specific routes in production.

Conclusion

Custom middleware is one of the most powerful features of ASP.NET Core, enabling centralized and reusable handling of common cross-cutting concerns.
By implementing a Request and Response Logging Middleware, you gain deep visibility into API traffic, simplify debugging, and establish a strong foundation for observability.

For production environments, pair this approach with tools like Serilog, Seq, or Application Insights for structured, queryable, and scalable log management.