Description
Is your feature request related to a problem? Please describe.
I'm trying to declare a strongly-typed code path for ILogger messages, but it is difficult to use the Action
-returning static method LoggerMessage.Define<T1,T2,...,Tn>
in a way that's approachable and maintainable by an entire team.
Describe the solution you'd like
It would be great if there was a strongly-typed struct which could wrap the underlying LoggerMessage.Define
call and Action<ILoggerFactory, ...., Exception>
field - even completely hide the actual Action
signature. Ideally it would also provide more appropriate intellisense on the struct method which calls the action. If would be even cleaner if it could be initialized without repeating the generic argument types, and in most end-user applications if the EventId number was optional it could be derived from the EventId name instead.
internal static class MyAppLoggerExtensions
{
private readonly static LogMessage<string, int> _logSayHello = (
LogLevel.Debug,
nameof(SayHello),
"The program named {ProgramName} is saying hello {HelloCount} times");
/// <summary>
/// The program named {ProgramName} is saying hello {HelloCount} times
/// </summary>
/// <param name="logger">The logger to write to</param>
/// <param name="programName">The {ProgramName} message property</param>
/// <param name="helloCount">The {HelloCount} message property</param>
public static void SayHello(this ILogger<MyApp> logger, string programName, int helloCount)
{
_logSayHello.Log(logger, programName, helloCount);
}
}
A LogMessage struct would be a allocation-free. Having implicit operators for tuples which match the constructor parameters enables field initialization without needing to repeat the generic argument types.
public struct LogMessage<T1, ..., Tn>
{
public LogMessage(LogLevel logLevel, EventId eventId, string formatString);
public LogMessage(LogLevel logLevel, int eventId, string formatString);
public LogMessage(LogLevel logLevel, string eventName, string formatString);
public LogLevel LogLevel {get;}
public EventId EventId {get;}
public string FormatString {get;}
public void Log(ILogger logger, T1 value1, ..., Tn valuen);
public void Log(ILogger logger, Exception exception, T1 value1, ..., Tn valuen);
public static implicit operator LogMessage<T1, ..., Tn>((LogLevel logLevel, EventId eventId, string formatString) parameters);
public static implicit operator LogMessage<T1, ..., Tn>((LogLevel logLevel, int eventId, string formatString) parameters);
public static implicit operator LogMessage<T1, ..., Tn>((LogLevel logLevel, string eventName, string formatString) parameters);
}
Describe alternatives you've considered
Having one struct per LogLevel could also remove the need to have the first "LogLevel" argument in the initializer - but that would be a larger number of classes and intellisense wouldn't list the level names for you like typing LogLevel.
does