12

I want to log all HTTP requests in a dotnet core 2.1 application. Logging should include HTTP headers, body and the host address. I need to bind my logging code globally without changing the existing code.

I tried this example https://www.azurefromthetrenches.com/capturing-and-tracing-all-http-requests-in-c-and-net/, but no HTTP event came to the Listener.

Is there any way to listen to HTTP events on dotnet core 2.1 globally?

cengaver
  • 302
  • 1
  • 2
  • 13
  • Are you hosting on IIS? Can you enable the logging there? – ste-fu Jul 09 '19 at 08:24
  • @ste-fu It works on a container that I have no access – cengaver Jul 09 '19 at 08:25
  • You can use a [Middleware](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.2). – Rabban Jul 09 '19 at 08:26
  • You must create an instance of that `HttpEventListener` class referenced in your example. I have used this method in the past and works great, also for .NET Core. – bartbje Jul 09 '19 at 08:40
  • @bartbje I did, but no events came to OnEventWritten method. Which version did you used? Is it possible .NET core 2.1 lack this feature? ( as mentioned here https://medium.com/criteo-labs/c-in-process-clr-event-listeners-with-net-core-2-2-ef4075c14e87 ) – cengaver Jul 09 '19 at 08:48
  • @cengaver, just to be clear, that article describes a way to log outgoing HTTP traffic, not incoming. Are you looking to log incoming, or outgoing? – bartbje Jul 09 '19 at 08:51
  • 1
    @bartbje outgoing traffic. I also need to log the response – cengaver Jul 09 '19 at 08:52
  • @cengaver, they did not remove it, as Application Insights for example, is able to capture this traffic without modifying the existing application. [Here](https://github.com/microsoft/ApplicationInsights-dotnet-server/blob/develop/Src/DependencyCollector/Shared/Implementation/FrameworkHttpEventListener.cs) is the source code, might help you out. - updated the url to the actual EventListener. – bartbje Jul 09 '19 at 08:54
  • @cengaver, these type of EventListeners will not provide you with the headers and body details, as these are not part of the event. I guess the only options for that would depend on how you do the request. Assuming you use `HttpClient` check [this](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.1#outgoing-request-middleware) out. It might require slight modification of your application though. – bartbje Jul 09 '19 at 09:12

4 Answers4

12

This is a good blog post on HttpClient logging in .Net Core 2.1 by Steve Gordon.

In essence, you need to set the logging level of System.Net.Http.HttpClient to Trace to get detailed information about requests and response.

A sample of the required section in your appsettings.json is below:

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

This will show all trace logging for all HttpClient requests and responses.

Jacques Snyman
  • 4,115
  • 1
  • 29
  • 49
  • 1
    This does nothing at all – Arrow_Raider Jun 24 '21 at 20:55
  • Is your logging configured correctly? See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0 – Jacques Snyman Jun 29 '21 at 06:14
  • There is something else that needs to be set in the code.. I always forget before this will work ... so annoying cause this is the best solution instead of middleware.. I have every thing setup and console logging.. but it doesnt show the http client traces.. what was needed.. a stupid little line of code – Piotr Kula Oct 26 '22 at 10:48
  • Apparently in NET6 there's a new way - https://josef.codes/asp-net-core-6-http-logging-log-requests-responses - but have to set in appsetting `"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"` I dont know why your solution does not work in my new project,, when my old project migrated to net6 logs all the requests this way.. – Piotr Kula Oct 26 '22 at 10:57
5

You can log all http request informations in middleware. Take a look at the example below

1.Create a class RequestHandlerMiddleware.cs

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

namespace Onsolve.ONE.WebApi.Middlewares
{
    public sealed class RequestHandlerMiddleware
    {
        private readonly RequestDelegate next;
        private readonly ILogger logger;

        public RequestHandlerMiddleware(ILogger<RequestHandlerMiddleware> logger, RequestDelegate next)
        {
            this.next = next;
            this.logger = logger;
        }

        public async Task Invoke(HttpContext context)
        {
            logger.LogInformation($"Header: {JsonConvert.SerializeObject(context.Request.Headers, Formatting.Indented)}");

            context.Request.EnableBuffering();
            var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
            logger.LogInformation($"Body: {body}");
            context.Request.Body.Position = 0;

            logger.LogInformation($"Host: {context.Request.Host.Host}");
            logger.LogInformation($"Client IP: {context.Connection.RemoteIpAddress}");
            await next(context);
        }

    }
}

2.Add RequestHandlerMiddleware to Configure method in Startup.cs

app.UseMiddleware<RequestHandlerMiddleware>();

or simpler

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
    app.Use(async (context, next) =>
    {
        logger.LogInformation($"Header: {JsonConvert.SerializeObject(context.Request.Headers, Formatting.Indented)}");

        context.Request.EnableBuffering();
        var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
        logger.LogInformation($"Body: {body}");
        context.Request.Body.Position = 0;

        logger.LogInformation($"Host: {context.Request.Host.Host}");
        logger.LogInformation($"Client IP: {context.Connection.RemoteIpAddress}");
        await next.Invoke();
    });
}

Reference:

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.2

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-2.2

Hung Quach
  • 2,079
  • 2
  • 17
  • 28
1

You need to use Observer design pattern and the hooks provided by .NET to observe all http requests.

  • DiagnosticSource
  • EventSource

This is a good blog post

Heidar Ahmadi
  • 174
  • 1
  • 6
-1

Tried below easier solution which worked without tampering with final response flow

Create middleware class to intercept all requests and responses. Enable middleware class association in startup.cs

app.UseMiddleware<HttpRequestResponseLogger>();

Implement a middleware class to intercept request and response . optionally you can store these logs in a database. I am ignoring secrets and unnecessary header values

 public class HttpRequestResponseLogger
{
    RequestDelegate next;

    public HttpRequestResponseLogger(RequestDelegate next)
    {
        this.next = next;
    }
    //can not inject as a constructor parameter in Middleware because only Singleton services can be resolved
    //by constructor injection in Middleware. Moved the dependency to the Invoke method
    public async Task InvokeAsync(HttpContext context, IHttpLogRepository repoLogs)
    {
        HttpLog logEntry = new HttpLog();
        await RequestLogger(context, logEntry);
        
        await next.Invoke(context);

        await ResponseLogger(context, logEntry);

        //store log to database repository
        repoLogs.SaveLog(logEntry);
    }

    // Handle web request values
    public async Task RequestLogger(HttpContext context, HttpLog log)
    {
        string requestHeaders = string.Empty;

        log.RequestedOn = DateTime.Now;
        log.Method = context.Request.Method;
        log.Path = context.Request.Path;
        log.QueryString = context.Request.QueryString.ToString();
        log.ContentType = context.Request.ContentType;

        foreach (var headerDictionary in context.Request.Headers)
        {
            //ignore secrets and unnecessary header values
            if (headerDictionary.Key != "Authorization" && headerDictionary.Key != "Connection" &&
                headerDictionary.Key != "User-Agent" && headerDictionary.Key != "Postman-Token" &&
                headerDictionary.Key != "Accept-Encoding")
            {
                requestHeaders += headerDictionary.Key + "=" + headerDictionary.Value + ", ";
            }
        }

        if (requestHeaders != string.Empty)
            log.Headers = requestHeaders;

        //Request handling. Check if the Request is a POST call 
        if (context.Request.Method == "POST")
        {
            context.Request.EnableBuffering();
            var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
            context.Request.Body.Position = 0;
            log.Payload = body;
        }
    }

    //handle response values
    public async Task ResponseLogger(HttpContext context, HttpLog log)
    {
        using (Stream originalRequest = context.Response.Body)
        {
            try
            {
                using (var memStream = new MemoryStream())
                {
                    context.Response.Body = memStream;
                    // All the Request processing as described above 
                    // happens from here.
                    // Response handling starts from here
                    // set the pointer to the beginning of the 
                    // memory stream to read
                    memStream.Position = 0;
                    // read the memory stream till the end
                    var response = await new StreamReader(memStream)
                        .ReadToEndAsync();
                    // write the response to the log object
                    log.Response = response;
                    log.ResponseCode = context.Response.StatusCode.ToString();
                    log.IsSuccessStatusCode = (
                        context.Response.StatusCode == 200 ||
                        context.Response.StatusCode == 201);
                    log.RespondedOn = DateTime.Now;

                    // since we have read till the end of the stream, 
                    // reset it onto the first position
                    memStream.Position = 0;

                    // now copy the content of the temporary memory 
                    // stream we have passed to the actual response body 
                    // which will carry the response out.
                    await memStream.CopyToAsync(originalRequest);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            finally
            {
                // assign the response body to the actual context
                context.Response.Body = originalRequest;
            }
        }

    }
rajquest
  • 535
  • 1
  • 5
  • 10