-
Notifications
You must be signed in to change notification settings - Fork 470
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
Add CA1873: Avoid potentially expensive logging #7290
base: main
Are you sure you want to change the base?
Conversation
This analyzer detects calls to 'ILogger.Log', extension methods in 'Microsoft.Extensions.Logging.LoggerExtensions' and methods decorated with '[LoggerMessage]'. It then checks if they evaluate expensive arguments without checking if logging is enabled with 'ILogger.IsEnabled'.
...ers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
Outdated
Show resolved
Hide resolved
...ers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
Outdated
Show resolved
Hide resolved
...ers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
Outdated
Show resolved
Hide resolved
return AreInvocationsOnSameInstance(logInvocation, invocation) && IsSameLogLevel(invocation.Arguments[0]); | ||
} | ||
|
||
static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) |
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.
A variation of this is used in other analyzers. I think this could be a good candidate for another extension method.
[Fact] | ||
public async Task WrongLogLevelGuardedWorkInLog_ReportsDiagnostic_CS() | ||
{ | ||
string source = """ | ||
using System; | ||
using Microsoft.Extensions.Logging; | ||
|
||
class C | ||
{ | ||
void M(ILogger logger, EventId eventId, Exception exception, Func<string, Exception, string> formatter) | ||
{ | ||
if (logger.IsEnabled(LogLevel.Critical)) | ||
logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter); |
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.
I was wondering if we should report another diagnostic if the log level does not match, as this is most likely a bug.
[Fact] | ||
public async Task WrongInstanceGuardedWorkInLog_ReportsDiagnostic_CS() | ||
{ | ||
string source = """ | ||
using System; | ||
using Microsoft.Extensions.Logging; | ||
|
||
class C | ||
{ | ||
private ILogger _otherLogger; | ||
|
||
void M(ILogger logger, EventId eventId, Exception exception, Func<string, Exception, string> formatter) | ||
{ | ||
if (_otherLogger.IsEnabled(LogLevel.Trace)) | ||
logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter); |
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.
Another candidate for a diagnostic: Checking the wrong logger instance.
cc @stephentoub @Youssef1313 (thanks for providing the prototype 👍) |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7290 +/- ##
==========================================
+ Coverage 96.50% 96.54% +0.04%
==========================================
Files 1452 1454 +2
Lines 347566 352505 +4939
Branches 11416 11478 +62
==========================================
+ Hits 335413 340336 +4923
+ Misses 9257 9255 -2
- Partials 2896 2914 +18 |
Thanks. The LoggerExtensions.cs ones are all false positives in that we'll want to suppress them, but that's also effectively the implementation of logging rather than consumption of logging, and we frequently have to suppress rules in such implementations. The others for runtime look like valid diagnostics. |
...sts/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
Outdated
Show resolved
Hide resolved
...sts/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
Outdated
Show resolved
Hide resolved
...sts/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
Outdated
Show resolved
Hide resolved
@drewnoakes @stephentoub Sorry for the ping, but I just wanted to follow up and see if you might have time to review this PR or know who the right person would be. Thank you very much! |
or ILocalReferenceOperation | ||
or IParameterReferenceOperation) | ||
{ | ||
return false; |
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.
This is taking an approach of saying some specific things aren't expensive and everything else is. Should it be inverted to instead special-case the things we know to be expensive? I'm concerned otherwise that as the language evolves and more kinds of operations are introduced, more and more possibly inexpensive things could end up getting flagged here. We should be intentional about what kinds of operations are considered expensive.
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.
Yes, I agree. I will change this to be inverted (to be future proof) and will also update what is considered expensive based on your other comments. Thanks!
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.
See 696f73e.
...ers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
Outdated
Show resolved
Hide resolved
...ers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs
Outdated
Show resolved
Hide resolved
...sts/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
Outdated
Show resolved
Hide resolved
...sts/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
Outdated
Show resolved
Hide resolved
...sts/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs
Outdated
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.
Thank you for working on this.
@jeffhandley, is there someone who can help shepherd this?
Previously, some specific operations were treated as non-expensive and the rest were. This is not really future-proof as the language evolves, and is generally more prone to false positives. So this has now been changed to the opposite: Treat certain operations as expensive, and the rest are not expensive by default and do not trigger a diagnostic.
@stephentoub Thanks for the review, I rewrote the logic to find expensive argument evaluations like you suggested: Focus on the known expensive ones and treat the rest as non-expensive (to avoid false positives). Detection of boxing operations and creation of implicit non-empty params arrays should now also work properly. I also allowed Found diagnostics also seem better now, especially in ca1873-runtime-2.txt |
Thanks. I randomly sampled some of the cited hits. Why is this one getting triggered, for example?
It appears to be properly guarded. |
This was due to not comparing symbols correctly (using W:\aspnetcore\src\Security\Authentication\OpenIdConnect\src\OpenIdConnectHandler.cs(1001,33): info CA1873: Evaluation of this argument may be expensive and unnecessary if logging is disabled
W:\aspnetcore\src\Security\Authentication\OpenIdConnect\src\OpenIdConnectHandler.cs(1005,33): info CA1873: Evaluation of this argument may be expensive and unnecessary if logging is disabled This should have been flagged by |
Thanks.
|
Thanks for checking, I've added the early exit (i.e. before the logging invocation) as a recognized guard pattern. For this to work, I am now searching each ancestor block of the logging invocation for appropriate guard conditionals. Re-running all tests leads to exactly the same findings, except the false positive in your last comment. |
@sharwell, could you review this as well? Thanks. |
Fixes dotnet/runtime#78402.
This analyzer detects
ILogger.Log
,Microsoft.Extensions.Logging.LoggerExtensions
and[LoggerMessage]
and flags them if they evaluate expensive arguments without checking if logging is enabled with
ILogger.IsEnabled
.There are 12 findings in
dotnet/runtime
(8 of them inMicrosoft.Extensions.Logging.Abstractions
which will probably disable this warning), 157 findings indotnet/roslyn
and 497 indotnet/aspnetcore
(including testing code).I skimmed through them and could not find any false positives.