{"id":56825,"date":"2025-05-29T10:05:00","date_gmt":"2025-05-29T17:05:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=56825"},"modified":"2025-05-29T10:05:00","modified_gmt":"2025-05-29T17:05:00","slug":"finetune-the-volume-of-logs-your-app-produces","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/finetune-the-volume-of-logs-your-app-produces\/","title":{"rendered":"Fine-tune the volume of logs your app produces"},"content":{"rendered":"<p>If you&#8217;re running a production application, you know the challenges of managing logs. Too few logs and you&#8217;re flying blind; too many and you&#8217;re drowning in data and paying excessive storage costs. It&#8217;s a classic observability dilemma &#8211; you want comprehensive information when things go wrong, but you don&#8217;t want to store every detail from your happy paths.<\/p>\n<p>Enter <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/extensions\/log-sampling\">log sampling<\/a> in .NET &#8211; a powerful capability that lets you strategically reduce log volume while maintaining observability. Unlike simple log filtering which uses binary decisions (emit or don&#8217;t emit logs), sampling gives you fine-grained control, letting you emit a precise percentage of logs from different parts of your application.<\/p>\n<h2>Get started<\/h2>\n<p>To get started, install the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Telemetry\">\ud83d\udce6 Microsoft.Extensions.Telemetry<\/a> NuGet package:<\/p>\n<pre><code class=\"language-console\">dotnet add package Microsoft.Extensions.Telemetry<\/code><\/pre>\n<p>or add it directly in the C# project file:<\/p>\n<pre><code class=\"language-xml\">&lt;ItemGroup&gt;\n  &lt;PackageReference Include=\"Microsoft.Extensions.Telemetry\"\n                    Version=\"*\" \/&gt;\n&lt;\/ItemGroup&gt;<\/code><\/pre>\n<h2>The challenge of logging at scale<\/h2>\n<p>As applications grow in complexity and traffic, logging strategies that worked well during development can quickly become problematic in production environments. Consider these common scenarios:<\/p>\n<ul>\n<li>Your application emits thousands of information-level logs per second, most providing redundant details about normal operations<\/li>\n<li>Your cloud logging costs increase dramatically as your user base grows<\/li>\n<li>During incidents, your ability to troubleshoot is hindered by a flood of routine log messages<\/li>\n<li>Latency increases as your application spends significant resources generating logs<\/li>\n<\/ul>\n<p>Traditional approaches to these problems often involve simply turning off lower-level logs in production &#8211; but this creates blind spots. What if you could instead keep the same logging instrumentation but intelligently sample those logs?<\/p>\n<h2>Log sampling to the rescue<\/h2>\n<p>Log sampling extends <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/extensions\/logging#configure-logging-with-code\">filtering capabilities<\/a> by giving you more fine-grained control over which logs are emitted. Instead of enabling or disabling entire categories of logs, sampling lets you emit a controlled percentage of them.<\/p>\n<p>For example, while filtering typically uses probabilities like <code>0<\/code> (emit no logs) or <code>1<\/code> (emit all logs), sampling lets you choose any value in between, such as <code>0.1<\/code> to emit 10% of logs, or <code>0.25<\/code> to emit 25%.<\/p>\n<p>.NET provides several sampling strategies out of the box:<\/p>\n<ol>\n<li>Random probabilistic sampling: Sample logs based on configured probability rules<\/li>\n<li>Trace-based sampling: Sample logs based on the sampling decision of the current trace<\/li>\n<li>Custom sampling: Implement your own sampling strategy<\/li>\n<\/ol>\n<p>Let&#8217;s walk through how to implement each approach.<\/p>\n<h3>Random probabilistic sampling: The simplest approach<\/h3>\n<p>Random probabilistic sampling is usually the easiest place to start. It allows you to define sampling rules based on log category, log level, or event ID.<\/p>\n<p>For basic scenarios, you can configure a single probability value:<\/p>\n<pre><code class=\"language-csharp\">builder.Logging.AddRandomProbabilisticSampler(0.01, LogLevel.Information);<\/code><\/pre>\n<p>This configuration samples 1% of Information logs.<\/p>\n<p>For more complex scenarios, you can define specific sampling rules:<\/p>\n<pre><code class=\"language-csharp\">builder.Logging.AddRandomProbabilisticSampler(options =&gt;\n{\n    \/\/ Sample 5% of logs with event ID 1001 across all categories\n    options.Rules.Add(\n        new RandomProbabilisticSamplerFilterRule(\n            probability: 0.05d,\n            eventId : 1001));\n});<\/code><\/pre>\n<h4>Change the sampling rate dynamically<\/h4>\n<p>Random probabilistic sampling supports runtime configuration updates via the &lt;xref:Microsoft.Extensions.Options.IOptionsMonitor%601&gt; interface. If you&#8217;re using a configuration provider that supports reloads\u2014such as the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/extensions\/configuration-providers#file-configuration-provider\">File Configuration Provider<\/a> &#8211; you can update sampling rules at runtime without restarting the application. For example, depending on how your application is currently behaving in production, you can increase sampling rate or even enable sampling for all logs in case you need more logs to help with diagnostics. After diagnostics and troubleshooting, you can revert to the original sampling rate &#8211; again, all without restarting the application!<\/p>\n<h3>Trace-based sampling: Keeping logs and traces in sync<\/h3>\n<p>When using distributed tracing, it&#8217;s often valuable to ensure that logs and traces are sampled consistently. Trace-based sampling ensures that logs are only emitted if the underlying trace is being recorded:<\/p>\n<pre><code class=\"language-csharp\">builder.Logging.AddTraceBasedSampler();<\/code><\/pre>\n<p>This approach is particularly useful in microservice architectures where you want to maintain correlation between traces and logs.<\/p>\n<h3>Going fancy: Implementing your own sampling strategy<\/h3>\n<p>For specialized requirements, you can create a custom sampler by deriving from the  abstract class.\nFor example, this is how a rate limiting sampler allowing no more than 1 log record per second might look like:<\/p>\n<pre><code class=\"language-csharp\">internal sealed class RateLimitingSampler : LoggingSampler\n{\n    private readonly Stopwatch stopwatch = Stopwatch.StartNew();\n    private readonly long maxBalance = Stopwatch.Frequency;\n    private long currentBalance;\n\n    public RateLimitingSampler()\n    {\n        currentBalance = stopwatch.ElapsedTicks - maxBalance;\n    }\n    public override bool ShouldSample&lt;TState&gt;(in LogEntry&lt;TState&gt; logEntry)\n    {\n        long currentTicks;\n        long currentBalanceTicks;\n        long availableBalanceAfterWithdrawal;\n        do\n        {\n            currentBalanceTicks = Interlocked.Read(ref currentBalance);\n            currentTicks = stopwatch.ElapsedTicks;\n            var currentAvailableBalance = currentTicks - currentBalanceTicks;\n            if (currentAvailableBalance &gt; maxBalance)\n            {\n                currentAvailableBalance = maxBalance;\n            }\n\n            availableBalanceAfterWithdrawal = currentAvailableBalance - Stopwatch.Frequency;\n            if (availableBalanceAfterWithdrawal &lt; 0)\n            {\n                return false;\n            }\n        }\n\n        while (Interlocked.CompareExchange(ref currentBalance, currentTicks - availableBalanceAfterWithdrawal, currentBalanceTicks) != currentBalanceTicks);\n        return true;\n    }\n}<\/code><\/pre>\n<p>Register it:<\/p>\n<pre><code class=\"language-csharp\">builder.Logging..AddSampler&lt;RateLimitingSampler&gt;();<\/code><\/pre>\n<p>And voil\u00e0! You have a custom log sampler that allows only one log record per second!<\/p>\n<h2>Performance considerations<\/h2>\n<p>The sampling logic introduces a minimal amount of CPU overhead in the application. However, whenever a specific log record is eliminated, it saves CPU cycles, transmission costs, storage costs.\nWe expect that for most situations, the savings or costs will be in the noise. But as always for performance issues, it&#8217;s always safer to measure and ensure there are no negative effects.<\/p>\n<h2>Summary<\/h2>\n<p>Log sampling gives you a powerful middle path between capturing every log (expensive) and capturing only high-level logs (insufficient detail). By strategically reducing the volume of routine logs while ensuring critical information is always captured, you can significantly reduce storage costs without sacrificing observability.<\/p>\n<p>Whether you use random sampling, trace-based sampling, or a custom implementation, the ability to fine-tune your logging volume is a valuable tool in your observability toolkit.<\/p>\n<h2>References<\/h2>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/extensions\/log-sampling\">Log Sampling Documentation<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/open-telemetry\/opentelemetry-dotnet-contrib\/blob\/eddf6c55126a980beda4ff535a9e4c228c4fec29\/src\/OpenTelemetry.Extensions\/Internal\/RateLimiter.cs\">OpenTelemetry .NET RateLimiter<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Explores the new log sampling feature<\/p>\n","protected":false},"author":190322,"featured_media":56826,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7699],"tags":[7701,7530],"class_list":["post-56825","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-dotnet-fundamentals","tag-dotnet-8","tag-logging"],"acf":[],"blog_post_summary":"<p>Explores the new log sampling feature<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/56825","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/190322"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=56825"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/56825\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/56826"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=56825"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=56825"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=56825"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}