请求/响应日志记录的响应主体
本文关键字:响应 主体 记录 日志 请求 | 更新日期: 2023-09-27 18:20:14
我正在尝试编写一个Owin midleware组件,该组件将记录每个传入的请求和对数据库的响应。
以下是我能走多远。
我一直在读回复。正文。说:
流不支持读取。
如何阅读回复正文?
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();
}
}
}
默认情况下,Katana主机的响应主体是一个只写的网络流。您需要将其替换为MemoryStream
,读取流,记录内容,然后将内存流内容复制回原始网络流。顺便说一句,如果中间件读取了请求体,那么下游组件就不能读取,除非请求体被缓冲。因此,您可能还需要考虑缓冲请求主体。如果您想查看一些代码,http://lbadri.wordpress.com/2013/08/03/owin-authentication-middleware-for-hawk-in-thinktecture-identitymodel-45/可能是一个起点。看看类别HawkAuthenticationHandler
。
响应主体可以通过以下方式记录:
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();
}
}
}
我已经通过将操作属性写入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);
}
}
如果您遇到Stream不支持读取的问题,在尝试多次读取请求正文时会发生错误,您可以尝试以下解决方法。
在Startup.cs文件中,添加以下中间件以启用请求主体的缓冲,这允许您出于日志目的重新读取请求主体:
app.Use(async (context, next) =>
{
using (var streamCopy = new MemoryStream())
{
await context.Request.Body.CopyToAsync(streamCopy);
streamCopy.Position = 0;
string body = new StreamReader(streamCopy).ReadToEnd();
streamCopy.Position = 0;
context.Request.Body = streamCopy;
await next();
}
});
该中间件创建请求主体流的副本,将整个流读取为字符串,将流位置设置回开头,将请求主体设置为复制的流,然后调用下一个中间件。
在这个中间件之后,您现在可以使用context.Request.Body.Position = 0;
将请求主体流的位置设置回开头,这样您就可以重新读取请求主体。
我希望这能有所帮助!