.net core loggerfactory

.NET Core LoggerFactory: How to use it correctly and lots of tips!

Matt Watson Live Queue Leave a Comment

If you have used .NET Core, you have probably battled with the new built-in LoggerFactory. It has created a lot of confusion around logging with ASP.NET Core. At Stackify we have logging integrations for log4net, NLog, Serilog, and our direct API. We have a lot of experience with building logging appenders and libraries to work with various logging frameworks. As we have been working on perfecting our .NET Core support, I felt like I should share some of our tips and findings.

Basics of the .NET Core LoggerFactory

It is designed as a logging API that developers can use to capture built-in ASP.NET logging as well as for their own custom logging. The logging API supports multiple output providers and is extensible to potentially be able to send your application logging anywhere.

Other logging frameworks like NLog and Serilog have even written providers for it. So you can use the LoggerFactory and it ends up working sort of like Common.Logging does as a facade above an actual logging library. By using it in this way, it also allows you to leverage all of the power of a library like NLog to overcome any limitations the built-in logging API may have. The biggest of those is being able to actually write your logs to a file on disk!

Where is the LoggerFactory Created?

For an ASP.NET Core, you first see the ILoggerFactory within your Startup class that looks something like this below. This is where you can add providers to the ILoggerFactory for things like Debug & Console but also more advanced things like ETW or 3rd party providers.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.AddConsole(Configuration.GetSection("Logging")); //log levels set in your configuration
	loggerFactory.AddDebug(); //does all log levels
        //removed rest of code
}

If you are like me, you immediately want to know what calls Configure() and where did the ILoggerFactory instance actually come from and live at.

When your app starts up you use the WebHostBuilder to essentially start up Kestrel and your web server.

public static void Main(string[] args)
{
	var host = new WebHostBuilder()
		.UseKestrel()
		.UseContentRoot(Directory.GetCurrentDirectory())
		.UseIISIntegration()
		.UseStartup()
		.Build();

	host.Run();
}

This process under the covers ultimately calls some code in the WebHostBuilder class that creates a new LoggerFactory() and stores the reference to it in the ServicesCollection and the WebHostBuilder itself.

private IServiceCollection BuildHostingServices()
{
	//removed code
	ServiceCollection services = new ServiceCollection();
	services.AddSingleton(this._hostingEnvironment);
	if (this._loggerFactory == null)
		this._loggerFactory = (ILoggerFactory) new LoggerFactory();
	
	using (List<Action>.Enumerator enumerator = this._configureLoggingDelegates.GetEnumerator())
	{
		while (enumerator.MoveNext())
			enumerator.Current(this._loggerFactory);
	}
	services.AddSingleton(this._loggerFactory);
	services.AddLogging();
	//removed code
}

Accessing the LoggerFactory Object via Dependency Injection and Services

In the example code below I am showing off 3 different ways to access the LoggerFactory from your MVC controller. Dependency injection can give you the factory or a logger either one. Also, because it is stored in the services collection, that later gets set on each HttpContext, you can potentially access it that way.

public class ValuesController : Controller
{
        private ILoggerFactory _Factory;
        private ILogger _Logger;

        //set by dependency injection
        public ValuesController(ILoggerFactory factory, ILogger logger)
        {
            _Factory = factory;
            _Logger = logger;
        }

	[HttpGet]
	public IEnumerable Get()
	{
            var loggerFromDI = _Factory.CreateLogger("Values");
            var loggerFactory = this.HttpContext.RequestServices.GetService();
            var loggerFromServices = loggerFactory.CreateLogger("Values");

            _Logger.LogDebug("From direct dependency injection");
            loggerFromDI.LogDebug("From dependency injection factory");
            loggerFromServices.LogDebug("From sevices");
	}
}

Accessing the logging API outside of a MVC controller

OK, so this is where the new logging API quickly becomes a nightmare. Dependency injection works great for accessing it in your MVC controller. But… how do you do logging in a class library that is consumed by this MVC project?

1. You could pass your existing LoggerFactory into every object/method you call. (which is a terrible idea)

2. You could create your own LoggerFactory within your class library

This is an option as long as you don’t use any providers like a File provider that can’t have multiple instances writing at the same time. If you are using a lot of different class libraries you would have a lot of LoggerFactory objects running around.

3. Create a centrally located static class or project to hold and wrap around the main LoggerFactory reference

I see this as the best solution here unless you aren’t using any providers that have concurrency issues.

How to use the Logging API from Everywhere

My suggestion is to create a little static helper class that becomes the owner of the LoggerFactory. The class can look something like this below. You can then use this ApplicationLogging class in any code that you want to use logging from and not have to worry about recreating LoggerFactory objects over and over. After all, logging needs to be fast!

public class ApplicationLogging
{
	private static ILoggerFactory _Factory = null;

	public static void ConfigureLogger(ILoggerFactory factory)
	{
		factory.AddDebug(LogLevel.None).AddStackify();
		factory.AddFile("logFileFromHelper.log"); //serilog file extension
	}

	public static ILoggerFactory LoggerFactory
	{
		get
		{
			if (_Factory == null)
			{
				_Factory = new LoggerFactory();
				ConfigureLogger(_Factory);
			}
			return _Factory;
		}
		set { _Factory = value; }
	}
	public static ILogger CreateLogger() => LoggerFactory.CreateLogger();
}    

How to Capture ASP.NET Built-In Logging and Your Logging

There is one really key thing you need to know if you want to capture the built-in ASP.NET logging messages.

ASP.NET will only write its internal logging to the LoggerFactory object that it creates at app startup.

So if you new up (new LoggerFactory()) on your own anywhere else in your code, ASP.NET is not going to write it’s logging to it. This even includes my recommendation around this ApplicationLogging class.

So, what you need to do is in Startup.Configure grab that reference of the LoggerFactory and set it to your static logging class so it becomes your primary reference.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	//call ConfigureLogger in a centralized place in the code
	ApplicationLogging.ConfigureLogger(loggerFactory);
	//set it as the primary LoggerFactory to use everywhere
	ApplicationLogging.LoggerFactory = loggerFactory;
	//other code removed
}

If you follow this pattern you will only have one LoggerFactory object in your application and you can access it anywhere via your ApplicationLogging static helper class. I sure wish the .NET team would make this built-in to the logging API somehow to simplify this.

Extend the Built-In Logging API Functionality by Using NLog or Serilog Providers

Both NLog and Serilog both have a provider that you can use to extend the functionality of the built-in logging API. They essentially redirect all of the logs being written to the new logging API to their libraries. This gives you all the power of their libraries for the output of your logs while your code is not tied to any particular library. This is similar to the benefit of using Common.Logging.

My Recommendation: Don’t Use the New ASP.NET Logging API!

Another option is to just use NLog or Serilog and don’t even worry about the new logging API. If you want to capture the built-in ASP.NET logging, you can plugin the NLog/Serilog provider and it will map those messages over. By doing it this way, you can use a different logging library directly and you don’t have to even think about LoggerFactory even existing. Just keep doing logging how you always have.

After all the time I have spent messing with the new logging API, I would suggest using NLog and not even worrying about the new LoggerFactory. NLog is extremely robust and the maintainers of it continue to improve it. Serilog is also a good option. Your third option is log4net which is not looking good in its old age.

About Matt Watson

Matt is the Founder & CEO of Stackify. When he isn't hacking away on new Stackify product features he is hacking away on new new blog posts like this.