Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
C#

C#: Fix a Source-Generated Logging Message That Still Evaluates Its Arguments Even When the Log Level Is Disabled

- 04.10.26 - ErcanOPAK

🔧 The Log Line That Ran Even When Nobody Was Listening

A source-generated logging method is supposed to skip the actual work of formatting a message entirely when its log level is disabled in configuration, which is the whole performance case for using it instead of a plain string interpolation. That guarantee covers the message template itself – it does not extend to whatever expression you pass in as an argument, because every argument is evaluated by the caller before the generated method is ever invoked. A seemingly innocent argument that calls a method, serializes an object, or walks a collection still runs in full on every single call, disabled log level or not, and the cost hides in code that looks like it should be free.

🔎 The Problem

public partial class OrderService
{
    [LoggerMessage(Level = LogLevel.Debug, Message = "Order snapshot: {Snapshot}")]
    partial void LogOrderSnapshot(string snapshot);

    public void Process(Order order)
    {
        // The generated method itself skips formatting when Debug
        // is disabled - but the argument expression right here is
        // evaluated by THIS line, before LogOrderSnapshot ever runs,
        // so SerializeFullSnapshot() executes on every single call
        // no matter what level is configured.
        LogOrderSnapshot(SerializeFullSnapshot(order));
    }

    string SerializeFullSnapshot(Order order)
    {
        // A full JSON serialization of a deep object graph - meant
        // to be a debug-only diagnostic, not something that runs on
        // every order in production with Debug logging turned off.
        return JsonSerializer.Serialize(order, DeepGraphOptions);
    }
}

✅ Fix: Keep Expensive Arguments Out of the Call Entirely

  • Guard an expensive argument expression with an explicit IsEnabled check before building it, so the serialization or formatting work only happens when the configured log level would actually use the result.
  • Pass the raw pieces the message template needs – an identifier, a count, a status – as separate structured arguments instead of pre-building one expensive composite string, letting the generated method’s own level check decide whether formatting ever happens.
  • Reserve a full object dump for a dedicated, clearly named diagnostic method gated by its own explicit check, rather than folding it into a routine log call that runs on every request regardless of the configured level.

⚠️ Why This Is Easy to Miss

  • The whole point of switching to source-generated logging is that the framework promises to skip unnecessary work, so it is easy to assume that promise covers everything inside the call, including the arguments, rather than just the formatting step after they arrive.
  • A local development environment usually runs with verbose logging enabled anyway, so the expensive argument runs there regardless of the guard – the actual waste only becomes visible once production runs with a quieter configured level.

A logging call that skips its own formatting still ran every argument you handed it first – the free log line was never free if what you passed it wasn’t.

— C# Best Practice

Related posts:

C#: Use Method Overloading for Multiple Signatures

C# Span & Memory — Zero-Allocation Performance Superpowers

String Interpolation, String Format, String Concat and String Builder in C#

Post Views: 0

Post navigation

Ajax: Fix a Fetch Call That Silently Ignores Your Abort Signal Because the Controller Was Created After the Request Already Started
Windows 11: Fix File Explorer Search That Returns Zero Results Even Though Indexing Shows as Complete

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Sep    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (947)
  • How to add default value for Entity Framework migrations for DateTime and Bool (938)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (836)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (624)
  • Add Constraint to SQL Table to ensure email contains @ (590)
  • Average of all values in a column that are not zero in SQL (553)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (526)
  • Find numbers with more than two decimal places in SQL (468)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps
  • Photoshop: Fix a Color Profile Mismatch That Makes Printed Output Look Nothing Like What You Saw On Screen
  • WordPress: Fix Search Results That Return Pages From a Theme You Deactivated Months Ago
  • Visual Studio: Fix a Test Project That Builds Fine Alone but Fails to Discover Any Tests After a NuGet Restore
  • ASP.NET Core: Fix a File Upload That Times Out on Slow Connections Only Because Kestrel’s Minimum Data Rate Feature Kicked In
  • JavaScript: Fix an Array Destructuring Default Value That Silently Never Applies Because null Was Passed Instead of Undefined

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (973)
  • How to make theater mode the default for Youtube (947)
  • How to add default value for Entity Framework migrations for DateTime and Bool (938)
  • Get the First and Last Word from a String or Sentence in SQL (847)
  • How to select distinct rows in a datatable in C# (836)

Recent Posts

  • CSS: Fix a prefers-color-scheme Media Query That Gets Silently Overridden by a Browser Extension’s Forced Dark Mode
  • Git: Fix a Merge Commit That Silently Drops a File Because Both Branches Deleted It Differently
  • HTML5: Fix a Native Lazy-Loading Image That Never Loads Because It Sits Inside a Hidden Tab Until the User Clicks It
  • The AI Prompt That Traces a Null Reference Exception Back to the Exact Line That First Produced the Null
  • The AI Prompt That Turns a Gym Membership Contract’s Fine Print Into a Plain-English List of Cancellation Steps

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com