❤️ 关注 Furion 微信公众号有惊喜哦!
Skip to main content

18. 日志记录

📝 模块更新日志
  • 新特性

    •   控制台日志 AddConsoleFormatter 服务支持 WriteFilter 属性过滤 4.8.8.52 ⏱️2023.11.07 516acb4
    •   监听日志 LoggingMonitor 支持打印输出 requestHeaders 请求头信息 4.8.8.50 ⏱️2023.10.27 #I8BHM3
    •   监听日志 LoggingMonitor 支持配置日志输出级别 4.8.8.41 ⏱️2023.08.25 #I7SRTP
    •   监听日志 LoggingMonitor 支持 Razor Pages 4.8.8.16 ⏱️2023.05.15 #I7332C
    •   日志配置 WithStackFrame,可控制是否输出产生日志的程序集,类型和具体方法 4.8.7.16 ⏱️2023.03.19 5ad6ae2
查看变化

启用 WithStackFrame 日志配置后,可输出程序集,类型,方法签名信息。

// 控制台日志
services.AddConsoleFormatter(options =>
{
options.WithStackFrame = true;
});

// 文件日志
services.AddFileLogging(options =>
{
options.WithStackFrame = true;
});

// 数据库日志
services.AddDatabaseLogging(options =>
{
options.WithStackFrame = true;
});

日志输出如下:

info: 2023-03-17 18:25:06.7988349 +08:00 星期五 L System.Logging.EventBusService[0] #1
[Furion.dll] async Task Furion.EventBus.EventBusHostedService.ExecuteAsync(CancellationToken stoppingToken)
EventBus hosted service is running.
info: 2023-03-17 18:25:08.1393952 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: https://localhost:5001
info: 2023-03-17 18:25:08.1620391 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: http://localhost:5000
info: 2023-03-17 18:25:08.1972456 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Application started. Press Ctrl+C to shut down.
info: 2023-03-17 18:25:08.2456579 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Hosting environment: Development
info: 2023-03-17 18:25:08.2746134 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[System.Private.CoreLib.dll] void System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(bool throwOnFirstException)
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2023-03-17 18:25:18.1917784 +08:00 星期五 L Furion.Application.TestLoggerServices[0] #16
[Furion.Application.dll] void Furion.Application.TestLoggerServices.测试日志()
我是一个日志 20

这样就清楚地知道日志是哪个程序集、哪个类型、哪个方法输出的了。

    •   审计日志 LoggingMonitor 支持对参数贴 [SuppressMonitor] 特性跳过记录 4.8.7.3 ⏱️2023.03.01 #I6IVGW
    •   审计日志 LoggingMonitor 监听 TraceIdThreadIdAccept-Language 4.8.7.1 ⏱️2023.02.27 df35201
    •   审计日志 LoggingMonitor 支持配置序列化属性命名规则 4.8.6.12 ⏱️2023.02.21 #I6GPUP
    •   审计日志 LoggingMonitor 支持 [DisplayName] 特性解析和 Title 属性记录 4.8.5.10 ⏱️2023.02.07 #I6DHMF
    •   审计日志 LoggingMonitor 记录 HTTP 响应状态码 4.8.5.2 ⏱️2023.01.30 abb4cbd
  • 突破性变化

    •   监听日志 WriteFilterConfigureLoggerActionExecutingContextActionExecutedContext 类型为 FilterContext 4.8.8.16 ⏱️2023.05.15 #I7332C
  • 问题修复

    •   审计日志不支持 dynamic/JsonElement 序列化问题 4.8.8.45 ⏱️2023.09.29 #I84SD5
    •   审计日志解析 DateTime 类型参数不是本地时间问题 4.8.8.33 ⏱️2023.06.29 #I7GW32
    •   LoggingMonitor 打印泛型类型如果存在多个泛型参数问题 4.8.8.8 ⏱️2023.05.04 8d9cb74
    •   日志输出 JSON 格式漏掉了 UseUtcTimestampTraceId 键值 4.8.7.21 ⏱️2023.03.27 5c90e65
    •   日志消息没有处理 \n 换行符对齐问题 4.8.7.6 ⏱️2023.03.10 759bcc5
    •   审计日志 LoggingMonitor 对特定参数贴有 [FromServices] 特性依旧记录问题 4.8.7.3 ⏱️2023.03.01 17b134e
    •   在数据库日志的 IDatabaseLoggingWriter 实现类中依赖注入 ILogger<> 导致死循环 4.8.5.4 ⏱️2023.02.01 #I6C6QU
    •   数据库日志提供程序在应用程序终止时出现空异常问题 4.8.5 ⏱️2023.01.28 #I6AZ8Y
    •   数据库日志注册在一些特殊情况下丢失日志上下文问题 4.8.4.6 ⏱️2023.01.04 #I68PDF
    •   在类中贴 [SuppressMonitor] 特性但 LoggingMonitor 依然输出问题 4.8.4 ⏱️2022.12.30 #I6882I
    •   LoggingMonitor 序列化 IQueryable<>OData 返回值类型出现死循环问题 4.8.3.4 ⏱️2022.12.10 7e8c9d0
    •   通过 Ctrl + C 终止应用程序后获取 TraceId 出现对象已释放异常 4.8.1.12 ⏱️2022.12.07 55c3e49
    •   日志模块因 v4.8.0+ 版本导致写入数据库日志空异常问题 4.8.2.1 ⏱️2022.11.28 8d9d72b
  • 其他更改

    •   审计日志日志 LoggingMonitor 返回值泛型字符串显示格式 4.8.7.1 ⏱️2023.02.27 df35201
    •   LoggingMonitor 解析授权逻辑,如果接口未授权则不打印授权信息 4.8.2.1 ⏱️2022.11.28 #I63D2E

18.1 关于日志

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

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

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

18.2 日志作用

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

  • 调试

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

  • 错误定位

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

  • 数据分析

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

18.3 日志级别

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

级别方法描述
Trace(跟踪)0LogTrace包含最详细的消息。 这些消息可能包含敏感的应用数据。 这些消息默认情况下处于禁用状态,并且不应在生产中启用。
Debug(调试)1LogDebug用于调试和开发。 由于量大,请在生产中小心使用。
Information(信息)2LogInformation跟踪应用的常规流。 可能具有长期值。
Warning(警告)3LogWarning对于异常事件或意外事件。 通常包括不会导致应用失败的错误或情况。
Error(错误)4LogError表示无法处理的错误和异常。 这些消息表示当前操作或请求失败,而不是整个应用失败。
Critical(严重)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 Log 静态类方式

版本说明

以下内容仅限 Furion 4.2.1 + 版本使用。

// 创建日志对象
var logger = Log.CreateLogger("日志名称");

// 创建日志工厂
using var loggerFactory = Log.CreateLoggerFactory(builder => {
// ....
});

// 日志记录
Log.Information("Information");
Log.Warning("Warning");
Log.Error("Error");
Log.Debug("Debug");
Log.Trace("Trace");
Log.Critical("Critical");

18.4.4 懒人模式 😁

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

"简单日志".LogInformation();

"百小僧 新增了一条记录".LogInformation<HomeController>();

"程序出现异常啦".LogError<HomeController>();

"这是自定义类别日志".SetCategory<HomeController>().LogInformation();

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

18.5 输出到控制台

ASP.NET Core 应用程序中,主机启动时默认注册了 ConsoleLoggerProvider 提供器,也就是控制台日志输出提供器,所以无需任何注册服务即可在控制台输出。

info: Furion.EventBus.EventBusHostedService[0]
EventBus Hosted Service is running.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Workplaces\Furion\samples\Furion.Web.Entry\

18.5.1 日志过滤/筛选

通过日志筛选器可以对日志进行归类写入。

// 例子一:根据日志级别输出
services.AddConsoleFormatter(options =>
{
options.WriteFilter = (logMsg) => // Furion 4.8.8.52+ 版本支持
{
return logMsg.LogLevel == LogLevel.Information;
};
});

// 例子二,根据任何规则,比如特定的类名
services.AddConsoleFormatter(options => // Furion 4.8.8.52+ 版本支持
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName == "System.Logging.LoggingMonitor";
};
});

18.5.2 日志标准化(美化)模板

版本说明

以下内容仅限 Furion 4.5.0 + 版本使用。

ASP.NET Core 默认控制台日志相对简洁,并未包含常见的日志时间、线程 Id 等,而且自定义模板也相对复杂,所以 Furion 4.5.0+ 版本提供了简化配置,如:

  1. Startup.cs 方式
services.AddConsoleFormatter();
  1. .NET5 方式
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddConsoleFormatter();
});
  1. .NET6 方式
var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddConsoleFormatter();
  1. Serve.Run() 方式
Serve.Run(RunOptions.Default.AddWebComponent<WebComponent>());

public class WebComponent : IWebComponent
{
public void Load(WebApplicationBuilder builder, ComponentContext componentContext)
{
builder.Logging.AddConsoleFormatter();
}
}

输出结果:

info: 2023-03-23 11:51:02.3757469 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: https://localhost:7025
info: 2023-03-23 11:51:02.4993301 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: http://localhost:5217
info: 2023-03-23 11:51:02.5058785 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2023-03-23 11:51:02.5100496 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2023-03-23 11:51:02.5127095 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Content root path: C:\Users\snrcsoft\source\repos\WebApplication1\WebApplication1

18.5.3 自定义日志模板

services.AddConsoleFormatter(options =>
{
options.MessageFormat = (logMsg) =>
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(DateTime.Now.ToString("o"));
// 其他的。。。自己组装
return stringBuilder.ToString();
};
});

// 输出为 JSON 格式,Furion 4.5.2+
services.AddConsoleFormatter(options =>
{
options.MessageFormat = LoggerFormatter.Json;
// Furion 4.8.0+ 新增 JSON 美化输出
options.MessageFormat = LoggerFormatter.JsonIndented;
});

18.5.4 自定义日志输出时间格式

版本说明

以下内容仅限 Furion 4.5.1 + 版本使用。

services.AddConsoleFormatter(options =>
{
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
});
info: 2022-09-28 02:02:20(+08:00) 星期三 System.Logging.EventBusService[0] #1
EventBus Hosted Service is running.
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[14] #1
Now listening on: https://localhost:5001
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[14] #1
Now listening on: http://localhost:5000
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[0] #1
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry\

18.5.5 自定义日志输出程序

版本说明

以下内容仅限 Furion 4.5.2 + 版本使用。

ASP.NET CoreFurion 框架都提供了标准化日志输出,如果对颜色,格式有要求,可使用下列代码进行自定义。

services.AddConsoleFormatter(options =>
{
options.WriteHandler = (logMsg, scopeProvider, writer, fmtMsg, opt) =>
{
writer.WriteLine(fmtMsg);
};
});

18.5.6 输出日志 TraceId/HttpContextId

版本说明

以下内容仅限 Furion 4.8.1.3 + 版本使用。

在生产环境中,日志的输出是非常频繁的,但是很难从日志文件中判断哪些日志是属于同一个请求输出的,这时启用 WithTraceId 配置即可。

services.AddConsoleFormatter(options =>
{
options.WithTraceId = true;
});

输出日志如下:

info: 2022-11-24 14:34:55.1717549 +08:00 星期四 L System.Logging.EventBusService[0] #1
EventBus Hosted Service is running.
info: 2022-11-24 14:34:55.2504015 +08:00 星期四 L System.Logging.ScheduleService[0] #1
Schedule Hosted Service is running.
info: 2022-11-24 14:34:56.4280796 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: https://localhost:5001
info: 2022-11-24 14:34:56.4331170 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: http://localhost:5000
info: 2022-11-24 14:34:56.4384567 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2022-11-24 14:34:56.4408766 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2022-11-24 14:34:56.4427659 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2022-11-24 14:35:06.8507338 +08:00 Thursday L Furion.Application.TestLoggerServices[0] #17 '00-48df9ac5c8280de2f301faa44a23a10c-b75678d9f3883b0b-00'
我是一个日志 20
info: 2022-11-24 14:35:16.0373384 +08:00 星期四 L Furion.Application.TestLoggerServices[0] #17 '00-ff4fb15d6ff41a0411784e66400f0dfd-962bc25eff788b25-00'
我是一个日志 20

18.5.7 输出日志 程序集/类型/方法

版本说明

以下内容仅限 Furion 4.8.7.16 + 版本使用。

在生产环境中,有时候我们希望知道这个日志是在哪个程序集,哪个类中哪个方法输出,这时启用 WithStackFrame 配置即可。

services.AddConsoleFormatter(options =>
{
options.WithStackFrame = true;
});

输出日志如下:

info: 2023-03-17 18:25:06.7988349 +08:00 星期五 L System.Logging.EventBusService[0] #1
[Furion.dll] async Task Furion.EventBus.EventBusHostedService.ExecuteAsync(CancellationToken stoppingToken)
EventBus hosted service is running.
info: 2023-03-17 18:25:08.1393952 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: https://localhost:5001
info: 2023-03-17 18:25:08.1620391 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: http://localhost:5000
info: 2023-03-17 18:25:08.1972456 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Application started. Press Ctrl+C to shut down.
info: 2023-03-17 18:25:08.2456579 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Hosting environment: Development
info: 2023-03-17 18:25:08.2746134 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[System.Private.CoreLib.dll] void System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(bool throwOnFirstException)
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2023-03-17 18:25:18.1917784 +08:00 星期五 L Furion.Application.TestLoggerServices[0] #16
[Furion.Application.dll] void Furion.Application.TestLoggerServices.测试日志()
我是一个日志 20

18.6 输出到文件

18.6.1 基础使用

// 例子一:启动层根目录输出
services.AddFileLogging("application.log");

// 例子二:支持路径
services.AddFileLogging("logs/application.log");

// 例子三:支持日志追加还是覆盖,设置 true 为追加,false 为覆盖
services.AddFileLogging("application.log", true);

18.6.2 从配置文件读取配置

特别注意

只有不在 .AddFile 第一个参数配置文件名才会自动加载配置,也就是文件名应该配置在配置文件中。

文件日志配置说明:

{
"Logging": {
"LogLevel": {
"Default": "Information"
// .... appsettings 默认配置
},
"File": {
"FileName": "application.log", // 日志文件完整路径或文件名,推荐 .log 作为拓展名
"Append": true, // 追加到已存在日志文件或覆盖它们
"MinimumLevel": "Information", // 最低日志记录级别
"FileSizeLimitBytes": 0, // 控制每一个日志文件最大存储大小,单位是 B,也就是 1024 才等于 1KB,默认无限制,如果指定了该值,那么日志文件大小超出了该配置就会创建新的日志文件,新创建的日志文件命名规则:文件名+[递增序号].log
"MaxRollingFiles": 0 // 控制最大创建的日志文件数量,默认无限制,配合 FileSizeLimitBytes 使用,如果指定了该值,那么超出该值将从最初日志文件中从头写入覆盖
}
},
// 自定义配置节点
"MyLogger": {
"FileName": "application.log",
"Append": true,
"MinimumLevel": "Information",
"FileSizeLimitBytes": 0,
"MaxRollingFiles": 0
}
}
// 例子一:默认读取 Logging:File 节点
services.AddFileLogging();

// 例子二:默认读取 Logging:File 节点,支持更多配置
services.AddFileLogging(options =>
{
options.MinimumLevel = LogLevel.Warning;

// 其他配置...
});

// 例子三:自定义配置节点
services.AddFileLogging(() => "MyLogger");

// 例子四:自定义配置节点,支持更多配置
services.AddFileLogging(() => "MyLogger", options =>
{
options.MinimumLevel = LogLevel.Warning;

// 其他配置...
});

18.6.3 自定义日志文件名规则

// 例子一:支持系统环境变量,如%SystemDrive%,%SystemRoot%
services.AddFileLogging("application%SystemDrive%-%SystemRoot%.log");

// 例子二:每天创建一个日志文件
services.AddFileLogging("application-{0:yyyy}-{0:MM}-{0:dd}.log", options =>
{
options.FileNameRule = fileName =>
{
return string.Format(fileName, DateTime.UtcNow);
};
});

// 例子三,任何自己喜欢的命名规则
services.AddFileLogging("application-{0:yyyy}-{0:MM}-{0:dd}.log", options =>
{
options.FileNameRule = fileName =>
{
// your rule...
};
});

// 例子四,批量设置多个
Array.ForEach(new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error }, logLevel =>
{
services.AddFileLogging("application-{1}-{0:yyyy}-{0:MM}-{0:dd}.log", options =>
{
options.FileNameRule = fileName => string.Format(fileName, DateTime.UtcNow, logLevel.ToString());
options.WriteFilter = logMsg => logMsg.LogLevel == logLevel;
});
});

18.6.4 日志过滤器/筛选器

通过日志筛选器可以对日志进行归类写入

// 例子一:根据日志级别输出
services.AddFileLogging("infomation.log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Information;
};
});

services.AddFileLogging("error.log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Error;
};
});

// 例子二,根据任何规则,比如特定的类名
services.AddFileLogging("someclass.log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName.Contains("SomeClassName");
};
});

18.6.5 自定义日志模板

默认情况下,Furion 提供了标准的日志输出模板,如:

2022-07-23T20:16:29.3459053+08:00	[INF]	[Furion.EventBus.EventBusHostedService]	[0]	EventBus Hosted Service is running.
2022-07-23T20:16:29.5827366+08:00 [INF] [Microsoft.Hosting.Lifetime] [0] Application started. Press Ctrl+C to shut down.
2022-07-23T20:16:29.5828798+08:00 [INF] [Microsoft.Hosting.Lifetime] [0] Hosting environment: Development
2022-07-23T20:16:29.5829377+08:00 [INF] [Microsoft.Hosting.Lifetime] [0] Content root path: C:\Workplaces\Furion\samples\Furion.Web.Entry\

如需自定义:

// 例子一,自定义日志模板(常用)
services.AddFileLogging("mytemplate.log", options =>
{
options.MessageFormat = (logMsg) =>
{
var stringBuilder = new StringBuilder();

stringBuilder.Append(DateTime.Now.ToString("o"));
// 其他的。。。自己组装

return stringBuilder.ToString();
};
});

// 例子二,需要输出 json 格式,比如对接阿里云日志,kibana第三方日志使用这个
services.AddFileLogging("mytemplate.log", options =>
{
options.MessageFormat = (logMsg) =>
{
// 高性能写入
return logMsg.WriteArray(writer =>
{
writer.WriteStringValue(DateTime.Now.ToString("o"));
writer.WriteStringValue(logMsg.LogLevel.ToString());
writer.WriteStringValue(logMsg.LogName);
writer.WriteNumberValue(logMsg.EventId.Id);
writer.WriteStringValue(logMsg.Message);
writer.WriteStringValue(logMsg.Exception?.ToString());
});
};
});

// 例子二,需要输出 json (自定义)格式,比如对接阿里云日志,kibana第三方日志使用这个
services.AddFileLogging("mytemplate.log", options =>
{
options.MessageFormat = (logMsg) =>
{
// 高性能写入
return logMsg.Write(writer =>
{
// write 对象为 Utf8JsonWriter,可通过流写入,性能极高
});
};
});

// 输出为 JSON 格式,Furion 4.5.2+
services.AddFileLogging("mytemplate.log", options =>
{
options.MessageFormat = LoggerFormatter.Json;
// Furion 4.8.0+ 新增 JSON 美化输出
options.MessageFormat = LoggerFormatter.JsonIndented;
});

18.6.6 日志写入失败处理

有时候可能因为日志文件被打开或者其他应用程序占用了,那么就会导致日志写入失败,这时候可以进行其他相关处理:

// 例子一:其他处理
services.AddFileLogging("template-obj.log", options =>
{
options.HandleWriteError = (writeError) =>
{
// ~~
};
});

// 例子二,启用备用日志文件功能,也就是如果文件被占用了,可以创建新的备用日志继续写入,推荐!!!
services.AddFileLogging("template-obj.log", options =>
{
options.HandleWriteError = (writeError) =>
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
});

18.6.7 自定义日志输出时间格式

版本说明

以下内容仅限 Furion 4.5.1 + 版本使用。

services.AddFileLogging("application.log", options =>
{
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";
});
info: 2022-09-28 02:02:20(+08:00) 星期三 System.Logging.EventBusService[0] #1
EventBus Hosted Service is running.
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[14] #1
Now listening on: https://localhost:5001
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[14] #1
Now listening on: http://localhost:5000
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2022-09-28 02:02:22(+08:00) 星期三 Microsoft.Hosting.Lifetime[0] #1
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry\

18.6.8 输出日志 TraceId/HttpContextId

版本说明

以下内容仅限 Furion 4.8.1.3 + 版本使用。

在生产环境中,日志的输出是非常频繁的,但是很难从日志文件中判断哪些日志是属于同一个请求输出的,这时启用 WithTraceId 配置即可。

services.AddFileLogging(options =>
{
options.WithTraceId = true;
});

输出日志如下:

info: 2022-11-24 14:34:55.1717549 +08:00 星期四 L System.Logging.EventBusService[0] #1
EventBus Hosted Service is running.
info: 2022-11-24 14:34:55.2504015 +08:00 星期四 L System.Logging.ScheduleService[0] #1
Schedule Hosted Service is running.
info: 2022-11-24 14:34:56.4280796 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: https://localhost:5001
info: 2022-11-24 14:34:56.4331170 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: http://localhost:5000
info: 2022-11-24 14:34:56.4384567 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2022-11-24 14:34:56.4408766 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2022-11-24 14:34:56.4427659 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2022-11-24 14:35:06.8507338 +08:00 Thursday L Furion.Application.TestLoggerServices[0] #17 '00-48df9ac5c8280de2f301faa44a23a10c-b75678d9f3883b0b-00'
我是一个日志 20
info: 2022-11-24 14:35:16.0373384 +08:00 星期四 L Furion.Application.TestLoggerServices[0] #17 '00-ff4fb15d6ff41a0411784e66400f0dfd-962bc25eff788b25-00'
我是一个日志 20

18.6.9 输出日志 程序集/类型/方法

版本说明

以下内容仅限 Furion 4.8.7.16 + 版本使用。

在生产环境中,有时候我们希望知道这个日志是在哪个程序集,哪个类中哪个方法输出,这时启用 WithStackFrame 配置即可。

services.AddFileLogging(options =>
{
options.WithStackFrame = true;
});

输出日志如下:

info: 2023-03-17 18:25:06.7988349 +08:00 星期五 L System.Logging.EventBusService[0] #1
[Furion.dll] async Task Furion.EventBus.EventBusHostedService.ExecuteAsync(CancellationToken stoppingToken)
EventBus hosted service is running.
info: 2023-03-17 18:25:08.1393952 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: https://localhost:5001
info: 2023-03-17 18:25:08.1620391 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: http://localhost:5000
info: 2023-03-17 18:25:08.1972456 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Application started. Press Ctrl+C to shut down.
info: 2023-03-17 18:25:08.2456579 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Hosting environment: Development
info: 2023-03-17 18:25:08.2746134 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[System.Private.CoreLib.dll] void System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(bool throwOnFirstException)
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2023-03-17 18:25:18.1917784 +08:00 星期五 L Furion.Application.TestLoggerServices[0] #16
[Furion.Application.dll] void Furion.Application.TestLoggerServices.测试日志()
我是一个日志 20

18.7 输出到数据库/其他存储介质

将日志输出到数据库中也是非常常见的需求,Furion 把该功能做到了非常简单,支持任何存储介质。

在写入数据库/其他存储介质之前需创建数据库日志写入器并实现 IDatabaseLoggingWriter 接口,支持多个,如:

using Furion.Logging;

namespace YourProject.Core;

public class DatabaseLoggingWriter : IDatabaseLoggingWriter
{
// 支持构造函数注入任何实例,会自动释放任何服务,比如注入 IRepository,或者 SqlSugarClient
public DatabaseLoggingWriter()
{
}

public void Write(LogMessage logMsg, bool flush)
{
// 这里写你任何插入数据库的操作,无需 try catch
}
}
注意事项

Furion 4.8.5.4 版本之前,如果在 DatabaseLoggingWriter 中注入 ILogger<> 对象将会导致死循环,原因是 IDatabaseLoggingWriter 本身就是日志输出的最终介质,如果这里还输出日志,将导致递归初始化日志实例。

但在 Furion 4.8.5.4+ 版本之后,如果注入了 ILogger<> 实例,那么将强制性将实例化对象为 EmptyLogger 对象,但调用其输出日志方法将不会有任何效果,如:logger.LogInformation("...");

你没看错,就这么简单!!

18.7.1 基础使用

// 例子一,默认配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options => {});

// 例子二:自定义配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.MinimumLevel = LogLevel.Warning;

// 其他配置...
});

18.7.2 从配置文件中读取

特别注意

只有 .AddDatabase 第一个参数为空才会自动加载配置。

数据库日志配置说明:

{
"Logging": {
"LogLevel": {
"Default": "Information"
// .... appsettings 默认配置
},
"Database": {
"MinimumLevel": "Information" // 最低日志记录级别
}
},
// 自定义配置节点
"MyLogger": {
"MinimumLevel": "Information"
}
}
// 例子一:默认读取 Logging:Database 节点
services.AddDatabaseLogging<DatabaseLoggingWriter>();

// 例子二:默认读取 Logging:Database 节点,支持更多配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(default(string), options =>
{
options.MinimumLevel = LogLevel.Warning;

// 其他配置...
});

// 例子三:自定义配置节点
services.AddDatabaseLogging<DatabaseLoggingWriter>("MyLogger");
// 或
services.AddDatabaseLogging<DatabaseLoggingWriter>(() => "MyLogger");

// 例子四:自定义配置节点,支持更多配置
services.AddDatabaseLogging<DatabaseLoggingWriter>("MyLogger", options =>
{
options.MinimumLevel = LogLevel.Warning;

// 其他配置...
});
// 或
services.AddDatabaseLogging<DatabaseLoggingWriter>(() => "MyLogger", options =>
{
options.MinimumLevel = LogLevel.Warning;

// 其他配置...
});

18.7.3 日志过滤器/筛选器

通过日志筛选器可以对日志进行归类写入

// 例子一:根据日志级别输出,可以分别定义 IDatabaseLoggingWriter,也可以用同一个底层进行判断
services.AddDatabaseLogging<InfomationLoggingWriter>(options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Information;
};
});
// 可以分别定义 IDatabaseLoggingWriter,也可以用同一个底层进行判断
services.AddDatabaseLogging<ErrorLoggingWriter>(options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Error;
};
});

// 例子二,根据任何规则,比如特定的类名
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName.Contains("SomeClassName");
};
});

18.7.4 自定义日志模板

services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.MessageFormat = (logMsg) =>
{
var stringBuilder = new StringBuilder();

stringBuilder.Append(DateTime.Now.ToString("o"));
// 其他的。。。自己组装

return stringBuilder.ToString();
};
});

// 输出为 JSON 格式,Furion 4.5.2+
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.MessageFormat = LoggerFormatter.Json;
// Furion 4.8.0+ 新增 JSON 美化输出
options.MessageFormat = LoggerFormatter.JsonIndented;
});

18.7.5 日志写入失败处理

有时候可能因为数据库连接异常或其他原因连接池满,那么就会导致日志写入失败,这时候可以进行其他相关处理:

// 例子一:其他处理
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.HandleWriteError = (writeError) =>
{
// ~~
};
});

18.7.6 输出日志 TraceId/HttpContextId

版本说明

以下内容仅限 Furion 4.8.1.3 + 版本使用。

在生产环境中,日志的输出是非常频繁的,但是很难从日志文件中判断哪些日志是属于同一个请求输出的,这时启用 WithTraceId 配置即可。

services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.WithTraceId = true;
});

输出日志如下:

info: 2022-11-24 14:34:55.1717549 +08:00 星期四 L System.Logging.EventBusService[0] #1
EventBus Hosted Service is running.
info: 2022-11-24 14:34:55.2504015 +08:00 星期四 L System.Logging.ScheduleService[0] #1
Schedule Hosted Service is running.
info: 2022-11-24 14:34:56.4280796 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: https://localhost:5001
info: 2022-11-24 14:34:56.4331170 +08:00 星期四 L Microsoft.Hosting.Lifetime[14] #1
Now listening on: http://localhost:5000
info: 2022-11-24 14:34:56.4384567 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2022-11-24 14:34:56.4408766 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2022-11-24 14:34:56.4427659 +08:00 星期四 L Microsoft.Hosting.Lifetime[0] #1
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2022-11-24 14:35:06.8507338 +08:00 Thursday L Furion.Application.TestLoggerServices[0] #17 '00-48df9ac5c8280de2f301faa44a23a10c-b75678d9f3883b0b-00'
我是一个日志 20
info: 2022-11-24 14:35:16.0373384 +08:00 星期四 L Furion.Application.TestLoggerServices[0] #17 '00-ff4fb15d6ff41a0411784e66400f0dfd-962bc25eff788b25-00'
我是一个日志 20

18.7.7 输出日志 程序集/类型/方法

版本说明

以下内容仅限 Furion 4.8.7.16 + 版本使用。

在生产环境中,有时候我们希望知道这个日志是在哪个程序集,哪个类中哪个方法输出,这时启用 WithStackFrame 配置即可。

services.AddDatabaseLogging(options =>
{
options.WithStackFrame = true;
});

输出日志如下:

info: 2023-03-17 18:25:06.7988349 +08:00 星期五 L System.Logging.EventBusService[0] #1
[Furion.dll] async Task Furion.EventBus.EventBusHostedService.ExecuteAsync(CancellationToken stoppingToken)
EventBus hosted service is running.
info: 2023-03-17 18:25:08.1393952 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: https://localhost:5001
info: 2023-03-17 18:25:08.1620391 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1
[System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine)
Now listening on: http://localhost:5000
info: 2023-03-17 18:25:08.1972456 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Application started. Press Ctrl+C to shut down.
info: 2023-03-17 18:25:08.2456579 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted()
Hosting environment: Development
info: 2023-03-17 18:25:08.2746134 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1
[System.Private.CoreLib.dll] void System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(bool throwOnFirstException)
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry
info: 2023-03-17 18:25:18.1917784 +08:00 星期五 L Furion.Application.TestLoggerServices[0] #16
[Furion.Application.dll] void Furion.Application.TestLoggerServices.测试日志()
我是一个日志 20

18.7.8 关于高频写入存储介质

在一些高频写入存储介质(如数据库)日志的场景,为避免频繁解析服务(创建和销毁链接)和创建作用域导致内存及 CPU 飙高,可使用 类全局作用域 和所有服务都采取单例的方式:

public class DatabaseLoggingWriter : IDatabaseLoggingWriter, IDisposable
{
private readonly IServiceScope _serviceScope;
private readonly IRepository<LogTable> _logRepository; // 可替换成任何数据库操作库

public DatabaseLoggingWriter(IServiceScopeFactory scopeFactory)
{
_serviceScope = scopeFactory.CreateScope();
_logRepository = _serviceScope.ServiceProvider.GetRequiredService<IRepository<LogTable>>();
}

public void Write(LogMessage logMsg, bool flush)
{
_logRepository.Insert(.....);
}

/// <summary>
/// 释放服务作用域
/// </summary>
public void Dispose()
{
_serviceScope.Dispose();
}
}

18.7.9 关于数据库日志循环输出日志

微软提供的 EFCore 或者第三方 ORM 本身操作数据库时自带日志输出,这就会导致 IDatabaseLoggingWriterWrite 死循环,解决这个问题有以下方法:

  1. 创建新的数据库操作实例并关闭日志
  2. 更新到 Furion 4.7.0+ 版本 (推荐)
  3. 自行根据业务逻辑过滤

如不存在该问题可关闭框架自带死循环检测功能(对性能有提升作用)

services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.IgnoreReferenceLoop = false;
});

18.8 添加日志提供器方式

框架提供了多种添加日志提供器方式,可在应用启动或运行时。

18.8.1 LoggerFactory 方式

Furion 也提供了运行时动态添加日志提供器并写入:

public class SomeController : IDynamicApiController, IDisposable
{
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;

public SomeController()
{
_loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFile("application.log");
});
_logger = _loggerFactory.CreateLogger("MyCategory");

// 可以多个重复上面代码
}

[HttpGet]
public void SomeAction()
{
_logger.LogInformation("some log...");
}

public void Dispose()
{
_loggerFactory.Dispose();
}
}
特别注意

通过运行时的方式需要自行释放 ILoggerFactory 对象,也就是需要实现 IDisposable 接口,然后进行 _loggerFactory 释放。

可查看相关 Issuehttps://gitee.com/dotnetchina/Furion/issues/I7KJ5H

18.8.2 ILoggingBuilder 方式

Furion 也提供了原生 services.AddLogging(builder => {}) 方式配置,如

services.AddLogging(builder =>
{
builder.AddFile("applicaion.log");

builder.AddDatabase<DatabaseLoggingWriter>();

//....
});

18.9 记录请求日志

ASP.NET 6 中,框架默认提供了 app.UseHttpLogging() 记录 HTTP 请求日志功能,详细了解可查看官方文档 ASP.NET Core - HTTP 日志记录

当然也可以自定义中间件的方式写,只需要注入 ILogger<> 接口即可。

18.10 DebugTrace 默认不输出问题

默认情况下,微软在 appsettings.jsonappsettings.Development.json 中配置了 Default 日志级别,如需自定义:

{
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}

这时候只需要修改 DefaultDebugTrace 即可,注意不同环境加载不同的配置文件。开发环境应修改 appsettings.Development.json 下的配置。

18.11 [LoggingMonitor] 监听日志

Furion 3.9.1 版本新增了 [LoggingMonitor] 特性,支持在控制器或操作中贴该特性,可以实现强大的请求日志监听,方便测试,如:

18.11.1 特性配置

using Furion.Logging;

namespace Furion.Application;

public class TestLoggerServices : IDynamicApiController
{
[LoggingMonitor]
public PersonDto GetPerson(int id)
{
return new PersonDto
{
Id = id
};
}
}
  • [LoggingMonitor] 支持以下配置:
    • Title:配置标题,string 类型,默认 Logging Monitor
    • WithReturnValue:是否包含返回值打印,bool 类型,默认 trueFurion 4.3.9+ 有效
    • ReturnValueThreshold:配置返回值字符串阈值,int 类型,默认 0 全量输出,Furion 4.3.9+ 有效
    • JsonBehavior:配置 LoggingMonitor Json 输出行为,默认 NoneFurion 4.5.2+ 有效
    • JsonIndented:配置 LoggingMonitor Json 格式化行为,bool 类型,默认 falseFurion 4.8.0+ 有效

输出日志为:

┏━━━━━━━━━━━  Logging Monitor ━━━━━━━━━━━
┣ Furion.Application.TestLoggerServices.GetPerson (Furion.Application)

┣ 控制器名称: TestLoggerServices
┣ 操作名称: GetPerson
┣ 路由信息: [area]: ; [controller]: test-logger; [action]: person
┣ 请求方式: POST
┣ 请求地址: https://localhost:44316/api/test-logger/person/11
┣ 来源地址: https://localhost:44316/api/index.html
┣ 浏览器标识: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62
┣ 客户端 IP 地址: 0.0.0.1
┣ 服务端 IP 地址: 0.0.0.1
┣ 服务端运行环境: Development
┣ 执行耗时: 31ms
┣ ━━━━━━━━━━━━━━━ 授权信息 ━━━━━━━━━━━━━━━
┣ JWT Token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIkFjY291bnQiOiJhZG1pbiIsImlhdCI6MTY1ODcxNjc5NywibmJmIjoxNjU4NzE2Nzk3LCJleHAiOjE2NTg3MTc5OTcsImlzcyI6ImRvdG5ldGNoaW5hIiwiYXVkIjoicG93ZXJieSBGdXJpb24ifQ.VYZkwwqCwlUy3aJjuL-og62I0rkxNQ96kSjEm3VgXtg

┣ UserId (integer): 1
┣ Account (string): admin
┣ iat (integer): 1658716797
┣ nbf (integer): 1658716797
┣ exp (integer): 1658717997
┣ iss (string): dotnetchina
┣ aud (string): powerby Furion
┣ ━━━━━━━━━━━━━━━ 参数列表 ━━━━━━━━━━━━━━━
┣ Content-Type:

┣ id (Int32): 11
┣ ━━━━━━━━━━━━━━━ 返回信息 ━━━━━━━━━━━━━━━
┣ 类型: Furion.Application.Persons.PersonDto
┣ 返回值: {"Id":11,"Name":null,"Age":0,"Address":null,"PhoneNumber":null,"QQ":null,"CreatedTime":"0001-01-01T00:00:00+00:00","Childrens":null,"Posts":null}
┗━━━━━━━━━━━ Logging Monitor ━━━━━━━━━━━

18.11.2 全局配置

如需全局启用 LoggingMonitor 功能,无需在每个控制器或者方法中贴,全局注册如下:

// 请使用 services.AddMonitorLogging(); 替代,并启用 GlobalEnabled: true
services.AddMvcFilter<LoggingMonitorAttribute>();
Furion 4.0.2 新推荐配置

Furion 4.0.2 版本中新增了非常灵活方便的 services.AddMonitorLogging() 服务配置,可在配置中随意控制哪个类哪个方法启用或不启用。

  • 注册服务
services.AddMonitorLogging();   // 默认读取 Logging:Monitor 下配置,支持传入参数自定义
  • 添加配置
{
"Logging": {
"Monitor": {
"GlobalEnabled": false, // 是否启用全局拦截,默认 `false`
"IncludeOfMethods": [], // 是否指定拦截特定方法,当 GlobalEnabled: false 有效
"ExcludeOfMethods": [], // 是否指定排除特定方法,当 GlobalEnabled: true 有效
"LogLevel": "Information", // 配置监听日志输出级别,默认 Information
"BahLogLevel": "Information", // 配置 Oops.Oh 和 Oops.Bah 业务日志输出级别,默认 Information
"WithReturnValue": true, // 配置是否包含返回值,默认 `true`,Furion 4.3.9+ 有效
"ReturnValueThreshold": 0, // 配置返回值字符串阈值,默认 0,全量输出,Furion 4.3.9+ 有效
"JsonBehavior": "None", // 配置 LoggingMonitor Json 输出行为,默认 None,Furion 4.5.2+ 有效
"JsonIndented": false, // 配置 LoggingMonitor Json 格式化行为,默认 false,Furion 4.8.2+ 有效
"ContractResolver": "CamelCase", // 配置 LoggingMonitor 序列化属性命名规则,默认 CamelCase,Furion 4.8.6.12+ 有效
"MethodsSettings": [
// 配置被监视方法更多信息,Furion 4.3.9+ 有效
{
"FullName": "Furion.Application.TestLoggerServices.MethodName", // 方法完全限定名
"WithReturnValue": true, // 配置是否包含返回值,默认 `true`,Furion 4.3.9+ 有效
"ReturnValueThreshold": 0, // 配置返回值字符串阈值,默认 0,全量输出,Furion 4.3.9+ 有效
"JsonIndented": false, // 配置 LoggingMonitor Json 格式化行为,默认 false,Furion 4.8.2+ 有效
"JsonBehavior": "None", // 配置 LoggingMonitor Json 输出行为,默认 None,Furion 4.5.2+ 有效
"ContractResolver": "CamelCase" // 配置 LoggingMonitor 序列化属性命名规则,默认 CamelCase,Furion 4.8.6.12+ 有效
}
]
}
}
}

IncludeOfMethodsExcludeOfMethods 方法签名格式为:类完全限定名.方法名,如:Furion.Application.TestNamedServices.GetNameFurion.Application.TestNamedServices 是类名,GetName 是方法名。

如果配置了全局请求监视日志,对个别不需要监视的接口方法只需要贴 [SuppressMonitor] 特性即可。

18.11.3 更多配置

版本说明

以下内容仅限 Furion 4.3.9 + 版本使用。

支持 LoggingMonitor 写入日志拦截,如添加额外数据:

services.AddMonitorLogging(options =>
{
options.ConfigureLogger((logger, logContext, context) =>
{
var httpContext = context.HttpContext;
logContext.Set("extra", "其他数据");
});
});

除此之外,还支持配置 json 路径:

services.AddMonitorLogging(jsonKey: "YourKey:Monitor");

18.11.4 JSON 格式

版本说明

以下内容仅限 Furion 4.5.2 + 版本使用。

  1. 全局/局部启用 Json 输出配置
// 全局
services.AddMonitorLogging(options =>
{
options.JsonBehavior = Furion.Logging.JsonBehavior.OnlyJson;
options.JsonIndented = true; // 是否美化 JSON,Furion 4.8.0+ 版本有效
});

// 局部
[LoggingMonitor(JsonBehavior = Furion.Logging.JsonBehavior.OnlyJson)]
// 是否美化 JSON,Furion 4.8.0+ 版本有效
[LoggingMonitor(JsonBehavior = Furion.Logging.JsonBehavior.OnlyJson, JsonIndented = true)]
关于 JsonBehavior

只有设置为 JsonBehavior.OnlyJson 时才不会输出美观的日志。

  1. 写入存储介质
using Furion.Logging;

namespace YourProject.Core;

public class DatabaseLoggingWriter : IDatabaseLoggingWriter
{
// 支持构造函数注入任何实例,会自动释放任何服务,比如注入 IRepository,或者 SqlSugarClient
public DatabaseLoggingWriter()
{
}

public void Write(LogMessage logMsg, bool flush)
{
// 如果 JsonBehavior 配置为 OnlyJson 或者 All,那么 Context 就包含 loggingMonitor 的值
// 如果 JsonBehavior 配置为 OnlyJson,那么可直接通过 logMsg.Message 获取结果就是 json 格式
if (logMsg.LogName == "System.Logging.LoggingMonitor")
{
var jsonString = logMsg.Context.Get("loggingMonitor");
}

// 这里写你任何插入数据库的操作,无需 try catch
}
}

Json 输出格式如下:

{
"controllerName": "test-logger",
"controllerTypeName": "TestLoggerServices",
"actionName": "person",
"actionTypeName": "GetPerson",
"areaName": null,
"displayName": "Furion.Application.TestLoggerServices.GetPerson (Furion.Application)",
"localIPv4": "0.0.0.1",
"remoteIPv4": "0.0.0.1",
"httpMethod": "GET",
"requestUrl": "https://localhost:5001/api/test-logger/person/2",
"refererUrl": "https://localhost:5001/api/index.html?urls.primaryName=数据库操作演示",
"environment": "Development",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.53",
"requestHeaderAuthorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsIkFjY291bnQiOiJhZG1pbiIsImlhdCI6MTY2NDQ1MDUwNSwibmJmIjoxNjY0NDUwNTA1LCJleHAiOjE2NjQ0NTE3MDUsImlzcyI6ImRvdG5ldGNoaW5hIiwiYXVkIjoicG93ZXJieSBGdXJpb24ifQ.-xocNcDQGoXClceoVU5QAHIkTcOZ7ZXo0hEbzghDfFI",
"timeOperationElapsedMilliseconds": 55,
"authorizationClaims": [
{
"type": "UserId",
"valueType": "integer",
"value": "1"
},
{
"type": "Account",
"valueType": "string",
"value": "admin"
},
{
"type": "iat",
"valueType": "integer",
"value": "1664450505"
},
{
"type": "nbf",
"valueType": "integer",
"value": "1664450505"
},
{
"type": "exp",
"valueType": "integer",
"value": "1664451705"
},
{
"type": "iss",
"valueType": "string",
"value": "dotnetchina"
},
{
"type": "aud",
"valueType": "string",
"value": "powerby Furion"
}
],
"parameters": [
{
"name": "id",
"type": "System.Int32",
"value": 2
}
],
"returnInformation": {
"type": "Furion.UnifyResult.RESTfulResult`1[[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]",
"actType": "Furion.Application.Persons.PersonDto",
"value": {
"StatusCode": 200,
"Data": {
"Id": 2,
"Name": null,
"Age": 0,
"Address": null,
"PhoneNumber": null,
"QQ": null,
"CreatedTime": "0001-01-01T00:00:00+00:00",
"Childrens": null,
"Posts": null
},
"Succeeded": true,
"Errors": null,
"Extras": null,
"Timestamp": 1664450517341
}
},
"exception": {
"type": "System.DivideByZeroException",
"message": "Attempted to divide by zero.",
"stackTrace": " at Furion.Application.TestLoggerServices.测试日志监听8(Int32 id) in D:\\Workplaces\\OpenSources\\Furion\\samples\\Furion.Application\\TestLoggerServices.cs:line 78\r\n at lambda_method103(Closure , Object , Object[] )\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)\r\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)"
},
"validation": {
"errorCode": null,
"originErrorCode": null,
"message": "出错了啊。。。。"
}
}

18.11.5 全局过滤 WriteFilter

版本说明

以下内容仅限 Furion 4.5.9 + 版本使用。

Furion 4.5.9+ 版本新增了 WriteFilter 过滤功能,可根据自定义逻辑自定义过滤拦截:

services.AddMonitorLogging(options =>
{
options.WriteFilter = (context) =>
{
// 获取控制器/操作描述器
var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;

// 你的逻辑....,需要拦截返回 false,否则 true

return true;
};
});

18.11.6 输出 JSON 支持忽略属性名或属性类型

版本说明

以下内容仅限 Furion 4.6.1 + 版本使用。

有时接口的返回值包含不能被序列化的类型或者想忽略某些属性名不被序列化,这时候就需要用到这个。

  • 局部配置
// 忽略名称和属性,支持单一配置或局部配置
[LoggingMonitor(JsonBehavior = JsonBehavior.OnlyJson
, IgnorePropertyNames = new[] { "Bytes" }
, IgnorePropertyTypes = new[] { typeof(byte[]) })]
public object MethodName(int id)
{
return new
{
Id = 10,
Bytes = File.ReadAllBytes("image.png")
};
}
  • 全局配置
// 忽略名称和属性,支持单一配置或局部配置
services.AddMonitorLogging(options =>
{
options.IgnorePropertyNames = new[] { "Byte" };
options.IgnorePropertyTypes = new[] { typeof(byte[]) };
});

18.11.7 将 LoggingMonitor 写入数据库

  1. 注册 .AddDatabaseLogging<> 服务:
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName == "System.Logging.LoggingMonitor";
};
});
  1. 写入数据库
using Furion.Logging;

namespace YourProject.Core;

public class DatabaseLoggingWriter : IDatabaseLoggingWriter
{
// 任何数据库 ORM 注入。。。
public DatabaseLoggingWriter()
{
}

public void Write(LogMessage logMsg, bool flush)
{
// 将 logMsg 的属性一一插入到数据库中~
}
}
单个 DatabaseLoggingWriter 情况

如果已经全局注册了:

services.AddDatabaseLogging<DatabaseLoggingWriter>();   // 注意这里没有过滤 logName

且不想多注册一个数据库日志服务,那么只需要在代码中过滤即可:

using Furion.Logging;

namespace YourProject.Core;

public class DatabaseLoggingWriter : IDatabaseLoggingWriter
{
// 任何数据库 ORM 注入。。。
public DatabaseLoggingWriter()
{
}

public void Write(LogMessage logMsg, bool flush)
{
// 将 logMsg 的属性一一插入到数据库中~
if(logMsg.LogName == "System.Logging.LoggingMonitor")
{
// 写入审计表数据库
}
else
{
// 写入其他表数据库
}
}
}

18.11.8 支持 [DisplayName] 特性

版本说明

以下内容仅限 Furion 4.8.5.10 + 版本使用。

通常我们会为每一个接口添加一个唯一名称,比如使用 [DisplayName] 特性,方便后续写入日志进行归类。

public class TestService: IDynamicApiController
{
[LoggingMonitor]
[DisplayName("测试方法")]
public void TestMethod()
{
}
}

那么如果输出为 JSON 格式日志将会自动添加 displayTitle 键,值为 测试方法

18.11.9 输出序列化键格式配置(属性大小写)

版本说明

以下内容仅限 Furion 4.8.6.12 + 版本使用。

该功能主要配置序列化时属性名输出规则,注意此项并不配置 JSON 输出规则。

  • 局部配置
[LoggingMonitor(ContractResolver = ContractResolverTypes.Default)]    // CamelCase 或者 Default,默认是 CamelCase
public DataTable MethodName()
{
var d = "select * from person".SqlQuery();
return d;
}
  • 全局配置
services.AddMonitorLogging(options =>
{
options.ContractResolver = ContractResolverTypes.Default; // CamelCase 或者 Default,默认是 CamelCase
});

18.11.10 跳过特定参数记录

版本说明

以下内容仅限 Furion 4.8.7.3 + 版本使用。

Furion 4.8.7.3+ 版本新增 [SuppressMonitor] 特性支持标记参数(支持类型,方法)不被记录,如:

[LoggingMonitor]
public string GetName([SuppressMonitor]SomeType type, int id) // type 参数将跳过记录
{
return nameof(Furion);
}

18.12 打印日志到 Swagger

Furion 框架中默认集成了 MiniProfiler 组件并与 Swagger 进行了结合,如需打印日志或调试代码,只需调用以下方法即可:

App.PrintToMiniProfiler("分类", "状态", "要打印的消息");

18.13 静态 Default() 方式构建

StringLoggingPart.Default().SetMessage("这是一个日志").LogInformation();

18.14 规范日志模板

Furion v3.5.3+ 新增了 TP.Wrapper(...) 规范模板,使用如下:

// 生成模板字符串
var template = TP.Wrapper("Furion 框架", "让 .NET 开发更简单,更通用,更流行。",
"##作者## 百小僧",
"##当前版本## v3.5.3",
"##文档地址## http://furion.baiqian.ltd",
"##Copyright## 百小僧, 百签科技(广东)有限公司");

Console.WriteLine(template);

日志打印模板如下:

┏━━━━━━━━━━━  Furion 框架 ━━━━━━━━━━━
┣ 让 .NET 开发更简单,更通用,更流行。

┣ 作者: 百小僧
┣ 当前版本: v3.5.3
┣ 文档地址: http://furion.baiqian.ltd
┣ Copyright: 百小僧, 百签科技(广东)有限公司
┗━━━━━━━━━━━ Furion 框架 ━━━━━━━━━━━
关于属性生成

如果列表项以 ##属性名## 开头,自动生成 属性名: 作为行首且自动等宽对齐。

Furion 3.9.1 之前版本使用 [属性名] 开头。

18.15 日志上下文

版本说明

以下内容仅限 Furion 4.6.0 + 版本使用。

18.15.1 添加上下文数据

有时候我们希望为日志提供额外数据,这时候可通过 .ScopeContext() 配置,如:

// 写法一
using (var scope = _logger.ScopeContext(ctx => ctx.Set("Name", "Furion").Set("UserId", 10)))
{
_logger.LogInformation("我是一个日志 {id}", 20);
}

// 写法二
using var scope = _logger.ScopeContext(new Dictionary<object, object> {
{ "Name", "Furion" },
{ "UserId", 10 }
});
_logger.LogInformation("我是一个日志 {id}", 20);

// 写法三
using var scope = _logger.ScopeContext(new LogContext {
// ....
});
_logger.LogInformation("我是一个日志 {id}", 20);

// 写法四
var (logger, scoped) = Log.ScopeContext(new LogContext {
// ...
});
logger.LogInformation("我是一个日志 {id}", 20);
scoped?.Dispose();

// 写法五
"我是一个日志 {id}".ScopeContext(new LogContext {
// ...
}).LogInformation();

18.15.2 读取上下文数据

LogMessage 对象中使用:

var value = logMsg.Context.Get("Key");

// 比如在过滤中使用
services.AddFileLogging("infomation.log", options =>
{
options.WriteFilter = (logMsg) =>
{
// 还可以设置给运行时使用:logMsg.Context.Set(...);
return logMsg.Context.Get("Name") == "Furion";
};
});

// 在 IDatabaseLoggingWriter 中使用
public void Write(LogMessage logMsg, bool flush)
{
var name = logMsg.Context.Get("Name");
}

18.15.3 日志上下文数据共享

public TestAppService: ITestAppService, IDisposable
{
private readonly ILogger<TestAppService> _logger;
private IDisposable _scopeProvider;

public TestAppService(ILogger<TestAppService> logger)
{
_logger = logger;

// 添加全局用户信息上下文数据
_scopeProvider = _logger.ScopeContext(ctx => ctx.Set("uid", "100").Set("uname", "百小僧"));
}

public string GetName(int id)
{
// 共享全局上下文数据
_logger.LogInformation("写入新的日志");

return "Furion";
}

public string GetTags(int id)
{
// 额外新增上下文数据
using var scope = _logger.ScopeContext(ctx => ctx.Set("key", "value"));
_logger.LogInformation("设置额外的上上下文日志");

return "百小僧";
}

public void Dispose()
{
_scopeProvider.Dispose();
}
}

18.16 关闭 .NET Core 底层日志

默认情况下,.NET Core 底层默认输出了很多日志,如需关闭只需在 appsettings.jsonappsettings.Development.json 中添加 "命名空间/日志类别":"最低日志级别" 日志类别过滤即可,如:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information",
"System.Net.Http.HttpClient": "Warning",
"命名空间/日志类别": "Warning"
}
}
}

小提醒:过滤 .NET Core 底层日志最低日志级别通常设置为 Warning

18.17 全局日志过滤/筛选

在一些特定的情况下,我们希望能够对所有日志输出介质中的日志进行过滤,可以通过以下方式进行全局配置。

特别注意

此方式会影响所有的日志介质输出,如控制台、文件、数据库等其他输出介质。

.NET5 版本:

Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddFilter((provider, category, logLevel) =>
{
return !new[] { "Microsoft.Hosting", "Microsoft.AspNetCore" }.Any(u => category.StartsWith(u))
&& logLevel >= LogLevel.Information;
});
})

.NET6+ 版本:

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
return !new[] { "Microsoft.Hosting", "Microsoft.AspNetCore" }.Any(u => category.StartsWith(u))
&& logLevel >= LogLevel.Information;
});

或者 Serve.Run 方式

Serve.Run(RunOptions.Default.AddWebComponent<WebComponent>());

public class WebComponent : IWebComponent
{
public void Load(WebApplicationBuilder builder, ComponentContext componentContext)
{
builder.Logging.AddFilter((provider, category, logLevel) =>
{
return !new[] { "Microsoft.Hosting", "Microsoft.AspNetCore" }.Any(u => category.StartsWith(u))
&& logLevel >= LogLevel.Information;
});
}
}
日志过滤无效情况

假如使用上述代码过滤无效(不能过滤默认的主机日志),那么请确认 appsettings.jsonappsettings.Development.jsonLogging:Level 是否如下:

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
}
}

如果配置了以下配置,请删除

"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",

18.18 反馈与建议

与我们交流

给 Furion 提 Issue


了解更多

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