<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Nicholas Blumhardt</title>
    <description>The static HTML of the http://nblumhardt.com site</description>
    <link>https://nblumhardt.com/</link>
    <atom:link href="https://nblumhardt.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 09 Dec 2025 03:08:11 +0000</pubDate>
    <lastBuildDate>Tue, 09 Dec 2025 03:08:11 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Trace sampling in SerilogTracing</title>
        <description>&lt;p&gt;Sampling in &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt; is taking shape. If you haven’t heard of SerilogTracing, that’s not surprising - it’s a brand new project &lt;a href=&quot;https://nblumhardt.com/2024/01/serilog-tracing/&quot;&gt;launched in January 2024&lt;/a&gt;. But, we’ve reached 250k downloads of the &lt;a href=&quot;https://www.nuget.org/packages/SerilogTracing/&quot;&gt;core package&lt;/a&gt;, and we’re still picking up steam. 😎&lt;/p&gt;

&lt;p&gt;This post takes a look at the sampling functionality that’s arrived in SerilogTracing 2.2.&lt;/p&gt;

&lt;h2 id=&quot;trace-sampling&quot;&gt;Trace sampling&lt;/h2&gt;

&lt;p&gt;There are a few things we can do to reduce the volume of structured log and trace data an application generates. We can reduce the granularity of events, reduce event size, and conditionally switch events on and off using leveling.&lt;/p&gt;

&lt;p&gt;Sampling is another popular option: if enough data is being generated, collecting only some portion of it can still provide reasonable visibility. Because it’s externally-controllable using standardized HTTP headers, sampling is known for being especially effective in situations where the source applications can’t otherwise be modified.&lt;/p&gt;

&lt;p&gt;The first thing to understand about sampling in SerilogTracing is that it’s &lt;em&gt;trace sampling&lt;/em&gt;: a trace is a collection of spans, and sampling aims to record either &lt;strong&gt;all spans&lt;/strong&gt; in a trace, or &lt;strong&gt;none of them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-10-sampling-in-serilog-tracing/example-trace-crossing-service-boundaries.png&quot; alt=&quot;An example distributed trace, crossing service boundaries&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Why not (also) sample spans?&lt;/p&gt;

&lt;p&gt;First, turning individual spans on and off using sampling would cause operations executing the same sequence of steps to have traces with &lt;em&gt;different shapes&lt;/em&gt;. When you’re troubleshooting using tracing, comparing the overall shape of two traces should reveal whether each follwed the same sequence of steps - and sampling individual spans prevents this.&lt;/p&gt;

&lt;p&gt;Second, a span on its own isn’t as interesting as a span in the context of its parent and child spans. By sampling individual spans, recorded spans would often be the &lt;em&gt;only&lt;/em&gt; recorded span in their containing trace, and at that point a lot of their value would have been lost.&lt;/p&gt;

&lt;h2 id=&quot;sampling-in-a-monolith&quot;&gt;Sampling in a monolith&lt;/h2&gt;

&lt;p&gt;If we first look at tracing within a monolith or single service, we can see how this plays out. I prefer self-contained code samples over realistic ones, so to illustrate the basic sampling API we’ll run &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing/blob/v2.2.0/example/Sampling/Program.cs&quot;&gt;this program&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTracing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTracing.Expressions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// The custom formatter here is provided by SerilogTracing.Expressions.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Formatters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateConsoleTextFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Set up SerilogTracing with the default options.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// The `outer` activity is the root of each trace the program generates.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Outer {i}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// The `inner` activity is a child of `outer`.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Inner {i}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The program produces this output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; dotnet run --project example/Sampling/Sampling.csproj
[09:41:44 INF] ├ Inner 0 (102.677 ms)
[09:41:44 INF] └─ Outer 0 (127.013 ms)
[09:41:44 INF] ├ Inner 1 (101.166 ms)
[09:41:44 INF] └─ Outer 1 (101.259 ms)
[09:41:44 INF] ├ Inner 2 (100.795 ms)
[09:41:44 INF] └─ Outer 2 (100.879 ms)
[09:41:44 INF] ├ Inner 3 (101.062 ms)
[09:41:44 INF] └─ Outer 3 (101.169 ms)
^C
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that when tracing to the console, spans appear in the order that they complete, which is the opposite of what you’d see in a tracing tool. It’s not an ideal presentation, but it’s easier to get multiple example traces into a compact format this way.&lt;/p&gt;

&lt;p&gt;The program isn’t currently using sampling: all of the traces are recorded.&lt;/p&gt;

&lt;p&gt;Now, let’s add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.OneTraceIn(7)&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListenerConfiguration&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OneTraceIn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running again:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; dotnet run --project example/Sampling/Sampling.csproj
[09:42:56 INF] ├ Inner 0 (102.621 ms)
[09:42:56 INF] └─ Outer 0 (125.868 ms)
[09:42:56 INF] ├ Inner 7 (100.233 ms)
[09:42:56 INF] └─ Outer 7 (100.703 ms)
[09:42:57 INF] ├ Inner 14 (101.172 ms)
[09:42:57 INF] └─ Outer 14 (101.462 ms)
^C
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, every seventh trace is now recorded. The child &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inner&lt;/code&gt; activity is always recorded if its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outer&lt;/code&gt; parent is — so we see one in seven complete traces.&lt;/p&gt;

&lt;p&gt;That’s just about all there is to say about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.OneTraceIn()&lt;/code&gt;; it’s intentionally the simplest possible strategy, serving as an example to build from, and an entrypoint to exploring the SerilogTracing API.&lt;/p&gt;

&lt;h2 id=&quot;sampling-across-services&quot;&gt;Sampling across services&lt;/h2&gt;

&lt;p&gt;Sampling gets more interesting when multiple services are involved. The first service involved in an operation might apply a sampler such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.OneTraceIn()&lt;/code&gt;, but when it calls out to other services, how do they know that any spans they generate will be part of a sampled trace?&lt;/p&gt;

&lt;h3 id=&quot;propagating-sampling-flags&quot;&gt;Propagating sampling flags&lt;/h3&gt;

&lt;p&gt;Along with the trace and span ids of the calling span, the &lt;a href=&quot;https://www.w3.org/TR/trace-context/#traceparent-header&quot;&gt;W3C &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceparent&lt;/code&gt; HTTP header&lt;/a&gt; carries sampling information from client to server in its final two hexadecimal digits:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this example from the spec, the trailing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-00&lt;/code&gt; means that the parent is not being recorded.&lt;/p&gt;

&lt;p&gt;The alternative &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-01&lt;/code&gt;, indicates that the parent has been chosen for recording:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a curious system because headers often come from untrusted sources. Should an application or service receiving &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceparent&lt;/code&gt; respect the caller’s sampling decision?&lt;/p&gt;

&lt;h3 id=&quot;public-and-internet-facing-endpoints&quot;&gt;Public and internet-facing endpoints&lt;/h3&gt;

&lt;p&gt;If the service is public or internet-facing then the answer is an obvious “no”: a malicious actor could use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-00&lt;/code&gt; sampling flags to turn off recording of their nefarious activities. Or, an external/third-party caller that always records all traces could consume excess storage by propagating its sampling decision with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-01&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By default, SerilogTracing’s ASP.NET Core integration assumes that the caller is untrusted, and sampling decisions by external callers are ignored.&lt;/p&gt;

&lt;p&gt;SerilogTracing &lt;em&gt;will&lt;/em&gt; pick up the trace and parent span ids from callers, however, so you’ll still see that they’re present unless you disable them entirely using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IncomingTraceParent.Ignore&lt;/code&gt;, which is the cleanest option for public or internet-facing services:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instrument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AspNetCoreRequests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class=&quot;c1&quot;&gt;// Ignore all tracing information provided by callers - start a new trace for&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// each request, and apply local sampling policies (if any).&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IncomingTraceParent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IncomingTraceParent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Ignore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;internal-endpoints&quot;&gt;Internal endpoints&lt;/h3&gt;

&lt;p&gt;Conversely, if you’re “inside the firewall” and want sampling decisions to propagate between your services (the whole point, really), then you’ll need to trust the sampling decision made in the incoming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceparent&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instrument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AspNetCoreRequests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Accept trace and span ids, and sampling decisions provided by callers,&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// overriding local sampling decisions.&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IncomingTraceParent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IncomingTraceParent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Trust&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you choose to trust the incoming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceparent&lt;/code&gt; sampling decision, this will override any decisions made locally by sampling policies like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.AllTraces()&lt;/code&gt;, for activities with remote parents.&lt;/p&gt;

&lt;h2 id=&quot;sampling-algorithms&quot;&gt;Sampling algorithms&lt;/h2&gt;

&lt;p&gt;Out of the box, SerilogTracing provides &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.AllTraces()&lt;/code&gt; (the default), &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.OneTraceIn()&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.Using()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The argument accepted by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample.Using()&lt;/code&gt; is a .NET &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener&lt;/code&gt; sampling delegate, which you can learn a bit more about in &lt;a href=&quot;https://nblumhardt.com/2024/10/activity-listener-sampling/&quot;&gt;this brief write-up&lt;/a&gt;, and use to construct custom sampling algorithms of your own.&lt;/p&gt;

&lt;p&gt;Many more pre-built algorithms are possible, and we’re hoping to collect examples of these to share more widely in the community: please &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing/issues&quot;&gt;open a ticket&lt;/a&gt; if you have one to request or contribute.&lt;/p&gt;
</description>
        <pubDate>Tue, 15 Oct 2024 00:12:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/10/sampling-in-serilog-tracing/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/10/sampling-in-serilog-tracing/</guid>
        
        
        <category>Serilog</category>
        
        <category>Tracing</category>
        
      </item>
    
      <item>
        <title>.NET&apos;s ActivityListener sampling API</title>
        <description>&lt;p&gt;Distributed tracing can generate a lot of data, and &lt;a href=&quot;https://rakyll.medium.com/sampling-in-observability-db0142cdda5b&quot;&gt;sampling&lt;/a&gt; is the most established method to keep data volumes manageable. In .NET, the &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitylistener&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Diagnostics.ActivityListener&lt;/code&gt;&lt;/a&gt; class exposes two properties to control sampling: &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitylistener.sample&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitylistener.sampleusingparentid&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How do these work?&lt;/p&gt;

&lt;p&gt;If you just switched browser tabs to check the documentation, I know why you’re back. There’s barely any useful information to be found about these, either in the .NET documentation or elsewhere online. Even the &lt;a href=&quot;https://github.com/dotnet/designs/blob/main/accepted/2020/diagnostics/activity-improvements.md&quot;&gt;original API design proposal&lt;/a&gt; is a dead end. The canonical &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener&lt;/code&gt; example, copied everywhere, includes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; implementation that’s something like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sample&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivityCreationOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ActivityContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
                                        &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You see, you &lt;strong&gt;need&lt;/strong&gt; to specify a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; function when creating an activity listener. The default sampling decision is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.None&lt;/code&gt;, and if all registered &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener&lt;/code&gt;s return this value, no activities will be created at all.&lt;/p&gt;

&lt;p&gt;If you want to use the sampling function to do something more sophisticated than simply capture all traces, it’s assumed you’ll plug in the OpenTelemetry SDK and use its sampling APIs to achieve this, and there really isn’t any guidance out there otherwise. Depending on your circumstances, the OpenTelemetry SDK might be the right tool for the job, but it’s still deeply unsatisfying to rely on a core .NET diagnostics API that’s practically undocumented.&lt;/p&gt;

&lt;p&gt;This year I’ve spent some time bridging &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Diagnostics.Activity&lt;/code&gt; and Serilog, and in the process had to dig deeper into how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener&lt;/code&gt; sampling works. Here are my conclusions, wrapped up in a tiny but non-trivial sampler. I’m fully aware that some of my conclusions and assumptions might be wrong; if you’re kind enough to send corrections I’ll make sure this article is updated.&lt;/p&gt;

&lt;h2 id=&quot;intervalsampler&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalSampler&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The sampler presented here is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalSampler&lt;/code&gt;. Its source code &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing/blob/dev/example/Sampling/Program.cs&quot;&gt;lives in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SerilogTracing&lt;/code&gt; example project&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntervalSampler&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SampleActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ActivityContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ulong&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ArgumentOutOfRangeException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ThrowIfZero&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivityCreationOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ActivityContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TraceFlags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivityTraceFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Recorded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt;
                                                    &lt;span class=&quot;n&quot;&gt;ActivityTraceFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Recorded&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllDataAndRecorded&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsRemote&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PropagationData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                        &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Interlocked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllDataAndRecorded&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PropagationData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalSampler&lt;/code&gt; aims to collect one in every &lt;em&gt;N&lt;/em&gt; possible traces (the “interval”), selected using modulo arithmetic. A more robust sampler might introduce some randomness into this process to avoid skewing the sample when an application produces the same types of traces in a very regular sequence, but those kinds of details would obscure the parts of the sampler that are important for our current purposes.&lt;/p&gt;

&lt;p&gt;The sampler creates a sampling function that is wired up like so:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sample&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IntervalSampler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;sample-vs-sampleusingparentid&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The first thing you’ll encounter when setting up a sampler are the apparent duplication of the sampling function between&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener.Sample&lt;/code&gt;, which describes the parent of the sampled activity using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityContext&lt;/code&gt;, and&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener.SampleUsingParentId&lt;/code&gt;, which describes the parent using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SampleActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SampleUsingParentId&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SampleActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ActivityContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Sample&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both APIs were added in .NET 5, so one isn’t an obsolete alternative to the other. When should each be used?&lt;/p&gt;

&lt;p&gt;It turns out that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt; supports both W3C and Microsoft’s legacy “hierarchical” tracing schemes. If a listener has both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; configured, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt; will be used. Otherwise, if the activity is using the W3C tracing scheme, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; will be used.&lt;/p&gt;

&lt;p&gt;So this suggests &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt; is the best, most general thing to implement? No, not really. Non-W3C tracing is on its way to extinction, and within &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt; you can’t directly access the modern, fundamental properties describing the parent activity, such as its trace id, span id, or trace flags.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalSampler&lt;/code&gt; supports the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; delegate signature:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivityCreationOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ActivityContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TL;DR: unless you’re writing code that has to work in a legacy tracing scheme, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; is the way to go, and you can safely ignore &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SampleUsingParentId&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;sampling-traces-vs-sampling-activities&quot;&gt;Sampling traces, vs sampling activities&lt;/h2&gt;

&lt;p&gt;The next thing to confront is the subtle difference between the purpose of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sample&lt;/code&gt; API — to determine whether or not to create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; — and the reason that you’re interested in it, which is to determine whether the &lt;em&gt;trace&lt;/em&gt; to which the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; belongs should be recorded.&lt;/p&gt;

&lt;p&gt;An &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; is just one single span within a hierarchical trace. Sampling generally aims to either create &lt;em&gt;all&lt;/em&gt; of the spans in a trace, or &lt;em&gt;none&lt;/em&gt; of them. Once a decision has been made for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; corresponding to the root span in a trace, then all of its child activities should be included in the sample, too.&lt;/p&gt;

&lt;p&gt;That’s what the first condition in our sampling delegate is concerned with:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TraceFlags&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivityTraceFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Recorded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; 
                                        &lt;span class=&quot;n&quot;&gt;ActivityTraceFlags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Recorded&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllDataAndRecorded&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsRemote&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PropagationData&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the activity we’re being asked to make a decision about would become the child of an existing activity, then we use the sampling decision already made for that activity.&lt;/p&gt;

&lt;p&gt;If the parent activity is recorded (included in the sample), then the child is too, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.AllDataAndRecorded&lt;/code&gt; is the correct result.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Take care:&lt;/strong&gt; the very similarly-named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.AllData&lt;/code&gt; causes an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; to be created, but it &lt;em&gt;doesn’t&lt;/em&gt; mark the trace as being recorded. If you return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.AllData&lt;/code&gt; from your sampler, activities likely won’t show up in your tracing system, and the sampling decision won’t be propagated downstream to other services and systems you call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the case that the parent isn’t included in the sample, we return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.PropagationData&lt;/code&gt; to ensure a local activity is still created when the parent is remote, and otherwise return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.None&lt;/code&gt; to save allocation of a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; instance.&lt;/p&gt;

&lt;h2 id=&quot;at-the-root-of-the-trace&quot;&gt;At the root of the trace&lt;/h2&gt;

&lt;p&gt;The next, and final part of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalSampler&lt;/code&gt;, is concerned with root activities. These don’t have a parent, so when we make a sampling decision for them, we’re really making a decision about the whole trace: this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt;, and its (potential) future children.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Interlocked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AllDataAndRecorded&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ActivitySamplingResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PropagationData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s why, when an activity isn’t included in the sample, we return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.PropagationData&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.None&lt;/code&gt;. If we returned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.None&lt;/code&gt;, no activity would be created, and so later on we’d have no way to remember our decision when looking at more deeply-nested activities. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySamplingResult.PropagationData&lt;/code&gt; option does cause creation of an activity, but it’ll be marked in such a way that only minimal processing is performed on it, and it will ultimately be discarded.&lt;/p&gt;

&lt;h2 id=&quot;so-there-you-have-it&quot;&gt;So there you have it&lt;/h2&gt;

&lt;p&gt;Hopefully the information here helps you to skip some of the digging I’ve had to do, and sheds some light on what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener.Sample&lt;/code&gt; is all about. Corrections and errata welcome - and if you spot other examples or documentation surrounding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivityListener.Sample&lt;/code&gt; that I’ve missed, I’d love to know about those, too.&lt;/p&gt;

&lt;p&gt;Happy tracing! 👋&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;2024-10-05:&lt;/strong&gt; added the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IsRemote&lt;/code&gt; check when sampling by parent, to ensure an activity is always created for propagation purposes.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Fri, 04 Oct 2024 04:20:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/10/activity-listener-sampling/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/10/activity-listener-sampling/</guid>
        
        
        <category>Tracing</category>
        
      </item>
    
      <item>
        <title>Visualizing the Serilog 4.1 batch retry algorithm</title>
        <description>&lt;p&gt;This is a quick post exploring how Serilog 4.1 (currently in preview) schedules retries when logging fails.&lt;/p&gt;

&lt;p&gt;Logs tucked away in a file on a remote server aren’t very useful, so sending log events across a network to a centralized collector, log server, or service is very common. But, networks are unreliable and services have outages, so logging systems need to support retries when a network request fails.&lt;/p&gt;

&lt;p&gt;Serilog 4.1 updates the algorithm used for scheduling retries, including the addition of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BatchingOptions.RetryTimeLimit&lt;/code&gt; to control how long a failed request batch will be retried before giving up.&lt;/p&gt;

&lt;p&gt;Since the algorithm uses exponential back-off and some internal thresholds to decide when a failed batch should be tried again, you might like to whip up a small test project to visualize this behavior under different conditions, with different options set.&lt;/p&gt;

&lt;p&gt;To save you from needing to write this yourself, here it is:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;System.Diagnostics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog.Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog.Core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog.Events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Stopwatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartNew&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FallbackChain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;NetworkSink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BatchingOptions&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;RetryTimeLimit&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TimeSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Sink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LocalSink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Event {I}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TimeSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromSeconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The program logs 1000 events at three second intervals. It uses a batched network sink and a local &lt;a href=&quot;https://nblumhardt.com/2024/10/fallback-logging/&quot;&gt;fallback sink&lt;/a&gt;, both included below, to simulate some failures and reveal what’s going on behind the scenes.&lt;/p&gt;

&lt;p&gt;The network sink accepts the first batch, and then fails for the next four:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NetworkSink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stopwatch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IBatchedLogEventSink&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;EmitBatchAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IReadOnlyCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LogEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;succeed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;succeed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;\ud83d\udfe2&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;\ud83d\uded1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;[&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Elapsed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; Network] &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; Batch &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; event(s))&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Delay&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TimeSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromMilliseconds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;succeed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InvalidOperationException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whether the batches succeed or fail, it takes 500 ms to make an attempt.&lt;/p&gt;

&lt;p&gt;The local sink just prints the events it receives to the terminal:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalSink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Stopwatch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ILogEventSink&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LogEvent&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;[&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Elapsed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;   Local] \ud83d\udfe2 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RenderMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s the program’s output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:00:00.0241652 Network] 🟢 Batch 0 (1 event(s))
[00:00:04.5297888 Network] 🛑 Batch 1 (1 event(s))
[00:00:07.0362428 Network] 🛑 Batch 2 (2 event(s))
[00:00:17.5382555 Network] 🛑 Batch 3 (5 event(s))
[00:00:18.0405741   Local] 🟢 Event 1
[00:00:18.0451773   Local] 🟢 Event 2
[00:00:18.0452174   Local] 🟢 Event 3
[00:00:18.0452246   Local] 🟢 Event 4
[00:00:18.0452297   Local] 🟢 Event 5
[00:00:38.0465922 Network] 🛑 Batch 4 (7 event(s))
[00:00:38.5476455   Local] 🟢 Event 6
[00:00:38.5477721   Local] 🟢 Event 7
[00:00:38.5478050   Local] 🟢 Event 8
[00:00:38.5478325   Local] 🟢 Event 9
[00:00:38.5478585   Local] 🟢 Event 10
[00:00:38.5478879   Local] 🟢 Event 11
[00:00:38.5479167   Local] 🟢 Event 12
[00:01:18.5473823 Network] 🟢 Batch 5 (14 event(s))
[00:01:21.0489512 Network] 🟢 Batch 6 (1 event(s))
[00:01:25.5509366 Network] 🟢 Batch 7 (1 event(s))
[00:01:28.0519726 Network] 🟢 Batch 8 (1 event(s))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s much more interesting to watch this in real-time: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet new console&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet add package serilog --prerelease&lt;/code&gt;, and pasting everything above into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt; will get you running, if you’re curious!&lt;/p&gt;

&lt;p&gt;Breaking down the output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:00:00.0241652 Network] 🟢 Batch 0 (1 event(s))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first batch succeeds as expected.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:00:04.5297888 Network] 🛑 Batch 1 (1 event(s))
[00:00:07.0362428 Network] 🛑 Batch 2 (2 event(s))
[00:00:17.5382555 Network] 🛑 Batch 3 (5 event(s))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The next three batches fail. We start with just one event, but by the time we’re trying the third failed batch, four more events have accumulated.&lt;/p&gt;

&lt;p&gt;The spacing between these is interesting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Batch 1 comes ~3 seconds after its predecessor,&lt;/li&gt;
  &lt;li&gt;Batch 2 comes ~3 seconds later,&lt;/li&gt;
  &lt;li&gt;Batch 3 comes ~10 seconds after that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The retry algorithm makes a couple of attempts at fairly short intervals, and then begins to exponentially back off. Exponential back-off helps conserve network bandwidth, and if the receiver is overloaded or restarting, gives it some breathing space to get back up and running. The gap between Batch 3 and Batch 4 will be even greater, as we’ll see in a moment.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:00:18.0405741   Local] 🟢 Event 1
[00:00:18.0451773   Local] 🟢 Event 2
[00:00:18.0452174   Local] 🟢 Event 3
[00:00:18.0452246   Local] 🟢 Event 4
[00:00:18.0452297   Local] 🟢 Event 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, the five events that were queued up in Batch 3 are written to the local, fallback sink. Why does the system give up at roughly 17 seconds from the first failure, and not at 30 seconds, as we configured using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RetryTimeLimit&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The answer is that because of exponential back-off, the next batch isn’t scheduled until around 37 seconds after the first failure, and this would exceed the time limit we’ve set, so the algorithm immediately triggers the failure handler and the batch is dropped.&lt;/p&gt;

&lt;p&gt;Here’s that next retry attempt:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:00:38.0465922 Network] 🛑 Batch 4 (7 event(s))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The system has collected seven more events, the batch fails, and the events are immediately passed to the fallback sink:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:00:38.5476455   Local] 🟢 Event 6
[00:00:38.5477721   Local] 🟢 Event 7
[00:00:38.5478050   Local] 🟢 Event 8
[00:00:38.5478325   Local] 🟢 Event 9
[00:00:38.5478585   Local] 🟢 Event 10
[00:00:38.5478879   Local] 🟢 Event 11
[00:00:38.5479167   Local] 🟢 Event 12
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Why aren’t these seven events retried in additional batches? After the retry delay has been reached, the algorithm switches modes, and all subsequent failing batches are immediately passed through to the fallback sink. This starts reducing the size of the internal buffer, while continuing to probe the remote service at reasonable intervals.&lt;/p&gt;

&lt;p&gt;In this particular simulation, we’re now back online, so the next batch attempt succeeds and all subsequent ones do, too:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[00:01:18.5473823 Network] 🟢 Batch 5 (14 event(s))
[00:01:21.0489512 Network] 🟢 Batch 6 (1 event(s))
[00:01:25.5509366 Network] 🟢 Batch 7 (1 event(s))
[00:01:28.0519726 Network] 🟢 Batch 8 (1 event(s))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we’d kept the network sink offline for longer, and simulated a higher logging rate or lower maximum batch size, we’d see the final failure state of the algorithm, which is to both drop batches and drain the entire queue at each subsequent failure. Ultimately, Serilog’s first priority is to avoid destabilizing the host application, and it’s not a good idea to keep on buffering events forever.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/serilog/serilog&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serilog/serilog&lt;/code&gt;&lt;/a&gt; is the place to find &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BatchingSink&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FallbackAwareBatchFailure&lt;/code&gt;, which implement the above behavior. Bug reports and suggestions are most welcome over there, or here below.&lt;/p&gt;

&lt;p&gt;I hope this post turns out to be useful when you need to better understand or tune batch retries in the future. We’ll have Serilog 4.1 out and ready for general production use soon. 😎&lt;/p&gt;
</description>
        <pubDate>Thu, 03 Oct 2024 20:22:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/10/retry-time-limit/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/10/retry-time-limit/</guid>
        
        
        <category>Serilog</category>
        
      </item>
    
      <item>
        <title>Serilog fallback sinks</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://www.nuget.org/packages/serilog#versions-body-tab&quot;&gt;Currently in preview&lt;/a&gt;, Serilog 4.1 adds support for falling back to a second sink when the first sink fails. This scenario shows up in a few variations, usually revolving around a network-based log collector, and a local, persistent destination such as a rolling log file that’s used when the network-based collector is unavailable. Some low-volume scenarios use email logging or a Slack channel as the fallback.&lt;/p&gt;

&lt;p&gt;Here’s what falling back to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File()&lt;/code&gt; sink looks like, when a network sink like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Seq()&lt;/code&gt; is unavailable:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FallbackChain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://seq.example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;log.txt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rollingInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RollingInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Super simple! Grab &lt;a href=&quot;https://www.nuget.org/packages/serilog#versions-body-tab&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4.1-dev-*&lt;/code&gt; versions of Serilog&lt;/a&gt; now and give it a try.&lt;/p&gt;

&lt;p&gt;Keep in mind that most network sinks will retry failed requests for some time period before reporting a failure, so it usually takes ten minutes or so for logs to show up in the fallback sink. The 4.1 release also includes some control over the maximum retry time to tighten this up, which I’ll post about here tomorrow.&lt;/p&gt;

&lt;p&gt;Fallback chains in Serilog 4.1 are built on another new feature, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILoggingFailureListener&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ILoggingFailureListener&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OnLoggingFailed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;LoggingFailureKind&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;IReadOnlyCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LogEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoggingFailureKind&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Temporary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Permanent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Final&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Failure listeners can try to record events elsewhere, or just report failures through metrics or some other mechanism. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteTo.FallbackChain()&lt;/code&gt; method uses failure listeners to move failed events from one sink to another.&lt;/p&gt;

&lt;p&gt;You can implement an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILoggingFailureListener&lt;/code&gt; yourself, and hook it up directly, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteTo.Fallible()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Implements `ILoggingFailureListener`&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyFailureListener&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fallible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://seq.example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sinks that throw exceptions from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogEventSink.Emit()&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IBatchedLogEventSink.EmitBatchAsync()&lt;/code&gt; get failure listener support out of the box, while other kinds of sinks can opt in by implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ISetLoggingFailureListener&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We’ll have a stable release of Serilog 4.1 out soon. In the meantime, please share any feedback you have on fallback sinks or failure listeners here, or via &lt;a href=&quot;https://github.com/serilog/serilog/issues&quot;&gt;the Serilog issue tracker&lt;/a&gt;. Thanks!&lt;/p&gt;
</description>
        <pubDate>Wed, 02 Oct 2024 22:50:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/10/fallback-logging/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/10/fallback-logging/</guid>
        
        
        <category>Serilog</category>
        
      </item>
    
      <item>
        <title>Serilog and .NET 8.0 minimal APIs</title>
        <description>&lt;p&gt;ASP.NET Core keeps evolving, and it’s been a while &lt;a href=&quot;https://nblumhardt.com/2019/10/serilog-in-aspnetcore-3/&quot;&gt;since I wrote one of these&lt;/a&gt;, so read on below for my take on an optimal Serilog configuration for ASP.NET Core 8 and the “minimal API” application model, a.k.a. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebApplicationBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I’ve arranged this into three parts: configuring Serilog, hooking up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.Extensions.Logging.ILogger&amp;lt;T&amp;gt;&lt;/code&gt;, and recording web requests. Each part builds on the one before it.&lt;/p&gt;

&lt;h2 id=&quot;configuring-serilog&quot;&gt;Configuring Serilog&lt;/h2&gt;

&lt;p&gt;First, it’s important to realize that ASP.NET Core apps are “just” regular console apps. You can set up Serilog and use it without anything ASP.NET Core-specific at all. Doing this first helps to validate that you have the foundations working, before moving onto anything more involved.&lt;/p&gt;

&lt;p&gt;Assuming you have a blank “minimal API” project (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet new web&lt;/code&gt;), your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt; file will look like this:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Install Serilog and its console sink from NuGet:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the top of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt;, create a logging pipeline and assign it to the static &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Log.Logger&lt;/code&gt; property:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Starting up&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running your program should now produce the “starting up” message from Serilog, then some default output from the framework.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-04-serilog-net8.0-minimal/01-console-sink-configured.png&quot; alt=&quot;Terminal with Serilog start-up message&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s one more thing to do. If the application fails to start, we want to capture any thrown exceptions, and also make sure any buffered log events are flushed before the process exits. Adding some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;catch&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;finally&lt;/code&gt; blocks will sort this out:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Starting up&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Unhandled exception&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CloseAndFlushAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point, you can use Serilog’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Log&lt;/code&gt; class to write your own structured log events. In the next section, we’ll route the framework’s logging through the same pipeline.&lt;/p&gt;

&lt;p&gt;You might be tempted to add some more sinks now. Hold on for just a moment, there are some decisions to make, which we’ll discuss in the next section.&lt;/p&gt;

&lt;h2 id=&quot;hooking-up-aspnet-core-and-iloggert&quot;&gt;Hooking up ASP.NET Core and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;This turns out to be ultra-simple. Install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serilog.Extensions.Hosting&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package Serilog.Extensions.Hosting
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder.Services.AddSerilog()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;-- add this&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, running the application will produce markedly different, and I think much nicer, results:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-04-serilog-net8.0-minimal/02-add-serilog.png&quot; alt=&quot;Terminal with all ASP.NET Core log output through Serilog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the time to start thinking about where else you would like to send your logs. Chances are, if you want to record events somewhere other than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STDOUT&lt;/code&gt;, you’ll need to configure a destination - perhaps setting a log file path, or log server URL.&lt;/p&gt;

&lt;p&gt;I’ll go out on a limb here, and suggest avoiding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt;. I don’t expect you’ll all agree with me, so we’ll examine two different approaches, and you can make up your mind what works best for your application.&lt;/p&gt;

&lt;p&gt;I’ll use the Seq sink as an example, since I built the first versions of Seq, and it’s now what I work on day-to-day. The process is very similar for any other typical Serilog sink; if you have trouble with the one you choose, let me know in the comments 🙂.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package Serilog.Sinks.Seq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you’ve installed your sink package, you’ll need to add that sink to the logging pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1&lt;/strong&gt; is to add your sink in code, and configure it there. You can make up environment variable names for any settings that you need to modify at deployment time:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;-- add these lines&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SEQ_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SEQ_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To get a Seq container running on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:5341&lt;/code&gt;, use:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --rm -it -e ACCEPT_EULA=y -p 5341:80 datalust/seq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run the app, open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:5341&lt;/code&gt; in a web browser, and you’ll see something resembling:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-04-serilog-net8.0-minimal/03-in-seq.png&quot; alt=&quot;Serilog output in the Seq web UI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I like this approach to configuration because it’s obvious, the compiler checks that I’ve provided all of the required parameters, and anything that’s not modified at deployment time can be specified in strongly-typed C#. It also works reliably alongside single-file publishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2&lt;/strong&gt; is to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serilog.Settings.Configuration&lt;/code&gt; to load your sinks from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt;. Skip this section if you’re happy configuring everything in code as above.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package Serilog.Settings.Configuration
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Configuration isn’t immediately available, which means giving up on capturing exceptions that occur early in start-up, unless you want to &lt;a href=&quot;https://nblumhardt.com/2020/10/bootstrap-logger/&quot;&gt;jump through some additional hoops&lt;/a&gt;. My advice is to keep things simple, stripping back your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt; to something like:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lc&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadFrom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appsettings.json&lt;/code&gt; file should look like this, with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Seq&quot;&lt;/code&gt; section replaced by your sink(s) of choice:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Serilog&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Using&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Serilog.Sinks.Seq&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;WriteTo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Seq&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;serverUrl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;apiKey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;API key here&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;AllowedHosts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Args&quot;&lt;/code&gt; names above are the parameter names of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteTo.Seq()&lt;/code&gt; method: Serilog’s configuration system is just a JSON-driven way of calling the same configuration methods we see in the C# version. Check out the &lt;a href=&quot;https://github.com/serilog/serilog-settings-configuration&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serilog.Extensions.Configuration&lt;/code&gt; project README&lt;/a&gt; for more information on the configuration syntax.&lt;/p&gt;

&lt;p&gt;Running the application will produce similar output to what we saw the first time. If the app fails to start, you’ll get an exception message and stack trace dumped to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;STDERR&lt;/code&gt; by the framework itself.&lt;/p&gt;

&lt;p&gt;JSON configuration can take a little more work to get right, is a bit more fragile, and needs a little more testing. It can still be very enjoyable to use if you have a scenario that requires it. The choice is in in your hands, but the remaining examples will be built on the configuration-as-code version.&lt;/p&gt;

&lt;h2 id=&quot;recording-web-requests&quot;&gt;Recording web requests&lt;/h2&gt;

&lt;p&gt;At this point, the application writes a handful of log events each time a web request is handled. These contain useful data, but they’re rather noisy, and the information we typically need about a web request is spread across multiple events. In this section, we look at how to collapse this information into a single, nicely-formatted event.&lt;/p&gt;

&lt;p&gt;The classic way to do this is to install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serilog.AspNetCore&lt;/code&gt;, and &lt;a href=&quot;https://github.com/serilog/serilog-aspnetcore?tab=readme-ov-file#request-logging&quot;&gt;add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.UseSerilogRequestLogging()&lt;/code&gt;&lt;/a&gt; to your web application’s startup code, right after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builder.Build()&lt;/code&gt;. This is super simple and effective, but ASP.NET Core is moving in a different direction. If you’re setting things up from scratch, you’ll have a better experience in the long run by relying on ASP.NET Core’s built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Activity&lt;/code&gt; support to record web requests.&lt;/p&gt;

&lt;p&gt;If you’re not familiar with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Diagnostics.Activity&lt;/code&gt;, for the purposes of this post it’s enough to know that an activity represents an operation with a beginning and an end. ASP.NET Core wraps an activity around each web request it handles. Because the activity is completed after all middleware runs, the activity carries a more accurate picture of the request’s duration and final status code than the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UseSerilogRequestLogging()&lt;/code&gt; middleware can provide. As a bonus, activities participate in distributed tracing, so you can view requests in and out of your app in a hierarchy if your chosen sink supports it.&lt;/p&gt;

&lt;p&gt;Step one is to install &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt;, its console output formatting support, and ASP.NET Core integration:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package SerilogTracing
dotnet add package SerilogTracing.Expressions
dotnet add package SerilogTracing.Instrumentation.AspNetCore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.cs&lt;/code&gt;, add the trace-aware formatter to the console sink, and reduce the verbosity of some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.AspNetCore.*&lt;/code&gt; log sources:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MinimumLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Override&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.AspNetCore.Hosting&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LogEventLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MinimumLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Override&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.AspNetCore.Routing&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LogEventLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Application&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Formatters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateConsoleTextFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TemplateTheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SEQ_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SEQ_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’ve used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enrich&lt;/code&gt; to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application&lt;/code&gt; property to all events, since this helps with tracking traced operations between systems. This is a purely optional addition, though.&lt;/p&gt;

&lt;p&gt;And then, configure the listener that will write ASP.NET Core’s activities to the Serilog pipeline:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instrument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AspNetCoreRequests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s the &lt;strong&gt;final, complete example&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog.Events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTracing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTracing.Expressions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MinimumLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Override&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.AspNetCore.Hosting&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LogEventLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MinimumLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Override&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.AspNetCore.Routing&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LogEventLevel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Warning&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Enrich&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Application&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Formatters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateConsoleTextFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TemplateTheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SEQ_URL&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;http://localhost:5341&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SEQ_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listener&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instrument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AspNetCoreRequests&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Starting up&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MapGet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RunAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Unhandled exception&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CloseAndFlushAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the output at the terminal:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-04-serilog-net8.0-minimal/04-traces-at-terminal.png&quot; alt=&quot;Terminal with Serilog log events and SerilogTracing spans&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you’re following along using Seq, you’ll see the same events there:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-04-serilog-net8.0-minimal/05-traces-in-seq.png&quot; alt=&quot;Seq with Serilog log events and SerilogTracing spans&quot; /&gt;&lt;/p&gt;

&lt;p&gt;SerilogTracing works with all Serilog sinks, but at the time of writing, the Seq sink, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SerilogTracing.Sinks.OpenTelemetry&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SerilogTracing.Sinks.Zipkin&lt;/code&gt; have been written or modified to support back-ends with hiearchical tracing features. In other sinks, spans will show up as regular log events.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; if you’re using a sink without hierarchical tracing support, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enrich.WithSpanTimingMilliseconds()&lt;/code&gt; to add a convenient &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Elapsed&lt;/code&gt; property to request completion events.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;where-to-next&quot;&gt;Where to next?&lt;/h2&gt;

&lt;p&gt;So this is how &lt;em&gt;I’d&lt;/em&gt; do it; you might find a different approach that works for you! Let me know how you go, or say “hi”, on &lt;a href=&quot;https://twitter.com/nblumhardt&quot;&gt;&lt;del&gt;Twitter&lt;/del&gt;X&lt;/a&gt; and &lt;a href=&quot;https://social.nblumhardt.com/@nblumhardt&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://stackoverflow.com/questions/tagged/serilog&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serilog&lt;/code&gt; tag on StackOverflow&lt;/a&gt; is the place for help with Serilog and how it integrates with other tech.&lt;/p&gt;

&lt;p&gt;Thanks for stopping by! 👋&lt;/p&gt;
</description>
        <pubDate>Thu, 18 Apr 2024 03:50:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/04/serilog-net8-0-minimal/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/04/serilog-net8-0-minimal/</guid>
        
        
        <category>Serilog</category>
        
        <category>Tracing</category>
        
      </item>
    
      <item>
        <title>From SerilogTimings to SerilogTracing</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://github.com/nblumhardt/serilog-timings&quot;&gt;SerilogTimings&lt;/a&gt; is a handy little library that wraps blocks of code in “operations”:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTimings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Say hello to {Name}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Measure temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Attached to the log event but not shown in text output&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When an operation completes, either explicitly or by being disposed, SerilogTimings writes an event with the duration of the operation through Serilog:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[13:53:20 INF] Hello!
[13:53:20 INF] Measure temperature completed in 0.6 ms
[13:53:20 INF] Say hello to nblumhardt completed in 26.5 ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SerilogTimings is simple, transparent, and gets its job done with a minimum of fuss. It hasn’t changed significantly in many years, now. So why this post? Timed operations show up absoultely everywhere, and so tooling has evolved to make working with them much more efficient.&lt;/p&gt;

&lt;p&gt;Operations might be nothing more than an event with a duration attached, but by adding a handful of other correlation ids behind the scenes, collections of timed operations in a hierarchy become &lt;em&gt;traces&lt;/em&gt;, and a whole range of tooling now exists that can visualize and manipulate them.&lt;/p&gt;

&lt;p&gt;A new Serilog extension called &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt; updates the SerilogTimings API to generate traces that interoperate with more tools and .NET libraries.&lt;/p&gt;

&lt;p&gt;Here’s the equivalent of the above example, using SerilogTracing instead:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Serilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTracing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;SerilogTracing.Expressions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Formatters&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateConsoleTextFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Say hello to {Name}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Information&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Measure temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Temperature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output at the console is similar:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[14:43:49 INF] │ Hello!
[14:43:49 INF] ├ Measure temperature (0.487 ms)
[14:43:49 INF] └─ Say hello to nblumhardt (19.015 ms)can
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But by writing to a tracing-enabled sink, the result can now also be visualized in Seq, Zipkin, and many other tools:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-03-from-serilog-timings-to-tracing/trace-as-gantt.png&quot; alt=&quot;Seq UI showing the same trace information in a Gantt-style chart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The image above uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteTo.Seq()&lt;/code&gt; and Seq 2024.1. Wider sink support is listed in the &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing?tab=readme-ov-file#tracing-enabled-sinks&quot;&gt;SerilogTracing README&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Along with trace visualization, SerilogTracing integrates with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Diagnostics&lt;/code&gt; so that HTTP requests, database operations, and pretty much any instrumented .NET activity will be automatically traced.&lt;/p&gt;

&lt;h3 id=&quot;api-conversion&quot;&gt;API conversion&lt;/h3&gt;

&lt;p&gt;SerilogTracing’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogger.BeginOperation()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogger.TimeOperation()&lt;/code&gt; are both covered by SerilogTracing’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogger.StartActivity()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unlike SerilogTimings, SerilogTracing doesn’t provide a static wrapper around the Serilog &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Log&lt;/code&gt; class, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Operation.Begin()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Operation.Time()&lt;/code&gt; map to the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogger.StartActivity()&lt;/code&gt; extension method, which needs to be called on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Log.Logger&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;The SerilogTracing API for completing operations is separate from adding properties: instead of passing properties to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Complete()&lt;/code&gt;, you explicitly complete a SerilogTracing activity using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Complete(level, exception)&lt;/code&gt;, or add properties using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddProperty()&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;formatting-conversion&quot;&gt;Formatting conversion&lt;/h3&gt;

&lt;p&gt;The example above uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Formatters.CreateConsoleTextFormatter()&lt;/code&gt; from the &lt;em&gt;SerilogTracing.Expressions&lt;/em&gt; NuGet package to add timings to console output. If you want to customize how spans are output, or match SerilogTimings more closely, there’s a template in the &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing?tab=readme-ov-file#formatting-output&quot;&gt;SerilogTracing README&lt;/a&gt; that you can modify to achieve this.&lt;/p&gt;

&lt;h3 id=&quot;the-future&quot;&gt;The future&lt;/h3&gt;

&lt;p&gt;SerilogTimings is not dead, but it’s done. If you’re interested in improving how your application exposes timing information, &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt; is the natural successor to SerilogTimings, and opens up a range of interesting new possibilities.&lt;/p&gt;
</description>
        <pubDate>Thu, 07 Mar 2024 05:19:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/03/from-serilog-timings-to-tracing/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/03/from-serilog-timings-to-tracing/</guid>
        
        
        <category>Serilog</category>
        
        <category>Tracing</category>
        
      </item>
    
      <item>
        <title>The case for an application-level tracing API in .NET</title>
        <description>&lt;p&gt;If you want to record a log event from your application in .NET, you can do that today without a lot of noise or ceremony using &lt;em&gt;Microsoft.Extensions.Logging&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LogInformation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Completing order {OrderId} for {Customer}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Admittedly it’s a long line, but from this statement, packed in alongside the timestamp, level, and whatever correlation identifiers are set behind the scenes, you get:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;A human-friendly message&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Completing order 15 for nblumhardt&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A low-cardinality event type&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Completing order {OrderId} for {Customer}&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A fully-structured payload&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;OrderId&quot;: 15, &quot;Customer&quot;: &quot;nblumhardt&quot;}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Packing all of this into a single statement is important: it’s easy for diagnostics to start obscuring the real application logic, otherwise.&lt;/p&gt;

&lt;p&gt;If you want to record a trace span (timed operation) from your application code in .NET today, you’ll need to use &lt;em&gt;System.Diagnostics&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyActivitySource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Complete order&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddTag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;OrderId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddTag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Customer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Custome&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Three lines in this case, but also, while we’ve maintained the structured payload and low-cardinality/machine-readable event type (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Complete order&quot;&lt;/code&gt;), there’s no pleasant human-readable description of the span that includes the vital details of this span itself. If you want that you’ll need to add at least one more line.&lt;/p&gt;

&lt;p&gt;There’s also the distracting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; language machinery needed to handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; activities, or danger of forgetting about them in projects that don’t enable strict null checks.&lt;/p&gt;

&lt;p&gt;But what do we actually get in return, that the logging API doesn’t provide? Just a duration and a parent span id, if there is one. The activity ends up producing output that isn’t all that different from a log event.&lt;/p&gt;

&lt;p&gt;So far, the major consumers of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Diagnostics.ActivitySource&lt;/code&gt; are building frameworks and libraries, and using the current API it’s possible to generate spans precisely and efficiently. Frameworks like ASP.NET Core have different needs from regular applications, and seeing a bit more plumbing exposed in those codebases isn’t a big problem.&lt;/p&gt;

&lt;p&gt;Noise and complexity is a bigger concern in application code, though.&lt;/p&gt;

&lt;p&gt;Couldn’t we combine the ideas from these APIs and have a tracing experience that’s the best of both worlds?&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyActivitySource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Complete order {OrderId} for {Customer}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is the API that &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt; provides:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;complete&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Complete order {OrderId} for {Customer}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In framework code it’s just sugar; but in &lt;em&gt;application code&lt;/em&gt; — this is how I want to record traces! As SerilogTracing picks up steam ahead of its 1.0, it will be interesting to see whether someone can bring this UX back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ILogger&amp;lt;T&amp;gt;&lt;/code&gt; and/or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActivitySource&lt;/code&gt;, too.&lt;/p&gt;
</description>
        <pubDate>Thu, 22 Feb 2024 22:58:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/02/tracing-with-message-templates/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/02/tracing-with-message-templates/</guid>
        
        
        <category>Serilog</category>
        
        <category>Tracing</category>
        
      </item>
    
      <item>
        <title>SerilogTracing</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;TL:DR:&lt;/strong&gt; Check out &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt;, a simple, minimal extension for Serilog that integrates with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Diagnostics.Activity&lt;/code&gt; to provide hierarchical, distributed traces and compatibility with the Serilog sink ecosystem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Traces are amazing for analyzing performance and for describing complex operations that flow across multiple systems. Modern .NET has tracing support built-in, so it’s now surprisingly easy to generate and consume traces with all of the hierarchical, distributed goodness provided for free by the framework.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-01-serilog-tracing/seq-build-process.jpg&quot; alt=&quot;Seq&apos;s own build process captured using SerilogTracing and displayed in Seq&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing&quot;&gt;SerilogTracing&lt;/a&gt; is a new project that handles the mechanics of:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Creating beautiful, clean, fully-structured traces using the message template syntax that Serilog users will know and love, and&lt;/li&gt;
  &lt;li&gt;Getting traces out into tracing systems, log files, the terminal, and any other place a Serilog sink can reach.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And because it’s built on the same tracing APIs as the rest of .NET, it can hope to interoperate with whatever other tracing or diagnostic infrastructure you might wish to use, now or in the future.&lt;/p&gt;

&lt;p&gt;This post is a summary of what you can expect from SerilogTracing, but the best examples to get started with are in &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing#readme&quot;&gt;the SerilogTracing README&lt;/a&gt;, which includes detailed instructions.&lt;/p&gt;

&lt;h3 id=&quot;creating-beautiful-traces&quot;&gt;Creating beautiful traces&lt;/h3&gt;

&lt;p&gt;A trace is made up of one or more &lt;em&gt;spans&lt;/em&gt;, which are generally represented using activities in .NET.&lt;/p&gt;

&lt;p&gt;You wrap an activity around some meaninful piece of work using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Serilog.ILogger.StartActivity()&lt;/code&gt; and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using&lt;/code&gt; statement:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Fulfill order {OrderId}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ... some application logic ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the activity is disposed or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Complete()&lt;/code&gt; is called, a span will be written through the logger.&lt;/p&gt;

&lt;p&gt;The first thing you’ll notice is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StartActivity()&lt;/code&gt; names the span using a message template. Any property values captured by the message template will be attached to the activity, so in this case, the span will carry an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderId&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;If you’ve used Serilog previously, this will be familiar. Message templates are a powerful way to pack a low-cardinality event type (the template itself), a high-cardinality formatted message, and multiple fully-structured properties, all into a single, clean, readable line of code.&lt;/p&gt;

&lt;p&gt;Locally, you get nicely-presented terminal output:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://nblumhardt.com/img/2024-01-serilog-tracing/serilog-tracing-terminal.png&quot; alt=&quot;Output to macOS Terminal with trace displayed as simple text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And, you can choose to send any combination of the template, the formatted message, and the structured properties to your tracing system, depending on what works best for you there.&lt;/p&gt;

&lt;h3 id=&quot;getting-traces-out&quot;&gt;Getting traces out&lt;/h3&gt;

&lt;p&gt;To get started, any Serilog sink will do! Traces are just collections of spans, and SerilogTracing represents spans as log events. This means that you can start instrumenting your code with tracing while logging to nothing more complex than a text file or the terminal.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// A very minimal configuration ...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// ... is still enough to start tracing.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;StartActivity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But, there are two things you’ll need to do if you want to view your spans in a hierarchy, or participate in distributed tracing.&lt;/p&gt;

&lt;h4 id=&quot;consume-external-activities&quot;&gt;Consume external activities&lt;/h4&gt;

&lt;p&gt;The first is to subscribe to activities created by other .NET components. This is important because the code generating these activities is also responsible for distributed trace propagation, and if no subscribers are listening, they won’t bother propagating important identifiers such as the incoming trace and parent span ids.&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ActivityListenerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;TraceToSharedLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re working on the server-side, check out the &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing#adding-instrumentation-for-aspnet-core&quot;&gt;instructions for adding ASP.NET Core instrumentation&lt;/a&gt; here, too.&lt;/p&gt;

&lt;h4 id=&quot;install-some-tracing-enabled-sinks&quot;&gt;Install some tracing-enabled sinks&lt;/h4&gt;

&lt;p&gt;Spans are just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LogEvent&lt;/code&gt;s, but a handful of conventions make it possible to render them as traces if you have a suitable back-end available.&lt;/p&gt;

&lt;p&gt;In particular, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SpanStartTimestamp&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ParenSpanId&lt;/code&gt; properties are enough to drive hierarchical trace visualization. The included Zipkin sink handles spans only (it ignores other events), and manages this in &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing/blob/dev/src/SerilogTracing.Sinks.Zipkin/Sinks/Zipkin/ZipkinSink.cs&quot;&gt;just a few dozen lines of interesting code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Right now, &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing/tree/dev#tracing-enabled-sinks&quot;&gt;unless you’re using Seq, Zipkin, or a back-end that supports OTLP&lt;/a&gt;, chances are you’ll need to modify an existing Serilog sink to recognize these properties: if you do, please reach out, I’m interested to hear how you go, and keen to help where I can.&lt;/p&gt;

&lt;h3 id=&quot;the-pitch&quot;&gt;The pitch!&lt;/h3&gt;

&lt;p&gt;So to wrap this whole thing up, the pitch for SerilogTracing comes down to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keep your existing Serilog logging unchanged&lt;/li&gt;
  &lt;li&gt;Generate rich spans with a single line of code each&lt;/li&gt;
  &lt;li&gt;Subscribe to spans from other .NET components with a single line of code at startup&lt;/li&gt;
  &lt;li&gt;Participate in distributed tracing and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceparent&lt;/code&gt; propagation thanks to .NET ❤️&lt;/li&gt;
  &lt;li&gt;Send everything through Serilog’s enrichers, filters, and sinks&lt;/li&gt;
  &lt;li&gt;See spans as structured log events in sinks that don’t support tracing&lt;/li&gt;
  &lt;li&gt;See spans as traces in compatible sinks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To balance out this very rosy picture 😊 please remember that SerilogTracing is brand new, pre-1.0, and still under very active development.&lt;/p&gt;

&lt;p&gt;And, it would be remiss of me not to mention that much of SerilogTracing has been designed and built by my colleagues Ashley and Liam at Datalust: hat tip to you both! 🎩&lt;/p&gt;

&lt;p&gt;Instructions for getting started are &lt;a href=&quot;https://github.com/serilog-tracing/serilog-tracing#readme&quot;&gt;in the README&lt;/a&gt;. I hope this is useful for you!&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Edited 2024-02-23:&lt;/strong&gt; updated the example to match the current type and method names.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Mon, 22 Jan 2024 21:58:00 +0000</pubDate>
        <link>https://nblumhardt.com/2024/01/serilog-tracing/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2024/01/serilog-tracing/</guid>
        
        
        <category>Serilog</category>
        
        <category>Tracing</category>
        
      </item>
    
      <item>
        <title>Serilog project update, May 2023</title>
        <description>&lt;p&gt;👋 Howdy! I hope you’re enjoying the, frankly, pretty fantastic state of structured logging in .NET these days. If you’re using &lt;a href=&quot;https://github.com/serilog/serilog&quot;&gt;Serilog&lt;/a&gt; then I hope you’ve also been enjoying the long period of stability we’ve maintained on the 2.x release series (I don’t want to rewrite my application logging every eighteen months, either 🙂).&lt;/p&gt;

&lt;p&gt;While we’ve been keeping Serilog fresh, it’s time to make some minor breaking changes, particularly in the target frameworks and library versions that the mainline Serilog packages will support. A few interesting things are coming in the next month or so, and so here’s a quick run through what to expect from the Serilog project going into mid-2023.&lt;/p&gt;

&lt;h3 id=&quot;serilog-30-progress&quot;&gt;Serilog 3.0 progress&lt;/h3&gt;

&lt;p&gt;Serilog 3.0 is coming together and should be finalized soon. It drops support for several ancient .NET versions and some long-unsupported minor releases. The directly-targeted frameworks are now:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstandard2.0&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstandard2.1&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net462&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net47&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net471&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net5.0&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net6.0&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;net7.0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leaves .NET Framework versions prior to 4.6.2, and .NET Core versions prior to 2.0, on the 2.x series of Serilog releases. The vast majority of sinks will remain compatible with both Serilog 2.x and 3.x so please don’t feel unloved if you’re maintaining an application that can’t move forward for one reason or another.&lt;/p&gt;

&lt;p&gt;3.0 also includes a large number of performance improvements, bug fixes, and full support for as much of modern .NET as we can reach. You can try it out now! Grab the latest pre-release version from NuGet with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dotnet add package Serilog --prerelease
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All feedback and bug reports — especially any compatibility problems! — are welcome over on &lt;a href=&quot;https://github.com/serilog/serilog/issues&quot;&gt;the project issue tracker&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;updated-versioning-for-microsoftextensions-dependent-packages&quot;&gt;Updated versioning for Microsoft.Extensions.* dependent packages&lt;/h3&gt;

&lt;p&gt;The following packages are about to ship 7.0.0 versions, and will match both the version (major and minor) and target framwork support of their matching &lt;em&gt;Microsoft.Extensions.*&lt;/em&gt; packages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Serilog.Extensions.Logging&lt;/em&gt; (tracks &lt;em&gt;Microsoft.Extensions.Logging&lt;/em&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Serilog.Extensions.Hosting&lt;/em&gt; (tracks &lt;em&gt;Microsoft.Extensions.Hosting&lt;/em&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Serilog.AspNetCore&lt;/em&gt; (also tracks &lt;em&gt;Microsoft.Extensions.Hosting&lt;/em&gt;, via its &lt;em&gt;Serilog.Extensions.Hosting&lt;/em&gt; dependency)&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Serilog.Settings.Configuration&lt;/em&gt; (tracks &lt;em&gt;Microsoft.Extensions.Configuration&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that, while you can use &lt;em&gt;Serilog.Extensions.*&lt;/em&gt; v7 packages on a wide range of .NET versions, they’ll always depend on the same (v7) underlying versions of the corresponding &lt;em&gt;Microsoft.*&lt;/em&gt; libraries. When &lt;em&gt;Microsoft.Extensions.*&lt;/em&gt; v8 packages ship, we’ll simultaneously release v8 packages, too.&lt;/p&gt;

&lt;p&gt;All up, this should make using the Serilog platform extensions packages more predictable, and result in fewer dependency resolution problems or security scanner false-positives.&lt;/p&gt;

&lt;h3 id=&quot;serilogsinksopentelemetry-100-rc&quot;&gt;Serilog.Sinks.OpenTelemetry 1.0.0 RC&lt;/h3&gt;

&lt;p&gt;OpenTelemetry includes a standardized protocol for transmitting structured log events. Our aim for &lt;em&gt;Serilog.Sinks.OpenTelemetry&lt;/em&gt; v1 is to provide first-class support for connecting Serilog to back-ends that might not have dedicated or well-maintained Serilog integrations today.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://nuget.org/packages/serilog.sinks.opentelemetry&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0.0-dev-*&lt;/code&gt; packages on NuGet&lt;/a&gt; have reached “release candidate” status; if you have applications instrumented with Serilog, and a collector that supports the OpenTelemetry Logs protocol, your feedback and questions &lt;a href=&quot;https://github.com/serilog/serilog-sinks-opentelemetry&quot;&gt;over in the project repository&lt;/a&gt; would be most welcome.&lt;/p&gt;

&lt;h3 id=&quot;who-is-to-thank-for-all-this&quot;&gt;Who is to thank for all this?&lt;/h3&gt;

&lt;p&gt;Only the tiniest slice of all this is my own work; listing everyone’s contributions will have to wait for the release notes, but I should call out that some very talented and generous programmers are behind this latest push forwards. If you have the time and inclination to get involved in the Serilog project, you can find repositories for all of these packages under https://github.com/serilog.&lt;/p&gt;
</description>
        <pubDate>Mon, 08 May 2023 02:45:00 +0000</pubDate>
        <link>https://nblumhardt.com/2023/05/serilog-3-update/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2023/05/serilog-3-update/</guid>
        
        
        <category>Serilog</category>
        
      </item>
    
      <item>
        <title>Hot-reload any Serilog sink</title>
        <description>&lt;p&gt;Hi! 👋&lt;/p&gt;

&lt;p&gt;Chances are you’ve found your way here because &lt;em&gt;Serilog.Settings.Configuration&lt;/em&gt;’s runtime reconfiguration logic only supports minimum levels, and not any other sink parameters. This stems from a strong preference for immutability (and hence clean concurrency) in the Serilog design, but the question remains: if I need to update a sink parameter at runtime, how can I do that?&lt;/p&gt;

&lt;h3 id=&quot;sinks-and-configuration&quot;&gt;Sinks and configuration&lt;/h3&gt;

&lt;p&gt;Serilog sinks often need to be configured with some information about where a log event should be sent. The &lt;a href=&quot;https://github.com/datalust/seq&quot;&gt;Seq sink&lt;/a&gt;, for example, accepts a URL and optional API key for the target Seq server:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://seq.example.com&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whether it’s a URL like the one above, a connection string, SMTP server, or some other detail, if it needs to change at runtime without reloading the app, this post has you covered.&lt;/p&gt;

&lt;h3 id=&quot;introducing-serilogsinksmap&quot;&gt;Introducing &lt;em&gt;Serilog.Sinks.Map&lt;/em&gt;&lt;/h3&gt;

&lt;p&gt;This &lt;a href=&quot;https://github.com/serilog/serilog-sinks-map&quot;&gt;great little package&lt;/a&gt; is normally used to route log events to different destinations based on event properties (custom ones, and typical ones like the level). To do that, &lt;em&gt;Serilog.Sinks.Map&lt;/em&gt; tracks a collection of sinks, initializing, flushing, and disposing of them as required.&lt;/p&gt;

&lt;p&gt;We’ll configure it to keep only a single Seq sink open, but replace it whenever the value of the server URL changes:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// dotnet add package serilog.sinks.map&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LoggerConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetServerUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Seq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sinkMapCountLimit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetServerUrl()&lt;/code&gt; in this example is your code, a function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string GetServerUrl()&lt;/code&gt; that returns the current address to configure the sink with. Whenever the return value of the function changes, &lt;em&gt;Serilog.Sinks.Map&lt;/em&gt; will invoke the callback in the second argument to reconfigure the sink.&lt;/p&gt;

&lt;h3 id=&quot;via-json-configuration&quot;&gt;Via JSON configuration&lt;/h3&gt;

&lt;p&gt;If the configuration you need is coming from &lt;em&gt;appsettings.json&lt;/em&gt;, you’ll need to do a little more work to hook into the change detection behavior implemented by the &lt;em&gt;Microsoft.Extensions.Configuration&lt;/em&gt; system.&lt;/p&gt;

&lt;p&gt;This example assumes that you’re using ASP.NET Core 7:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// In Program.cs&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebApplication&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// &amp;lt;snip&amp;gt;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configVersion&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0L&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseSerilog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lc&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Interlocked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writeTo&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadFrom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sinkMapCountLimit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ChangeToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OnChange&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&amp;gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetReloadToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Interlocked&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configVersion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And remarkably, that’s enough to have your entire Serilog pipeline reloaded whenever a configuration change occurs. 🙌&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; this does not work for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MinimumLevel&lt;/code&gt;, because levels have already been processed by the time they reach the sink. So… &lt;em&gt;almost&lt;/em&gt; your entire Serilog pipeline 😉.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Thanks to Brad Lindberg for &lt;a href=&quot;https://github.com/datalust/seq-forwarder/issues/64#issuecomment-1410893893&quot;&gt;raising this question&lt;/a&gt; recently on GitHub.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
        <pubDate>Wed, 01 Feb 2023 00:30:00 +0000</pubDate>
        <link>https://nblumhardt.com/2023/02/dynamically-reload-any-serilog-sink/</link>
        <guid isPermaLink="true">https://nblumhardt.com/2023/02/dynamically-reload-any-serilog-sink/</guid>
        
        
        <category>Serilog</category>
        
      </item>
    
  </channel>
</rss>
