-
Notifications
You must be signed in to change notification settings - Fork 5.1k
initial support for '@' symbol in message template #79038
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
initial support for '@' symbol in message template #79038
Conversation
Tagging subscribers to this area: @dotnet/area-extensions-logging Issue DetailsWith this change, users will be able to create message templates containing '@' symbol. @maryamariyan fixed an issue not related with templating, she added support for reseverd keyword parameters by prefixing them with '@' symbol (#64779) I made those changes, tried to not break things and tried to not add performance issues. Please let me know if there's anything I have to change to allow this PR to be accepted.
|
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
@tkrafael looks the CI is waiting for you to accept the |
I have read the CLA Document and I hereby sign the CLA |
Resulting generate file for Roslyn3.11: // <auto-generated/>
#nullable enable
namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
{
partial class AtSymbolTestExtensions
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Exception?> __M0Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, nameof(M0)), "M0 {event}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
internal static partial void M0(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String @event)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__M0Callback(logger, @event, null);
}
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Exception?> __M1Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(1, nameof(M1)), "M1 {@myevent}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
internal static partial void M1(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String @myevent)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__M1Callback(logger, @myevent, null);
}
}
}
} Roslyn4.0: // <auto-generated/>
#nullable enable
namespace Microsoft.Extensions.Logging.Generators.Tests.TestClasses
{
partial class AtSymbolTestExtensions
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Exception?> __M0Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, nameof(M0)), "M0 {event}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
internal static partial void M0(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String @event)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__M0Callback(logger, @event, null);
}
}
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Exception?> __M1Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(1, nameof(M1)), "M1 {@myevent}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
internal static partial void M1(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String @myevent)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__M1Callback(logger, @myevent, null);
}
}
}
} |
Found that if LoggerMessageAttribute is used without loglevel, it creates a struct object, thus causing the issue described by @tarekgh |
Please note I added comments inside code to describe where changes were made. Roslyn does not generated those comments. /// <summary> This API supports the logging infrastructure and is not intended to be used directly from your code. It is subject to change in the future. </summary>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
private readonly struct __UseAtSymbol4Struct : global::System.Collections.Generic.IReadOnlyList<global::System.Collections.Generic.KeyValuePair<string, object?>>
{
private readonly global::System.String @myevent;
private readonly global::System.Int32 _otherevent;
public __UseAtSymbol4Struct(global::System.String @myevent, global::System.Int32 otherevent)
{
// solved here.
this.@myevent = @myevent;
this._otherevent = otherevent;
}
public override string ToString()
{
var otherevent = this._otherevent;
// not creating local var as @myevent can be read directly from "this".
return $"Force use of Struct with error, {@myevent} {otherevent}";
}
public static readonly global::System.Func<__UseAtSymbol4Struct, global::System.Exception?, string> Format = (state, ex) => state.ToString();
public int Count => 3;
public global::System.Collections.Generic.KeyValuePair<string, object?> this[int index]
{
get => index switch
{
// adds name correctly when name contains '@'
0 => new global::System.Collections.Generic.KeyValuePair<string, object?>("@myevent", this.@myevent),
1 => new global::System.Collections.Generic.KeyValuePair<string, object?>("otherevent", this._otherevent),
2 => new global::System.Collections.Generic.KeyValuePair<string, object?>("{OriginalFormat}", "Force use of Struct with error, {@myevent} {otherevent}"),
_ => throw new global::System.IndexOutOfRangeException(nameof(index)), // return the same exception LoggerMessage.Define returns in this case
};
}
public global::System.Collections.Generic.IEnumerator<global::System.Collections.Generic.KeyValuePair<string, object?>> GetEnumerator()
{
for (int i = 0; i < 3; i++)
{
yield return this[i];
}
}
global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
} And partial method implementation: [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
public static partial void UseAtSymbol4(this global::Microsoft.Extensions.Logging.ILogger logger, global::Microsoft.Extensions.Logging.LogLevel level, global::System.String @myevent, global::System.Int32 otherevent, global::System.Exception ex)
{
if (logger.IsEnabled(level))
{
logger.Log(
level,
new global::Microsoft.Extensions.Logging.EventId(3, nameof(UseAtSymbol4)),
new __UseAtSymbol4Struct(@myevent, otherevent),
ex,
__UseAtSymbol4Struct.Format);
}
} When using LoggerMessage.Define: [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.String, global::System.Exception?> __M1Callback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.String>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(1, nameof(M1)), "M1 {@myevent}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "42.42.42.42")]
internal static partial void M1(global::Microsoft.Extensions.Logging.ILogger logger, global::System.String @myevent)
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))
{
__M1Callback(logger, @myevent, null);
}
} |
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
Outdated
Show resolved
Hide resolved
@tarekgh |
What's about $ caracter? It's also used like @ but to force .ToString() using instead of Json. Will it works? |
We can track this to fix it later. I think fixing |
I've added unit tests to validate parsing and code emit. |
src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Emitter.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @tkrafael for the provided help here!
With this change, users will be able to create message templates containing '@' symbol.
This allows initial compatibility with https://messagetemplates.org/ and with serilog sink.
This issue #197 can be resolved (it was closed without a proper solution).
@maryamariyan fixed an issue not related with templating, she added support for reseverd keyword parameters by prefixing them with '@' symbol (#64779)
I made those changes, tried to not break things and tried to not add performance issues. Please let me know if there's anything I have to change to allow this PR to be accepted.