-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logging-Generator should allow to skip the IsEnabled check #51927
Comments
Tagging subscribers to this area: @maryamariyan Issue DetailsTaking 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 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 Note: the 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 /cc: @maryamariyan PS: should this be a formal api proposal? I didn't chose that template, as this api isn't shipped public
|
It should be doing this by default AFAIK. The if check should always be in the generated code. |
Currently it is, but I'd like to get rid of it to avoid the double-check. |
Oh I thought it was skipping already, I don't think it should be an option, just always skip the check. |
Agree. From the proposal above we could get these cases: SkipEnabledCheck = false
SkipEnabledCheck = true
The Generated code will in all cases look like: partial class Log
{
private static readonly Action<ILogger, string, Exception?> __ConnectionStartedCallback =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(1, "ConnectionStarted"), "Connection {ConnectionId} started", /* value from the SkipEnabledCheck property */);
private static partial void ConnectionStarted(this global::Microsoft.Extensions.Logging.ILogger logger, global::System.String connectionId)
{
__ConnectionStartedCallback(logger, connectionId, null);
}
} |
I don't think so. It's always better to turn it off and generate the if directly so that that check can be inlined into the caller. The check inside of the delegate call can't be inlined into the caller (the JIT can't see though delegates yet). |
Then we are back to the initial proposal (top comment). If the user-code already has a |
Ah that makes sense now |
@maryamariyan re your question #50334 (comment)
It saves the interface dispatch and the compare. This shows up in a micro-benchmark (see below). For a trivial benchmark:
Codeusing System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using Microsoft.Extensions.Logging;
BenchmarkRunner.Run<Bench>();
[MemoryDiagnoser]
public class Bench
{
private readonly ILogger _logger;
private string _name = "Batman";
private int _age = 82;
public Bench()
{
_logger = LoggerFactory.Create(builder =>
{
builder.ClearProviders();
builder.AddProvider(new MyLoggerProvider());
}).CreateLogger(typeof(Bench));
}
[Benchmark(Baseline = true)]
public void Default() => Log.Log0(_logger, _name, _age);
[Benchmark]
public void SkipEnabledCheck() => Log.Log1(_logger, _name, _age);
private static class Log
{
private static readonly Action<ILogger, string, int, Exception?> s_log0 = LoggerMessage.Define<string, int>(
LogLevel.Information,
new EventId(1001),
"Name {Name} is {Age} years old");
private static readonly Action<ILogger, string, int, Exception?> s_log1 = LoggerMessage.Define<string, int>(
LogLevel.Information,
new EventId(1001),
"Name {Name} is {Age} years old",
skipEnabledCheck: true);
public static void Log0(ILogger logger, string name, int age) => s_log0(logger, name, age, null);
public static void Log1(ILogger logger, string name, int age) => s_log1(logger, name, age, null);
}
}
public class MyLogger : ILogger
{
public IDisposable BeginScope<TState>(TState state) => throw new NotImplementedException();
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
=> LogCore(formatter(state, exception));
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogCore(string _) { }
}
public class MyLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName) => new MyLogger();
public void Dispose() { }
} <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-preview.5.21267.12" />
</ItemGroup>
</Project> Note: the allocations in the benchmark come from boxing, this is tracked in #50768 |
Looks good as proposed namespace Microsoft.Extensions.Logging
{
[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;
}
} |
#54305 got merged, so this one is done --> closing. |
Taking the example from #45290 and bring it to the logging-generator:
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):
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:
and the generated code to be like:
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
The text was updated successfully, but these errors were encountered: