欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.net core日志系统相关总结

程序员文章站 2022-05-03 11:53:51
前言本节开始整理日志相关的东西。先整理一下日志的基本原理。正文首先介绍一下包:1.microsoft.extengsion.logging.abstrations这个是接口包。2.microsoft....

前言

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

正文

首先介绍一下包:

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"
      }
    }
  }
}

结果:

.net core日志系统相关总结

首先是配置级别的问题,查看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日志的资料请关注其它相关文章!

相关标签: .net core 日志