前言

本节开始整理日志相关的东西。先整理一下日志的基本原理。

正文

首先介绍一下包:

  1. Microsoft.Extengsion.Logging.Abstrations

这个是接口包。

  1. Microsoft.Extengsion.Logging

这个是实现包

  1. Microsoft.Extengsion.Logging.Console

这个是扩展包

代码如下:

static void Main(string[] args)
{
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.json",optional:false,reloadOnChange:true);
var config = configurationBuilder.Build(); IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IConfiguration>(p=>config); serviceCollection.AddLogging(builder =>
{
builder.AddConfiguration(config.GetSection("Logging"));
builder.AddConsole();
}); IServiceProvider service = serviceCollection.BuildServiceProvider(); ILoggerFactory loggerFactory = service.GetService<ILoggerFactory>(); var loggerObj = loggerFactory.CreateLogger("Default"); loggerObj.LogInformation(2021, "Default,now that is 2021"); var loggerObj2 = loggerFactory.CreateLogger("loggerObj"); loggerObj2.LogDebug(2021, "loggerObj,now that is 2021"); Console.ReadKey();
}

配置文件:

{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"LogLevel": {
"Default": "Information",
"Program": "Trace",
"loggerObj": "Debug"
}
}
}
}

结果:

首先是配置级别的问题,查看loglevel 文件:

public enum LogLevel
{
/// <summary>Logs that contain the most detailed messages. These messages may contain sensitive application data.
/// These messages are disabled by default and should never be enabled in a production environment.</summary>
Trace,
/// <summary>Logs that are used for interactive investigation during development. These logs should primarily contain
/// information useful for debugging and have no long-term value.</summary>
Debug,
/// <summary>Logs that track the general flow of the application. These logs should have long-term value.</summary>
Information,
/// <summary>Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the
/// application execution to stop.</summary>
Warning,
/// <summary>Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a
/// failure in the current activity, not an application-wide failure.</summary>
Error,
/// <summary>Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires
/// immediate attention.</summary>
Critical,
/// <summary>Not used for writing log messages. Specifies that a logging category should not write any messages.</summary>
None,
}

从上之下,依次提高log级别。

比如说设置了log 级别是Error,那么Debug、Information、Warning 都不会被答应出来。

那么就来分析一下代码吧。

AddLogging:

public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} services.AddOptions(); services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
new DefaultLoggerLevelConfigureOptions(LogLevel.Information))); configure(new LoggingBuilder(services));
return services;
}

这里面给注册了ILoggerFactory和ILogger。然后设置了一个打印log的级别配置,LogLevel.Information,这个就是如果我们没有配置文件默认就是Information这种级别了。

configure(new LoggingBuilder(services)) 给我们的委托提供了一个LoggingBuilder的实例化对象。这个对象就是用来专门做扩展的,是解耦的一种方式。

internal class LoggingBuilder : ILoggingBuilder
{
public LoggingBuilder(IServiceCollection services)
{
Services = services;
} public IServiceCollection Services { get; }
}

这个LoggingBuilder 类基本什么功能都没有,但是因为有了这样一个类,就可以作为扩展的标志了。

比如说上文的:

builder.AddConfiguration(config.GetSection("Logging"));
builder.AddConsole();

看下AddConfiguration:

public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
{
builder.AddConfiguration(); builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>>(new LoggerFilterConfigureOptions(configuration));
builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>>(new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration)); builder.Services.AddSingleton(new LoggingConfiguration(configuration)); return builder;
}

这里面给我们注入了配置文件的配置:builder.Services.AddSingleton<IConfigureOptions>(new LoggerFilterConfigureOptions(configuration))

同时给我们注册监听令牌:builder.Services.AddSingleton<IOptionsChangeTokenSource>(new ConfigurationChangeTokenSource(configuration));

这里给我们注册配置保存在LoggingConfiguration中:builder.Services.AddSingleton(new LoggingConfiguration(configuration));

因为LoggingConfiguration 保存了,故而我们随时可以获取到LoggingConfiguration 的配置。

看下AddConsole:

/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
public static ILoggingBuilder AddConsole(this ILoggingBuilder builder)
{
builder.AddConfiguration(); builder.AddConsoleFormatter<JsonConsoleFormatter, JsonConsoleFormatterOptions>();
builder.AddConsoleFormatter<SystemdConsoleFormatter, ConsoleFormatterOptions>();
builder.AddConsoleFormatter<SimpleConsoleFormatter, SimpleConsoleFormatterOptions>(); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());
LoggerProviderOptions.RegisterProviderOptions<ConsoleLoggerOptions, ConsoleLoggerProvider>(builder.Services); return builder;
}

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>()) 里面给我们ILoggerProvider 增加了一个ConsoleLoggerProvider,故而我们多了一个打印的功能。

LoggerProviderOptions.RegisterProviderOptions<ConsoleLoggerOptions, ConsoleLoggerProvider>(builder.Services) 给我们加上了ConsoleLoggerOptions 绑定为ConsoleLoggerProvider的配置。

RegisterProviderOptions 如下:

public static void RegisterProviderOptions<TOptions, TProvider>(IServiceCollection services) where TOptions : class
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<TOptions>, LoggerProviderConfigureOptions<TOptions, TProvider>>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<TOptions>, LoggerProviderOptionsChangeTokenSource<TOptions, TProvider>>());
}

接下来就是调用服务:

var loggerObj = loggerFactory.CreateLogger("Default");

loggerObj.LogInformation(2021, "Default,now that is 2021");

看下LoggerFactory的CreateLogger:

public ILogger CreateLogger(string categoryName)
{
if (CheckDisposed())
{
throw new ObjectDisposedException(nameof(LoggerFactory));
} lock (_sync)
{
if (!_loggers.TryGetValue(categoryName, out Logger logger))
{
logger = new Logger
{
Loggers = CreateLoggers(categoryName),
}; (logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers); _loggers[categoryName] = logger;
} return logger;
}
}

里面做了缓存,如果categoryName有缓存的话直接使用缓存,如果没有那么调用CreateLoggers创建。

查看CreateLoggers:

private LoggerInformation[] CreateLoggers(string categoryName)
{
var loggers = new LoggerInformation[_providerRegistrations.Count];
for (int i = 0; i < _providerRegistrations.Count; i++)
{
loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);
}
return loggers;
}

这里面就用我们前面注册过的全部logger的provider,封装进LoggerInformation。

查看LoggerInformation:

internal readonly struct LoggerInformation
{
public LoggerInformation(ILoggerProvider provider, string category) : this()
{
ProviderType = provider.GetType();
Logger = provider.CreateLogger(category);
Category = category;
ExternalScope = provider is ISupportExternalScope;
} public ILogger Logger { get; } public string Category { get; } public Type ProviderType { get; } public bool ExternalScope { get; }
}

里面调用了我们,每个provider的CreateLogger。

那么这个时候我们就找一个provider 看下CreateLogger到底做了什么,这里就找一下ConsoleLoggerProvider,因为我们添加了这个。

[ProviderAlias("Console")]
public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope
{
private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;
public ILogger CreateLogger(string name)
{
if (_options.CurrentValue.FormatterName == null || !_formatters.TryGetValue(_options.CurrentValue.FormatterName, out ConsoleFormatter logFormatter))
{
#pragma warning disable CS0618
logFormatter = _options.CurrentValue.Format switch
{
ConsoleLoggerFormat.Systemd => _formatters[ConsoleFormatterNames.Systemd],
_ => _formatters[ConsoleFormatterNames.Simple],
};
if (_options.CurrentValue.FormatterName == null)
{
UpdateFormatterOptions(logFormatter, _options.CurrentValue);
}
#pragma warning disable CS0618
} return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue)
{
Options = _options.CurrentValue,
ScopeProvider = _scopeProvider,
Formatter = logFormatter,
});
}
}

看到这个IOptionsMonitor,就知道console 配置是支持热更新的,里面创建了ConsoleLogger,这个ConsoleLogger就是用来打log正在的调用类。

值得注意的是_messageQueue这个,看了打印log还是有一个队列的,按照先进先出原则。

那么最后来看一下loggerObj.LogInformation(2021, "Default,now that is 2021");:

第一层
public static void LogInformation(this ILogger logger, EventId eventId, string message, params object[] args)
{
logger.Log(LogLevel.Information, eventId, message, args);
}
第二层
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, string message, params object[] args)
{
logger.Log(logLevel, eventId, null, message, args);
}
第三层
public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
} logger.Log(logLevel, eventId, new FormattedLogValues(message, args), exception, _messageFormatter);
}

那么这个logger.Log 是调用具体某个logger,像consoleLogger 吗? 不是,我们看LoggerFactory的CreateLogger时候封装了:

logger = new Logger
{
Loggers = CreateLoggers(categoryName),
};

那么看下Logger的Log到底干了什么。

internal class Logger : ILogger
{
public LoggerInformation[] Loggers { get; set; }
public MessageLogger[] MessageLoggers { get; set; }
public ScopeLogger[] ScopeLoggers { get; set; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
MessageLogger[] loggers = MessageLoggers;
if (loggers == null)
{
return;
} List<Exception> exceptions = null;
for (int i = 0; i < loggers.Length; i++)
{
ref readonly MessageLogger 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);
}
}
}
}

里面循环判断是否当前级别能够输出:!loggerInfo.IsEnabled(logLevel)

然后调用对应的具体ILog实现的Log,这里贴一下ConsoleLogger 的实现:

[ThreadStatic]
private static StringWriter t_stringWriter; public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
t_stringWriter ??= new StringWriter();
LogEntry<TState> logEntry = new LogEntry<TState>(logLevel, _name, eventId, state, exception, formatter);
Formatter.Write(in logEntry, ScopeProvider, t_stringWriter); var sb = t_stringWriter.GetStringBuilder();
if (sb.Length == 0)
{
return;
}
string computedAnsiString = sb.ToString();
sb.Clear();
if (sb.Capacity > 1024)
{
sb.Capacity = 1024;
}
_queueProcessor.EnqueueMessage(new LogMessageEntry(computedAnsiString, logAsError: logLevel >= Options.LogToStandardErrorThreshold));
}

把这个队列的也贴一下,比较经典吧。

internal class ConsoleLoggerProcessor : IDisposable
{
private const int _maxQueuedMessages = 1024; private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
private readonly Thread _outputThread; public IConsole Console;
public IConsole ErrorConsole; public ConsoleLoggerProcessor()
{
// Start Console message queue processor
_outputThread = new Thread(ProcessLogQueue)
{
IsBackground = true,
Name = "Console logger queue processing thread"
};
_outputThread.Start();
} public virtual void EnqueueMessage(LogMessageEntry message)
{
if (!_messageQueue.IsAddingCompleted)
{
try
{
_messageQueue.Add(message);
return;
}
catch (InvalidOperationException) { }
} // Adding is completed so just log the message
try
{
WriteMessage(message);
}
catch (Exception) { }
} // for testing
internal virtual void WriteMessage(LogMessageEntry entry)
{
IConsole console = entry.LogAsError ? ErrorConsole : Console;
console.Write(entry.Message);
} private void ProcessLogQueue()
{
try
{
foreach (LogMessageEntry message in _messageQueue.GetConsumingEnumerable())
{
WriteMessage(message);
}
}
catch
{
try
{
_messageQueue.CompleteAdding();
}
catch { }
}
} public void Dispose()
{
_messageQueue.CompleteAdding(); try
{
_outputThread.Join(1500); // with timeout in-case Console is locked by user input
}
catch (ThreadStateException) { }
}
}

因为是实践篇,只是具体执行过程带过一下,细节篇的时候,会详细介绍一下机制,比如说ConsoleLoggerProcessor的这种队列机制,又比如说Logger模型设计等。

以上只是个人整理,如有错误,望请指出。

下一节,服务与logger系统之间。

重新整理 .net core 实践篇—————日志系统之战地记者[十五]的更多相关文章

  1. 重新整理 .net core 实践篇—————日志系统之结构化[十八]

    前言 什么是结构化呢? 结构化,就是将原本没有规律的东西进行有规律话. 就比如我们学习数据结构,需要学习排序然后又要学习查询,说白了这就是一套,没有排序,谈如何查询是没有意义的,因为查询算法就是根据某 ...

  2. 重新整理 .net core 实践篇—————日志系统之作用域[十七]

    前言 前面介绍了服务与日志之间的配置,那么我们服务会遇到下面的场景会被遇到一些打log的问题. 前面我提及到我们的log,其实是在一个队列里面,而我们的请求是在并发的,多个用户同时发送请求这个时候我们 ...

  3. 重新整理 .net core 实践篇—————日志系统之服务与日志之间[十六]

    前言 前文介绍了一些基本思路,那么这里介绍一下,服务如何与配置文件配合. 正文 服务: public interface ISelfService { void ShowLog(); } public ...

  4. 重新整理 .net core 实践篇—————3种配置验证[十四]

    前言 简单整理一些配置的验证. 正文 配置的验证大概分为3类: 直接注册验证函数 实现IValidteOptions 使用Microsoft.Extensions.Options.DataAnnota ...

  5. 重新整理 .net core 实践篇————配置系统之盟约[五]

    前言 在asp .net core 中我们会看到一个appsettings.json 文件,它就是我们在服务中的各种配置,是至关重要的一部门. 不管是官方自带的服务,还是我们自己编写的服务都是用它来实 ...

  6. 重新整理 .net core 实践篇—————配置系统之军令状[七](配置文件)

    前言 介绍一下配置系统中的配置文件,很多服务的配置都写在配置文件中,也是配置系统的大头. 正文 在asp .net core 提供了下面几种配置文件格式的读取方式. Microsoft.extensi ...

  7. 重新整理 .net core 实践篇—————配置系统之间谍[八](文件监控)

    前言 前文提及到了当我们的配置文件修改了,那么从 configurationRoot 在此读取会读取到新的数据,本文进行扩展,并从源码方面简单介绍一下,下面内容和前面几节息息相关. 正文 先看一下,如 ...

  8. 重新整理 .net core 实践篇—————配置系统之强类型配置[十]

    前言 前文中我们去获取value值的时候,都是通过configurationRoot 来获取的,如configurationRoot["key"],这种形式. 这种形式有一个不好的 ...

  9. 重新整理 .net core 实践篇————配置系统——军令(命令行)[六]

    前言 前文已经基本写了一下配置文件系统的一些基本原理.本文介绍一下命令行导入配置系统. 正文 要使用的话,引入Microsoft.extensions.Configuration.commandLin ...

随机推荐

  1. 1036 Boys vs Girls

    This time you are asked to tell the difference between the lowest grade of all the male students and ...

  2. nginx 完美解决tp3.2.3 404问题

    最近我把Apache给换成nginx,当我把tp项目搬过去运行的时候发现404 错误 ,原来是因为nginx不支持 pathinfo 模式,需要自己配置 下面我配置 在server配置里面 locat ...

  3. showdan

    from shodan import Shodan import json api = Shodan("") result = api.search('windows') with ...

  4. DVWA之File Upload (文件上传漏洞)

    目录 Low: Medium: 方法一:抓包修改文件的type 方法二:00截断 High: Impossible : Low: 源代码: <?php if( isset( $_POST[ 'U ...

  5. (转)VMware中桥接模式与NAT模式的区别

    bridged networking(桥接模式) 在这样的模式下.VMWare虚拟出来的操作系统就像是局域网中的一台独立的主机,它能够訪问网内不论什么一台机器. 在桥接模式下.你须要手工为虚拟系统配置 ...

  6. Day006 可变参数

    可变参数(不定项参数) 在jdk1.5开始,java支持传递同类型的可变参数给一个方法. 在方法声明中,在指定参数类型后加一个省略号(...). 一个方法只能指定一个可变参数,它必须是方法的最后一个参 ...

  7. pr2019快键键

    pr快捷键 平时用到就更新一下(持续更新),算是日积月累吧.虽然是pr2019,但是其他的版本估计差不多 视频剪辑的时候,快速预览--L(英文输入法).按一次,速度*2,如果想恢复原来速度,按空格键暂 ...

  8. HDU - 3347 Calculate the expression — 模拟 + map存变量

    传送门 题意:从输入开始,1.输入样例数:2.然后输入一组样例中的行数n:3.前n-1行为定义变量(之间使用空格隔开),只需要map存进去就可以了(这里有覆盖的情况,故使用mp["s&quo ...

  9. 微言Netty:百万并发基石上的epoll之剑

    说道本章标题,相信很多人知道我在暗喻石中剑这个典故,在此典故中,天命注定的亚瑟很容易的就拔出了这把石中剑,但是由于资历不被其他人认可,所以他颇费了一番周折才成为了真正意义上的英格兰全境之王,亚瑟王.说 ...

  10. [bug] vue cli 部署在 springboot中报404

    复制资源时,在static目录下新建了一个static目录,估计是引起了spring解析的混乱,改为one后即可正常访问 参考 https://www.cnblogs.com/qianjinyan/p ...