22. 日志记录
后端手册日志记录大约 4 分钟约 1173 字
提示
Admin.NET 已实现包括行为日志、异常日志、审计日志、访问日志等功能。日志采用 Furion
自带的日志组件,简单方便。.NET 本身自带的内置日志组件ILogger 也很好,可以通过配置不同的记录提供程序将日志写入不同的目标。 基本日志记录提供程序是内置的,并且还可以使用许多第三方提供程序,比如 log4net。
目前日志存储支持数据库存储、文件存储、ElasticSearch,配置文件 /Configuration/Logging.json。其中 Monitor.GlobalEnabled
可以配置全局是否启动日志(如若不需要记录日志,可以关闭此项配置)。
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Information"
},
"File": {
"Enabled": false, // 启用文件日志
"FileName": "logs/{0:yyyyMMdd}_{1}.log", // 日志文件
"Append": true, // 追加覆盖
// "MinimumLevel": "Information", // 日志级别
"FileSizeLimitBytes": 10485760, // 10M=10*1024*1024
"MaxRollingFiles": 30 // 只保留30个文件
},
"Database": {
"Enabled": true, // 启用数据库日志
"MinimumLevel": "Information"
},
"ElasticSearch": {
"Enabled": false, // 启用ES日志
"AuthType": "Basic", // ES认证类型,可选 Basic、ApiKey、Base64ApiKey
"User": "admin", // Basic认证的用户名,使用Basic认证类型时必填
"Password": "123456", // Basic认证的密码,使用Basic认证类型时必填
"ApiId": "", // 使用ApiKey认证类型时必填
"ApiKey": "", // 使用ApiKey认证类型时必填
"Base64ApiKey": "TmtrOEszNEJuQ0NyaWlydGtROFk6SG1RZ0w3YzBTc2lCanJTYlV3aXNzZw==", // 使用Base64ApiKey认证类型时必填
"Fingerprint": "37:08:6A:C6:06:CC:9A:43:CF:ED:25:A2:1C:A4:69:57:90:31:2C:06:CA:61:56:39:6A:9C:46:11:BD:22:51:DA", // ES使用Https时的证书指纹
"ServerUris": [ "http://192.168.1.100:9200" ], // 地址
"DefaultIndex": "adminnet" // 索引
},
"Monitor": {
"GlobalEnabled": true, // 启用全局拦截日志
"IncludeOfMethods": [], // 拦截特定方法,当GlobalEnabled=false有效
"ExcludeOfMethods": [], // 排除特定方法,当GlobalEnabled=true有效
"BahLogLevel": "Information", // Oops.Oh 和 Oops.Bah 业务日志输出级别
"WithReturnValue": true, // 是否包含返回值,默认true
"ReturnValueThreshold": 500, // 返回值字符串阈值,默认0全量输出
"JsonBehavior": "None", // 是否输出Json,默认None(OnlyJson、All)
"JsonIndented": false, // 是否格式化Json
"UseUtcTimestamp": false, // 时间格式UTC、LOCAL
"ConsoleLog": true // 是否显示控制台日志
}
}
}
系统集成实现类 Admin.NET.Core/Logging/LoggingSetup.cs 供其参考,都已经自动化,只需要改改配置文件即可,无需写额外的代码逻辑实现。
namespace Admin.NET.Core;
public static class LoggingSetup
{
/// <summary>
/// 日志注册
/// </summary>
/// <param name="services"></param>
public static void AddLoggingSetup(this IServiceCollection services)
{
// 日志监听
services.AddMonitorLogging(options =>
{
options.IgnorePropertyNames = new[] { "Byte" };
options.IgnorePropertyTypes = new[] { typeof(byte[]) };
});
// 控制台日志
var consoleLog = App.GetConfig<bool>("Logging:Monitor:ConsoleLog", true);
services.AddConsoleFormatter(options =>
{
options.DateFormat = "yyyy-MM-dd HH:mm:ss(zzz) dddd";
//options.WithTraceId = true; // 显示线程Id
//options.WithStackFrame = true; // 显示程序集
options.WriteFilter = (logMsg) =>
{
return consoleLog;
};
});
// 日志写入文件
if (App.GetConfig<bool>("Logging:File:Enabled", true))
{
var loggingMonitorSettings = App.GetConfig<LoggingMonitorSettings>("Logging:Monitor", true);
Array.ForEach(new[] { LogLevel.Information, LogLevel.Warning, LogLevel.Error }, logLevel =>
{
services.AddFileLogging(options =>
{
options.WithTraceId = true; // 显示线程Id
options.WithStackFrame = true; // 显示程序集
options.FileNameRule = fileName => string.Format(fileName, DateTime.Now, logLevel.ToString()); // 每天创建一个文件
options.WriteFilter = logMsg => logMsg.LogLevel == logLevel; // 日志级别
options.HandleWriteError = (writeError) => // 写入失败时启用备用文件
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
if (loggingMonitorSettings.JsonBehavior == JsonBehavior.OnlyJson)
{
options.MessageFormat = LoggerFormatter.Json;
// options.MessageFormat = LoggerFormatter.JsonIndented;
options.MessageFormat = (logMsg) =>
{
var jsonString = logMsg.Context.Get("loggingMonitor");
return jsonString?.ToString();
};
}
});
});
}
// 日志写入ElasticSearch
if (App.GetConfig<bool>("Logging:ElasticSearch:Enabled", true))
{
services.AddDatabaseLogging<ElasticSearchLoggingWriter>(options =>
{
options.WithTraceId = true; // 显示线程Id
options.WithStackFrame = true; // 显示程序集
options.IgnoreReferenceLoop = false; // 忽略循环检测
options.MessageFormat = LoggerFormatter.Json;
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName == CommonConst.SysLogCategoryName; // 只写LoggingMonitor日志
};
});
}
// 日志写入数据库
if (App.GetConfig<bool>("Logging:Database:Enabled", true))
{
services.AddDatabaseLogging<DatabaseLoggingWriter>(options =>
{
options.WithTraceId = true; // 显示线程Id
options.WithStackFrame = true; // 显示程序集
options.IgnoreReferenceLoop = false; // 忽略循环检测
options.WriteFilter = (logMsg) =>
{
return logMsg.LogName == CommonConst.SysLogCategoryName; // 只写LoggingMonitor日志
};
});
}
}
}
1、泛型方式:通过注入 ILogger<T>
对象进行操作日志,如
public class xxxTest
{
private readonly ILogger<xxxTest> _logger;
public xxxTest(ILogger<xxxTest> logger)
{
_logger = logger;
}
public void Test()
{
_logger.LogInformation("日志内容。。。");
}
}
2、工厂方式:ILoggerFactory 工厂方式
public class xxxTest
{
private readonly ILogger _logger;
public xxxTest(ILoggerFactory logger)
{
_logger = logger.CreateLogger("我的分组");
}
public void Test()
{
_logger.LogInformation("日志内容。。。");
}
}
3、静态方式:Log 静态类方式
// 创建日志对象
var logger = Log.CreateLogger("日志名称");
// 创建日志工厂
using var loggerFactory = Log.CreateLoggerFactory(builder => {
});
// 日志记录
Log.Information("xxx");
Log.Warning("xxx");
Log.Error("xxx");
Log.Debug("xxx");
Log.Trace("xxx");
Log.Critical("xxx");
4、字符串拓展的方式
"xxx".LogInformation();
"xxx".LogError<xxxController>();
"xxx".SetCategory<xxxController>().LogInformation();
提示
内置了日志分组常量 CommonConst.SysLogCategoryName
,在需要自定义日志且与系统匹配起来自动存储时,_logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName);
这时自定义的日志就会自动存储。
下面时自定义日志存储的实现:
public async Task WriteAsync(LogMessage logMsg, bool flush)
{
var jsonStr = logMsg.Context?.Get("loggingMonitor")?.ToString();
if (string.IsNullOrWhiteSpace(jsonStr))
{
await _db.Insertable(new SysLogOp
{
DisplayTitle = "自定义操作日志",
LogDateTime = logMsg.LogDateTime,
EventId = logMsg.EventId.Id,
ThreadId = logMsg.ThreadId,
TraceId = logMsg.TraceId,
Exception = logMsg.Exception == null ? null : JSON.Serialize(logMsg.Exception),
Message = logMsg.Message,
LogLevel = logMsg.LogLevel,
Status = "200",
}).ExecuteCommandAsync();
return;
}
}