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();
         }
    }
}

我想这样的处理应该是比较优(麻)雅(烦)的。

posted @ 2022-05-08 22:38  whyfate  阅读(155)  评论(0编辑  收藏  举报