如何自动记录 .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,那么您可以尝试继续这样做...
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();
}
我希望自动记录每个请求。在之前的.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,那么您可以尝试继续这样做...
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();
}