extensions: [LoggerMessage] Fails to compile if a ILogger property is used instead of a field
Description
In when using the logging source generator, if you use a property for the logger object instead of a field, the source generator gets wonky and fails to produce valid C#.
👇 This code works as intended. ✅
private partial class LogFunctions(ILogger<MyCode> logger)
{
private readonly ILogger<MyCode> loggerObject = logger;
[LoggerMessage(1, LogLevel.Warning, "This is a warning message")]
public partial void TestLogMethod();
}
👇 This code fails to compile ❌
private partial class LogFunctions(ILogger<MyCode> logger)
{
private ILogger<MyCode> loggerObject { get; } = logger; //note the use of a property
[LoggerMessage(1, LogLevel.Warning, "This is a warning message")]
public partial void TestLogMethod();
}
The specific issue is that the source generator tries to use the property’s backing field, which has a mangled/unspeakable name of <loggerObject>k__BackingField. When this mangled name is injected into the source generator’s code template, it breaks the code because it isn’t valid C#.
Here is the output produced by the generator
// <auto-generated/>
#nullable enable
partial class MyCode
{
partial class LogFunctions
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "7.0.7.1805")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.Exception?> __TestLogMethodCallback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define(global::Microsoft.Extensions.Logging.LogLevel.Warning, new global::Microsoft.Extensions.Logging.EventId(1, nameof(TestLogMethod)), "This is a warning message", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "7.0.7.1805")]
public partial void TestLogMethod()
{
if (<loggerObject>k__BackingField.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Warning))
{
__TestLogMethodCallback(<loggerObject>k__BackingField, null);
}
}
}
}
Also note that this becomes very weird to debug if you have multiple classes or logger methods using logger method. The compile errors show up on whatever method is the last in the source generated file, even if that method was using a field. That is to say that the use of just one class anywhere in your code that uses a property for ILogger will cause strange errors to all of the source generated logger code in your assembly
Reproduction Steps
Entire C# program and csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0-rc.2.23479.6" />
</ItemGroup>
</Project>
// See https://aka.ms/new-console-template for more information
using Microsoft.Extensions.Logging;
Console.WriteLine("Hello, World!");
public partial class MyCode
{
public MyCode(ILogger<MyCode> logger)
{
Log = new LogFunctions(logger);
}
private LogFunctions Log { get; }
public void MyMethod()
{
Log.TestLogMethod();
}
private partial class LogFunctions(ILogger<MyCode> logger)
{
private ILogger<MyCode> loggerObject { get; } = logger;
[LoggerMessage(1, LogLevel.Warning, "This is a warning message")]
public partial void TestLogMethod();
}
}
Expected behavior
Either
- A compiler error specifically forbidding the use of a property OR
- No errors and correct behavior
Actual behavior
The following errors on build
D:\dev\loggermessagebug\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(15,21): error CS1525: Invalid expression term '<' [D:\dev\loggermessagebug\loggermessagebug.csproj]
D:\dev\loggermessagebug\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(17,45): error CS1525: Invalid expression term '<' [D:\dev\loggermessagebug\loggermessagebug.csproj]
D:\dev\loggermessagebug\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(15,35): error CS0103: The name 'k__BackingField' does not exist in the current context [D:\dev\loggermessagebug\loggermessagebug.csproj]
D:\dev\loggermessagebug\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(17,59): error CS0103: The name 'k__BackingField' does not exist in the current context [D:\dev\loggermessagebug\loggermessagebug.csproj]
Regression?
Seems to have been here since at least 7.0.0
Known Workarounds
Use a field instead of a property
Configuration
dotnet 8.0.100-rc.2.23502.2
Other information
No response
About this issue
- Original URL
- State: closed
- Created 8 months ago
- Comments: 16 (7 by maintainers)
Properties were indeed not part of the original design, but there’s no reason it shouldn’t be supported. Or at the very least, the generator should produce an error instead of spewing out bad code.
@AlgorithmsAreCool (first, I have to say I love your GitHub handle 😃)
A think there are a couple easy changes:
Now separate from that is deciding on the behavior when there are multiple ILogger instances in scope. I worry that if the generator simply throws an error in tha case, it would make it so code that is storing ILogger in a field and then wrapping a property around it would fail because the generator would see both the property and the backing field. So I think if there are both property and field, the generator will silently just use the field.
@dariusclay
If i understand the way primary ctors work, this should not have introduced a new field into the class.
But even so, I can change the source to a normal ctor and get the same compile errors