Skip to content

[API Proposal]: Extend LoggingGenerator to support scopes #93924

Open
@kimsey0

Description

@kimsey0

Background and motivation

#51064 introduced a new LoggingGenerator source generator which allows writing code like

public partial class LoggingSample3
{
    private readonly ILogger _logger;

    public LoggingSample3(ILogger logger)
    {
        _logger = logger;
    }

    [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Hello {name}")]
    public partial void LogName(string name);
}

and having an implementation using LoggerMessage.Define generated like

partial class LoggingSample3
{
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "1.0.0.0")]
    private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, string, global::System.Exception?> _LogNameCallback =
        global::Microsoft.Extensions.Logging.LoggerMessage.Define<string>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, nameof(LogName)), "Hello {name}", skipEnabledCheck: true);

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "1.0.0.0")]
    public partial void LogName(string name)
    {
        if (_logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
        {
            _LogNameCallback(_logger, name, null);
        }
    }
}

However, this doesn't support the other method for high-performance logging, LoggerMessage.DefineScope. This means that, if you use scopes, you will have manual calls to LoggerMessage.DefineScope and the requisite delegate fields side-by-side with the new source generator approach:

[LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Hello {name}")]
public partial void LogName(string name);

private static readonly Func<ILogger, string, int, IDisposable?> ScopeCallback =
    LoggerMessage.DefineScope<string, int>("Value={Value};OtherValue={OtherValue}");

public IDisposable? Scope(string value, int otherValue) => ScopeCallback(_logger, value, otherValue);

API Proposal

I suggest adding a new attribute side-by-side with the existing LoggerMessageAttribute

namespace Microsoft.Extensions.Logging;

[AttributeUsage(AttributeTargets.Method)]
public sealed partial class LoggerMessageScopeAttribute : Attribute
{
    public LoggerMessageScopeAttribute();
    public string Message { get; set; } = "";
}

and having LoggingGenerator generate calls to LoggerMessage.DefineScope, similar to the way it currently generates them for LoggerMessage.Define.

API Usage

This will allow defining logger messages and scopes in the same way:

[LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Hello {name}")]
public partial void LogName(string name);

[LoggerMessageScope(Message = "Value={Value};OtherValue={OtherValue}")]
public partial IDisposable? Scope(string value, int otherValue);

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions