Created
January 9, 2015 08:03
-
-
Save coenm/c997a6f645d9a31acd98 to your computer and use it in GitHub Desktop.
Owin Middleware Logging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Threading.Tasks; | |
using Microsoft.Owin; | |
using NLog; // in this example, we use NLog | |
namespace net.github.gist.coenm | |
{ | |
public class LoggingMiddleware : OwinMiddleware | |
{ | |
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); | |
public LoggingMiddleware(OwinMiddleware next) : base(next) | |
{ } | |
public override async Task Invoke(IOwinContext context) | |
{ | |
LogRequestResponseHelper.LogDebugRequest(Logger, context.Request); | |
var responseBody = ""; | |
if(Logger.IsTraceEnabled) //use trace for logging the response | |
{ | |
using (var captureResponseBody = new CaptureResponseBody(context)) | |
{ | |
await Next.Invoke(context); | |
responseBody = await captureResponseBody.GetBody(); | |
} | |
} | |
else | |
await Next.Invoke(context); | |
LogRequestResponseHelper.LogDebugResponse(Logger, context.Response); | |
if (Logger.IsTraceEnabled | |
&& !string.IsNullOrEmpty(context.Response.ContentType) && context.Response.ContentType.ToLower().StartsWith("application/json")) | |
LogRequestResponseHelper.LogTraceResponse(Logger, responseBody); | |
} | |
private class CaptureResponseBody : IDisposable | |
{ | |
// Response body is a write-only network stream by default for Katana hosts. | |
// You will need to replace context.Response.Body with a MemoryStream, | |
// read the stream, log the content and then copy the memory stream content | |
// back into the original network stream | |
private readonly Stream stream; | |
private readonly MemoryStream buffer; | |
public CaptureResponseBody(IOwinContext context) | |
{ | |
stream = context.Response.Body; | |
buffer = new MemoryStream(); | |
context.Response.Body = buffer; | |
} | |
public async Task<string> GetBody() | |
{ | |
buffer.Seek(0, SeekOrigin.Begin); | |
var reader = new StreamReader(buffer); | |
return await reader.ReadToEndAsync(); | |
} | |
public async void Dispose() | |
{ | |
await GetBody(); | |
// You need to do this so that the response we buffered | |
// is flushed out to the client application. | |
buffer.Seek(0, SeekOrigin.Begin); | |
await buffer.CopyToAsync(stream); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Microsoft.Owin; | |
using NLog; // Using NLog in this example | |
namespace com.github.gist.coenm | |
{ | |
public static class LogRequestResponseHelper | |
{ | |
public static void LogDebugResponse(Logger logger, IOwinResponse response) | |
{ | |
if (!logger.IsDebugEnabled) | |
return; | |
MappedDiagnosticsContext.Clear(); | |
MappedDiagnosticsContext.Set("response.StatusCode", response.StatusCode.ToString()); | |
logger.Debug(String.Format("Response statuscode:'{0}'.", response.StatusCode)); | |
MappedDiagnosticsContext.Clear(); | |
} | |
public static void LogTraceResponse(Logger logger, string body) | |
{ | |
if (!logger.IsTraceEnabled) | |
return; | |
logger.Trace("Response body: {0}", body); | |
} | |
public static void LogDebugRequest(Logger logger, IOwinRequest request) | |
{ | |
if (!logger.IsDebugEnabled) | |
return; | |
MappedDiagnosticsContext.Clear(); | |
MappedDiagnosticsContext.Set("request.MediaType", request.MediaType); | |
MappedDiagnosticsContext.Set("request.Host", request.Host.ToString()); | |
MappedDiagnosticsContext.Set("request.ContentType", request.ContentType); | |
MappedDiagnosticsContext.Set("request.Scheme", request.Scheme); | |
MappedDiagnosticsContext.Set("request.Method", request.Method); | |
MappedDiagnosticsContext.Set("request.Path", request.Path.ToString()); | |
MappedDiagnosticsContext.Set("request.QueryString", request.QueryString.ToString()); | |
MappedDiagnosticsContext.Set("request.Accept", request.Accept); | |
var logMsg = string.Format("Request scheme:'{0}'; method:'{1}'; path:'{2}'; query:'{3}'; accept:'{4}'", | |
request.Scheme, | |
request.Method, | |
request.Path, | |
request.QueryString, | |
request.Accept); | |
logger.Debug(logMsg); | |
MappedDiagnosticsContext.Clear(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
using System.Net.Http.Formatting; | |
using System.Web.Http; | |
using Owin; | |
namespace com.github.gist.coen | |
{ | |
public class Startup | |
{ | |
private readonly Container container = new Container(); | |
public void Configuration(IAppBuilder appBuilder) | |
{ | |
// .... | |
appBuilder.Use<LoggingMiddleware>(); | |
// .... | |
} | |
} | |
} |
I noticed that the async
Dispose
is not a good idea. If too much requests come through eventually you get IO Exceptions. A "safer" way is to only have a method that will do what Dispose
does that is also async void
but you rather use try / finally
block instead of a using
block in the Task Invoke(IOwinContext context)
method.
You also don't need that GetBody
call inside the Dispose
method. It just a MemoryStream
which is already populated with data from other Middlewares so calling it will make 0 difference.
@dandrejvv - Please never use async void
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, great work. Helped a lot to resolve my problem. But one question:
LoggingMiddleware.cs Line 64, why do we need this in Dispose?