diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
index 7a743fdb2d..579df745a8 100644
--- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
+++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
@@ -4,5 +4,6 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
+CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023)
CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024)
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
index 84ad020608..3de9a32206 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
@@ -2132,6 +2132,15 @@ Widening and user defined conversions are not supported with generic types.
Use char overload
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+ Avoid potentially expensive logging
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
new file mode 100644
index 0000000000..8806a6c080
--- /dev/null
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
@@ -0,0 +1,420 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Analyzer.Utilities;
+using Analyzer.Utilities.Extensions;
+using Analyzer.Utilities.Lightup;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.NetCore.Analyzers.Performance
+{
+ using static MicrosoftNetCoreAnalyzersResources;
+
+ ///
+ /// CA1873:
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+ public sealed class AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer : DiagnosticAnalyzer
+ {
+ private const string RuleId = "CA1873";
+
+ private const string Level = nameof(Level);
+ private const string LogLevel = nameof(LogLevel);
+
+ private const string Log = nameof(Log);
+ private const string IsEnabled = nameof(IsEnabled);
+ private const string LogTrace = nameof(LogTrace);
+ private const string LogDebug = nameof(LogDebug);
+ private const string LogInformation = nameof(LogInformation);
+ private const string LogWarning = nameof(LogWarning);
+ private const string LogError = nameof(LogError);
+ private const string LogCritical = nameof(LogCritical);
+
+ private const int LogLevelTrace = 0;
+ private const int LogLevelDebug = 1;
+ private const int LogLevelInformation = 2;
+ private const int LogLevelWarning = 3;
+ private const int LogLevelError = 4;
+ private const int LogLevelCritical = 5;
+ private const int LogLevelPassedAsParameter = int.MinValue;
+
+ private static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
+ RuleId,
+ CreateLocalizableResourceString(nameof(AvoidPotentiallyExpensiveCallWhenLoggingTitle)),
+ CreateLocalizableResourceString(nameof(AvoidPotentiallyExpensiveCallWhenLoggingMessage)),
+ DiagnosticCategory.Performance,
+ RuleLevel.IdeSuggestion,
+ CreateLocalizableResourceString(nameof(AvoidPotentiallyExpensiveCallWhenLoggingDescription)),
+ isPortedFxCopRule: false,
+ isDataflowRule: false);
+
+ public sealed override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
+
+ public sealed override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.RegisterCompilationStartAction(OnCompilationStart);
+ }
+
+ private void OnCompilationStart(CompilationStartAnalysisContext context)
+ {
+ if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols))
+ {
+ return;
+ }
+
+ context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
+
+ void AnalyzeInvocation(OperationAnalysisContext context)
+ {
+ var invocation = (IInvocationOperation)context.Operation;
+
+ // Check if we have a log invocation and capture the log level used, either as IOperation or as int.
+ // Then, check if the invocation is guarded by 'ILogger.IsEnabled' and bail out if it is.
+ if (!symbols.IsLogInvocation(invocation, out var logLevel, out var logLevelArgumentOperation) ||
+ symbols.IsGuardedByIsEnabled(invocation, logLevel, logLevelArgumentOperation))
+ {
+ return;
+ }
+
+ var arguments = invocation.Arguments.Skip(invocation.IsExtensionMethodAndHasNoInstance() ? 1 : 0);
+
+ // Check each argument if it is potentially expensive to evaluate and raise a diagnostic if it is.
+ foreach (var argument in arguments)
+ {
+ if (IsPotentiallyExpensive(argument.Value))
+ {
+ context.ReportDiagnostic(argument.CreateDiagnostic(Rule));
+ }
+ }
+ }
+ }
+
+ private static bool IsPotentiallyExpensive(IOperation? operation)
+ {
+ if (operation is null)
+ {
+ return false;
+ }
+
+ if (ICollectionExpressionOperationWrapper.IsInstance(operation) ||
+ operation is IAnonymousObjectCreationOperation or
+ IAwaitOperation or
+ IInvocationOperation or
+ IObjectCreationOperation { Type.IsReferenceType: true } or
+ IWithOperation)
+ {
+ return true;
+ }
+
+ if (operation is IArrayCreationOperation arrayCreationOperation)
+ {
+ return !IsEmptyImplicitParamsArrayCreation(arrayCreationOperation);
+ }
+
+ if (operation is IConversionOperation conversionOperation)
+ {
+ return IsBoxing(conversionOperation) || IsPotentiallyExpensive(conversionOperation.Operand);
+ }
+
+ if (operation is IArrayElementReferenceOperation arrayElementReferenceOperation)
+ {
+ return IsPotentiallyExpensive(arrayElementReferenceOperation.ArrayReference) ||
+ arrayElementReferenceOperation.Indices.Any(IsPotentiallyExpensive);
+ }
+
+ if (operation is IBinaryOperation binaryOperation)
+ {
+ return IsPotentiallyExpensive(binaryOperation.LeftOperand) ||
+ IsPotentiallyExpensive(binaryOperation.RightOperand);
+ }
+
+ if (operation is ICoalesceOperation coalesceOperation)
+ {
+ return IsPotentiallyExpensive(coalesceOperation.Value) ||
+ IsPotentiallyExpensive(coalesceOperation.WhenNull);
+ }
+
+ if (operation is IConditionalAccessOperation conditionalAccessOperation)
+ {
+ return IsPotentiallyExpensive(conditionalAccessOperation.WhenNotNull);
+ }
+
+ if (operation is IIncrementOrDecrementOperation incrementOrDecrementOperation)
+ {
+ return IsPotentiallyExpensive(incrementOrDecrementOperation.Target);
+ }
+
+ if (operation is IInterpolatedStringOperation interpolatedStringOperation)
+ {
+ return interpolatedStringOperation.Parts.Any(p => p is
+ IInterpolationOperation { Expression.ConstantValue.HasValue: false } or
+ IInterpolatedStringTextOperation { Text.ConstantValue.HasValue: false });
+ }
+
+ if (operation is IMemberReferenceOperation memberReferenceOperation)
+ {
+ if (IsPotentiallyExpensive(memberReferenceOperation.Instance))
+ {
+ return true;
+ }
+
+ if (memberReferenceOperation is IPropertyReferenceOperation { Arguments.IsEmpty: false } indexerReferenceOperation)
+ {
+ return indexerReferenceOperation.Arguments.Any(a => IsPotentiallyExpensive(a.Value));
+ }
+ }
+
+ if (operation is IUnaryOperation unaryOperation)
+ {
+ return IsPotentiallyExpensive(unaryOperation.Operand);
+ }
+
+ return false;
+
+ static bool IsBoxing(IConversionOperation conversionOperation)
+ {
+ var targetIsReferenceType = conversionOperation.Type?.IsReferenceType ?? false;
+ var operandIsValueType = conversionOperation.Operand.Type?.IsValueType ?? false;
+
+ return targetIsReferenceType && operandIsValueType;
+ }
+
+ static bool IsEmptyImplicitParamsArrayCreation(IArrayCreationOperation arrayCreationOperation)
+ {
+ return arrayCreationOperation.IsImplicit &&
+ arrayCreationOperation.DimensionSizes.Length == 1 &&
+ arrayCreationOperation.DimensionSizes[0].ConstantValue.HasValue &&
+ arrayCreationOperation.DimensionSizes[0].ConstantValue.Value is int size &&
+ size == 0;
+ }
+ }
+
+ internal sealed class RequiredSymbols
+ {
+ private RequiredSymbols(
+ IMethodSymbol logMethod,
+ IMethodSymbol isEnabledMethod,
+ ImmutableDictionary logExtensionsMethodsAndLevel,
+ INamedTypeSymbol? loggerMessageAttributeType)
+ {
+ _logMethod = logMethod;
+ _isEnabledMethod = isEnabledMethod;
+ _logExtensionsMethodsAndLevel = logExtensionsMethodsAndLevel;
+ _loggerMessageAttributeType = loggerMessageAttributeType;
+ }
+
+ public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols)
+ {
+ symbols = default;
+
+ var iLoggerType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingILogger);
+
+ if (iLoggerType is null)
+ {
+ return false;
+ }
+
+ var logMethod = iLoggerType.GetMembers(Log)
+ .OfType()
+ .FirstOrDefault();
+
+ var isEnabledMethod = iLoggerType.GetMembers(IsEnabled)
+ .OfType()
+ .FirstOrDefault();
+
+ if (logMethod is null || isEnabledMethod is null)
+ {
+ return false;
+ }
+
+ var loggerExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerExtensions);
+ var logExtensionsMethodsBuilder = ImmutableDictionary.CreateBuilder(SymbolEqualityComparer.Default);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogTrace).OfType(), LogLevelTrace);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogDebug).OfType(), LogLevelDebug);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogInformation).OfType(), LogLevelInformation);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogWarning).OfType(), LogLevelWarning);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogError).OfType(), LogLevelError);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogCritical).OfType(), LogLevelCritical);
+ AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(Log).OfType(), LogLevelPassedAsParameter);
+
+ var loggerMessageAttributeType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerMessageAttribute);
+
+ symbols = new RequiredSymbols(logMethod, isEnabledMethod, logExtensionsMethodsBuilder.ToImmutable(), loggerMessageAttributeType);
+
+ return true;
+
+ void AddRangeIfNotNull(ImmutableDictionary.Builder builder, IEnumerable? range, int value)
+ {
+ if (range is not null)
+ {
+ builder.AddRange(range.Select(s => new KeyValuePair(s, value)));
+ }
+ }
+ }
+
+ public bool IsLogInvocation(IInvocationOperation invocation, out int logLevel, out IArgumentOperation? logLevelArgumentOperation)
+ {
+ logLevel = LogLevelPassedAsParameter;
+ logLevelArgumentOperation = default;
+
+ var method = invocation.TargetMethod.ReducedFrom ?? invocation.TargetMethod;
+
+ // ILogger.Log
+ if (SymbolEqualityComparer.Default.Equals(method.ConstructedFrom, _logMethod) ||
+ method.ConstructedFrom.IsOverrideOrImplementationOfInterfaceMember(_logMethod))
+ {
+ logLevelArgumentOperation = invocation.Arguments.GetArgumentForParameterAtIndex(0);
+
+ return true;
+ }
+
+ // LoggerExtensions.Log and named variants (e.g. LoggerExtensions.LogInformation)
+ if (_logExtensionsMethodsAndLevel.TryGetValue(method, out logLevel))
+ {
+ // LoggerExtensions.Log
+ if (logLevel == LogLevelPassedAsParameter)
+ {
+ logLevelArgumentOperation = invocation.Arguments.GetArgumentForParameterAtIndex(invocation.IsExtensionMethodAndHasNoInstance() ? 1 : 0);
+ }
+
+ return true;
+ }
+
+ var loggerMessageAttribute = method.GetAttribute(_loggerMessageAttributeType);
+
+ if (loggerMessageAttribute is null)
+ {
+ return false;
+ }
+
+ // Try to get the log level from the attribute arguments.
+ logLevel = loggerMessageAttribute.NamedArguments
+ .FirstOrDefault(p => p.Key.Equals(Level, StringComparison.Ordinal))
+ .Value.Value as int?
+ ?? LogLevelPassedAsParameter;
+
+ if (logLevel == LogLevelPassedAsParameter)
+ {
+ logLevelArgumentOperation = invocation.Arguments
+ .FirstOrDefault(a => a.Value.Type?.Name.Equals(LogLevel, StringComparison.Ordinal) ?? false);
+
+ if (logLevelArgumentOperation is null)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public bool IsGuardedByIsEnabled(IInvocationOperation logInvocation, int logLevel, IArgumentOperation? logLevelArgumentOperation)
+ {
+ // Check each block for conditionals that contain an 'ILogger.IsEnabled' invocation that guards the log invocation:
+ // 1. If the 'ILogger.IsEnabled' invocation is negated, the 'WhenTrue' branch must contain a return.
+ // 2. If the 'ILogger.IsEnabled' invocation is not negated, the 'WhenTrue' branch must contain the log invocation.
+ // This is also not perfect, but should be good enough to prevent false positives.
+ var currentBlockAncestor = logInvocation.GetAncestor(OperationKind.Block);
+ while (currentBlockAncestor is not null)
+ {
+ var guardConditionals = currentBlockAncestor.Descendants().OfType();
+ if (guardConditionals.Any(IsValidGuardConditional))
+ {
+ return true;
+ }
+
+ currentBlockAncestor = currentBlockAncestor.GetAncestor(OperationKind.Block);
+ }
+
+ return false;
+
+ bool IsValidGuardConditional(IConditionalOperation conditional)
+ {
+ if (conditional.Syntax.SpanStart > logInvocation.Syntax.SpanStart)
+ {
+ return false;
+ }
+
+ var conditionInvocations = conditional.Condition
+ .DescendantsAndSelf()
+ .OfType();
+
+ if (conditionInvocations.Any(IsValidIsEnabledGuardInvocation))
+ {
+ return true;
+ }
+
+ return false;
+
+ bool IsValidIsEnabledGuardInvocation(IInvocationOperation invocation)
+ {
+ if (!IsIsEnabledInvocation(invocation) ||
+ !AreInvocationsOnSameInstance(logInvocation, invocation) ||
+ !IsSameLogLevel(invocation.Arguments[0]))
+ {
+ return false;
+ }
+
+ var isNegated = invocation.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not };
+ var descendants = conditional.WhenTrue.DescendantsAndSelf();
+
+ return isNegated && descendants.OfType().Any() || !isNegated && descendants.Contains(logInvocation);
+ }
+ }
+
+ bool IsIsEnabledInvocation(IInvocationOperation invocation)
+ {
+ return SymbolEqualityComparer.Default.Equals(_isEnabledMethod, invocation.TargetMethod) ||
+ invocation.TargetMethod.IsOverrideOrImplementationOfInterfaceMember(_isEnabledMethod);
+ }
+
+ static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2)
+ {
+ return SymbolEqualityComparer.Default.Equals(
+ GetInstanceResolvingConditionalAccess(invocation1).GetReferencedMemberOrLocalOrParameter(),
+ GetInstanceResolvingConditionalAccess(invocation2).GetReferencedMemberOrLocalOrParameter());
+ }
+
+ static IOperation? GetInstanceResolvingConditionalAccess(IInvocationOperation invocation)
+ {
+ var instance = invocation.GetInstance()?.WalkDownConversion();
+
+ if (instance is IConditionalAccessInstanceOperation conditionalAccessInstance)
+ {
+ return conditionalAccessInstance.GetConditionalAccess()?.Operation;
+ }
+
+ return instance;
+ }
+
+ bool IsSameLogLevel(IArgumentOperation isEnabledArgument)
+ {
+ if (isEnabledArgument.Value.ConstantValue.HasValue)
+ {
+ int isEnabledLogLevel = (int)isEnabledArgument.Value.ConstantValue.Value!;
+
+ return logLevel == LogLevelPassedAsParameter
+ ? logLevelArgumentOperation?.Value.HasConstantValue(isEnabledLogLevel) ?? false
+ : isEnabledLogLevel == logLevel;
+ }
+
+ return SymbolEqualityComparer.Default.Equals(
+ isEnabledArgument.Value.GetReferencedMemberOrLocalOrParameter(),
+ logLevelArgumentOperation?.Value.GetReferencedMemberOrLocalOrParameter());
+ }
+ }
+
+ private readonly IMethodSymbol _logMethod;
+ private readonly IMethodSymbol _isEnabledMethod;
+ private readonly ImmutableDictionary _logExtensionsMethodsAndLevel;
+ private readonly INamedTypeSymbol? _loggerMessageAttributeType;
+ }
+ }
+}
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf
index 8620824df4..09eda85d7d 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf
@@ -67,6 +67,21 @@
Vyhněte se konstantním polím jako argumentům
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Vyhněte se vytvoření nové instance JsonSerializerOptions pro každou operaci serializace. Místo toho ukládejte instance do mezipaměti a znovu je používejte. Instance JsonSerializerOptions pro jedno použití můžou výrazně snížit výkon vaší aplikace.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf
index edbf16152c..9f60a4f73f 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf
@@ -67,6 +67,21 @@
Konstantenmatrizen als Argumente vermeiden
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Erstellen Sie für Serialisierungsvorgänge nicht jeweils eine neue JsonSerializerOptions-Instanz. Speichern Sie stattdessen Instanzen zwischen, und verwenden Sie sie wieder. Die einmalige Verwendung von JsonSerializerOptions-Instanzen kann die Leistung Ihrer Anwendung erheblich beeinträchtigen.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf
index 741e88f833..58bdd810fd 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf
@@ -67,6 +67,21 @@
Evitar matrices constantes como argumentos
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Evite crear una nueva instancia de "JsonSerializerOptions" para cada operación de serialización. En su lugar, almacene en caché y reutilice instancias. Las instancias "JsonSerializerOptions" de uso único pueden degradar considerablemente el rendimiento de la aplicación.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf
index 9bbae9c121..a4f7ef0d7d 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf
@@ -67,6 +67,21 @@
Éviter les tableaux constants en tant qu’arguments
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Évitez de créer une instance « JsonSerializerOptions » pour chaque opération de sérialisation. Mettez en cache et réutilisez les instances à la place. Les instances « JsonSerializerOptions » à usage unique peuvent considérablement dégrader les performances de votre application.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf
index 727d2d0c72..e55ae22733 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf
@@ -67,6 +67,21 @@
Evitare matrici costanti come argomenti
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Evitare di creare una nuova istanza di 'JsonSerializerOptions' per ogni operazione di serializzazione. Memorizzare nella cache le istanze e riutilizzarle. Le istanze monouso di 'JsonSerializerOptions' possono ridurre notevolmente le prestazioni dell'applicazione.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf
index 6b265d4a6c..baa1b0b00f 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf
@@ -67,6 +67,21 @@
引数として定数配列を使用しない
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
シリアル化操作ごとに新しい 'JsonSerializerOptions' インスタンスを作成しないでください。代わりにインスタンスをキャッシュして再利用します。'JsonSerializerOptions' インスタンスの単独使用では、アプリケーションのパフォーマンスが大幅に低下する可能性があります。
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf
index 957ea51b8a..0ae09fed49 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf
@@ -67,6 +67,21 @@
상수 배열을 인수로 사용하지 않습니다.
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
모든 직렬화 작업에 대해 새 'JsonSerializerOptions' 인스턴스를 만들지 마세요. 대신 인스턴스를 캐시하고 다시 사용합니다. 단일 사용 'JsonSerializerOptions' 인스턴스는 애플리케이션의 성능을 크게 저하시킬 수 있습니다.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf
index 5e5fc39b5d..88e60d33a2 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf
@@ -67,6 +67,21 @@
Unikaj tablic stałych jako argumentów
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Unikaj tworzenia nowego wystąpienia „JsonSerializerOptions” dla każdej operacji serializacji. Zamiast tego buforuj i ponownie wykorzystuj wystąpienia. Jednorazowe użycie wystąpienia „JsonSerializerOptions” może znacznie obniżyć wydajność aplikacji.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf
index 392e385097..d5ded437ee 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf
@@ -67,6 +67,21 @@
Evite matrizes constantes como argumentos
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Evite criar uma nova instância 'JsonSerializerOptions' para cada operação de serialização. Em vez disso, armazene em cache e reutilize instâncias. Instâncias 'JsonSerializerOptions' de uso único podem degradar substancialmente o desempenho de seu aplicativo.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf
index f809ac49ff..2257594ed7 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf
@@ -67,6 +67,21 @@
Избегайте использования константных массивов в качестве аргументов
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Избегайте создания нового экземпляра "JsonSerializerOptions" для каждой операции сериализации. Выполняйте кэширование и повторно используйте экземпляры. Однократное использование экземпляров "JsonSerializerOptions" может значительно снизить производительность приложения.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf
index e76c270811..868029639b 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf
@@ -67,6 +67,21 @@
Sabit dizileri bağımsız değişkenler olarak kullanmaktan sakının
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
Her serileştirme işlemi için yeni bir 'JsonSerializerOptions' örneği oluşturmaktan kaçının. Bunun yerine örnekleri önbelleğe alıp yeniden kullanın. Tek kullanımlık 'JsonSerializerOptions' örnekleri uygulamanızın performansını önemli ölçüde düşürebilir.
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf
index 495e53c115..86f3156882 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf
@@ -67,6 +67,21 @@
不要将常量数组作为参数
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
避免为每个序列化操作创建新的“JsonSerializerOptions”实例。请改为缓存和重用实例。仅使用“JsonSerializerOptions”实例可能会显著降低应用程序的性能。
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf
index 7e383f5b72..60b58f36ef 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf
@@ -67,6 +67,21 @@
避免常數陣列作為引數
+
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+ In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+
+
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+ Evaluation of this argument may be expensive and unnecessary if logging is disabled
+
+
+
+ Avoid potentially expensive logging
+ Avoid potentially expensive logging
+
+
Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application.
避免為每個序列化作業建立新的 'JsonSerializerOptions' 執行個體。改為快取並重新使用執行個體。單一使用 'JsonSerializerOptions' 執行個體可能會大幅降低應用程式的效能。
diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
index 2c54407efb..bfb110318e 100644
--- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
+++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md
@@ -1860,6 +1860,18 @@ Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a
|CodeFix|True|
---
+## [CA1873](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873): Avoid potentially expensive logging
+
+In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.
+
+|Item|Value|
+|-|-|
+|Category|Performance|
+|Enabled|True|
+|Severity|Info|
+|CodeFix|False|
+---
+
## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope
If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead.
diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
index dbb96f5541..d6d19ab152 100644
--- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
+++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif
@@ -3428,6 +3428,26 @@
]
}
},
+ "CA1873": {
+ "id": "CA1873",
+ "shortDescription": "Avoid potentially expensive logging",
+ "fullDescription": "In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.",
+ "defaultLevel": "note",
+ "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873",
+ "properties": {
+ "category": "Performance",
+ "isEnabledByDefault": true,
+ "typeName": "AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer",
+ "languages": [
+ "C#",
+ "Visual Basic"
+ ],
+ "tags": [
+ "Telemetry",
+ "EnabledRuleInAggressiveMode"
+ ]
+ }
+ },
"CA2000": {
"id": "CA2000",
"shortDescription": "Dispose objects before losing scope",
diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md
index b59a35228c..aca4ac13b4 100644
--- a/src/NetAnalyzers/RulesMissingDocumentation.md
+++ b/src/NetAnalyzers/RulesMissingDocumentation.md
@@ -2,5 +2,5 @@
Rule ID | Missing Help Link | Title |
--------|-------------------|-------|
+CA1873 | | Avoid potentially expensive logging |
CA2023 | | Invalid braces in message template |
-CA2024 | | Do not use 'StreamReader.EndOfStream' in async methods |
diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
new file mode 100644
index 0000000000..52ef278988
--- /dev/null
+++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
@@ -0,0 +1,5633 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Testing;
+using Xunit;
+
+using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
+ Microsoft.NetCore.Analyzers.Performance.AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer,
+ Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
+using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier<
+ Microsoft.NetCore.Analyzers.Performance.AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer,
+ Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
+
+namespace Microsoft.NetCore.Analyzers.Performance.UnitTests
+{
+ public class AvoidPotentiallyExpensiveCallWhenLoggingTests
+ {
+ public static readonly TheoryData LogLevels = new()
+ {
+ "Trace",
+ "Debug",
+ "Information",
+ "Warning",
+ "Error",
+ "Critical"
+ };
+
+ [Fact]
+ public async Task LiteralInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, "literal", exception, formatter);
+
+ logger.Log(LogLevel.Debug, "literal");
+ logger.Log(LogLevel.Information, eventId, "literal");
+ logger.Log(LogLevel.Warning, exception, "literal");
+ logger.Log(LogLevel.Error, eventId, exception, "literal");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task LiteralInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}("literal");
+ logger.Log{{logLevel}}(eventId, "literal");
+ logger.Log{{logLevel}}(exception, "literal");
+ logger.Log{{logLevel}}(eventId, exception, "literal");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task LiteralInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel("literal");
+ logger.DynamicLogLevel(LogLevel.Debug, "literal");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task LocalInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ string local = "local";
+
+ logger.Log(LogLevel.Trace, eventId, local, exception, formatter);
+
+ logger.Log(LogLevel.Debug, local);
+ logger.Log(LogLevel.Information, eventId, local);
+ logger.Log(LogLevel.Warning, exception, local);
+ logger.Log(LogLevel.Error, eventId, exception, local);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task LocalInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ string local = "local";
+
+ logger.Log{{logLevel}}(local);
+ logger.Log{{logLevel}}(eventId, local);
+ logger.Log{{logLevel}}(exception, local);
+ logger.Log{{logLevel}}(eventId, exception, local);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task LocalInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ string local = "local";
+
+ logger.StaticLogLevel(local);
+ logger.DynamicLogLevel(LogLevel.Debug, local);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task FieldInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ private string _field;
+
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, _field, exception, formatter);
+
+ logger.Log(LogLevel.Debug, _field);
+ logger.Log(LogLevel.Information, eventId, _field);
+ logger.Log(LogLevel.Warning, exception, _field);
+ logger.Log(LogLevel.Error, eventId, exception, _field);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task FieldInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ private string _field;
+
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}(_field);
+ logger.Log{{logLevel}}(eventId, _field);
+ logger.Log{{logLevel}}(exception, _field);
+ logger.Log{{logLevel}}(eventId, exception, _field);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task FieldInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ private static string _field;
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(_field);
+ logger.DynamicLogLevel(LogLevel.Debug, _field);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task PropertyInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ public string Property { get; set; }
+
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, Property, exception, formatter);
+
+ logger.Log(LogLevel.Debug, Property);
+ logger.Log(LogLevel.Information, eventId, Property);
+ logger.Log(LogLevel.Warning, exception, Property);
+ logger.Log(LogLevel.Error, eventId, exception, Property);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task PropertyInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ public string Property { get; set; }
+
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}(Property);
+ logger.Log{{logLevel}}(eventId, Property);
+ logger.Log{{logLevel}}(exception, Property);
+ logger.Log{{logLevel}}(eventId, exception, Property);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task PropertyInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ public static string Property { get; set; }
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(Property);
+ logger.DynamicLogLevel(LogLevel.Debug, Property);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IndexerInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using System.Collections.Generic;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ private Dictionary _messages;
+
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, _messages[LogLevel.Trace], exception, formatter);
+
+ logger.Log(LogLevel.Debug, _messages[LogLevel.Debug]);
+ logger.Log(LogLevel.Information, eventId, _messages[LogLevel.Information]);
+ logger.Log(LogLevel.Warning, exception, _messages[LogLevel.Warning]);
+ logger.Log(LogLevel.Error, eventId, exception, _messages[LogLevel.Error]);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task IndexerInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using System.Collections.Generic;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ private Dictionary _messages;
+
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}(_messages[LogLevel.{{logLevel}}]);
+ logger.Log{{logLevel}}(eventId, _messages[LogLevel.{{logLevel}}]);
+ logger.Log{{logLevel}}(exception, _messages[LogLevel.{{logLevel}}]);
+ logger.Log{{logLevel}}(eventId, exception, _messages[LogLevel.{{logLevel}}]);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IndexerInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using System.Collections.Generic;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ private static Dictionary _messages;
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(_messages[LogLevel.Information]);
+ logger.DynamicLogLevel(LogLevel.Debug, _messages[LogLevel.Debug]);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task ArrayIndexerInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ private string[] _messages;
+
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, _messages[0], exception, formatter);
+
+ logger.Log(LogLevel.Debug, _messages[0]);
+ logger.Log(LogLevel.Information, eventId, _messages[0]);
+ logger.Log(LogLevel.Warning, exception, _messages[0]);
+ logger.Log(LogLevel.Error, eventId, exception, _messages[0]);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task ArrayIndexerInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ private string[] _messages;
+
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}(_messages[0]);
+ logger.Log{{logLevel}}(eventId, _messages[0]);
+ logger.Log{{logLevel}}(exception, _messages[0]);
+ logger.Log{{logLevel}}(eventId, exception, _messages[0]);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task ArrayIndexerInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ private static string[] _messages;
+
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(_messages[0]);
+ logger.DynamicLogLevel(LogLevel.Debug, _messages[0]);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task ConditionalAccessInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception? exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, exception?.Message, exception, formatter);
+
+ logger.Log(LogLevel.Debug, exception?.Message);
+ logger.Log(LogLevel.Information, eventId, exception?.Message);
+ logger.Log(LogLevel.Warning, exception, exception?.Message);
+ logger.Log(LogLevel.Error, eventId, exception, exception?.Message);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task ConditionalAccessInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception? exception)
+ {
+ logger.Log{{logLevel}}(exception?.Message);
+ logger.Log{{logLevel}}(eventId, exception?.Message);
+ logger.Log{{logLevel}}(exception, exception?.Message);
+ logger.Log{{logLevel}}(eventId, exception, exception?.Message);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task ConditionalAccessInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string? argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string? argument);
+
+ static void M(ILogger logger, Exception? exception)
+ {
+ logger.StaticLogLevel(exception?.Message);
+ logger.DynamicLogLevel(LogLevel.Debug, exception?.Message);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task BinaryOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, 4 + 2, exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task BinaryOperationInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}("a" + "b");
+ logger.Log{{logLevel}}(eventId, "a" + "b");
+ logger.Log{{logLevel}}(exception, "a" + "b");
+ logger.Log{{logLevel}}(eventId, exception, "a" + "b");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task BinaryOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel("a" + "b");
+ logger.DynamicLogLevel(LogLevel.Debug, "a" + "b");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task CoalesceOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception? exception, Func formatter, string? message)
+ {
+ logger.Log(LogLevel.Trace, eventId, message ?? "null", exception, formatter);
+
+ logger.Log(LogLevel.Debug, message ?? "null");
+ logger.Log(LogLevel.Information, eventId, message ?? "null");
+ logger.Log(LogLevel.Warning, exception, message ?? "null");
+ logger.Log(LogLevel.Error, eventId, exception, message ?? "null");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task CoalesceOperationInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception? exception, string? message)
+ {
+ logger.Log{{logLevel}}(message ?? "null");
+ logger.Log{{logLevel}}(eventId, message ?? "null");
+ logger.Log{{logLevel}}(exception, message ?? "null");
+ logger.Log{{logLevel}}(eventId, exception, message ?? "null");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task CoalesceOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger, string? message)
+ {
+ logger.StaticLogLevel(message ?? "null");
+ logger.DynamicLogLevel(LogLevel.Debug, message ?? "null");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task DefaultValueOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, default, exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task DefaultValueOperationInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}(default);
+ logger.Log{{logLevel}}(eventId, default);
+ logger.Log{{logLevel}}(exception, default);
+ logger.Log{{logLevel}}(eventId, exception, default);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task DefaultValueOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(default);
+ logger.DynamicLogLevel(LogLevel.Debug, default);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IncrementOrDecrementOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter, int input)
+ {
+ logger.Log(LogLevel.Debug, eventId, input++, exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IncrementOrDecrementOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, int argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, int argument);
+
+ static void M(ILogger logger, int input)
+ {
+ logger.StaticLogLevel(input++);
+ logger.DynamicLogLevel(LogLevel.Debug, input++);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IsPatternOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception? exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, exception is not null, exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IsPatternOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ #nullable enable
+
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, bool argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, bool argument);
+
+ static void M(ILogger logger, Exception? exception)
+ {
+ logger.StaticLogLevel(exception is not null);
+ logger.DynamicLogLevel(LogLevel.Debug, exception is not null);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IsTypeOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter, object input)
+ {
+ logger.Log(LogLevel.Debug, eventId, input is Exception, exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task IsTypeOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, bool argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, bool argument);
+
+ static void M(ILogger logger, object input)
+ {
+ logger.StaticLogLevel(input is Exception);
+ logger.DynamicLogLevel(LogLevel.Debug, input is Exception);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task NameOfOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Trace, eventId, nameof(logger), exception, formatter);
+
+ logger.Log(LogLevel.Debug, nameof(logger));
+ logger.Log(LogLevel.Information, eventId, nameof(logger));
+ logger.Log(LogLevel.Warning, exception, nameof(logger));
+ logger.Log(LogLevel.Error, eventId, exception, nameof(logger));
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task NameOfOperationInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}(nameof(logger));
+ logger.Log{{logLevel}}(eventId, nameof(logger));
+ logger.Log{{logLevel}}(exception, nameof(logger));
+ logger.Log{{logLevel}}(eventId, exception, nameof(logger));
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task NameOfOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(nameof(logger));
+ logger.DynamicLogLevel(LogLevel.Debug, nameof(logger));
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task ObjectCreationOperationValueTypeInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, new TimeSpan(), exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task ObjectCreationOperationValueTypeInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, TimeSpan argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, TimeSpan argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(new TimeSpan());
+ logger.DynamicLogLevel(LogLevel.Debug, new TimeSpan());
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task SizeOfOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, sizeof(int), exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task SizeOfOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, int argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, int argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(sizeof(int));
+ logger.DynamicLogLevel(LogLevel.Debug, sizeof(int));
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task TypeOfOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, typeof(int), exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task TypeOfOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, Type argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, Type argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel(typeof(int));
+ logger.DynamicLogLevel(LogLevel.Debug, typeof(int));
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task UnaryOperationInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter, bool input)
+ {
+ logger.Log(LogLevel.Debug, eventId, !input, exception, formatter);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task UnaryOperationInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, bool argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, bool argument);
+
+ static void M(ILogger logger, bool input)
+ {
+ logger.StaticLogLevel(!input);
+ logger.DynamicLogLevel(LogLevel.Debug, !input);
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task InterpolatedStringOperationLiteralInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, $"literal", exception, formatter);
+
+ logger.Log(LogLevel.Debug, $"literal");
+ logger.Log(LogLevel.Information, eventId, $"literal");
+ logger.Log(LogLevel.Warning, exception, $"literal");
+ logger.Log(LogLevel.Error, eventId, exception, $"literal");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task InterpolatedStringOperationLiteralInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}($"literal");
+ logger.Log{{logLevel}}(eventId, $"literal");
+ logger.Log{{logLevel}}(exception, $"literal");
+ logger.Log{{logLevel}}(eventId, exception, $"literal");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task InterpolatedStringOperationLiteralInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel($"literal");
+ logger.DynamicLogLevel(LogLevel.Debug, $"literal");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task InterpolatedStringOperationConstantInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ const string constant = "constant";
+
+ logger.Log(LogLevel.Debug, eventId, $"{constant}", exception, formatter);
+
+ logger.Log(LogLevel.Debug, $"{constant}");
+ logger.Log(LogLevel.Information, eventId, $"{constant}");
+ logger.Log(LogLevel.Warning, exception, $"{constant}");
+ logger.Log(LogLevel.Error, eventId, exception, $"{constant}");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task InterpolatedStringOperationConstantInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ const string constant = "constant";
+
+ logger.Log{{logLevel}}($"{constant}");
+ logger.Log{{logLevel}}(eventId, $"{constant}");
+ logger.Log{{logLevel}}(exception, $"{constant}");
+ logger.Log{{logLevel}}(eventId, exception, $"{constant}");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task InterpolatedStringOperationConstantInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ const string constant = "constant";
+
+ logger.StaticLogLevel($"{constant}");
+ logger.DynamicLogLevel(LogLevel.Debug, $"{constant}");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task InterpolatedStringOperationNameOfInLog_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func formatter)
+ {
+ logger.Log(LogLevel.Debug, eventId, $"logger name: {nameof(logger)}", exception, formatter);
+
+ logger.Log(LogLevel.Debug, $"logger name: {nameof(logger)}");
+ logger.Log(LogLevel.Information, eventId, $"logger name: {nameof(logger)}");
+ logger.Log(LogLevel.Warning, exception, $"logger name: {nameof(logger)}");
+ logger.Log(LogLevel.Error, eventId, exception, $"logger name: {nameof(logger)}");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Theory]
+ [MemberData(nameof(LogLevels))]
+ public async Task InterpolatedStringOperationNameOfInLogNamed_NoDiagnostic_CS(string logLevel)
+ {
+ string source = $$"""
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception)
+ {
+ logger.Log{{logLevel}}($"logger name: {nameof(logger)}");
+ logger.Log{{logLevel}}(eventId, $"logger name: {nameof(logger)}");
+ logger.Log{{logLevel}}(exception, $"logger name: {nameof(logger)}");
+ logger.Log{{logLevel}}(eventId, exception, $"logger name: {nameof(logger)}");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task InterpolatedStringOperationNameOfInLoggerMessage_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ static partial class C
+ {
+ [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")]
+ static partial void StaticLogLevel(this ILogger logger, string argument);
+
+ [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")]
+ static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument);
+
+ static void M(ILogger logger)
+ {
+ logger.StaticLogLevel($"logger name: {nameof(logger)}");
+ logger.DynamicLogLevel(LogLevel.Debug, $"logger name: {nameof(logger)}");
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ [Fact]
+ public async Task OtherILoggerMethodCalled_NoDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger)
+ {
+ logger.BeginScope(ExpensiveMethodCall());
+ logger.BeginScope("Processing calculation result {CalculationResult}", ExpensiveMethodCall());
+ }
+
+ string ExpensiveMethodCall()
+ {
+ return "very expensive call";
+ }
+ }
+ """;
+
+ await VerifyCSharpDiagnosticAsync(source);
+ }
+
+ // Tests for operations that get flagged.
+
+ [Fact]
+ public async Task AnonymousObjectCreationOperation_ReportsDiagnostic_CS()
+ {
+ string source = """
+ using System;
+ using Microsoft.Extensions.Logging;
+
+ class C
+ {
+ void M(ILogger logger, EventId eventId, Exception exception, Func