以前 .NET Framework WebApi 記錄接口訪問日志,一般是通過Filter的方式進行攔截,通過重寫ActionFilterAttribute的OnActionExecuting實現攔截記錄Request內容,通過重寫OnActionExecuted實現攔截記錄Response內容,具體實現代碼就不貼了。這篇簡單介紹.Net Core WebApi 下通過中間件的攔截方式記錄接口訪問日志,關鍵部分是通過讀取獲取 Request.Body 時需要開啟 Request.EnableRewind () 啟用倒帶功能;讀取 Response.Body 時需要用到的技巧,詳細看代碼。該例子中我使用的日志組件是Log4Net,獲取到的信息通過Log4Net保存到本地文件。
創建日志類
using System; using System.Collections.Generic; using System.Linq; namespace DYDGame.Web.Host { public class RequestResponseLog { public string Url {get;set;} public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>(); public string Method { get; set; } public string RequestBody { get; set; } public string ResponseBody { get; set; } public DateTime ExcuteStartTime { get; set; } public DateTime ExcuteEndTime { get; set; } public override string ToString() { string headers = "[" + string.Join(",", this.Headers.Select(i => "{" + $"\"{i.Key}\":\"{i.Value}\"" + "}")) + "]"; return $"Url: {this.Url},\r\nHeaders: {headers},\r\nMethod: {this.Method},\r\nRequestBody: {this.RequestBody},\r\nResponseBody: {this.ResponseBody},\r\nExcuteStartTime: {this.ExcuteStartTime.ToString("yyyy-MM-dd HH:mm:ss.fff")},\r\nExcuteStartTime: {this.ExcuteEndTime.ToString("yyyy-MM-dd HH:mm:ss.fff")}"; } } }
創建記錄接口日志中間件 Middleware
using System; using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Text; using System.Threading; using DYDGame.Utility; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; namespace DYDGame.Web.Host { public class RequestResponseLoggingMiddleware { private readonly RequestDelegate _next; private RequestResponseLog _logInfo; public RequestResponseLoggingMiddleware (RequestDelegate next) { _next = next; } public async Task Invoke (HttpContext context) { _logInfo = new RequestResponseLog (); HttpRequest request = context.Request; _logInfo.Url = request.Path.ToString (); _logInfo.Headers = request.Headers.ToDictionary (k => k.Key, v => string.Join (";", v.Value.ToList ())); _logInfo.Method = request.Method; _logInfo.ExcuteStartTime = DateTime.Now; //獲取request.Body內容 if (request.Method.ToLower ().Equals ("post")) { request.EnableRewind (); //啟用倒帶功能,就可以讓 Request.Body 可以再次讀取 Stream stream = request.Body; byte[] buffer = new byte[request.ContentLength.Value]; stream.Read (buffer, 0, buffer.Length); _logInfo.RequestBody = Encoding.UTF8.GetString (buffer); request.Body.Position = 0; } else if (request.Method.ToLower ().Equals ("get")) { _logInfo.RequestBody = request.QueryString.Value; } //獲取Response.Body內容 var originalBodyStream = context.Response.Body; using (var responseBody = new MemoryStream ()) { context.Response.Body = responseBody; await _next (context); _logInfo.ResponseBody = await FormatResponse (context.Response); _logInfo.ExcuteEndTime = DateTime.Now; Log4Net.LogInfo ($"VisitLog: {_logInfo.ToString()}"); await responseBody.CopyToAsync (originalBodyStream); } } private async Task<string> FormatResponse (HttpResponse response) { response.Body.Seek (0, SeekOrigin.Begin); var text = await new StreamReader (response.Body).ReadToEndAsync (); response.Body.Seek (0, SeekOrigin.Begin); return text; } } public static class RequestResponseLoggingMiddlewareExtensions { public static IApplicationBuilder UseRequestResponseLogging (this IApplicationBuilder builder) { return builder.UseMiddleware<RequestResponseLoggingMiddleware> (); } } }
把中間件添加到管道中 Pipeline
在 Startup.cs 添加
public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment ()) { app.UseDeveloperExceptionPage (); } else { app.UseHsts (); } loggerFactory.AddLog4Net (); app.UseRequestResponseLogging(); // app.UseHttpsRedirection(); app.UseMvc (); }