一、日志记录:
1、日志的作用:
程序中记录日志一般有两个目的,故障定位和显示程序运行状态。好的日志记录方式可以提供足够多定位问题的依据。
2、日志的等级:
有良好工作习惯的人,工作的时候会将领导交待下来的工作分为:紧急重要、重要不紧急、紧急不重要、不紧急不重要等;同样 ASP.NET Core 定义了以下日志级别(按严重性从低到高排列)。
每次写入日志时都需指定其 LogLevel。 日志级别指示严重性或重要程度。 例如,如果方法正常结束则写入 Information
日志,如果方法返回 404 返回代码则写入 Warning
日志,如果捕获未知异常则写入 Error
日志。 可以使用日志级别控制写入到特定存储介质或显示窗口的日志输出量。 例如在生产中,可将所有 Information
及以下级别的日志放在卷数据存储,将所有 Warning
及以上级别的日志放在值数据存储。 在开发期间,通常要将严重性为 Warning
或以上级别的日志发送到控制台。 需要进行故障排除时,可添加 Debug
级别。
- Trace = 0
表示仅对于开发人员调试问题有价值的信息。 这些消息可能包含敏感应用程序数据,因此不得在生产环境中启用它们。 默认情况下禁用。 示例:Credentials: {"User":"someuser", "Password":"P@ssword"}
- Debug = 1
表示在开发和调试过程中短期有用的信息。 示例:Entering method Configure with flag set to true.。除非要排查问题,否则通常不会在生产中启用 Debug 级别日志,因为日志数量过多。
- Information = 2
用于跟踪应用程序的常规流。 这些日志通常有长期价值。 示例:Request received for path /api/todo
- Warning = 3
表示应用程序流中的异常或意外事件。 可能包括不会中断应用程序运行但仍需调查的错误或其他条件。 Warning 日志级别常用于已处理的异常。 示例:FileNotFoundException for file quotes.txt.
- Error = 4
表示无法处理的错误和异常。 这些消息指示的是当前活动或操作(如当前 HTTP 请求)中的失败,而不是应用程序范围的失败。日志消息示例:Cannot insert record due to duplicate key violation.
- Critical = 5
需要立即关注的失败。 例如数据丢失、磁盘空间不足。
二、使用内置的Logger
微软官方文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1
因为Logger是asp.net core 的内置service,所以我们就不需要在ConfigureService里面注册了。如果是asp.net core 1.0版本的话,我们需要配置一个或者多个Logger,但是asp.net core 2.0的话就不需要做这个工作了,因为在CreateDefaultBuilder方法里默认给配置了输出到Console和Debug窗口的Logger。这是源码:
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.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } return builder; }
注入Logger
我们可以在ProductController里面注入ILoggerFactory然后再创建具体的Logger。但是还有更好的方式,Container可以直接提供一个ILogger<T>的实例,这时候呢Logger就会使用T的名字作为日志的类别:
namespace CoreBackend.Api.Controllers { [Route("api/[controller]")] public class ProductController : Controller { private ILogger<ProductController> _logger; public ProductController(ILogger<ProductController> logger) { _logger = logger; } ......
如果通过Constructor注入的方式不可用,那么我们也可以直接从Container请求来得到它:HttpContext.RequestServices.GetService(typeof(ILogger<ProductController>)); 如果你在Constructor写这句话可能会空指针,因为这个时候HttpContext应该是null吧。
不过还是建议使用Constructor注入的方式!!!
然后我们记录一些日志吧:
[Route("{id}", Name = "GetProduct")] public IActionResult GetProduct(int id) { var product = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (product == null) { _logger.LogInformation($"Id为{id}的产品没有被找到.."); return NotFound(); } return Ok(product); }
Log记录时一般都分几个等级,这点我假设大家都知道吧,就不介绍了。
然后试一下:通过Postman访问一个不存在的产品:‘/api/product/22’,然后看看Debug输出窗口:
嗯,出现了,前边是分类,也就是ILogger<T>里面T的名字,然后是级别 Information,然后就是我们记录的Log内容。
再Log一个Exception:
[Route("{id}", Name = "GetProduct")] public IActionResult GetProduct(int id) { try { throw new Exception("来个异常!!!"); var product = ProductService.Current.Products.SingleOrDefault(x => x.Id == id); if (product == null) { _logger.LogInformation($"Id为{id}的产品没有被找到.."); return NotFound(); } return Ok(product); } catch (Exception ex) { _logger.LogCritical($"查找Id为{id}的产品时出现了错误!!", ex); return StatusCode(500, "处理请求的时候发生了错误!"); } }
记录Exception就建议使用LogCritical了,这里需要注意的是Exception的发生就表示服务器发生了错误,我们应该处理这个exception并返回500。使用StatusCode这个方法返回特定的StatusCode,然后可以加一个参数来解释这个错误(这里一般不建议返回exception的细节)。
运行试试:
OK。
Log到Debug窗口或者Console窗口还是比较方便的,但是正式生产环境中这肯定不够用。
正式环境应该Log到文件或者数据库。虽然asp.net core 的log内置了记录到Windows Event的方法,但是由于Windows Event是windows系统独有的,所以这个方法无法跨平台,也就不建议使用了。
官方文档上列出了这几个建议使用的第三发Log Provider:
把这几个Log provider注册到asp.net core的方式几乎是一摸一样的,所以介绍一个就行。我们就用比较火的NLog吧。
三、NLog
官方文档:https://github.com/NLog/NLog.Web/wiki/Getting-started-with-ASP.NET-Core-2
首先通过nuget安装Nlog:
注意要勾上include prerelease,目前还不是正式版。
装完之后,我们就需要为Nlog添加配置文件了。默认情况下Nlog会在根目录寻找一个叫做nlog.config的文件作为配置文件。那么我们就手动改添加一个nlog.config:
<?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <targets> <target name="logfile" xsi:type="File" fileName="logs/${shortdate}.log" /> </targets> <rules> <logger name="*" minlevel="Info" writeTo="logfile" /> </rules> </nlog>
然后设置该文件的属性如下:
对于Nlog的配置就不进行深入介绍了。具体请看官方文档的.net core那部分。
然后需要把Nlog集成到asp.net core,也就是把Nlog注册到ILoggerFactory里面。所以打开Startup.cs,首先注入ILoggerFactory,然后对ILoggerFactory进行配置,为其注册NLog的Provider:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // loggerFactory.AddProvider(new NLogLoggerProvider()); loggerFactory.AddNLog(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(); } app.UseStatusCodePages(); app.UseMvc(); }
针对LoggerFactory.AddProvider()这种写法,Nlog一个简单的ExtensionMethod做了这个工作,就是AddNlog();
添加完NLog,其余的代码都不需要改,然后我们试下:
在如图所示的位置出现了log文件。内容如下:
四、Serilog
官方文档: Asp.net core https://github.com/serilog/serilog-aspnetcore
Sinks-file: https://github.com/serilog/serilog-sinks-file
Sinks-Console:https://github.com/serilog/serilog-sinks-console
(1) 添加:Serilog
参考资料:
https://www.cnblogs.com/cgzl/p/7652413.html;
https://v.qq.com/x/page/m0762gzo2l6.html
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1