1

I have Asp.Net.Core app in which there have to be some logging, today I tried to add exception handling middleware. I've used UseExceptionHandler which is from asp.net Diagonstics. The problem was that when my logging middleware was after ExceptionHandler middleware

app.UseExceptionHandler("/error");

app.UseSerilogMiddleware();

It wasnot redirectig me to my custom Error page when there was some exception.

public async Task Invoke(HttpContext httpContext)
        {
            if (httpContext == null)
            {
                Log.Write(LogEventLevel.Fatal, "No HTTP context found!");
            }

            HttpRequest request = httpContext.Request;
            var logger = GetExtendedLogger(request);

            var stopwatchStart = Stopwatch.GetTimestamp();
            try
            {
                var originalResponseBody = httpContext.Response.Body;
                using (MemoryStream stream = new MemoryStream())
                {
                    **httpContext.Response.Body = stream;**

                    await _next(httpContext);

                    stream.Seek(0, SeekOrigin.Begin);
                    var responseBody = new StreamReader(stream).ReadToEnd();

                    var elapsedMs = GetElapsedMilliseconds(stopwatchStart, Stopwatch.GetTimestamp());
                    var statusCode = httpContext.Response?.StatusCode;

                    var level = GetLogEventLevel(statusCode);

                    var log = Log
                    .ForContext("RequestHeaders", httpContext.Request.Headers.ToDictionary(h => h.Key, h => h.Value.ToString()), destructureObjects: true)
                    .ForContext("RequestHost", httpContext.Request.Host)
                    .ForContext("RequestProtocol", httpContext.Request.Protocol);
                    logger.Write(level, MessageTemplate, httpContext.Request.Method, httpContext.Request.Path, statusCode, elapsedMs, responseBody);

                    stream.Seek(0, SeekOrigin.Begin);
                    await stream.CopyToAsync(originalResponseBody);
                }

            }
            // Never caught, because LogException() returns false.
            catch (Exception ex) when (AddExceptionLogEntry(logger, httpContext, GetElapsedMilliseconds(stopwatchStart, Stopwatch.GetTimestamp()), ex))
            {
            }
        }' 

This is part of the logging middleware, however I found out what was causing the problem but I don't have explanation for it. The problem is in this line httpContext.Response.Body = stream; when I remove seting the responce body to the stream everything works fine.

P.S. The logging method is copy pasted from here

1 Answer 1

1

TL;DR: Don't write to the stream, before UseExceptionHandler otherwise the error middleware can't write redirect headers.

stream.Seek(0, SeekOrigin.Begin);
await stream.CopyToAsync(originalResponseBody);

You are writing to the response stream, before the exception middleware is called, so a redirect can't work. A redirect in HTTP is a response which returns http code 301 or 302 with a Location: http://example.com header with the new url.

But when you write to the stream, the headers are already flushed and can't be changed anymore.

Sign up to request clarification or add additional context in comments.

4 Comments

I see.. but why removing httpContext.Response.Body = stream; this line where the Body is setting to the newly created stream fixes that problem?
It's simple: Because if you remove httpContext.Response.Body = stream;, the underlying middleware never write anything into the memory stream, so the memory stream is EMPTY. When you return to your middleware, await stream.CopyToAsync(originalResponseBody); also do not write anything because it contains 0 bytes of data, so nothing is ever send over the wire to the client, which in the end allows the Exception Middleware to write it's header
In conclusion: You will have to split your middleware into two. First middleware which uses try/catch with AddExceptionLogEntry which is registered AFTER the exception middleware and another middlware which uses the memory stream and logs the successful calls which you place BEFORE the exception middleware
I think this response is misleading although the conclusions are correct. Yes, your logging middleware should be the first one in the chain because exception handler can alter the response which you want to log. But I think it doesn't do it by returning redirect headers. I just checked in my test app and there was just one HTTP call and no change of URL. I think redirecting to error controller is done internally. The error controller probably writes its own content to the response, so writing it in the logging middleware is a bad idea anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.