Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 7 additions & 97 deletions docs/fundamentals/code-analysis/quality-rules/ca1873.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,117 +30,27 @@ In many situations, logging is disabled or set to a log level that results in an

## Rule description

When logging methods are called, their arguments are evaluated regardless of whether the logging level is enabled. This can result in expensive operations being executed even when the log message won't be written. For better performance, guard expensive logging calls with a check to <xref:Microsoft.Extensions.Logging.ILogger.IsEnabled%2A> or use the `LoggerMessage` pattern.
When logging methods are called, their arguments are evaluated regardless of whether the logging level is enabled. This can result in expensive operations being executed even when the log message won't be written. For better performance, guard expensive logging calls with a check to <xref:Microsoft.Extensions.Logging.ILogger.IsEnabled%2A> or use source-generated logging with <xref:Microsoft.Extensions.Logging.LoggerMessageAttribute>.

## How to fix violations

To fix a violation of this rule, use one of the following approaches:

- Guard the logging call with a check to <xref:Microsoft.Extensions.Logging.ILogger.IsEnabled%2A>.
- Use the `LoggerMessage` pattern with <xref:Microsoft.Extensions.Logging.LoggerMessageAttribute>.
- Use source-generated logging with <xref:Microsoft.Extensions.Logging.LoggerMessageAttribute>.
- Ensure expensive operations aren't performed in logging arguments unless necessary.

## Example

The following code snippet shows violations of CA1873:

```csharp
using Microsoft.Extensions.Logging;

class Example
{
private readonly ILogger _logger;

public Example(ILogger<Example> logger)
{
_logger = logger;
}

public void ProcessData(int[] data)
{
// Violation: expensive operation in logging argument.
_logger.LogDebug($"Processing {string.Join(", ", data)} items");

// Violation: object creation in logging argument.
_logger.LogTrace("Data: {Data}", new { Count = data.Length, Items = data });
}
}
```

```vb
Imports Microsoft.Extensions.Logging

Class Example
Private ReadOnly _logger As ILogger

Public Sub New(logger As ILogger(Of Example))
_logger = logger
End Sub

Public Sub ProcessData(data As Integer())
' Violation: expensive operation in logging argument.
_logger.LogDebug($"Processing {String.Join(", ", data)} items")

' Violation: object creation in logging argument.
_logger.LogTrace("Data: {Data}", New With {.Count = data.Length, .Items = data})
End Sub
End Class
```
:::code language="csharp" source="./snippets/ca1873/csharp/CA1873Example/Violation.cs" id="ViolationExample":::
:::code language="vb" source="./snippets/ca1873/vb/CA1873Example/Violation.vb" id="ViolationExample":::

The following code snippet fixes the violations:
The following code snippet fixes the violations by using source-generated logging:

```csharp
using Microsoft.Extensions.Logging;

class Example
{
private readonly ILogger _logger;

public Example(ILogger<Example> logger)
{
_logger = logger;
}

public void ProcessData(int[] data)
{
// Fixed: guard with IsEnabled check.
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug($"Processing {string.Join(", ", data)} items");
}

// Fixed: guard with IsEnabled check.
if (_logger.IsEnabled(LogLevel.Trace))
{
_logger.LogTrace("Data: {Data}", new { Count = data.Length, Items = data });
}
}
}
```

```vb
Imports Microsoft.Extensions.Logging

Class Example
Private ReadOnly _logger As ILogger

Public Sub New(logger As ILogger(Of Example))
_logger = logger
End Sub

Public Sub ProcessData(data As Integer())
' Fixed: guard with IsEnabled check.
If _logger.IsEnabled(LogLevel.Debug) Then
_logger.LogDebug($"Processing {String.Join(", ", data)} items")
End If

' Fixed: guard with IsEnabled check.
If _logger.IsEnabled(LogLevel.Trace) Then
_logger.LogTrace("Data: {Data}", New With {.Count = data.Length, .Items = data})
End If
End Sub
End Class
```
:::code language="csharp" source="./snippets/ca1873/csharp/CA1873Example/Fix.cs" id="FixExample":::
:::code language="vb" source="./snippets/ca1873/vb/CA1873Example/Fix.vb" id="FixExample":::

## When to suppress warnings

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Logging;

// <FixExample>
partial class FixExample
{
private readonly ILogger _logger;

public FixExample(ILogger<FixExample> logger)
{
_logger = logger;
}

public void ProcessData(int[] data)
{
// Fixed: use source-generated logging.
// The data array is passed directly; no expensive operation executed unless log level is enabled.
LogProcessingData(data);

// Fixed: use source-generated logging.
LogTraceData(data.Length, data);
}

[LoggerMessage(Level = LogLevel.Debug, Message = "Processing {Data} items")]
private partial void LogProcessingData(int[] data);

[LoggerMessage(Level = LogLevel.Trace, Message = "Data: Count={Count}, Items={Items}")]
private partial void LogTraceData(int count, int[] items);
}
// </FixExample>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.Extensions.Logging;

// <ViolationExample>
class ViolationExample
{
private readonly ILogger _logger;

public ViolationExample(ILogger<ViolationExample> logger)
{
_logger = logger;
}

public void ProcessData(int[] data)
{
// Violation: expensive operation in logging argument.
_logger.LogDebug($"Processing {string.Join(", ", data)} items");

// Violation: object creation in logging argument.
_logger.LogTrace("Data: {Data}", new { Count = data.Length, Items = data });
}
}
// </ViolationExample>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>CA1873Example</RootNamespace>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Imports Microsoft.Extensions.Logging

' <FixExample>
Partial Class FixExample
Private ReadOnly _logger As ILogger

Public Sub New(logger As ILogger(Of FixExample))
_logger = logger
End Sub

Public Sub ProcessData(data As Integer())
' Fixed: use source-generated logging.
' The data array is passed directly; no expensive operation executed unless log level is enabled.
LogProcessingData(data)

' Fixed: use source-generated logging.
LogTraceData(data.Length, data)
End Sub

<LoggerMessage(Level:=LogLevel.Debug, Message:="Processing {Data} items")>
Private Partial Sub LogProcessingData(data As Integer())
End Sub

<LoggerMessage(Level:=LogLevel.Trace, Message:="Data: Count={Count}, Items={Items}")>
Private Partial Sub LogTraceData(count As Integer, items As Integer())
End Sub
End Class
' </FixExample>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Imports System

Module Program
Sub Main(args As String())
Console.WriteLine("Hello World!")
End Sub
End Module
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Imports Microsoft.Extensions.Logging

' <ViolationExample>
Class ViolationExample
Private ReadOnly _logger As ILogger

Public Sub New(logger As ILogger(Of ViolationExample))
_logger = logger
End Sub

Public Sub ProcessData(data As Integer())
' Violation: expensive operation in logging argument.
_logger.LogDebug($"Processing {String.Join(", ", data)} items")

' Violation: object creation in logging argument.
_logger.LogTrace("Data: {Data}", New With {.Count = data.Length, .Items = data})
End Sub
End Class
' </ViolationExample>
Loading