<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: amin parsa</title>
    <description>The latest articles on DEV Community by amin parsa (@apardev).</description>
    <link>https://dev.to/apardev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1700115%2F51e9c0e0-8cce-4d3a-8948-f4b7d8a903ef.jpg</url>
      <title>DEV Community: amin parsa</title>
      <link>https://dev.to/apardev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/apardev"/>
    <language>en</language>
    <item>
      <title>Self-hosted observability for .NET: logs, traces and metrics over plain OpenTelemetry.</title>
      <dc:creator>amin parsa</dc:creator>
      <pubDate>Mon, 28 Sep 2026 11:37:16 +0000</pubDate>
      <link>https://dev.to/apardev/self-hosted-observability-for-net-logs-traces-and-metrics-over-plain-opentelemetry-585c</link>
      <guid>https://dev.to/apardev/self-hosted-observability-for-net-logs-traces-and-metrics-over-plain-opentelemetry-585c</guid>
      <description>&lt;p&gt;If you run .NET services and want logs, traces and metrics in one place, the usual choices are a hosted vendor that bills by the gigabyte or a stack of separate open-source tools you wire together yourself. Flare is my attempt at a third option: a self-hosted, MIT-licensed observability platform for .NET. Your apps send logs, traces and metrics over standard OpenTelemetry (OTLP), Flare stores them in ClickHouse, and a web dashboard lets you search, correlate and alert on them. There's no Flare agent to install and no Flare SDK in your code. If your app can export OTLP, it can already talk to Flare.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/aminparsa18/Flare.Net" rel="noopener noreferrer"&gt;https://github.com/aminparsa18/Flare.Net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcyx1qkoo3fisl5arol5r.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcyx1qkoo3fisl5arol5r.webp" alt="Flare's logs explorer: event volume chart above a searchable list of log events" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in two minutes
&lt;/h2&gt;

&lt;p&gt;You need Docker. Clone the repo and start the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aminparsa18/Flare.Net
&lt;span class="nb"&gt;cd &lt;/span&gt;Flare.Net
docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather not clone anything, the install script pulls the published images and starts the same stack (it installs Docker first if it's missing):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/aminparsa18/Flare.Net/main/scripts/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard comes up at &lt;a href="http://localhost:7777" rel="noopener noreferrer"&gt;http://localhost:7777&lt;/a&gt;. Sign-in is off by default, so you land straight on the Logs page. You can turn on local accounts, Microsoft Entra ID, Active Directory/LDAP, OpenID Connect or reverse-proxy headers later from the &lt;code&gt;/auth&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;Now point an app at it. With plain &lt;code&gt;Microsoft.Extensions.Logging&lt;/code&gt; you need two OpenTelemetry packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeFormattedMessage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeScopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;UseOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and two environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:4317
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;ILogger&lt;/code&gt; call now shows up in Flare. Serilog, NLog and ZLogger work the same way through their existing OpenTelemetry sinks, and the dashboard's Data sources page has copy-paste snippets for Python, Node.js, Java, Go, Kubernetes and Prometheus scraping.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you use .NET Aspire
&lt;/h2&gt;

&lt;p&gt;Flare has an Aspire hosting integration, so it can live in your AppHost next to everything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Flare.Hosting.Aspire
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;flare&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFlare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flare"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MyApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"myapi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForFlare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flare&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the project itself, the &lt;code&gt;Flare.Aspire&lt;/code&gt; client package reads that reference and registers OTLP exporters for logs, traces and metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFlareOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flare"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sits next to whatever OpenTelemetry setup you already have, so the Aspire dashboard keeps working too. Flare starts and stops with the rest of your app like any other resource.&lt;/p&gt;

&lt;p&gt;For the opposite case, where you have several unrelated projects on one machine and want a single Flare instance they all share, there's a global tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; Flare.Cli
flare start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;The logs explorer has full-text and structured search, a facet sidebar, live tail over WebSocket, and pattern grouping, which collapses thousands of similar lines into one template like &lt;code&gt;User &amp;lt;*&amp;gt; logged in from &amp;lt;*&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Traces get a waterfall and a flame graph, a service map built from span data, and search by trace structure, so you can ask for traces where service A calls service B and B then fails. Logs and traces are linked in both directions through the trace ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc1cz1ixcx7s41jd29fy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdc1cz1ixcx7s41jd29fy.webp" alt="A trace waterfall with the slowest spans listed on top and the critical path highlighted" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Metrics come in as regular OTLP metrics (gauges, sums, histograms and exponential histograms). You can chart them in the explorer or build custom dashboards with variables, formula panels and collapsible rows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofds4otx32jdp6ielzlq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fofds4otx32jdp6ielzlq.webp" alt="The metrics explorer showing p50, p90 and p99 of a histogram metric" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alert rules can fire on log counts, metric thresholds, exception counts, missing data or anomalies, and they notify Slack, Telegram, email, PagerDuty or a plain webhook. Maintenance windows mute them during planned work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8344lkqqexvnfcm8qs3v.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8344lkqqexvnfcm8qs3v.webp" alt="Creating a log-count alert rule with a threshold, a cooldown and a Slack webhook" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are also pages built on standard OpenTelemetry data for things people usually want next: an exceptions view, hosts from the collector's hostmetrics receiver, Kubernetes nodes and pods, message queues, and outbound calls to external APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's put together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your apps --OTLP (gRPC :4317 / HTTP :4318)--&amp;gt; Flare.Ingest
    --&amp;gt; Redis Streams (buffer) --&amp;gt; ClickHouse
                                       ^
                          Flare.Api (search, aggregate, live tail, alerts)
                                       ^
                              SvelteKit dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the decisions behind it that I'd want to know about before trusting a tool with my telemetry.&lt;/p&gt;

&lt;h3&gt;
  
  
  OTLP is the only way in
&lt;/h3&gt;

&lt;p&gt;I didn't write a Serilog sink or an NLog target. Every logging library already has a maintained OpenTelemetry exporter, so Flare accepts OTLP and nothing else. That keeps the ingest side small, and it means switching away from Flare later is a config change in your app, not a code change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ingest writes to Redis Streams before ClickHouse
&lt;/h3&gt;

&lt;p&gt;ClickHouse wants large batched inserts, so something has to hold events between the OTLP request and the flush. An in-memory buffer would lose everything in flight whenever Ingest restarts or redeploys. Redis Streams gives a durable queue with consumer groups: the flush worker reads with &lt;code&gt;XREADGROUP&lt;/code&gt; and only acknowledges after ClickHouse has the batch, so delivery is at least once. It isn't zero-loss. The bundled Redis saves RDB snapshots (at most every 30 seconds, not an append-only log), so a Redis crash can lose events that arrived since the last snapshot, and a retry after a failed acknowledgement can produce a duplicate row. I'd rather say that here than have someone find out in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ClickHouse schema follows the queries
&lt;/h3&gt;

&lt;p&gt;The logs table is ordered by &lt;code&gt;(ServiceName, SeverityNumber, Timestamp, TraceId)&lt;/code&gt;, going from low to high cardinality and leading with the two filters people pick most in the dashboard. That lets ClickHouse skip most of the data for a typical "errors from checkout in the last hour" search. Every query also carries execution limits (time, rows read, result size), because a self-hosted ClickHouse has none by default and one unfiltered search shouldn't be able to take the server down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern grouping runs once, at flush time
&lt;/h3&gt;

&lt;p&gt;Log templates are computed with the Drain algorithm right before each batch is written, and stored as columns next to the log line. Grouping by pattern later is a plain &lt;code&gt;GROUP BY&lt;/code&gt; instead of re-clustering millions of rows on every query.&lt;/p&gt;

&lt;p&gt;All of these are written up as architecture decision records in the repo (there are 73 so far), including the alternatives I rejected. If you disagree with one, that's the place to argue with me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;Flare is young and moving fast. The Docker images, the Aspire integration and the CLI are published and usable, and a multi-node ClickHouse setup is available for larger installs. The biggest open item on the roadmap is retention and cold storage to S3-compatible object storage.&lt;/p&gt;

&lt;p&gt;If you try it, I'd like to hear what broke, what was missing, or what felt more complicated than it should be. Issues and discussions are open on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aminparsa18/Flare.Net" rel="noopener noreferrer"&gt;https://github.com/aminparsa18/Flare.Net&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
