2014-08-30 143 views
5

我正在嘗試編寫一個Owin midleware組件,它將記錄每個傳入請求和對數據庫的響應。請求/響應的響應正文日誌記錄

這裏是我設法得到多遠。

我在閱讀response.body時被卡住了。說:

流不支持閱讀。

如何閱讀Response.Body?

public class LoggingMiddleware : OwinMiddleware 
{ 
     private static Logger log = LogManager.GetLogger("WebApi"); 

     public LoggingMiddleware(OwinMiddleware next, IAppBuilder app) 
      : base(next) 
     { 
     } 

    public override async Task Invoke(IOwinContext context) 
    { 
     using (var db = new HermesEntities()) 
     { 

      var sw = new Stopwatch(); 
      sw.Start(); 

      var logRequest = new log_Request 
      { 
       Body = new StreamReader(context.Request.Body).ReadToEndAsync().Result, 
       Headers = Json.Encode(context.Request.Headers), 
       IPTo = context.Request.LocalIpAddress, 
       IpFrom = context.Request.RemoteIpAddress, 
       Method = context.Request.Method, 
       Service = "Api", 
       Uri = context.Request.Uri.ToString(), 
       UserName = context.Request.User.Identity.Name 

      }; 
      db.log_Request.Add(logRequest); 
      context.Request.Body.Position = 0; 

      await Next.Invoke(context); 

      var mem2 = new MemoryStream(); 
      await context.Response.Body.CopyToAsync(mem2); 

      var logResponse = new log_Response 
      { 
       Headers = Json.Encode(context.Response.Headers), 
       Body = new StreamReader(mem2).ReadToEndAsync().Result, 
       ProcessingTime = sw.Elapsed, 
       ResultCode = context.Response.StatusCode, 
       log_Request = logRequest 
      }; 

      db.log_Response.Add(logResponse); 

      await db.SaveChangesAsync(); 
     } 
    } 
} 
+0

你得到這個工作? – 2014-11-18 21:59:17

+0

有點愚蠢的工作,但不是我最初想要的。總之 - 沒有 – Marty 2014-11-18 22:08:20

+2

這解決它對我來說:http://stackoverflow.com/questions/26214113/how-can-i-safely-intercept-the-response-stream-in-a-custom-owin-middleware – 2014-11-18 22:18:20

回答

7

對於Katana主機,Response body是默認的只寫網絡流。您需要將其替換爲MemoryStream,讀取流,記錄內容,然後將內存流內容複製回原始網絡流。順便說一句,如果你的中間件讀取請求體,下游組件不能,除非請求體被緩衝。因此,您可能還需要考慮緩衝請求主體。如果你想看一些代碼,http://lbadri.wordpress.com/2013/08/03/owin-authentication-middleware-for-hawk-in-thinktecture-identitymodel-45/可能是一個起點。看看HawkAuthenticationHandler

+0

我試圖用內存流替換身體,但然後該API停止工作。沒有結果返回。任何想法爲什麼? – Marty 2014-10-04 17:06:01

+2

確保將內存流中的內容重新插入網絡流中。否則,客戶不會收到任何迴應。在我給出的博客文章中,看看'TeardownCore'。 – Badri 2014-10-06 04:30:12

7

響應體可以以這種方式被記錄:

public class LoggingMiddleware : OwinMiddleware 
{ 
    private static Logger log = LogManager.GetLogger("WebApi"); 

    public LoggingMiddleware(OwinMiddleware next, IAppBuilder app) 
     : base(next) 
    { 
    } 

    public override async Task Invoke(IOwinContext context) 
    { 
     using (var db = new HermesEntities()) 
     { 

      var sw = new Stopwatch(); 
      sw.Start(); 

      var logRequest = new log_Request 
      { 
       Body = new StreamReader(context.Request.Body).ReadToEndAsync().Result, 
       Headers = Json.Encode(context.Request.Headers), 
       IPTo = context.Request.LocalIpAddress, 
       IpFrom = context.Request.RemoteIpAddress, 
       Method = context.Request.Method, 
       Service = "Api", 
       Uri = context.Request.Uri.ToString(), 
       UserName = context.Request.User.Identity.Name 
      }; 

      db.log_Request.Add(logRequest); 
      context.Request.Body.Position = 0; 

      Stream stream = context.Response.Body; 
      MemoryStream responseBuffer = new MemoryStream(); 
      context.Response.Body = responseBuffer; 

      await Next.Invoke(context); 

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

      //do logging 

      var logResponse = new log_Response 
      { 
       Headers = Json.Encode(context.Response.Headers), 
       Body = responseBody, 
       ProcessingTime = sw.Elapsed, 
       ResultCode = context.Response.StatusCode, 
       log_Request = logRequest 
      }; 

      db.log_Response.Add(logResponse); 

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

      await db.SaveChangesAsync(); 
     } 
    } 
} 
1

我已經通過應用action屬性寫入請求的身體OWIN環境字典解決了這個問題。之後,日誌記錄中間件可以通過密鑰訪問它。

public class LogResponseBodyInterceptorAttribute : ActionFilterAttribute 
{ 
    public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) 
    { 
     if (actionExecutedContext?.Response?.Content is ObjectContent) 
     { 
      actionExecutedContext.Request.GetOwinContext().Environment["log-responseBody"] = 
       await actionExecutedContext.Response.Content.ReadAsStringAsync(); 
     } 
    } 
} 

然後在中間件:

public class RequestLoggingMiddleware 
{ 
    ... 
    private void LogResponse(IOwinContext owinContext) 
    { 
     var message = new StringBuilder() 
      .AppendLine($"{owinContext.Response.StatusCode}") 
      .AppendLine(string.Join(Environment.NewLine, owinContext.Response.Headers.Select(x => $"{x.Key}: {string.Join("; ", x.Value)}"))); 

     if (owinContext.Environment.ContainsKey("log-responseBody")) 
     { 
      var responseBody = (string)owinContext.Environment["log-responseBody"]; 
      message.AppendLine() 
       .AppendLine(responseBody); 
     } 

     var logEvent = new LogEventInfo 
     { 
      Level = LogLevel.Trace, 
      Properties = 
      { 
       {"correlationId", owinContext.Environment["correlation-id"]}, 
       {"entryType", "Response"} 
      }, 
      Message = message.ToString() 
     }; 

     _logger.Log(logEvent); 
    } 
}