ASP.NET Core 3中的自定义日志记录
根据我的经验,通常在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”
}