.NET Logging Guide:
Custom Logging Providers

Arfan Sharif - January 27, 2023

In the previous articles in this series, we covered core concepts for logging in .NET, including frameworks, exception logging, common mistakes, security considerations and best practices. In part four, we discuss custom loggers and how to use ILogger and ILoggerProviderto build a logger that logs to other destinations beyond the .NET defaults.

A log provider is a component that facilitates logging to a particular destination, such as a file, database or cloud service. Many organizations use other sophisticated platforms to store their logs, so it is important to know how to integrate custom logger providers into your .NET application.

Learn More

Explore the complete .NET Logging Guide series:

The Importance of Custom Loggers

Custom loggers are unique classes or functions for handling message logging from a single module or section of an application. They enable programmers to define custom behaviors such as message formatting and log filtering. By using custom loggers to help control the flow of log messages in distributed applications, software developers can more easily spot and fix problems when they occur.

.NET applications that need to log to particular destinations, such as cloud-based services or private databases, can leverage custom log providers to achieve this. Engineers can take advantage of the built-in logging capabilities of the .NET framework while creating custom log providers to easily integrate with their preferred logging destination.

Incorporating organization-specific tools into logging providers is also possible with custom loggers. These tools can be additional services that the organization uses for managing and analyzing log data in addition to the built-in features of a logging provider. For example, an organization might have a custom tool for searching and filtering log messages or creating alerts when specific types of log messages occur. This integration can enhance the overall effectiveness and efficiency of an organization’s logging and monitoring procedures.

How to Create a Custom Logger

Before creating the custom logger, you must configure logging in your project. The Logging section of the app settings typically offers logging configuration. The code snippet below shows a sample configuration in JSON:

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

The above configuration sets the default logging of all categories at log level Information or higher, while categories beginning with Microsoft.AspNetCore log at the level Error or higher. An example of such a category is Microsoft.AspNetCore.Routing.EndpointMiddleware.

Creating a custom logger class requires implementing the ILogger interface in the Microsoft.Extensions.Logging namespace. ILogger defines a set of functions, properties and events that deliver a uniform logging interface for diverse application logging providers. This interface allows software developers to use a single logging API to write log messages which can then route to different logging providers, such as a file, the console, or a third-party logging service.

The following code snippet shows the structure for creating a logger called CustomLogger:

using Microsoft.Extensions.Logging;

// Implement the ILogger interface
public class CustomLogger : ILogger
{
    public IDisposable BeginScope<TState>(TState state)
    {
        // Your code here
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        // Your code here
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        // Your code here
    }
}

The three functions to implement are BeginScope, IsEnabled and Log.

BeginScope develops a new logging scope. A logging scope collects related log messages and links them to a particular operation or context.

IsEnabled verifies that a specified log level can write log messages. This method aids in optimizing code by bypassing the overhead of formatting and writing invalid log messages.

Log is the main logging method in .NET. This method writes a log message with a specified log level, event ID and state.

To implement your custom logger, you would override the three methods above.

The code snippet below shows the implementation of a custom logger that emits logs to a custom destination:

using Microsoft.Extensions.Logging;

public class CustomDestinationLogger : ILogger
{
    // This is a client for an external log destination
    private CustomLogDestinationClient logClient;

    public CustomDestinationLogger()
    {
       // initialization code
    }

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if(!IsEnabled(logLevel)) 
        {
           return;
        }
        
        // This line emits logs to a custom destination
        logClient.Emit(eventId, state, exception, formatter);
    }
}

In the preceding code, the Log method calls logClient.Emit(), a function that emits your application logs to an external log destination beyond the .NET defaults. This destination can be anywhere of your choosing. Depending on the SDK provided for that destination, you would use the appropriate method to emit your logs.

After creating the custom logger, the next step is to wire up the custom logger provider.

How to Create a Custom Logger Provider

Creating a custom logger provider requires implementing the ILoggerProvider interface in the Microsoft.Extensions.Logging namespace. It defines a set of methods a logger provider must implement to instantiate a logger.

The code snippet below shows the structure for creating a logger provider called CustomLoggerProvider:

public interface CustomLoggerProvider: ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
    // Your code here
    }

    public void Dispose()
    {
       // Your code here
    }
}

The ILoggerProvider interface contains theCreateLogger and Dispose methods. The CreateLogger method initiates an ILogger for a specified category. The Dispose method removes the logger provider resources when they are no longer needed. To implement your custom logger provider, override the two methods above.

The code snippet below shows the implementation of a custom logger provider that creates and returns instances of CustomDestinationLogger:

using Microsoft.Extensions.Logging;

public class CustomLoggerProvider : ILoggerProvider
{
    private readonly ConcurrentDictionary<string, CustomDestinationLogger> _loggers;

    public CustomLoggerProvider()
    {
      // initialization code
    }

    public ILogger CreateLogger(string categoryName)
    {
       var logger = _loggers.GetOrAdd(categoryName, new CustomDestinationLogger());
    }

    public void Dispose()
    {
        _loggers.Clear();
    }
}

In the code above, the CreateLogger method creates an instance of the CustomDestinationLogger for each category name, then stores it in the ConcurrentDictionary.

To free up memory, use the Dispose method to clear all the loggers in the ConcurrentDictionary.

How to Use the Custom Logger

In .NET, dependency injection is a design pattern that supplies the object dependencies for a class in a structured way. Instead of creating object dependencies directly within the class, the class gets its dependencies through a constructor or method parameter. This technique makes the class more adaptable and testable; the design implies that mocking the dependencies or replacing them with alternative implementations is possible.

To use a custom .NET logger in your application, register the logger with the dependency injection system in your app. This process typically involves creating a class that implements ILogger, and then adding an instance of that class to the dependency injection container. After registering the logger, use dependency injection to obtain a reference to the custom logger in any .NET class that needs it.

The code snippet below shows how to add the custom logger provider to the dependency injection container.

var builder = Host.CreateDefaultBuilder(args);

builder.ConfigureServices();
builder.Services.AddSingleton<ILoggerProvider, CustomLoggerProvider>();

using var host = builder.Build();

host.Run();

We now have CustomLoggerProvider in our dependency injection container. Next, we import it into any class in our .NET project. The following code snippet shows a sample AccountsController class that uses the logger we created:

public class AccountsController {
     
     private ILogger _logger;

     public AccountsController(ILoggerProvider loggerProvider)
     {
        _logger = loggerProvider.CreateLogger(nameof(AccountsController));
     }

     public void Create() {
        // ... your implementation here
        _logger.Log(logLevel, eventId, state, exception, formatter);
     }
}

The AccountsController constructor contains an ILoggerProvider instance from the dependency injection container. The constructor in this class also calls the CreateLogger method to fetch a logger for the AccountsController class. Now, we use the custom logger we created to log to a unique destination, similar to how the Create method uses the logger above.

Log your data with CrowdStrike Falcon Next-Gen SIEM

Elevate your cybersecurity with the CrowdStrike Falcon® platform, the premier AI-native platform for SIEM and log management. Experience security logging at a petabyte scale, choosing between cloud-native or self-hosted deployment options. Log your data with a powerful, index-free architecture, without bottlenecks, allowing threat hunting with over 1 PB of data ingestion per day. Ensure real-time search capabilities to outpace adversaries, achieving sub-second latency for complex queries. Benefit from 360-degree visibility, consolidating data to break down silos and enabling security, IT, and DevOps teams to hunt threats, monitor performance, and ensure compliance seamlessly across 3 billion events in less than 1 second.

Schedule Falcon Next-Gen SIEM Demo

GET TO KNOW THE AUTHOR

Arfan Sharif is a product marketing lead for the Observability portfolio at CrowdStrike. He has over 15 years experience driving Log Management, ITOps, Observability, Security and CX solutions for companies such as Splunk, Genesys and Quest Software. Arfan graduated in Computer Science at Bucks and Chilterns University and has a career spanning across Product Marketing and Sales Engineering.