18. 日志记录

18.1 关于日志#

通常日志指的是系统日志程序日志

系统日志 是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。系统日志包括系统日志、应用程序日志和安全日志。

程序日志 是程序运行中产生的日志,通常由框架运行时或开发者提供的日志。包括请求日志,异常日志、审计日志、行为日志等。

18.2 日志作用#

在项目开发中,都不可避免的使用到日志。没有日志虽然不会影响项目的正确运行,但是没有日志的项目可以说是不完整的。日志在调试,错误或者异常定位,数据分析中的作用是不言而喻的。

  • 调试

在项目调试时,查看栈信息可以方便地知道当前程序的运行状态,输出的日志便于记录程序在之前的运行结果。

  • 错误定位

不要以为项目能正确跑起来就可以高枕无忧,项目在运行一段时候后,可能由于数据问题,网络问题,内存问题等出现异常。这时日志可以帮助开发或者运维人员快速定位错误位置,提出解决方案。

  • 数据分析

大数据的兴起,使得大量的日志分析成为可能,ELK 也让日志分析门槛降低了很多。日志中蕴含了大量的用户数据,包括点击行为,兴趣偏好等,用户画像对于公司下一步的战略方向有一定指引作用。

18.3 日志级别#

日志级别可以有效的对日志信息进行归类,方便准确的查看特定日志内容。通常日志类别有以下级别:

级别方法描述
Trace0LogTrace包含最详细的消息。 这些消息可能包含敏感的应用数据。 这些消息默认情况下处于禁用状态,并且不应在生产中启用。
调试1LogDebug用于调试和开发。 由于量大,请在生产中小心使用。
信息2LogInformation跟踪应用的常规流。 可能具有长期值。
警告3LogWarning对于异常事件或意外事件。 通常包括不会导致应用失败的错误或情况。
错误4LogError表示无法处理的错误和异常。 这些消息表示当前操作或请求失败,而不是整个应用失败。
严重5LogCritical需要立即关注的失败。 例如数据丢失、磁盘空间不足。

18.4 如何使用#

.NET 5 框架中,微软已经为我们内置了 日志组件,正常情况下,无需我们引用第三方包进行日志记录。.NET 5 框架为我们提供了两种日志对象创建方式。

18.4.1 ILogger<T> 泛型方式#

使用非常简单,可以通过 ILogger<T> 对象进行注入,如:

public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
_logger.LogInformation("GET Pages.PrivacyModel called.");
}
}
小知识

通过泛型 ILogger<T> 方式写入日志,那么默认将 T 类型完整类型名称作为 日志类别

18.4.2 ILoggerFactory 工厂方式#

使用工厂方式,需手动传入 日志类别,如:

public class ContactModel : PageModel
{
private readonly ILogger _logger;
public ContactModel(ILoggerFactory logger)
{
_logger = logger.CreateLogger("MyCategory");
}
public void OnGet()
{
_logger.LogInformation("GET Pages.ContactModel called.");
}
}

18.4.3 懒人模式 😁#

Furion 框架中,提供了更懒的方式写入日志,也就是通过字符串拓展的方式写入,如:

"百小僧 新增了一条记录".LogInformation<HomeController>();
"程序出现异常啦".LogError<HomeController>();
"这是自定义类别日志".LogInformation("类别");

通过字符串拓展方式可以在任何时候方便记录日志,专门为懒人提供的。

18.5 写入其他介质#

.NET 5 框架中并未提供写入文件、数据库 或其他介质的提供器,默认只提供了 Debug、Console 两种方式。这个时候我们就需要引用第三方日志组件,方便我们写入到多个介质中。

在这里,Furion 官方推荐使用 Serilog 日志组件,为此,Furion 提供了 Furion.Extras.Logging.Serilog 拓展包,方便快速和 Furion 框架结合。

18.5.1 Serilog 拓展包使用#

  • 安装 Furion.Extras.Logging.Serilog 拓展包

Furion.Core 层安装 Furion.Extras.Logging.Serilog 拓展包

  • Program.cs 中调用 UseSerilogDefault()
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace Furion.Web.Entry
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.Inject()
.UseStartup<Startup>();
})
.UseSerilogDefault();
}
}
}
特别注意

.UseSerilogDefault() 默认集成了 控制台文件 方式。如需自定义写入,则传入需要写入的介质即可:

.UseSerilogDefault(config =>
{
config.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true);
});
  • 替换 appsetting.json 默认日志内容
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
}

替换为:

"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"System": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Information"
}
}
}

18.5.2 记录请求日志#

Serilog 日志组件也提供了非常方便快捷的请求日志中间件,只需要在 Startup.cs 中启用即可。如:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
app.UseSerilogRequestLogging(); // 必须在 UseStaticFiles 和 UseRouting 之间
app.UseRouting();
}

18.6 日志示例#

下面便是日志输出日志的模板,支持各种自定义方式

2020-12-21 15:54:43.775 +08:00 [INF] Application started. Press Ctrl+C to shut down.
2020-12-21 15:54:43.897 +08:00 [INF] Hosting environment: Development
2020-12-21 15:54:43.899 +08:00 [INF] Content root path: D:\MONK\Furion\samples\Furion.Web.Entry
2020-12-21 15:55:00.651 +08:00 [WRN] Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
2020-12-21 15:55:00.817 +08:00 [INF] Entity Framework Core 5.0.1 initialized 'DefaultDbContext' using provider 'Microsoft.EntityFrameworkCore.Sqlite' with options: SensitiveDataLoggingEnabled DetailedErrorsEnabled MaxPoolSize=100 MigrationsAssembly=Furion.Database.Migrations
2020-12-21 15:55:01.711 +08:00 [WRN] Compiling a query which loads related collections for more than one collection navigation either via 'Include' or through projection but no 'QuerySplittingBehavior' has been configured. By default Entity Framework will use 'QuerySplittingBehavior.SingleQuery' which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277 for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))'
2020-12-21 15:55:01.919 +08:00 [INF] Executed DbCommand (31ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Name", "p"."Age", "p"."Address", "p0"."PhoneNumber", "p0"."QQ", "p"."CreatedTime", "p0"."Id", "c"."Id", "c"."Name", "c"."Gender", "t"."Id", "t"."Name", "t"."PersonsId", "t"."PostsId"
FROM "Person" AS "p"
LEFT JOIN "PersonDetail" AS "p0" ON "p"."Id" = "p0"."PersonId"
LEFT JOIN "Children" AS "c" ON "p"."Id" = "c"."PersonId"
LEFT JOIN (
SELECT "p2"."Id", "p2"."Name", "p1"."PersonsId", "p1"."PostsId"
FROM "PersonPost" AS "p1"
INNER JOIN "Post" AS "p2" ON "p1"."PostsId" = "p2"."Id"
) AS "t" ON "p"."Id" = "t"."PersonsId"
ORDER BY "p"."Id", "p0"."Id", "c"."Id", "t"."PersonsId", "t"."PostsId", "t"."Id"
2020-12-21 15:55:25.354 +08:00 [INF] Executed DbCommand (3ms) [Parameters=[], CommandType='"Text"', CommandTimeout='30']
SELECT "p"."Id", "p"."Name", "p"."Age", "p"."Address", "p0"."PhoneNumber", "p0"."QQ", "p"."CreatedTime", "p0"."Id", "c"."Id", "c"."Name", "c"."Gender", "t"."Id", "t"."Name", "t"."PersonsId", "t"."PostsId"
FROM "Person" AS "p"
LEFT JOIN "PersonDetail" AS "p0" ON "p"."Id" = "p0"."PersonId"
LEFT JOIN "Children" AS "c" ON "p"."Id" = "c"."PersonId"
LEFT JOIN (
SELECT "p2"."Id", "p2"."Name", "p1"."PersonsId", "p1"."PostsId"
FROM "PersonPost" AS "p1"
INNER JOIN "Post" AS "p2" ON "p1"."PostsId" = "p2"."Id"
) AS "t" ON "p"."Id" = "t"."PersonsId"
ORDER BY "p"."Id", "p0"."Id", "c"."Id", "t"."PersonsId", "t"."PostsId", "t"."Id"
2020-12-21 15:58:27.328 +08:00 [INF] Application started. Press Ctrl+C to shut down.
2020-12-21 15:58:27.442 +08:00 [INF] Hosting environment: Development
2020-12-21 15:58:27.444 +08:00 [INF] Content root path: D:\MONK\Furion\samples\Furion.Web.Entry
2020-12-21 15:58:27.909 +08:00 [INF] HTTP GET / responded 200 in 457.0657 ms
2020-12-21 15:58:33.336 +08:00 [INF] HTTP GET /api/index.html responded 200 in 95.9277 ms
2020-12-21 15:58:34.187 +08:00 [INF] HTTP GET /swagger/Default/swagger.json responded 200 in 674.9800 ms

18.7 反馈与建议#

与我们交流

给 Furion 提 Issue


了解更多

想了解更多 日志 知识可查阅 ASP.NET Core - 日志 章节 和 Serilog 文档。