如何自动记录 .NET Core WebAPI 中的每个请求?

How to auto log every request in .NET Core WebAPI?

我希望自动记录每个请求。在之前的.Net Framwork WebAPI 项目中,我曾经注册过一个delegateHandler 来做到这一点。

WebApiConfig.cs

public static void Register(HttpConfiguration config)
{
    config.MessageHandlers.Add(new AutoLogDelegateHandler());
}

AutoLogDelegateHandler.cs

public class AutoLogDelegateHandler : DelegatingHandler
{

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var requestBody = request.Content.ReadAsStringAsync().Result;

        return await base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                HttpResponseMessage response = task.Result;

                //Log use log4net
                _LogHandle(request, requestBody, response);

                return response;
            });
    }
}

日志内容示例:

------------------------------------------------------
2017-08-02 19:34:58,840
uri: /emp/register
body: {
    "timeStamp": 1481013427,
    "id": "0322654451",
    "type": "t3",
    "remark": "system auto reg"
}
response: {"msg":"c556f652fc52f94af081a130dc627433","success":"true"}
------------------------------------------------------

但是在.NET Core WebAPI项目中,没有WebApiConfig,或者Global.asax处的注册函数 GlobalConfiguration.Configure(WebApiConfig.Register);

那么在 .NET Core WebAPI 中有什么方法可以实现吗?

您可以创建自己的过滤器属性...

public class InterceptionAttribute : ActionFilterAttribute
{
  public override void OnActionExecuting(HttpActionContext actionContext)
  {
    var x = "This is my custom line of code I need executed before any of the controller actions, for example log stuff";
    base.OnActionExecuting(actionContext);
  }
}

...您可以将其注册到 GlobalFilters,但既然您说您使用的是 .NET Core,那么您可以尝试继续这样做...

来自 docs.microsoft.com

You can register a filter globally (for all controllers and actions) by adding it to the MvcOptions.Filters collection in the ConfigureServices method in the Startup class:

让我们知道它是否有效。

P.S。 这是一个 whole tutorial on intercepting requests with WebAPI,以防有人需要更多详细信息。

演示:

AutologArribute.cs(新文件)

/// <summary>
/// <see cref="https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#Dependency injection"/>
/// </summary>
public class AutoLogAttribute : TypeFilterAttribute
    {
        public AutoLogAttribute() : base(typeof(AutoLogActionFilterImpl))
        {

        }

        private class AutoLogActionFilterImpl : IActionFilter
        {
            private readonly ILogger _logger;
            public AutoLogActionFilterImpl(ILoggerFactory loggerFactory)
            {
                _logger = loggerFactory.CreateLogger<AutoLogAttribute>();
            }

            public void OnActionExecuting(ActionExecutingContext context)
            {
                // perform some business logic work
            }

            public void OnActionExecuted(ActionExecutedContext context)
            {
                //TODO: log body content and response as well
                _logger.LogDebug($"path: {context.HttpContext.Request.Path}"); 
            }
        }
    }

StartUp.cs

public void ConfigureServices(IServiceCollection services)
{
    //....

    // https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#filter-scopes-and-order-of-execution
    services.AddMvc(opts=> {
        opts.Filters.Add(new AutoLogAttribute());
    });

    //....
}

ActionFilter 将一直工作,直到您需要记录 由 MVC 中间件处理的请求(作为控制器操作)。

如果您需要记录所有传入请求,则需要使用中间件方法。

视觉效果不错explanation

请注意中间件顺序很重要,如果您的日志记录应该在管道执行开始时完成,您的中间件应该是第一个。

来自 docs 的简单示例:

public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do loging
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

对于那些想要一个快速且(非常)肮脏的调试解决方案(适用于 .Net Core 3)的人来说,这里是 的扩展,这就是您所需要的...

app.Use(async (context, next) =>
{
    var initialBody = context.Request.Body;

    using (var bodyReader = new StreamReader(context.Request.Body))
    {
        string body = await bodyReader.ReadToEndAsync();
        Console.WriteLine(body);
        context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
        await next.Invoke();
        context.Request.Body = initialBody;
    }
});

这是 .NET Core 2.2 Web 的完整日志组件 API。 它将记录请求和响应,包括 Headers 和正文。 只要确保您有一个“日志”文件夹即可。

AutoLogMiddleWare.cs(新文件)

public class AutoLogMiddleWare
{
    private readonly RequestDelegate _next;

    public AutoLogMiddleWare(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            string route = context.Request.Path.Value;
            string httpStatus = "0";

            // Log Request
            var originalRequestBody = context.Request.Body;
            originalRequestBody.Seek(0, SeekOrigin.Begin);
            string requestBody = new StreamReader(originalRequestBody).ReadToEnd();
            originalRequestBody.Seek(0, SeekOrigin.Begin);

            // Log Response
            string responseBody = string.Empty;
            using (var swapStream = new MemoryStream())
            {

                var originalResponseBody = context.Response.Body;
                context.Response.Body = swapStream;
                await _next(context);
                swapStream.Seek(0, SeekOrigin.Begin);
                responseBody = new StreamReader(swapStream).ReadToEnd();
                swapStream.Seek(0, SeekOrigin.Begin);
                await swapStream.CopyToAsync(originalResponseBody);
                context.Response.Body = originalResponseBody;
                httpStatus = context.Response.StatusCode.ToString();
            }

            // Clean route
            string cleanRoute = route;
            foreach (var c in Path.GetInvalidFileNameChars())
            {
                cleanRoute = cleanRoute.Replace(c, '-');
            }

            StringBuilder sbRequestHeaders = new StringBuilder();
            foreach (var item in context.Request.Headers)
            {
                sbRequestHeaders.AppendLine(item.Key + ": " + item.Value.ToString());
            }

            StringBuilder sbResponseHeaders = new StringBuilder();
            foreach (var item in context.Response.Headers)
            {
                sbResponseHeaders.AppendLine(item.Key + ": " + item.Value.ToString());
            }


            string filename = DateTime.Now.ToString("yyyyMMdd.HHmmss.fff") + "_" + httpStatus + "_" + cleanRoute + ".log";
            StringBuilder sbLog = new StringBuilder();
            sbLog.AppendLine("Status: " + httpStatus + " - Route: " + route);
            sbLog.AppendLine("=============");
            sbLog.AppendLine("Request Headers:");
            sbLog.AppendLine(sbRequestHeaders.ToString());
            sbLog.AppendLine("=============");
            sbLog.AppendLine("Request Body:");
            sbLog.AppendLine(requestBody);
            sbLog.AppendLine("=============");
            sbLog.AppendLine("Response Headers:");
            sbLog.AppendLine(sbResponseHeaders.ToString());
            sbLog.AppendLine("=============");
            sbLog.AppendLine("Response Body:");
            sbLog.AppendLine(responseBody);
            sbLog.AppendLine("=============");

            var path = Directory.GetCurrentDirectory();
            string filepath = ($"{path}\Logs\{filename}");
            File.WriteAllText(filepath, sbLog.ToString());
        }
        catch (Exception ex)
        {
            // It cannot cause errors no matter what
        }

    }
}

public class EnableRequestRewindMiddleware
{
    private readonly RequestDelegate _next;

    public EnableRequestRewindMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        context.Request.EnableRewind();
        await _next(context);
    }
}

public static class EnableRequestRewindExtension
{
    public static IApplicationBuilder UseEnableRequestRewind(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<EnableRequestRewindMiddleware>();
    }
}

Startup.cs(现有文件)

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMapper mapper, ILoggerFactory loggerFactory)
{
    bool isLogEnabled = true; // Replace it by some setting, Idk

    if(isLogEnabled)
        app.UseEnableRequestRewind(); // Add this first

    (...)

    if(isLogEnabled)
        app.UseMiddleware<AutoLogMiddleWare>(); // Add this just above UseMvc()

    app.UseMvc();
}