一、简介#
日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息。ASP.NET Core中内置了一个通用日志接口ILogger
,并实现了多种内置的日志提供器,例如
-
Console
-
Debug
-
EventSource
-
EventLog
-
TraceSource
-
Azure App Service
除了内置的日志提供器,ASP.NET Core还支持了多种第三方日志工具,例如
开发人员在ASP.Net Core中可以自由指定日志提供器,并将日志发送到指定的位置。
本篇博文中,我们将由浅入深的介绍ASP.Net Core中通用日志接口,最后我们将实现一些自定义的日志提供器(Log Provider)。
二、使用系统提供的内置日志提供器#
(一)、日志级别(Log Level)#
ASP.NET Core中提供了6种日志级别,分别是Trace, Debug, Information, Warning, Error, Critical。以下是他们的具体使用场景
日志级别 |
常用场景 |
---|---|
Trace |
记录一些对程序员调试问题有帮助的信息, 其中可能包含一些敏感信息, 所以应该避免在 生产环境中启用Trace日志 |
Debug |
记录一些在开发和调试阶段有用的短时变 量(Short-term usefulness), 所以除非为了临时排除生产环境的 故障,开发人员应该尽量避免在生产环境中启用Debug日志 |
Information |
记录应用程序的一些流程, 例如,记录当前api请求的url |
Warning |
记录应用程序中发生的不正常或者未预期的事件信息。 这些信息中可能包含错误消息或者错误产生的条件, 例如, 文件未找到 |
Error |
记录应用程序中某个操作产生的错误和异常信息。 |
Critical |
记录一些需要立刻修复的问题。例如数据丢失,磁盘空间不足。 |
(二)、如何创建日志#
为了创建一个日志,我们首先需要通过依赖注入获取一个实现ILogger<日志类别>的泛型接口对象。
已以下代码为例, 在ValuesController
的构造函数中,我们注入了一个ILogger
的日志记录器
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly ILogger<ValuesController> _logger = null; public ValuesController(ILogger<ValuesController> logger) { _logger = logger; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { _logger.LogInformation("[Test Log]Getting items."); return new string[] { "value1", "value2" }; } }
然后我们使用ILogger
接口提供的LogInformation
方法添加了一个Information类型日志"[Test Log]Getting items"。
注:
ILogger
为了提供了6个可用的输出日志方法,分别对应了6个不同的日志级别
LogTrace
LogDebug
LogInformation
LogWarning
LogError
LogCritical!
项目产生的日志如下,我们手动输出的日志出现在控制台中。
(三)、日志配置
可能针对以上的代码,有些同学可能有疑问,我们没有在Startup.cs中注入任何日志提供器,但是日志却正常产生了。这是由于Program.cs中默认使用WebHost.CreateDefaultBuilder方法添加了2个日志提供器。
(四)、默认日志提供器
当创建一个新的ASP.NET Core WebApi项目,我们通常在Program.cs中看到以下代码。
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); }
下面我们看一下WebHost.CreateDefaultBuilder的源代码
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.UseConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }) .ConfigureServices(services => { services.AddTransient<IConfigureOptions<KestrelServerOptions>, KestrelServerOptionsSetup>(); }); return builder; }
你会发现代码中通过logging.AddConsole
和logging.AddDebug
默认配置了Console和Debug类型的日志提供器,这也就是为什么我们没有注入任何日志提供器,日志却能正常产生了。
手动添加日志提供器#
看了以上代码后,你应该可以也清楚了如何自己添加其他内置的日志提供器。我们只需要在Program.cs中使用ConfigureLogging方法就可以配置我们需要的日志提供器了。
例:
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureLogging((hostingContext, logging) => { logging.AddConsole(); logging.AddDebug(); logging.AddEventSourceLogger(); }) .UseStartup<Startup>(); }
除了在Program.cs添加日志提供器之外,我们还可以在Startup.cs中添加日志提供器。
在Startup.cs中,我们可以为Configure方法添加第三个参数ILoggerFactory loggerFactory
, 并使用该参数添加日志提供器。
例:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } loggerFactory.AddConsole(); loggerFactory.AddDebug(); app.UseMvc(); }
(五)、配置文件及日志级别过滤
ASP.NET Core默认会从appSetting.json中的Logging属性读取日志的配置(当然你也可以从其他文件中读取配置),这里设置了不同的日志提供器产生的最低的日志级别,配置样例如下。
{ "Logging": { "Debug": { "LogLevel": { "Default": "Information" } }, "Console": { "LogLevel": { "Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning", "Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug", "Microsoft.AspNetCore.Mvc.Razor": "Error", "Default": "Trace" } }, "LogLevel": { "Default": "Debug" } } }
以上代码中的Debug表示Debug日志提供器, Console表示Console日志提供器, 最后一个LogLevel表示其他日志提供器通用。
Debug中的Default设置为Information, 即Debug中产生的日志最低级别是Information, 低于Information级别的日志不会输出。Console中的配置同理。
(六)、自定义日志组件
在学习了以上基础知识之后,我们应该对内置的日志提供器有了简单的认识。下面我们尝试自定义2个日志提供器。
在ASP.NET Core中,我们可以通过实现ILogger
, ILoggerProvider
2个接口来创建我们自己的日志提供器。
1、编写一个自定义样式的控制台日志组件
这里我们希望添加一个在控制台中输出的日志,但是和内置Console类型日志的区别是我们为不同的日志类型设置了不同的颜色。
首先我们创建一个新的Api项目ColoredConsoleLoggerSample
然后我们创建一个针对不同日志级别的字体颜色配置类ColoredConsoleLoggerConfiguration
, 代码如下
public class ColoredConsoleLoggerConfiguration { public LogLevel LogLevel { get; set; } = LogLevel.Warning; public ConsoleColor Color { get; set; } = ConsoleColor.Yellow; }
这个类中定义了针对不同日志类型设置不同的字体颜色。
然后我们创建一个日志类ColoredConsoleLogger
, 它实现了ILogger
接口,代码如下
public class ColoredConsoleLogger : ILogger { private readonly string _name; private readonly ColoredConsoleLoggerConfiguration _config; public ColoredConsoleLogger(string name, ColoredConsoleLoggerConfiguration config) { _name = name; _config = config; } public IDisposable BeginScope<TState>(TState state) { return null; } public bool IsEnabled(LogLevel logLevel) { return logLevel == _config.LogLevel; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) { return; } var color = Console.ForegroundColor; Console.ForegroundColor = _config.Color; Console.WriteLine($"{logLevel.ToString()} - {_name} - {formatter(state, exception)}"); Console.ForegroundColor = color; } }
代码解释
-
ColoredConsoleLogger仅针对一种日志级别
-
只要当前产生的日志级别和ColoredConsoleLogger中定义的日志级别一样时,日志才会输出,这里我们是用
IsEnable
方法判断的 -
Log
是ILogger接口中定义的方法,我们就是在这个方法中输出日志的 -
这里我们在输入日志前记录下了当前控制台的原始字体颜色, 当输出日志完成之后,我们将字体颜色恢复成了原来的颜色
然后我们添加一个Logger提供器类ColoredConsoleLoggerProvider
,代码如下
public class ColoredConsoleLoggerProvider : ILoggerProvider { private readonly ColoredConsoleLoggerConfiguration _config; public ColoredConsoleLoggerProvider(ColoredConsoleLoggerConfiguration config) { _config = config; } public ILogger CreateLogger(string categoryName) { return new ColoredConsoleLogger(categoryName, _config); } public void Dispose() { } }
代码解释
-
ColoredConsoleLoggerProvider仅针对一种日志级别
-
CreateLogger
是ILoggerProvider
接口中定义的方法,它是用来返回一个日志生成器的
最后我们修改Startup.cs中的Configure方法, 代码如下
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(new ColoredConsoleLoggerConfiguration { LogLevel = LogLevel.Information, Color = ConsoleColor.Blue })); loggerFactory.AddProvider(new ColoredConsoleLoggerProvider(new ColoredConsoleLoggerConfiguration { LogLevel = LogLevel.Debug, Color = ConsoleColor.Gray })); app.UseMvc(); }
这里我们添加了2个日志日志提供器,分别是针对Information级别日志和Debug级别日志的
最终效果
我们的日志根据我们预设的字体颜色正确的显示了出来
2、编写一个与SignalR集成的实时日志组件
下面我们再来自定义一个与SignalR集成的日志提供器,我们希望产生的日志通过一个SignalR服务器推送到一个网页中。
首先我们创建一个ASP.NET Core WebApi项目,命名为SignalrServer, 创建之后,我们右键项目属性,修改App Url为http://localhost:5000
然后我们创建一个LogHub类,它集成自Hub类,代码如下
public class LogHub : Hub { public async Task WriteLog(Log log) { await Clients.All.SendAsync("showLog", log); } } public class Log { public LogLevel Level { get; set; } public string Content { get; set; } }
代码解释
-
这里我们创建了一个写日志的方法,它会把日志推送到所有连接到SignalR服务器的客户端,并调用客户端的showLog方法来展示推送的日志信息。
然后我们修改Startup.cs文件,代码如下
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddCors(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSignalR(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseCors(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials()); app.UseSignalR(routes => { routes.MapHub<LogHub>("/logHub"); }); app.UseMvc(); } }
代码解释
-
我们通过
service.AddSignalR
注册了SignalR服务 -
我们通过
app.UserSignalR
方法注册一个logHub -
这里我们启用了CORS, 因为需要提供跨域访问
然后我们创建一个另外一个ASP.NET Core WebApi项目, SignalRLoggerSample
项目创建成功之后,我们右键点击项目属性,并设置App URL为http://localhost:5001
然后我们使用Package Console Manager, 安装Microsoft.AspNetCore.SignalR.Client
PM> install-package Microsoft.AspNetCore.SignalR.Client
为了创建一个SignalR日志提供器, 我们分别创建一个SignalRLogger类和一个SignalRLoggerProvider类, 代码如下
SignalRLogger.cs
public class SignalRLogger : ILogger { HubConnection connection; public SignalRLogger() { connection = new HubConnectionBuilder() .WithUrl("http://localhost:5000/LogHub") .Build(); } public IDisposable BeginScope<TState>(TState state) { return null; } public bool IsEnabled(LogLevel logLevel) { return true; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) { return; } connection.StartAsync().Wait(); var task = connection.SendAsync("writeLog", new { Level = logLevel, Content = formatter(state, exception) }); task.Wait(); } }
SignalRLoggerProvider.cs
public class SignalRLoggerProvider : ILoggerProvider { public SignalRLoggerProvider() { } public ILogger CreateLogger(string categoryName) { return new SignalRLogger(); } public void Dispose() { } }
代码解释
-
这里使用
HubConnectionBuilder
创建了一个SignalR连接 -
连接启动成功之后,我们使用
connection.SendAsync
方法,将当前产生的日志信息发送到SignalR服务器
添加完成之后,我们在wwwroot文件夹中创建一个index.html, 在其中引入jquery和signalr的js库,并指定连接的signalR服务器是http://localhost:5000/logHub
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script src="jquery-1.10.2.min.js"></script> <script src="signalr.min.js"></script> </head> <body> <h1>Logs</h1> <div id="content" style="border:1px solid #0094ff"> </div> <script type="text/javascript"> var levels = [ { level: 0, name: 'Trace', backgroundColor: 'gray' }, { level: 1, name: 'Debug', backgroundColor: 'green' }, { level: 2, name: 'Information', backgroundColor: 'blue' }, { level: 3, name: 'Warning', backgroundColor: 'yellow' }, { level: 4, name: 'Error', backgroundColor: 'orange' }, { level: 5, name: 'Critical', backgroundColor: 'red' }, ]; function getLevelName(level) { return levels.find(function (o) { return o.level == level; }).name; } function getLevelColor(level) { return levels.find(function (o) { return o.level == level; }).backgroundColor; } var connection = new signalR.HubConnectionBuilder().withUrl("http://localhost:5000/logHub").build(); connection.on("showLog", function (message) { var div = "<div style='background-color:" + getLevelColor(message.level)+"'>[" + getLevelName(message.level) + "]:" + message.content + "</div>"; $("#content").append(div); }); connection.start().catch(function (err) { return console.error(err.toString()); }); </script> </body> </html>
然后我们修改ValuesController文件,代码如下
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private ILogger<ValuesController> _logger = null; public ValuesController(ILogger<ValuesController> logger) { _logger = logger; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { _logger.LogTrace("User call the /api/values api"); _logger.LogDebug("User call the /api/values api"); _logger.LogInformation("User call the /api/values api"); _logger.LogWarning("User call the /api/values api"); _logger.LogError("User call the /api/values api"); _logger.LogCritical("User call the /api/values api"); return new string[] { "value1", "value2" }; } }
代码解释
-
我们创建了一个ValueController类的日志
-
当用户请求/api/values时,我们输出了6个不同级别的日志
最后我们修改Startup.cs中的Configure方法,使用我们之前介绍的方法,将SignalRLoggerProvider添加到管道中
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); loggerFactory.AddProvider(new SignalRLoggerProvider()); app.UseMvc(); }
最终效果