Description
Background and motivation
Hi,
I noticed that ILogger.Log
method has as input parameter a Func<TState, System.Exception?, string> formatter
which is great since it could support the skipping of the calculation and formatting of the message string when the log must not be done (e.g. you use LogDebug
in code and in configuration you have set to log Info
or above).
Unluckily this possibility is thwarted by the extensions methods in Microsoft.Extensions.Logging.LoggerExtensions which have always as input parameters a string message, params object?[] args
which are always evaluated before making the Log
function call (e.g. call-by-value).
What about providing also extension methods with Lazy<FormattedLogValues>
or Func<FormattedLogValues>
and a language construct to quickly create with a short syntax a general new Lazy<T>( () => return object/struct of type t)
? This could be a great speedup in performance since sometimes when you call the Log[Critical|Debug|Error|Information|Trace|Warning]
methods the args
parameters you pass might contain function calls which are always evaluated even when the log entry must not be written.
Summing up the main idea behind this suggestion is that Log function should only pass by value or reference the data needed to evaluate if the log entry must be written or not and if needed then call a function in order to format the message with parameters. Moreover the short syntax language construct for creating a Lazy could be useful in order context too.
API Proposal
public static partial class LoggerExtensions
{
public static System.IDisposable? BeginScope(this Microsoft.Extensions.Logging.ILogger logger, string messageFormat, params object?[] args) { throw null; }
public static void Log(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void Log(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void Log(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel logLevel, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void Log(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.LogLevel logLevel, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogCritical(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogCritical(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogCritical(this Microsoft.Extensions.Logging.ILogger logger, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogCritical(this Microsoft.Extensions.Logging.ILogger logger, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogDebug(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogDebug(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogDebug(this Microsoft.Extensions.Logging.ILogger logger, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogDebug(this Microsoft.Extensions.Logging.ILogger logger, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogError(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogError(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogError(this Microsoft.Extensions.Logging.ILogger logger, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogError(this Microsoft.Extensions.Logging.ILogger logger, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogInformation(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogInformation(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogInformation(this Microsoft.Extensions.Logging.ILogger logger, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogInformation(this Microsoft.Extensions.Logging.ILogger logger, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogTrace(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogTrace(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogTrace(this Microsoft.Extensions.Logging.ILogger logger, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogTrace(this Microsoft.Extensions.Logging.ILogger logger, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogWarning(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogWarning(this Microsoft.Extensions.Logging.ILogger logger, Microsoft.Extensions.Logging.EventId eventId, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogWarning(this Microsoft.Extensions.Logging.ILogger logger, System.Exception? exception, Lazy<FormattedLogValues> messageWithArgs) { }
public static void LogWarning(this Microsoft.Extensions.Logging.ILogger logger, Lazy<FormattedLogValues> messageWithArgs) { }
}
API Usage
ILogger log;
log.LogInformation(new Lazy<FormattedLogValues>(() => new FormattedLogValues(message,args));
the short syntax might be [(message,args)] where the Lazy type would be inferred from the function signature and what is inside the square brackets would be passed to its constructor (it's just the first thing I came up with, obviously you can use whatever you think is the best with the objective of being as shortest as possible), so the above method could be easily rewritten as
log.LogInformation([(message,args)]);
Alternative Designs
An alternative design would be add the call-by-name to C# language, but I fear it would be quite demanding and troublesome; with this however you would achieve the performance improvement without even changing the signature.
Risks
No breaking changes since the new api are overloads.