-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Taking the example from #45290 and bring it to the logging-generator:
public class Connection
{
private string? _connectionId;
public string ConnectionId => _connectionId ??= GenerateConnectionId();
private string GenerateConnectionId() => Guid.NewGuid().ToString(); // or something more expensive
}
public static partial class Log
{
public static void ConnectionStarted(this ILogger logger, Connection connection)
{
if (logger.IsEnabled(LogLevel.Debug))
{
ConnectionStarted(logger, connection.ConnectionId);
}
}
[LoggerMessage(EventId = 1, EventName = "ConnectionStarted", Level = LogLevel.Debug, Message = "Connection {ConnectionId} started")]
private static partial void ConnectionStarted(this ILogger logger, string connectionId);
}
The key-point of the logger.IsEnabled
check is to avoid the creation of a expensive resource if not needed.
The generated code looks like (simplifyed some names, to make it easiert to read):
partial class Log
{
private static readonly Action<ILogger, string, Exception?> __ConnectionStartedCallback =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1, "ConnectionStarted"), "Connection {ConnectionId} started", true);
private static partial void ConnectionStarted(this global::Microsoft.Extensions.Logging.ILogger logger, global::System.String connectionId)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Debug))
{
__ConnectionStartedCallback(logger, connectionId, null);
}
}
}
So in LoggerMessage.Define
true
is passed in to skip the IsEnabled-check, which is good.
But in the "logging method" IsEnabled
is checked, regardless if our code already checked it.
Note: the IsEnabled
check isn't free either, I'll defer to @davidfowl's comment: #50334 (comment)
Thus I propose to extend
[AttributeUsage(AttributeTargets.Method)]
public sealed class LoggerMessageAttribute : Attribute
{
public LoggerMessageAttribute() { }
public int EventId { get; set; } = -1;
public string? EventName { get; set; }
public LogLevel Level { get; set; } = LogLevel.None;
public string Message { get; set; } = "";
+ public bool SkipEnabledCheck { get; set; } = false;
}
so the above example could be written as:
public static partial class Log
{
// ...
[LoggerMessage(EventId = 1, EventName = "ConnectionStarted", Level = LogLevel.Debug, Message = "Connection {ConnectionId} started", SkipEnabledCheck = true)]
private static partial void ConnectionStarted(this ILogger logger, string connectionId);
}
and the generated code to be like:
partial class Log
{
private static readonly Action<ILogger, string, Exception?> __ConnectionStartedCallback =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1, "ConnectionStarted"), "Connection {ConnectionId} started", true);
private static partial void ConnectionStarted(this global::Microsoft.Extensions.Logging.ILogger logger, global::System.String connectionId)
{
__ConnectionStartedCallback(logger, connectionId, null);
}
}
That way IsEnabled
is checked only once, if the user is aware of that pattern and opts-into this behaviour.
/cc: @maryamariyan
PS: should this be a formal api proposal? I didn't chose that template, as this api isn't shipped public