Logging in ASP.NET
Tags: programming, coding, csharp, dotnet
Published: 2023-05-23 19:10:59

> ℹ️ **Information**
> _The code in the following article was tested with ASP.NET 6 and 7._

Logging is an essential component for software development and maintenance. It helps us understand what is happening inside our applications, identify errors and unexpected behaviors, troubleshoot problems, and is also useful for auditing operations.

## Create logs

In ASP.NET, logging is typically performed by obtaining an `ILogger` or `ILogger<T>` instance through dependency injection, and then using that instance to log messages.

Here is a step-by-step guide on how to set up and use logging:

**Step 1: Configure logging in `appsettings.json`**

One of the first steps you often do is to set up the logging configuration in your application's `appsettings.json` file. This allows you to set the default logging level and define different logging levels for different categories.

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}
```

**Step 2: Add logging services in `Program.cs`**

In your `Program.cs` file, you can add and configure various logging providers, such as Console, Debug, or others.

```csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddLogging(config =>
{
    config.AddConsole();
    config.AddDebug();
    // Other logging providers
});

// ...
```

**Step 3: Set up dependency injection for the logger in your class**

Within the classes where you want to do the logging, such as a controller, you would set up dependency injection to get an instance of `ILogger` or `ILogger<T>`.

```csharp
public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

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

**Step 4: Use the logger to log messages**

Finally, you can use the `_logger` instance to log messages at various severity levels (from `Trace` to `Critical`).

```csharp
public IActionResult Index()
{
    _logger.LogInformation("Calling the Index method");
    return View();
}
```

## Configure logging

Logging can be easily configured either in the code or through configuration files, such as `appsettings.json`. This configuration file can be used to enable or disable log providers, modify log levels, or even filter log messages. Typically, the `Logging` section of the `appsettings.{ENVIRONMENT}.json` files (where `{ENVIRONMENT}` is the environment placeholder) provides the logging configuration.

A standard example of what a logging configuration might look like in an `appsettings.json` file is as follows:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}
```

In this example, the default log level is set to `Information`, and the log level for all categories that begin with `Microsoft.AspNetCore` is set to `Warning`.

The `LogLevel` can be set for specific providers (like `Logging:EventSource:LogLevel:Default:Information`), specific categories (like `Logging:LogLevel:Microsoft.AspNetCore:Critical`), or all providers and all categories (like `Logging:LogLevel:Default:Warning`). Any logs below the set minimum level will not be logged or displayed.

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "Microsoft.AspNetCore": "Critical"
    },
    "EventSource": {
      "LogLevel": {
        "Default": "Information"
      }
    }
  }
}
```

To mute all logs, you can specify `LogLevel.None`.

```csharp
{
  "Logging": {
    "LogLevel": {
      "Default": "None"
    }
  }
}
```

This logging configuration offers a high degree of flexibility, enabling you to customize logging behavior to suit your application's requirements.

## Log category

When you make a new `ILogger`, you pick a category. Each log message from this `ILogger` will have this category attached. Typically, the category is the name of the class. For instance, you might use `ILogger<T>` to automatically set the category to be the same as the type name `T`.

```csharp
public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

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

    public IActionResult Index()
    {
        _logger.LogInformation("Calling the Index method");
        return View();
    }
}
```

If you want to pick the category yourself, you can use `CreateLogger` from `ILoggerFactory`. This is handy if you want to group log messages from different places under the same category.

```csharp
public class HomeController : Controller
{
    private readonly ILogger _logger;

    public HomeController(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger("MyCategory");
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Calling the Index method");
        return View();
    }
}
```

So, using `ILogger<T>` is like using `CreateLogger`, but with the category being automatically set to the type name of `T`.

## Log level

A log level acts as a control mechanism to determine the depth of detail for your log messages. Each log level corresponds to a particular degree of seriousness and is often utilized to exclude logs of less critical nature.

The following table presents an overview of the different log levels:

| LogLevel  | Value | Method | Description |
|-----------|-------|--------|-------------|
| Trace     | 0     | LogTrace | Contains the most detailed messages. These messages may contain sensitive application data and are typically used for debugging. |
| Debug     | 1     | LogDebug | Used for informational messages that might be useful during development and debugging. |
| Information | 2   | LogInformation | Used to track the general flow of the application. These messages may have long-term value in understanding app behavior. |
| Warning   | 3     | LogWarning | Used for abnormal or unexpected events. Typically includes errors or conditions that don't cause the app to fail but might indicate potential issues. |
| Error     | 4     | LogError | Used for errors and exceptions that cannot be handled. These messages indicate a failure in the current operation or request, not an app-wide failure. |
| Critical  | 5     | LogCritical | Used for severe failures that require immediate attention, such as data loss scenarios or out of disk space. |
| None      | 6     | | Indicates that a logging category should not write any messages. This level is typically used to completely disable logging. |

## Logging providers

A logging provider is a component that receives log messages from the application and decides how and where to store or display these messages.

👉 **Built-in logging providers**

ASP.NET includes some registration providers as part of its shared framework, which are:

- **Console**: Logs output to the console.
- **Debug**: Writes log output by using the `System.Diagnostics.Debug` class.
- **EventSource**: Writes to a cross-platform event source.
- **EventLog**: Sends log output to the Windows Event Log. Unlike the other providers, the `EventLog` provider does not inherit the default non-provider settings. If `EventLog` log settings aren't specified, they default to `LogLevel.Warning`.

There are other registration providers provided by Microsoft that are not part of the shared framework. To use them, you must install them as extra nuget packages. These include:

- **Azure App Services**: It is part of the [Microsoft.Extensions.Logging.AzureAppServices](https://www.nuget.org/packages/Microsoft.Extensions.Logging.AzureAppServices) NuGet package  and writes logs to text files in the file system of an Azure App Service application and to a blob archive in an Azure Storage account.
- **Azure Application Insights**: It is part of the [Microsoft.Extensions.Logging.ApplicationInsights](https://www.nuget.org/packages/Microsoft.Extensions.Logging.ApplicationInsights) NuGet package and writes logs to Azure Application Insights, a service that monitors a web app and provides tools for querying and analyzing telemetry data.

👉 **Third-party logging providers**

There are various third-party logging tools that are compatible with ASP.NET, including:

- [Log4Net](https://logging.apache.org/log4net/)
- [NLog](https://nlog-project.org/)
- [Sentry](https://sentry.io/welcome/)
- [Serilog](https://serilog.net/)
- [LogScope.NET](https://github.com/devInstance/LogScope)

Some third-party frameworks can perform **semantic logging**, also known as **structured logging**.

Semantic logging is an approach to logging that goes beyond simply logging text messages. Instead of writing unstructured log messages, it writes log events as well-defined data structures.

The advantage of semantic logging is that it makes it much easier to search, filter, and analyze logs. Because the data is structured, you can easily search for all login events by a specific user, or all events that occurred within a certain time range, without having to parse the text of each log message.

## High-performance logging

When aiming for high-performance log registration, it is advisable to use `LoggerMessage` class. This class provides an efficient approach to create cached delegates that require fewer object allocations and lower computational overhead compared to logger extension methods like `LogInformation` and `LogDebug`.

`LoggerMessage` offers the following performance advantages over logger extension methods:

- It avoids the "boxing conversion" of value types such as `int` to `object`, which is required by logger extension methods. This is achieved by using static `Action` fields and extension methods with strongly-typed parameters in the `LoggerMessage` approach.
- It reduces the need to parse the message template (a named format string) with each log message write. `LoggerMessage` only requires parsing the template once during the message definition, saving processing time during actual log registration.

```csharp
using Microsoft.Extensions.Logging;

public class MyClass
{
    private static readonly Action<ILogger, string, int, bool> _logMessage = LoggerMessage.Define<string, int, bool>(
        LogLevel.Information,
        new EventId(1001, "MyEvent"),
        "My message: {message}, {number}, {flag}");

    private readonly ILogger _logger;

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

    public void DoSomething()
    {
        var message = "Hello world";
        var number = 10;
        var flag = true;

        _logMessage(_logger, message, number, flag);
    }
}
```

In this example, the `MyClass` class uses the `LoggerMessage` class to define a cached delegate named `_logMessage`. The delegate takes three parameters: a string `message`, an integer `number`, and a boolean `flag`.

Inside the `DoSomething()` method, the `_logMessage` delegate is used to log a message using the `_logger` instance. The value of the `message` string, the `number` integer, and the `flag` boolean are passed to the delegate. The log message is formatted using the template specified in the delegate's definition.

This approach allows for avoiding "boxing conversion" of values and parsing the message template only during the definition of the `_logMessage` delegate, thus providing better performance compared to logger extension methods.

## Conclusion

Logging is a key feature in software development that allows for easy troubleshooting and auditing in applications. ASP.NET provides powerful logging capabilities, including built-in and third-party logging providers, enabling you to configure, categorize, and analyze logs according to your specific needs. The semantic or structured logging approach offers easier log analysis, and for performance-critical applications, the `LoggerMessage` class enables efficient creation of cached delegates. Overall, good logging practices can significantly enhance an application's reliability, maintainability, and performance.

## References

- [Logging in .NET Core and ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-6.0)
- [High-performance logging in .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/high-performance-logging)