3

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.

1 Answer 1

4

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);
}

enter image description here

enter image description here

But this breaks analysis for the Microsoft provided one:

enter image description here

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your answer. The first link you provided states that I must have Visual Studio installed, which I cannot do since I use a Mac, but this seems like the only solution, therefore I'll mark your answer as the correct one. Anyway, I'm buffled that they didn't think of creating a simple attribute to be put on parameters in order to generalize the triggering of such warnings. What a bummer.
@MassimilianoKraus analyzers work as part of standard build pipeline, you don't need VS to write/use one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.