网站首页 > 技术教程 正文
前言
鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色;之前有很多第三方的日志框架也很给力,如Log4Net、NLog和Serilog等,在.NetCore中也集成了日志模型,使用便捷,同时很方便的与第三方日志框架进行集成扩展;
正文
实例演示之前,先了解一下日志级别,后续如果不想输出全部日志,可以通过日志级别进行过滤,同时通过日志级别可以标注日志内容的重要程度:
namespace Microsoft.Extensions.Logging
{
// 日志级别从下往上递增,所以根据级别可以过滤掉低级别的日志信息
public enum LogLevel
{
Trace,
Debug,
Information,
Warning,
Error,
Critical,
None
}
}
来一个控制台程序实例演示:
运行结果:
咋样,使用还是依旧简单,这里是控制台程序,还需要写配置框架和依赖注入相关的代码逻辑,如果在WebAPI项目,直接就可以使用日志记录了,如下:
对于WebAPI项目而言,在项目启动流程分析的时候,就提到内部已经注册了相关服务了,所以才能这样如此简单的使用;
难道日志就这样结束了吗?猜想看到这的小伙伴也不甘心,是的,得进一步了解,不需要特别深入,但至少得知道关键嘛,对不对?
老规矩,程序中能看到日志相关点,当然就从这开始,看看是如何注册日志啊相关服务的:
对应代码:
namespace Microsoft.Extensions.DependencyInjection
{
// IServiceCollection的扩展方法,用于注册日志相关服务
public static class LoggingServiceCollectionExtensions
{
public static IServiceCollection AddLogging(this IServiceCollection services)
{
return services.AddLogging(delegate
{
});
}
// 核心方法,上面的方法就是调用下面这个
public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
// 为了支持Options选项,得注册Options相关服务,上篇讲过
services.AddOptions();
// 注册ILoggerFactory
services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
// 注册ILogger
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
// 注册日志级别过滤,并默认设置级别为Information
services.TryAddEnumerable(ServiceDescriptor.Singleton((IConfigureOptions<LoggerFilterOptions>)new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
// 执行传入的委托方法
configure(new LoggingBuilder(services));
return services;
}
}
}
日志相关服务注册了解了,那接着看看关键实现,其实日志记录有三个核心类型:ILogger、ILoggerFactory和ILoggerProvider,对应的实现分别是Logger、LoggerFactory、xxxLoggerProvider;
- xxxLoggerProvider:针对于不同的目的地创建对应的xxxLogger,这里的xxxLogger负责在目的地(文件、数据库、控制台等)写入内容;
- LoggerFactory:负责创建Logger,其中包含所有注册的xxxLoggerProvider对应Logger;
- Logger:以上两种;
扒开这三个类型的定义,简单看看都定义了什么....
- ILogger/Logger
namespace Microsoft.Extensions.Logging
{
public interface ILogger
{
// 记录日志方法,其中包含日志级别、事件ID、写入的内容、格式化内容等
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
// 判断对应的日志级别是否可用
bool IsEnabled(LogLevel logLevel);
// 日志作用域
IDisposable BeginScope<TState>(TState state);
}
}
Logger中挑了比较关键的属性和方法简单说说
internal class Logger : ILogger
{
// 用于缓存真正Logger记录器的
public LoggerInformation[] Loggers { get; set; }
public MessageLogger[] MessageLoggers { get; set; }
// 这个用于缓存日志作用域Loggers
public ScopeLogger[] ScopeLoggers { get; set; }
// Log日志记录方法
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var loggers = MessageLoggers;
if (loggers == null)
{
return;
}
List<Exception> exceptions = null;
// 遍历对应的Loggers
for (var i = 0; i < loggers.Length; i++)
{
ref readonly var loggerInfo = ref loggers[i];
if (!loggerInfo.IsEnabled(logLevel))
{
continue;
}
// 执行内部方法
LoggerLog(logLevel, eventId, loggerInfo.Logger, exception, formatter, ref exceptions, state);
}
if (exceptions != null && exceptions.Count > 0)
{
ThrowLoggingError(exceptions);
}
static void LoggerLog(LogLevel logLevel, EventId eventId, ILogger logger, Exception exception, Func<TState, Exception, string> formatter, ref List<Exception> exceptions, in TState state)
{
try
{
// 记录日志内容
logger.Log(logLevel, eventId, state, exception, formatter);
}
catch (Exception ex)
{
if (exceptions == null)
{
exceptions = new List<Exception>();
}
exceptions.Add(ex);
}
}
}
}
ILoggerFactory/LoggerFactory
namespace Microsoft.Extensions.Logging
{
// 创建 ILogger和注册LoggerProvider
public interface ILoggerFactory : IDisposable
{
// 根据名称创建ILogger
ILogger CreateLogger(string categoryName);
// 注册ILoggerProvider
void AddProvider(ILoggerProvider provider);
}
}
........省略方法-私下研究......
// LoggerFactory挑了几个关键方法进行说明
// 创建Logger
public ILogger CreateLogger(string categoryName)
{
if (CheckDisposed())
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
lock (_sync)
{
if (!_loggers.TryGetValue(categoryName, out var logger))
{
// new一个Logger,这是LoggerFactory管理的Logger
logger = new Logger
{
// 根据注册的xxxLoggerProvider创建具体的xxxLogger
// 并将其缓存到LoggerFactory创建的Logger对应的Loggers属性中
Loggers = CreateLoggers(categoryName),
};
// 根据消息级别和作用域范围,赋值对应的MessageLoggers、ScopeLoggers
(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);
// 同时将创建出来的logger缓存在字典中
_loggers[categoryName] = logger;
}
return logger;
}
}
// 这个用于注册具体的xxxLoggerProvider
public void AddProvider(ILoggerProvider provider)
{
if (CheckDisposed())
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
lock (_sync)
{
// 将传入的provider封装了结构体进行缓存
AddProviderRegistration(provider, dispose: true);
// 同时创建对应的logger,创建过程和上面一样
foreach (var existingLogger in _loggers)
{
var logger = existingLogger.Value;
var loggerInformation = logger.Loggers;
// 在原来基础上增加具体的xxxLogger
var newLoggerIndex = loggerInformation.Length;
Array.Resize(ref loggerInformation, loggerInformation.Length + 1);
loggerInformation[newLoggerIndex] = new LoggerInformation(provider, existingLogger.Key);
logger.Loggers = loggerInformation;
(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);
}
}
}
// 封装对应的xxxLoggerProvider,然后进行缓存
private void AddProviderRegistration(ILoggerProvider provider, bool dispose)
{
// 先封装成结构体,然后在缓存,方便后续生命周期管理
_providerRegistrations.Add(new ProviderRegistration
{
Provider = provider,
ShouldDispose = dispose
});
// 判断是否继承了ISupportExternalScope
if (provider is ISupportExternalScope supportsExternalScope)
{
if (_scopeProvider == null)
{
_scopeProvider = new LoggerExternalScopeProvider();
}
supportsExternalScope.SetScopeProvider(_scopeProvider);
}
}
// 创建具体的xxxLogger
private LoggerInformation[] CreateLoggers(string categoryName)
{
// 根据注册的xxxLoggerProvider个数初始化一个数组
var loggers = new LoggerInformation[_providerRegistrations.Count];
// 遍历注册的xxxLoggerProvider,创建具体的xxxLogger
for (var i = 0; i < _providerRegistrations.Count; i++)
{
// 创建具体的xxxLogger
loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);
}
return loggers;
}
........省略方法-私下研究......
- ILoggerProvider/xxxLoggerProvider
namespace Microsoft.Extensions.Logging
{
public interface ILoggerProvider : IDisposable
{
// 根据名称创建对应的Logger
ILogger CreateLogger(string categoryName);
}
}
namespace Microsoft.Extensions.Logging.Console
{
[ProviderAlias("Console")]
public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope
{
// 支持Options动态监听
private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;
// 缓存对应的xxxLogger
private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;
// 日志处理
private readonly ConsoleLoggerProcessor _messageQueue;
private IDisposable _optionsReloadToken;
private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance;
// 构造函数,初始化
public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options)
{
_options = options;
_loggers = new ConcurrentDictionary<string, ConsoleLogger>();
ReloadLoggerOptions(options.CurrentValue);
_optionsReloadToken = _options.OnChange(ReloadLoggerOptions);
_messageQueue = new ConsoleLoggerProcessor();
// 判断是否是Windows系统,因为即日至的方式不一样
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// 如果是windows
_messageQueue.Console = new WindowsLogConsole();
_messageQueue.ErrorConsole = new WindowsLogConsole(stdErr: true);
}
else
{
// 如果是其他平台
_messageQueue.Console = new AnsiLogConsole(new AnsiSystemConsole());
_messageQueue.ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));
}
}
private void ReloadLoggerOptions(ConsoleLoggerOptions options)
{
foreach (var logger in _loggers)
{
logger.Value.Options = options;
}
}
// 根据名称获取或创建对应xxxLogger
public ILogger CreateLogger(string name)
{
// 根据名称获取,如果没有,则根据传入的委托方法进行创建
return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue)
{
Options = _options.CurrentValue,
ScopeProvider = _scopeProvider
});
}
......省略一些方法,私下研究.......
}
}
想了想,这里就不一一针对不同目的地(比如Trace、EventLog)扒代码看了,不然说着说着就变成了代码解读了,如果有兴趣,可以私下照着以下思路去看看代码:
每一个目的地日志记录都会有一个实现xxxLoggerProvider和对应的记录器xxxLogger(真实记录日志内容),LoggerFactory创建的Logger(暴露给程序员使用的)包含了对应的具体的记录器,比如以写入日志控制台为例:
有一个ConsoleLoggerProvider的实现和对应的ConsoleLogger,ConsoleLoggerProvider负责通过名称创建对应的ConsoleLogger,而LoggerFactory创建出来的Logger就是包含已注册ConsoleLoggerProvider创建出来的ConsoleLogger;从而我们调用记录日志方法的时候,其实最终是调用ConsoleLoggerProvider创建的ConsoleLogger对象方法;
总结
本来想着日志应该用的很频繁了,直接举例演示就OK了,但是写着写着,用的多不一定清除关键步骤,于是又扒了下代码,挑出了几个关键方法简单的说说,希望使用的小伙伴不困惑,深入研究就靠私下好好瞅瞅代码了;
下一节实例演示日志的使用、日志的作用域、集成第三方日志框架进行日志扩展.....
感谢小伙伴的:点赞、收藏和评论,下期继续~~~
一个被程序搞丑的帅小伙,关注"Code综艺圈",跟我一起学~~~
猜你喜欢
- 2024-10-27 NET 6 NLog教程与使用 .net nlog
- 2024-10-27 .NET 远程日志组件 Jack.RemoteLog
- 2024-10-27 如何在企业中轻松地管理群聊(上) 企业群管理规章制度
- 2024-10-27 在 ASP.NET Core 中使用 Serilog 使用 Fluentd 将日志写入 Elasticsearch
- 2024-10-27 asp.net如何添加日志NLog aspnet日历控件
- 2024-10-27 Asp.Net Core中使用NLog记录日志 netcore日志框架对比
- 2024-10-27 EvnetLog Analyzer作为一款日志管理软件能给企业带来哪些帮助?
- 2024-10-27 .Net Core下NLog日志框架使用入门
- 2024-10-27 C# .NET 6 校园图书管理系统:第六章 .Net6之NLog日志的配置与使用
- 2024-10-27 ASP.NET Core:ASP.NET Core中使用NLog记录日志
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- sd分区 (65)
- raid5数据恢复 (81)
- 地址转换 (73)
- 手机存储卡根目录 (55)
- tcp端口 (74)
- project server (59)
- 双击ctrl (55)
- 鼠标 单击变双击 (67)
- debugview (59)
- 字符动画 (65)
- flushdns (57)
- ps复制快捷键 (57)
- 清除系统垃圾代码 (58)
- web服务器的架设 (67)
- 16进制转换 (69)
- xclient (55)
- ps源文件 (67)
- filezilla server (59)
- 句柄无效 (56)
- word页眉页脚设置 (59)
- ansys实例 (56)
- 6 1 3固件 (59)
- sqlserver2000挂起 (59)
- vm虚拟主机 (55)
- config (61)
本文暂时没有评论,来添加一个吧(●'◡'●)