.net core日志系统相关总结
前言
本节开始整理日志相关的东西。先整理一下日志的基本原理。
正文
首先介绍一下包:
1.microsoft.extengsion.logging.abstrations
这个是接口包。
2.microsoft.extengsion.logging
这个是实现包
3.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) { } } }
以上就是.net core日志系统相关总结的详细内容,更多关于.net core日志的资料请关注其它相关文章!
推荐阅读
-
abp(net core)+easyui+efcore实现仓储管理系统——展现层实现增删改查之列表视图(七)
-
abp(net core)+easyui+efcore实现仓储管理系统——展现层实现增删改查之增删改视图(八)
-
详解ASP.NET Core应用中如何记录和查看日志
-
在Windows系统中构建还原ASP.NET Core 源码
-
Aso.Net Core 的配置系统Configuration
-
20190705-记IIS发布.NET CORE框架系统之所遇
-
(14)ASP.NET Core 中的日志记录
-
abp(net core)+easyui+efcore实现仓储管理系统——展现层实现增删改查之菜单与测试(九)
-
Asp.Net Core2.2 源码阅读系列——控制台日志源码解析
-
.NET Core开发日志——Middleware