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

时间:2017-08-03 08:40:37

标签: c# asp.net-web-api asp.net-core .net-core log4net

我希望自动记录每个请求。在之前的.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项目中,Global.asax WebApiConfig

上没有GlobalConfiguration.Configure(WebApiConfig.Register);或注册函数

那么有没有办法在.NET Core WebAPI中实现这一目标?

5 个答案:

答案 0 :(得分:16)

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

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

良好的视觉效果explanationenter image description here

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

来自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.
        });

答案 1 :(得分:7)

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

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

  

您可以全局注册过滤器(适用于所有控制器和操作)   通过将其添加到MvcOptions.Filters集合中   Startup类中的ConfigureServices方法:

告诉我们是否有效。

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

答案 2 :(得分:1)

演示:

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

    //....
}

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

对于需要快速(非常)肮脏的解决方案以进行调试的人(可在.Net Core 3上运行),这里是this answer的扩展,这就是您所需要的...

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