[Tech] Logging Patterns
Engineering guidelines part 3
Logging patterns
- Always specify an
EventId
. Include a numeric ID and a name. The name should be aPascalCasedCompoundWord
(i.e. no spaces, and each "word" within the name starts with a capital letter). - In production code, use “pre-compiled logging functions” (see below). Test code can use any kind of logging
- Prefer defining pre-compiled messages in a static class named
Log
that is a nested class within the class you are logging from. Messages that are used by multiple components can be defined in a shared class (but this is discouraged). - Consider separating the
Log
nested class into a separate file by usingpartial
classes. Name the additional file[OriginalClassName].Log.cs
- Never use string-interpolation (
$"Foo {bar}"
) for log messages. Log message templates are designed so that structured logging systems can make individual parameters queryable and string-interpolation breaks this. - Always use
PascalCasedCompoundWords
as template replacement tokens.
Pre-compiled Logging Functions
Production code should use “pre-compiled” logging functions. This means using LoggerMessage.Define
to create a compiled function that can be used to perform logging.
For example, consider the following log statement:
public class MyComponent
{
public void MyMethod()
{
_logger.LogError(someException, new EventId(1, "ABadProblem"), "You are having a bad problem and will not go to {Location} today", "Space");
}
}
The logging infrastructure has to parse the template ("You are having a bad problem and will not go to {Location} today"
) every time the log is written. A pre-compiled logging function allows you to compile the template once and get back a delegate that can be invoked to log the message without requiring the template be re-parsed.
For example:
public class MyComponent
{
public void MyMethod()
{
Log.ABadProblem(_logger, "Space", someException);
} private static class Log
{
private static readonly Action<ILogger, string, Exception> _aBadProblem =
LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(2, "ABadProblem"),
"You are having a bad problem and will not go to {Location} today"); public static void ABadProblem(ILogger logger, string location, Exception ex) => _aBadProblem(logger, location, ex);
}
}
If MyComponent
is a large class, consider splitting the Log
nested class into a separate file:
MyComponent.cs
public partial class MyComponent
{
public void MyMethod()
{
Log.ABadProblem(_logger, "Space", someException);
}
}
MyComponent.Log.cs
public partial class MyComponent
{
private static class Log
{
private static readonly Action<ILogger, string, Exception> _aBadProblem =
LoggerMessage.Define<string>(
LogLevel.Error,
new EventId(2, "ABadProblem"),
"You are having a bad problem and will not go to {Location} today");public static void ABadProblem(ILogger logger, string location, Exception ex) => _aBadProblem(logger, location, ex);
}
}
Summary
- 🤔????