.NET Logging Guide:
The Basics

October 31, 2022

Logs are essential for companies building software applications. They provide detailed information about an application’s execution and performance, helping with troubleshooting, debugging, and testing your application. Additionally, the data from logs can inform better business or technical decisions for your application.

In Part One of this two-part overview guide, we will introduce you to logging facilities in .NET applications, the available frameworks for .NET, and the configuration settings for logging frameworks (such as log level, log scopes, and filter functions). Let’s begin with a brief explanation of .NET logging frameworks.

Understanding .NET Logging Frameworks

.NET provides a logging API with  Microsoft.Extensions.Logging. Microsoft-supplied and third-party plugins extend this framework across multiple logging systems. You can use this framework when logging in .NET applications written with C#, F#, and Visual Basic. The framework includes various components that help with logging, which include:

  • ILogger
  • LoggerFactory
  • ILoggerFactory
  • ILoggerProvider

ILogger

The  ILoggerinterface represents a type that performs logging actions. It contains method definitions for writing log events. The following code snippet shows some of the included methods:

// Writes a log message
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);


// Checks if a log level is enabled 
bool IsEnabled(LogLevel logLevel); 

// Begins a logical operation scope
IDisposable BeginScope<TState>(TState state);

ILoggerFactory

ILoggerFactoryrepresents a type that registers an  ILoggerProviderand instantiates an  ILogger. It contains method definitions for two operations:

// Instantiates an ILogger that logs events
ILogger CreateLogger(string categoryName); 

// Add provider to list of providers
void AddProvider(ILoggerProvider provider);

.NET also provides a class called  LoggerFactory, which implements  ILoggerFactory. This factory class creates an  ILoggerfor your application.

ILoggerProvider

The ILoggerProvidercontains definitions that instantiate a logger with the specified category name. This interface has one method:

ILogger CreateLogger(string categoryName);

Extensibility of the .NET Logging API

You can extend the logging API by implementing any of the interfaces in Microsoft.Extensions.Logging. To create a custom logger, you can extend the ILoggerclass and override the methods with your custom implementation.

The following code snippet shows an example of how a  DefaultLoggerclass extends the ILogger interface:

using Microsoft.Extensions.Logging;

public class DefaultLogger : ILogger
{

    ...

    public IDisposable BeginScope<TState>(TState state) => default!;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        //Write code to log to destination
    }
}

Likewise, you can extend the ILoggerFactoryand ILoggerProvider interfaces with this approach.

Default Log File Location

Logging with .NET allows you to specify the default location for your logs throughout the lifecycle of your application. You can start Visual Studio (Microsoft’s development environment for .NET) and log all activities to a specified file with the following command:

Devenv /log LogFile

In the above code snippet,  LogFileshould be the full valid path to an existing file. Logs will subsequently be written to that file.

Logging Providers in .NET

A logging provider saves logs to a specified destination. .NET has four logging providers, which are as follows:

Console

.NET provides a console provider library called  Microsoft.Extensions.Logging.Console. This provider logs the output to the console.  ConsoleLoggerProvidercan be used to log while testing an application locally so that you can see the status of your application in real time. Logs written to the console can also help with debugging.

Debug

.NET provides a library for the debug provider called  Microsoft.Extensions.Logging.Debug. This provider writes logs using the  Debug.WriteLinemethod in the  System.Diagnostics.Debugclass.  DebugLoggerProvidercreates instances of  DebugLogger, a class that implements the  ILoggerinterface. Logs written to the debug console can also help with debugging.

EventSource

.NET provides an event source provider library called  Microsoft.Extensions.Logging.EventSource. This provider writes to a cross-platform event source called  Microsoft-Extensions-Logging. You can use this provider when you set up event listeners in different parts of your schedule and want to log these events when they are triggered.

EventLog

.NET provides a library for the event source provider named  Microsoft.Extensions.Logging.EventLog.  EventLogallows you to access or customize Windows event logs. These logs are information about critical software or hardware events, such as when a user logs into a virtual machine. EventLogis valuable for microservices deployed to different machines because it can specify the computer that owns the event, the log message, and the event ID. With EventLog, you can read from existing logs, write logs, manage event sources, and delete logs.

Logging Frameworks in .NET

The .NET Logging API provides fundamental features for logging. However, these are building blocks for more advanced features that you will need when building applications that collect different types of log messages. Third-party logging frameworks, such as Log4Net, SeriLog, and NLog, have implemented additional logging functions like structured logging and layout log renderers in .NET.

Log4Net

Log4Net helps programmers log messages to various outputs. Log4Net has been the first choice for .NET logging since its inception in 2001. However, it’s an older project, and developers haven’t released a new major version for several years.

SeriLog

SeriLog is a modern framework for structured logging. Structured logging helps programmers write queryable logs. SeriLog has a wide user base and is easy to integrate. Many .NET projects have been using SeriLog since its inception in 2013.

NLog

NLog has been popular since its inception in 2006. It allows programmers to write to multiple destinations. NLog also supports structured logging and, when compared to other logging libraries, is widely used in various .NET projects today.

Implementing Logging with the Logging API

Create and use Loggers

To create and use a logger, pass an instance of a  Loggerobject through dependency injection so that you can use it from your constructor.

public class CustomClass
{
    private readonly ILogger _logger;

    public CustomClass(ILogger<CustomClass> logger)
    {
        _logger = logger;
    }
}

To write logs using the Loggeryou created, invoke a log method for the specified level. The following code snippet writes an information-level log:

public class CustomClass
{
    private readonly ILogger _logger;

        public CustomClass(ILogger<CustomClass> logger)
    {
        _logger = logger;
    }

    public void CustomMethod()
    {
        _logger.LogInformation("Logging a custom event");
    }
}

Configure logging

Logging configurations are in the  Loggingsection of the  appsettings.[environment].jsonfile, where environment refers to the environment in which the application runs. An example file, named  appsettings.production.json, is shown below:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Debug"
    }
  }
}

Structured logging

Structured logging simplifies storing and querying log events because the log event maintains the message template and parameters instead of transforming them to a formatted message.

The .NET String.Format() method only accepts input strings like this:

...
var emailAddress = "janedoe@website.com";
var name = "Jane Doe"
logger.LogInformation("Error from:{0} with email-address:{1}", name, emailAddress);

When you store these log events, it’s challenging to query for information retrieval. Log message templates facilitate structured logging. They can contain placeholders for arguments.

logger.LogInformation("Error from:{username} with email-address:{email}", name, emailAddress);

With the code snippet above, you can query logs with query parameters such as username or email.

Using log levels

Knowing log levels and when to use them can be incredibly helpful. Log levels allow software developers to group logs into different classes. Log data is easier to filter and analyze when you specify the correct log level with the log message. For example, you should not write a success log message at the Error log level because this will misrepresent your logs and make debugging difficult.

The following table shows the various log levels for .NET, in increasing severity (except for None):

Log LevelDescription
TraceThese logs include elaborate messages about an application's execution trail. Disable them in production as they retain sensitive application data and increase logging volume.
DebugThese logs contain data for debugging during development.
InformationThese logs monitor typical application flows.
WarningThese logs indicate anomalous behavior during program execution.
ErrorThese logs signify errors during program execution.
CriticalThese logs display critical errors that demand prompt mitigation.
NoneThis log level establishes that a logging category should not log messages.

Defining a logging level enables logging of that level and any levels more severe. For example, if the log level is Warning, only events marked as Warning, Error, or Critical will fire.

The following code snippet writes a log message at the Error level:

logger.Log(LogLevel.Error, "This is an error!");

Using log scopes

In some cases, you may want to add the same value to every log message. For example, if you are capturing logs for an ecommerce backend service, you might want to add an order ID to every log message until an order is completed. You can manually write the ID to every message, but .NET provides the concept of log scopes to make this easier. A scope can group several logical operations. The BeginScope method of ILogger returns the scope.

To utilize a scope, wrap logger calls in a using block as illustrated below:

public void TestMethod()
{
    ...

    using (_logger.BeginScope("test method scope"))
    {
       // code 
        _logger.Log(LogLevel.Information, "This is a log message");
    }
}

Conclusion

In this first part of the .NET Logging Guide Overview, we’ve learned the basics of .NET logging. We covered how to create a logger, write structured log messages, and leverage log levels and log scopes. We also touched on third-party logging frameworks that you can use to extend your logging capabilities. In Part Two, we’ll cover advanced concepts in .NET logging, such as exception handling and high-performance logging.

Log Everything, Answer Anything – For Free

Falcon LogScale Community Edition (previously Humio) offers a free modern log management platform for the cloud. Leverage streaming data ingestion to achieve instant visibility across distributed systems and prevent and resolve incidents.

Falcon LogScale Community Edition, available instantly at no cost, includes the following:

  • Ingest up to 16GB per day
  • 7-day retention
  • No credit card required
  • Ongoing access with no trial period
  • Index-free logging, real-time alerts and live dashboards
  • Access our marketplace and packages, including guides to build new packages
  • Learn and collaborate with an active community