目录

前言

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

正文

首先介绍一下包:

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日志的资料请关注www.887551.com其它相关文章!