Using aspects to implement high-resolution logging (also called tracing) is so popular that it has become Metalama's hello, world. There is probably no better example of repetitive code that should never be written by hand in the first place.
Logging and tracing are often used as synonyms (including on this website), but they are slightly different:
Logging is the practice of generating and capturing significant events, errors, and messages generated by an application. Adding logging to an app is often done manually because it's difficult to automatically determine what constitutes a significant event. However, some events can be added automatically using a Metalama aspect:
- A method throwing an exception, including its parameter values.
- A method taking more time than expected to execute.
Tracing is the process of generating and capturing detailed information about the execution flow of a program, focusing on individual method calls. Adding tracing to your app is very useful if you want to be prepared to collect detailed information about an issue in production without the ability to use a debugger. Implementing tracing in your app manually would be extremely time-consuming and error-prone. Using Metalama, this can be done in minutes.
Features
Metalama comes with several examples of logging aspects as we learned that many teams prefer to customize their logging and tracing logic.
You will value the following Metalama features when implementing logging:
- Fabrics. No need to add a custom attribute to hundreds of methods. Execute a build-time code query and add logging to hundreds of methods at a time.
- Dependency injection. Aspects can automatically pull the
ILogger
class from the DI container.
Example
The following snippet adds logging to all public methods of public types except ToString
:
internal class Fabric : ProjectFabric
{
public override void AmendProject(IProjectAmender amender) =>
amender
.SelectTypes()
.Where(type => type.Accessibility == Accessibility.Public)
.SelectMany(type => type.Methods)
.Where(method =>
method.Accessibility == Accessibility.Public && method.Name != "ToString")
.AddAspectIfEligible<LogAttribute>();
}
Show me how it works!
Fabric classes execute at compile time. The code above selects all public types and methods and adds a logging aspect to them.
Suppose that you have this class in your project:
internal class Calculator
{
public double Add(double a, double b) => a + b;
}
If you select one of the most sophisticated versions of the example logging aspects, it will transform the code into this during the build:
public class Calculator
{
private ILogger _logger;
public Calculator(ILogger<Calculator> logger)
{
this._logger = logger;
}
[Log]
public double Add(double a, double b)
{
var isTracingEnabled = _logger.IsEnabled(LogLevel.Trace);
if (isTracingEnabled)
{
_logger.LogTrace($"Calculator.Add(a = {a}, b = {b}) started.");
}
try
{
var result = a + b;
if (isTracingEnabled)
{
_logger.LogTrace($"Calculator.Add(a = {a}, b = {b}) returned {result}.");
}
return (double) result;
}
catch (Exception e) when (_logger.IsEnabled(LogLevel.Warning))
{
_logger.LogWarning($"Calculator.Add(a = {a}, b = {b}) failed: {e.Message}");
throw;
}
}
}
As you can see, the aspect pulled a dependency, added a try/catch
block, and a lot of boilerplate code.
You probably don't want to write this code by hand, ever!
Metalama benefits
- Productivity boost. No need to write these thousands of lines of code.
- Clean code. The tracing code is generated on-the-fly during compilation, so your code remains crystal clear and is easier to maintain.
- High performance. The generated code is as fast as hand-written code.
- Consistency. Your logs will be consistent across your application. Not only the message structure but also the log level, the log category, and where the logs will be written.
- Easily add or remove. Detailed tracing is no longer an expensive feature that needs to be planned. You can add it or remove it at any time. You can also enable logging only in select build configurations.
Resources
- Example: Implementing logging without boilerplate
- Blog article: Serilog log levels, and how to use them
- Blog article: Adding Serilog to ASP.NET Core: a practical guide
- Blog article: Fast and Compact Structured Logging in C# Using String Interpolation