Open sandboxFocusImprove this doc

Implementing logging without boilerplate

Logging is a key aspect of application development, enabling developers to understand the execution flow and identify bottlenecks. By providing detailed insights into system interactions, tracing helps optimize performance, pinpoint issues, and improve user experience. It plays a significant role in ensuring efficient and reliable software.

Objective

This series of examples shows how to build an aspect that automatically writes a log record when a method execution starts, completes, or fails.

The objective is to transform the code as illustrated in the following example. If the generated code does not fit entirely to your preferences, do not worry, you can learn how to personalize it.

Source Code



1internal class Calculator
2{
3    [Log]
4    public double Add(double a, double b) => a + b;



























5}
Transformed Code
1using System;
2using Microsoft.Extensions.Logging;
3
4internal class Calculator
5{
6    [Log]
7    public double Add(double a, double b) { var isTracingEnabled = _logger.IsEnabled(LogLevel.Trace);
8        if (isTracingEnabled)
9        {
10            _logger.LogTrace($"Calculator.Add(a = {{{a}}}, b = {{{b}}}) started.");
11        }
12
13        try
14        {
15            double result;
16            result = a + b;if (isTracingEnabled)
17            {
18                _logger.LogTrace($"Calculator.Add(a = {{{a}}}, b = {{{b}}}) returned {result}.");
19            }
20
21            return (double)result;
22        }
23        catch (Exception e) when (_logger.IsEnabled(LogLevel.Warning))
24        {
25            _logger.LogWarning($"Calculator.Add(a = {{{a}}}, b = {{{b}}}) failed: {e.Message}");
26            throw;
27        }
28    }
29private ILogger _logger;
30
31    public Calculator(ILogger<Calculator> logger = default(global::Microsoft.Extensions.Logging.ILogger<global::Calculator>))
32    {
33        this._logger = logger ?? throw new System.ArgumentNullException(nameof(logger));
34    }
35}

In this series

We start with the most trivial implementation and progressively add new features:

Article Description
Logging a constant message This is the simplest possible implementation of logging.
Adding the method name Instead of logging a constant, generic message, we now include the method name in the message.
Adding the parameter values We now add the parameter values and the return value to the message.
Using to ILogger Instead of using Console.WriteLine, we inject an ILogger into the target type using dependency injection.
ILogger without dependency injection Instead of using dependency injection, we expect the source code to contain an ILogger field and report errors if it does not.
Adding logging to many methods So far, we have manually added a custom attribute to each method. In this example, we show how to target several methods programmatically using compile-time code queries.
Redacting sensitive data Passwords and other sensitive data are excluded from the log.
Avoiding infinite recursions Adds a recursion guard to avoid infinite recursions due to logging.