根據我的經驗,通常在API中記錄請求和響應。這樣做可以幫助開發人員調試問題並提供有價值的性能指標。在本教程中,我將介紹如何為ASP.NET Core 3 Web API創建基本的日志記錄解決方案。在這篇文章的結尾,我們將有一個有效的日志記錄解決方案,它將記錄每個請求以及對控制台和文件系統的響應,並且日志將包括API處理每個請求所花費的時間。以下是概述:
1. 先決條件
2. 創建RequestLog和ResponseLog模型
3. 創建ILogForWebAPI
4. 創建WebAPIConsoleLogger
5. 創建WebAPIFileLogger
6. 創建CustomLoggingMiddleware
7. 在啟動中添加自定義日志記錄,然后進行測試
先決條件
您應該熟悉 ASP.NET Core Web API請求管道。
首先,創建一個ASP.NET Core 3 Web API項目。
創建RequestLog和ResponseLog模型
這些類將攜帶我們要記錄的請求和響應數據。
1 public class RequestLog 2 { 3 public Guid Id { get; set; } 4 public string Action { get; set; } 5 public string URL { get; set; } 6 public string IPAddress { get; set; } 7 public DateTime TimeStampUtc { get; set; } 8 }
1 public class ResponseLog 2 { 3 public Guid Id { get; set; } 4 public string Action { get; set; } 5 public string URL { get; set; } 6 public int StatusCode { get; set; } 7 public long ResponseTimeInMilliseconds { get; set; } 8 public DateTime TimeStampUtc { get; set; } 9 }
創建ILogForWebAPI
在這里,我們創建了可以執行兩個操作的日志記錄抽象—日志記錄請求和日志記錄響應。
1 public interface ILogForWebAPIs 2 { 3 Task LogAsync(RequestLog requestLog); 4 Task LogAsync(ResponseLog responseLog); 5 }
創建WebAPIConsoleLogger
遵循單一職責原則(SRP),我們將創建ILogForWebAPI的兩個實現。WebAPIConsoleLogger將負責登錄到控制台,而WebAPIFileLogger將負責登錄到文件系統。我們可以使用Decorator Pattern在單個ILogForWebAPI實例中提供兩個記錄器的功能。每個ILogForWebAPIs實現都將包含ILogForWebAPIs的嵌套實例,如果該實例不為null,則將其調用。
1 public class WebAPIConsoleLogger : ILogForWebAPIs 2 { 3 private readonly ILogForWebAPIs _nextLogger; 4 private readonly string _dateTimeFormat = "hh:mm:ss tt"; 5 6 public WebAPIConsoleLogger(ILogForWebAPIs nextLogger = null) 7 { 8 _nextLogger = nextLogger; 9 } 10 11 public async Task LogAsync(RequestLog requestLog) 12 { 13 Console.WriteLine($"Request received from {requestLog.IPAddress} @ {requestLog.TimeStampUtc.ToString(_dateTimeFormat)} (Utc)"); 14 Console.WriteLine($"{requestLog.Action} {requestLog.URL}"); 15 Console.WriteLine(); 16 17 if (_nextLogger != null) 18 { 19 await _nextLogger.LogAsync(requestLog); 20 } 21 } 22 23 public async Task LogAsync(ResponseLog responseLog) 24 { 25 Console.WriteLine($"Response sent @ {responseLog.TimeStampUtc.ToString(_dateTimeFormat)} (Utc)"); 26 Console.WriteLine($"{responseLog.StatusCode}: {responseLog.Action} {responseLog.URL}"); 27 Console.WriteLine($"Response time: {responseLog.ResponseTimeInMilliseconds} ms"); 28 Console.WriteLine(); 29 30 if (_nextLogger != null) 31 { 32 await _nextLogger.LogAsync(responseLog); 33 } 34 } 35 }
創建WebAPIFileLogger
WebAPIFileLogger將序列化模型並將其ID用作文件名,從而為每個請求和響應創建一個json文件。
1 public class WebAPIFileLogger : ILogForWebAPIs 2 { 3 private readonly string _requestDirectory; 4 private readonly string _responseDirectory; 5 private readonly ILogForWebAPIs _nextLogger; 6 7 public WebAPIFileLogger(string path, ILogForWebAPIs nextLogger = null) 8 { 9 if (string.IsNullOrWhiteSpace(path)) 10 { 11 throw new ArgumentNullException(nameof(path)); 12 } 13 14 _requestDirectory = Path.Combine(path, "requests"); 15 _responseDirectory = Path.Combine(path, "responses"); 16 17 if (!Directory.Exists(_requestDirectory)) 18 { 19 Directory.CreateDirectory(_requestDirectory); 20 } 21 22 if (!Directory.Exists(_responseDirectory)) 23 { 24 Directory.CreateDirectory(_responseDirectory); 25 } 26 27 _nextLogger = nextLogger; 28 } 29 30 public async Task LogAsync(RequestLog requestLog) 31 { 32 var serializedLog = JsonConvert.SerializeObject(requestLog, Formatting.Indented); 33 var filePath = Path.Combine(_requestDirectory, $"{requestLog.Id}.json"); 34 await File.WriteAllTextAsync(filePath, serializedLog); 35 36 if (_nextLogger != null) 37 { 38 await _nextLogger.LogAsync(requestLog); 39 } 40 } 41 42 public async Task LogAsync(ResponseLog responseLog) 43 { 44 var serializedLog = JsonConvert.SerializeObject(responseLog, Formatting.Indented); 45 var filePath = Path.Combine(_responseDirectory, $"{responseLog.Id}.json"); 46 await File.WriteAllTextAsync(filePath, serializedLog); 47 48 if (_nextLogger != null) 49 { 50 await _nextLogger.LogAsync(responseLog); 51 } 52 } 53 }
創建CustomLoggingMiddleware
CustomLoggingMiddleware需要將自身附加到請求管道,然后使用ApplicationServices提供的記錄器記錄請求,最后執行請求管道並記錄響應。
1 public static class CustomLoggingMiddleware 2 { 3 public static void UseCustomLogging(this IApplicationBuilder app) 4 { 5 app.Use(async (context, next) => 6 { 7 var logger = app.ApplicationServices.GetService<ILogForWebAPIs>(); 8 9 if (logger is null) 10 { 11 throw new Exception($"Add ILogForWebAPIs to your service provider in {nameof(Startup)}.{nameof(Startup.ConfigureServices)}"); 12 } 13 14 await LogRequestAsync(context, logger); 15 var stopWatch = new Stopwatch(); 16 stopWatch.Start(); 17 18 // execute request pipeline 19 await next?.Invoke(); 20 21 stopWatch.Stop(); 22 23 await LogResponseAsync(context, stopWatch.ElapsedMilliseconds, logger); 24 }); 25 } 26 27 private static async Task LogRequestAsync(HttpContext context, ILogForWebAPIs logger) 28 { 29 var requestLog = new RequestLog 30 { 31 Id = Guid.NewGuid(), 32 Action = context.Request.Method, 33 URL = context.Request.Path, 34 IPAddress = context.Request.HttpContext.Connection.RemoteIpAddress.ToString(), 35 TimeStampUtc = DateTime.UtcNow 36 }; 37 38 await logger.LogAsync(requestLog); 39 } 40 41 private static async Task LogResponseAsync(HttpContext context, long responseTimeInMilliseconds, ILogForWebAPIs logger) 42 { 43 var responseLog = new ResponseLog 44 { 45 Id = Guid.NewGuid(), 46 Action = context.Request.Method, 47 URL = context.Request.Path, 48 StatusCode = context.Response.StatusCode, 49 ResponseTimeInMilliseconds = responseTimeInMilliseconds, 50 TimeStampUtc = DateTime.UtcNow 51 }; 52 53 await logger.LogAsync(responseLog); 54 } 55 }
在啟動中添加自定義日志記錄,然后進行測試
要獲取我們的API日志記錄,我們只需要做兩件事:
- 將記錄器添加到Startup.ConfigureServices中的IServiceCollection中
- 在Startup.Configure中調用UseCustomLogging
注意:如果像下面的示例那樣使用https重定向,建議將自定義日志記錄添加到請求管道中。這樣,您可以確保不記錄重定向。
1 public class Startup 2 { 3 public Startup(IConfiguration configuration) 4 { 5 Configuration = configuration; 6 } 7 8 public IConfiguration Configuration { get; } 9 10 // This method gets called by the runtime. Use this method to add services to the container. 11 public void ConfigureServices(IServiceCollection services) 12 { 13 services.AddControllers(); 14 services.AddTransient<ILogForWebAPIs>((serviceProvider) => new WebAPIConsoleLogger(new WebAPIFileLogger("APILogs"))); 15 } 16 17 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 18 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 19 { 20 if (env.IsDevelopment()) 21 { 22 app.UseDeveloperExceptionPage(); 23 } 24 25 app.UseHttpsRedirection(); 26 27 app.UseCustomLogging(); 28 29 app.UseRouting(); 30 31 app.UseAuthorization(); 32 33 app.UseEndpoints(endpoints => 34 { 35 endpoints.MapControllers(); 36 }); 37 } 38 }
要在Visual Studio中查看控制台輸出,請使用項目配置文件運行應用程序並進行測試。
導航到日志目錄以檢查日志文件
{
“ Id”:“ 0c7ffe14-66c3-428c-bffe-0da1dccd9546”,
“ Action”:“ GET”,
“ URL”:“ / weatherforecast”,
“ IPAddress”:“ :: 1”,
“ TimeStampUtc”:“ 2020 -02-13T15:05:27.3373827Z”
}