Skip to content

[API Proposal]: Add support for lazy evalution of message and args parameters in Microsoft.Extensions.Logging.LoggerExtensions to improve performance #98200

Open
@fededim

Description

@fededim

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions