Let's say I want to wrap the classical LogInformation() method with a custom one:
public static void Info(this ILogger log, string message, params object[] objects) {
// ... some custom logic
log.LogInformation(message, objects);
}
This means that I can use it in the way logs are supposed to be written:
_logger.Info("Logged User Id: {UserId}", userId);
But I can also use it in the incorrect way, i.e. using a ”dynamic" string:
_logger.Info($"Logged User Id: {userId}");
If I used the LogInformation() method, the compiler would have issued the CA2254 warning: "Template should be a static expression".
How can I cause the compiler to issue the CA2254 warning also for my Info() method? I thought I'd find some sort of attribute ready to use for all cases like this, but I couldn't find one.
By the way, even a different warning/info would be fine, I just want that whoever (myself included) uses my Info() method passing a dynamically constructed string instead of a static template string, is warned that that's not the correct way to use the method.
TL;DR
Write your own custom analyzer for your class/methods basing it on the Microsoft one.
Details
This diagnostic is specific to the Microsoft provided logging types and is reported for their methods via LoggerMessageDefineAnalyzer based on types and method parameters naming conventions:
context.RegisterCompilationStartAction(context =>
{
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
if (!wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerExtensions, out var loggerExtensionsType) ||
!wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingILogger, out var loggerType) ||
!wellKnownTypeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerMessage, out var loggerMessageType))
{
return;
}
context.RegisterOperationAction(context => AnalyzeInvocation(context, loggerType, loggerExtensionsType, loggerMessageType), OperationKind.Invocation);
});
// ... somewhere in AnalyzeInvocation
if (containingType.Equals(loggerExtensionsType, SymbolEqualityComparer.Default))
{
usingLoggerExtensionsTypes = true;
context.ReportDiagnostic(invocation.CreateDiagnostic(CA1848Rule, methodSymbol.ToDisplayString(GetLanguageSpecificFormat(invocation))));
}
else if (
!containingType.Equals(loggerType, SymbolEqualityComparer.Default) &&
!containingType.Equals(loggerMessageType, SymbolEqualityComparer.Default))
{
return;
}
and:
// ....
if (FindLogParameters(methodSymbol, out var messageArgument, out var paramsArgument))
// ... somewhere in FindLogParameters:
if (parameter.Type.SpecialType == SpecialType.System_String &&
(string.Equals(parameter.Name, "message", StringComparison.Ordinal) ||
string.Equals(parameter.Name, "messageFormat", StringComparison.Ordinal) ||
string.Equals(parameter.Name, "formatString", StringComparison.Ordinal)))
{
message = parameter;
}
You can roll out your own Microsoft.Extensions.Logging.LoggerExtensions class with your methods and it even will work (though 1) it probably is brittle 2)you need to follow the naming conventions):
namespace Microsoft.Extensions.Logging;
public static class LoggerExtensions
{
public static void MyLogInformation(this ILogger logger, string? message, params object?[] args)
=> logger.Log(LogLevel.Information, message, args);
}


But this breaks analysis for the Microsoft provided one:

And probably can cause some other issues.
My recommendation is to roll out your own custom analyzer for your specific methods basing it on the code of the Microsoft one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With