asp.net core 在发生异常的时候打印Request信息
起因
众所周知打印日志是一门技术活,一个合格的开发者打印的日志信息是非常漂亮的,但是一些小萌新在打日志上可能就会存在疏忽,以至于在发生异常的时候可能会丢失掉重要的信息;
所以如果能在发生异常的时候将Request的信息打印出来供debug对于开发者来说就非常友好了。
解决
对于这个想法实现起来其实非常简单,只需要在请求处理之前添加一个全局异常处理的middleware,在catch到异常的时候打印请求信息即可;
public class ExceptionHandlerMiddleware
{
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch(Exception ex)
{
// 打印 Request 信息
}
}
}
那么为什么还要写一篇博客来讲这件事儿呢?
这里有一个小问题,那就是读取Request.Body的问题,我们知道Request.Body是不允许重复读取的(具体哪个版本开始不清楚,鄙人在遇到这个问题的时候用的.net core版本是3.1),
也就是说当Action中已经读取过Body了,就算之后发生了异常,在catch的时候也读不到Body的内容了。
想深入探究可看一念大佬的深入探究ASP.NET Core读取Request.Body的正确方式
那这还不简单,直接改成如下代码不就可以了;
public class ExceptionHandlerMiddleware
{
public async Task Invoke(HttpContext context)
{
try
{
context.Request.EnableBuffering();
await _next(context);
}
catch(Exception ex)
{
// 打印 Request 信息
}
}
}
是的没错这样就解决了读取Request.Body的问题了,但是这是不是最佳方案呢?
我想可能不是的,因为引入了context.Request.EnableBuffering()
之后,相当于就将Request.Body完全暴露给了下层方法,
而我们这个中间件的功能仅仅只是一个全局错误记录,如此的破坏Request.Body的是相当不合理的,那么应该如何做才不破坏Body原有的设计呢?
答案是代理模式
,构建一个Stream的代理,重写读取的方法。
internal class ProxyRequestStream : Stream
{
private Stream _innerStream;
public ProxyRequestStream(Stream innerStream)
{
_innerStream = innerStream;
}
public override bool CanRead => _innerStream.CanRead;
public override bool CanSeek => _innerStream.CanSeek;
public override bool CanWrite => _innerStream.CanWrite;
public override long Length => _innerStream.Length;
public override long Position
{
get => _innerStream.Position;
set => _innerStream.Position = value;
}
// 此处省略1w行
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
var len = await _innerStream.ReadAsync(buffer, cancellationToken);
// 可以在此处保存buffer
return len;
}
// 再次省略1w行
}
public class ExceptionHandlerMiddleware
{
public async Task Invoke(HttpContext context)
{
ProxyRequestStream proxy;
try
{
proxy = new ProxyRequestStream(context.Request.Body);
context.Request.Body = proxy;
await _next(context);
}
catch(Exception ex)
{
// 打印 Request 信息
}
finally
{
if(proxy != null)
proxy.Dispose();
}
}
}
我想这样的处理应该是比较优(麻)雅(烦)的。