2014-08-30 99 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); 
    } 
}