18. 日志记录
📝 模块更新日志
新特性
- 审计日志
LoggingMonitor
支持对参数贴[SuppressMonitor]
特性跳过记录 4.8.7.3 ⏱️2023.03.01 #I6IVGW - 审计日志
LoggingMonitor
监听TraceId
、ThreadId
、Accept-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
- 审计日志
问题修复
- 日志消息没有处理
\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
- 日志消息没有处理
其他更改
18.1 关于日志
通常日志指的是系统日志和程序日志。
系统日志 是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。系统日志包括系统日志、应用程序日志和安全日志。
程序日志 是程序运行中产生的日志,通常由框架运行时或开发者提供的日志。包括请求日志,异常日志、审计日志、行为日志等。
18.2 日志作用
在项目开发中,都不可避免的使用到日志。没有日志虽然不会影响项目的正确运行,但是没有日志的项目可以说是不完整的。日志在调试,错误或者异常定位,数据分析中的作用是不言而喻的。
- 调试
在项目调试时,查看栈信息可以方便地知道当前程序的运行状态,输出的日志便于记录程序在之前的运行结果。
- 错误定位
不要以为项目能正确跑起来就可以高枕无忧,项目在运行一段时候后,可能由于数据问题,网络问题,内存问题等出现异常。这时日志可以帮助开发或者运维人员快速定位错误位置,提出解决方案。
- 数据分析
大数据的兴起,使得大量的日志分析成为可能,ELK 也让日志分析门槛降低了很多。日志中蕴含了大量的用户数据,包括点击行为,兴趣偏好等,用户画像对于公司下一步的战略方向有一定指引作用。
18.3 日志级别
日志级别可以有效的对日志信息进行归类,方便准确的查看特定日志内容。通常日志类别有以下级别:
级别 | 值 | 方法 | 描述 |
---|---|---|---|
Trace(跟踪) | 0 | LogTrace | 包含最详细的消息。 这些消息可能包含敏感的应用数据。 这些消息默认情况下处于禁用状态,并且不应在生产中启用。 |
Debug(调试) | 1 | LogDebug | 用于调试和开发。 由于量大,请在生产中小心使用。 |
Information(信息) | 2 | LogInformation | 跟踪应用的常规流。 可能具有长期值。 |
Warning(警告) | 3 | LogWarning | 对于异常事件或意外事件。 通常包括不会导致应用失败的错误或情况。 |
Error(错误) | 4 | LogError | 表示无法处理的错误和异常。 这些消息表示当前操作或请求失败,而不是整个应用失败。 |
Critical(严重) | 5 | LogCritical | 需要立即关注的失败。 例如数据丢失、磁盘空间不足。 |
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("类别").LogInformation();
通过字符串拓展方式可以在任何时候方便记录日志,专门为懒人提供的。
18.5 配置日志输出介质
以下小节仅在 Furion 3.9.0+
版本提供。
在 ASP.NET Core
应用程序中,主机启动时默认注册了 ConsoleLoggerProvider
提供器,也就是控制台日志输出提供器,所以无需任何注册服务即可在控制台输出。
18.5.1 输出到控制台
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\
如果希望不输出某些日志类别的日志可以添加以下过滤:
- 控制台输出过滤
.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.json
和 appsettings.Development.json
的 Logging:Level
是否如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
}
}
}
如果配置了以下配置,请删除:
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
- 控制台日志标准化模板
以下内容仅限 Furion 4.5.0 +
版本使用。
在 ASP.NET Core
默认控制台日志相对简洁,并未包含常见的日志时间、线程 Id
等,而且自定义模板也相对复杂,所以 Furion 4.5.0+
版本提供了简化配置,如:
Startup.cs
方式
services.AddConsoleFormatter();
.NET5
方式
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddConsoleFormatter();
});
.NET6
方式
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsoleFormatter();
Serve.Run()
方式
Serve.Run(RunOptions.Default.AddWebComponent<WebComponent>());
public class WebComponent : IWebComponent
{
public void Load(WebApplicationBuilder builder, ComponentContext componentContext)
{
builder.Logging.AddConsoleFormatter();
}
}
输出结果:
info: 2022-09-27T13:17:33.5958395+08:00 System.Logging.EventBusService[0] #1
EventBus Hosted Service is running.
info: 2022-09-27T13:17:35.1666167+08:00 Microsoft.Hosting.Lifetime[ListeningOnAddress] #1
Now listening on: https://localhost:5001
info: 2022-09-27T13:17:35.1728106+08:00 Microsoft.Hosting.Lifetime[ListeningOnAddress] #1
Now listening on: http://localhost:5000
info: 2022-09-27T13:17:35.1893063+08:00 Microsoft.Hosting.Lifetime[0] #1
Application started. Press Ctrl+C to shut down.
info: 2022-09-27T13:17:35.1941484+08:00 Microsoft.Hosting.Lifetime[0] #1
Hosting environment: Development
info: 2022-09-27T13:17:35.1996545+08:00 Microsoft.Hosting.Lifetime[0] #1
Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry\
- 自定义控制台日志输出模板
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;
});
- 自定义日志输出时间格式
以下内容仅限 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\
- 完整自定义日志格式化写入
以下内容仅限 Furion 4.5.2 +
版本使用。
services.AddConsoleFormatter(options =>
{
options.WriteHandler = (logMsg, scopeProvider, writer, fmtMsg, opt) =>
{
writer.WriteLine(fmtMsg);
};
});
- 输出日志
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.2 输出到文件
- 基础使用
// 例子一:启动层根目录输出
services.AddFileLogging("application.log");
// 例子二:支持路径
services.AddFileLogging("logs/application.log");
// 例子三:支持日志追加还是覆盖,设置 true 为追加,false 为覆盖
services.AddFileLogging("application.log", true);
- 从配置文件读取配置
只有不在 .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;
// 其他配置...
});
- 自定义日志文件名规则
// 例子一:支持系统环境变量,如%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;
});
});
- 日志过滤器/筛选器
通过日志筛选器可以对日志进行归类写入
// 例子一:根据日志级别输出
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");
};
});
- 自定义日志模板
默认情况下,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;
});
- 日志写入失败处理
有时候可能因为日志文件被打开或者其他应用程序占用了,那么就会导致日志写入失败,这时候可以进行其他相关处理:
// 例子一:其他处理
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));
};
});
- 自定义日志输出时间格式
以下内容仅限 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\
- 输出日志
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.5.3 输出到数据库/其他存储介质
将日志输出到数据库中也是非常常见的需求,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("...")
;
你没看错,就这么简单!!
- 基础使用
// 例子一,默认配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options => {});
// 例子二:自定义配置
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.MinimumLevel = LogLevel.Warning;
// 其他配置...
});
- 从配置文件中读取
只有 .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;
// 其他配置...
});
- 日志过滤器/筛选器
通过日志筛选器可以对日志进行归类写入
// 例子一:根据日志级别输出,可以分别定义 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");
};
});
- 自定义日志模板
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;
});
- 日志写入失败处理
有时候可能因为数据库连接异常或其他原因连接池满,那么就会导致日志写入失败,这时候可以进行其他相关处理:
// 例子一:其他处理
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.HandleWriteError = (writeError) =>
{
// ~~
};
});
- 输出日志
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.5.4 关于高频写入存储介质
在一些高频写入存储介质(如数据库)日志的场景,为避免频繁解析服务(创建和销毁链接)和创建作用域导致内存及 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.GetRequestService<IRepository<LogTable>>();
}
public void Write(LogMessage logMsg, bool flush)
{
_logRepository.Insert(.....);
}
/// <summary>
/// 释放服务作用域
/// </summary>
public void Dispose()
{
_serviceScope.Dispose();
}
}
18.5.5 ILoggerFactory
方式
Furion
也提供了运行时动态创建日志提供器并写入:
public class ContactModel : PageModel
{
private readonly ILogger _logger;
public ContactModel(ILoggerFactory logger)
{
// 支持所有 AddLoggingFile 和 AddDatabaseFile 配置
_logger = logger.AddFile(....).CreateLogger("MyCategory");
}
public void OnGet()
{
_logger.LogInformation("GET Pages.ContactModel called.");
}
}
18.5.6 ILoggingBuilder
方式
Furion
也提供了原生 services.AddLogging(builder => {})
方式配置,如
services.AddLogging(builder =>
{
builder.AddFile("applicaion.log");
builder.AddDatabase<DatabaseLoggingWriter>();
//....
});
18.5.7 记录请求日志
在 ASP.NET 6
中,框架默认提供了 app.UseHttpLogging()
记录 HTTP
请求日志功能,详细了解可查看官方文档 ASP.NET Core - HTTP 日志记录
当然也可以自定义中间件的方式写,只需要注入 ILogger<>
接口即可。
18.5.8 Debug
和 Trace
默认不输出问题
默认情况下,微软在 appsettings.json
和 appsettings.Development.json
中配置了 Default
日志级别,如需自定义:
{
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
这时候只需要修改 Default
为 Debug
或 Trace
即可,注意不同环境加载不同的配置文件。开发环境应修改 appsettings.Development.json
下的配置。
18.6 [LoggingMonitor]
监听日志
在 Furion 3.9.1
版本新增了 [LoggingMonitor]
特性,支持在控制器或操作中贴该特性,可以实现强大的请求日志监听,方便测试,如:
18.6.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
类型,默认true
,Furion 4.3.9+ 有效
ReturnValueThreshold
:配置返回值字符串阈值,int
类型,默认0
全量输出,Furion 4.3.9+ 有效
JsonBehavior
:配置LoggingMonitor
Json
输出行为,默认None
,Furion 4.5.2+
有效JsonIndented
:配置LoggingMonitor
Json
格式化行为,bool
类型,默认false
,Furion 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.6.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 有效
"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+ 有效
}
]
}
}
}
IncludeOfMethods
和 ExcludeOfMethods
方法签名格式为:类完全限定名.方法名
,如:Furion.Application.TestNamedServices.GetName
,Furion.Application.TestNamedServices
是类名,GetName
是方法名。
如果配置了全局请求监视日志,对个别不需要监视的接口方法只需要贴 [SuppressMonitor]
特性即可。
18.6.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.6.4 JSON
格式
以下内容仅限 Furion 4.5.2 +
版本使用。
- 全局/局部启用
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
时才不会输出美观的日志。
- 写入存储介质
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.6.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.6.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.6.7 将 LoggingMonitor
写入数据库
- 注册
.AddDatabaseLogging<>
服务:
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName == "System.Logging.LoggingMonitor";
};
});
- 写入数据库
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.6.8 支持 [DisplayName]
特性
以下内容仅限 Furion 4.8.5.10 +
版本使用。
通常我们会为每一个接口添加一个唯一名称,比如使用 [DisplayName]
特性,方便后续写入日志进行归类。
public class TestService: IDynamicApiController
{
[LoggingMonitor]
[DisplayName("测试方法")]
public void TestMethod()
{
}
}
那么如果输出为 JSON
格式日志将会自动添加 displayTitle
键,值为 测试方法
。
18.6.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.6.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.7 打印日志到 Swagger
中
在 Furion
框架中默认集成了 MiniProfiler
组件并与 Swagger
进行了结合,如需打印日志或调试代码,只需调用以下方法即可:
App.PrintToMiniProfiler("分类", "状态", "要打印的消息");
18.8 静态 Default()
方式构建
StringLoggingPart.Default().SetMessage("这是一个日志").LogInformation();
18.9 规范日志模板
在 Furion v3.5.3+
新增了 TP.Wrapper(...)
规范模板,使用如下:
// 生成模板字符串
var template = TP.Wrapper("Furion 框架", "让 .NET 开发更简单,更通用,更流行。",
"##作者## 百小僧",
"##当前版本## v3.5.3",
"##文档地址## https://furion.baiqian.ltd",
"##Copyright## 百小僧, 百签科技(广东)有限公司");
Console.WriteLine(template);
日志打印模板如下:
┏━━━━━━━━━━━ Furion 框架 ━━━━━━━━━━━
┣ 让 .NET 开发更简单,更通用,更流行。
┣
┣ 作者: 百小僧
┣ 当前版本: v3.5.3
┣ 文档地址: https://furion.baiqian.ltd
┣ Copyright: 百小僧, 百签科技(广东)有限公司
┗━━━━━━━━━━━ Furion 框架 ━━━━━━━━━━━
如果列表项以 ##属性名##
开头,自动生成 属性名:
作为行首且自动等宽对齐。
Furion 3.9.1
之前版本使用 [属性名]
开头。
18.10 日志上下文
以下内容仅限 Furion 4.6.0 +
版本使用。
有时候我们希望为日志提供额外数据,这时候可通过 .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();
在 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");
}
还可以实现共享日志上下文,如:
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.11 关闭 .NET Core
底层的日志
默认情况下,.NET Core
底层默认输出了很多日志,如需关闭只需在 appsettings.json
和 appsettings.Development.json
中添加 "命名空间/日志类别":"最低日志级别"
日志类别过滤即可,如:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information",
"System.Net.Http.HttpClient": "Warning",
"命名空间/日志类别": "Warning"
}
}
}
小提醒:过滤 .NET Core
底层日志最低日志级别通常设置为 Warning
。
18.12 关于数据库日志循环输出日志
微软提供的 EFCore
或者第三方 ORM
本身操作数据库时自带日志输出,这就会导致 IDatabaseLoggingWriter
的 Write
死循环,解决这个问题有以下方法:
- 创建新的数据库操作实例并关闭日志
- 更新到
Furion 4.7.0+
版本 (推荐) - 自行根据业务逻辑过滤
如不存在该问题可关闭框架自带死循环检测功能(对性能有提升作用):
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.IgnoreReferenceLoop = false;
});
18.13 反馈与建议
给 Furion 提 Issue。
想了解更多 日志
知识可查阅 ASP.NET Core - 日志 章节 和 Serilog 文档。