<?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: Samuel Dahunsi</title>
    <description>The latest articles on DEV Community by Samuel Dahunsi (@psalmuel1st).</description>
    <link>https://dev.to/psalmuel1st</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%2F4032384%2Fa7b6986a-ddb9-41f9-9118-7f5df3e2340d.jpg</url>
      <title>DEV Community: Samuel Dahunsi</title>
      <link>https://dev.to/psalmuel1st</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/psalmuel1st"/>
    <language>en</language>
    <item>
      <title>Building a Drop-in SLO &amp; Error-Budget Pack for SigNoz Across Traces, Metrics, and Logs</title>
      <dc:creator>Samuel Dahunsi</dc:creator>
      <pubDate>Sun, 26 Jul 2026 10:44:58 +0000</pubDate>
      <link>https://dev.to/psalmuel1st/building-a-drop-in-slo-error-budget-pack-for-signoz-across-traces-metrics-and-logs-4321</link>
      <guid>https://dev.to/psalmuel1st/building-a-drop-in-slo-error-budget-pack-for-signoz-across-traces-metrics-and-logs-4321</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a production-grade observability pack with multi-window burn rate alerts, custom ClickHouse dashboards, and automated fault-injection drills for the Agents of SigNoz Hackathon.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hook: Why Most SLO Setup Fails in Practice
&lt;/h2&gt;

&lt;p&gt;Setting up Service Level Objectives (SLOs) sounds simple on paper: pick a target like 99.9% availability, track errors, and stop shipping features when your budget burns out. &lt;/p&gt;

&lt;p&gt;In reality, most teams hit a wall. Raw APM tools show request counts and average latencies, but they don’t tell you &lt;strong&gt;how fast you are consuming your monthly error budget&lt;/strong&gt; or whether a 5-minute fault requires waking up an engineer on call. Worse, configuring multi-window burn rate alerts across three separate telemetry signals (Traces, Metrics, and Logs) usually takes days of custom ClickHouse query tuning and YAML wrangling.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;Agents of SigNoz Hackathon (Track 02: Signals &amp;amp; Dashboards)&lt;/strong&gt;, I wanted to solve this permanently by building the &lt;strong&gt;SigNoz SLO &amp;amp; Error-Budget Pack&lt;/strong&gt;: an all-in-one, drop-in observability suite that instruments Node.js/Express applications across all three OpenTelemetry signals and automatically ships three production-grade dashboards and multi-window burn alerts into SigNoz.&lt;/p&gt;

&lt;p&gt;Here is how I built it, the mathematical decisions behind the error budget burn rates, and how to test it end-to-end with live-fire fault injection drills.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: The 3-Signal Telemetry Pipeline
&lt;/h2&gt;

&lt;p&gt;To make SLO calculation accurate, relying on just one signal is a mistake. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; provide high-frequency RED (Rate, Errors, Duration) counters without sampling overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traces&lt;/strong&gt; expose p99 latency and child-span performance across downstream API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt; provide human-readable diagnostic context when an incident occurs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is how data flows through the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌─────────────────────────────────────────┐
                  │          demo-service (Express)         │
                  │  - tracing.js (OTel SDK)                │
                  │  - RED Middleware (http_requests_total) │
                  │  - Winston Logger (JSON + trace_id)     │
                  └────────────────────┬────────────────────┘
                                       │ OTLP / gRPC &amp;amp; HTTP
                                       ▼
                  ┌─────────────────────────────────────────┐
                  │       SigNoz OpenTelemetry Collector    │
                  └────────────────────┬────────────────────┘
                                       │ ClickHouse Tables
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                             SigNoz Engine                                   │
│  ┌───────────────────────┐ ┌────────────────────────┐ ┌──────────────────┐  │
│  │ Service Health        │ │ SLO &amp;amp; Error Budget     │ │ Cost Efficiency  │  │
│  │ Dashboard             │ │ Dashboard              │ │ Dashboard        │  │
│  └───────────────────────┘ └────────────────────────┘ └──────────────────┘  │
│                                      │                                      │
│                                      ▼                                      │
│                    Multi-Window Alert Rules (API v2)                        │
│                                      │ Webhook Payload                      │
│                                      ▼                                      │
│                     Webhook Catcher Service (:3002)                         │
└─────────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. Instrumenting the Three Signals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. RED Metrics Middleware
&lt;/h3&gt;

&lt;p&gt;Rather than relying solely on trace sampling for request rates, I implemented custom RED metrics in &lt;code&gt;metrics.js&lt;/code&gt; using &lt;code&gt;@opentelemetry/api-metrics&lt;/code&gt;. Every HTTP request is captured by an Express middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// demo-service/index.js&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finish&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;recordRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;durationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This records three crucial series into SigNoz’s &lt;code&gt;signoz_metrics.distributed_time_series_v4&lt;/code&gt; table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http_requests_total&lt;/code&gt;: Counter partitioned by &lt;code&gt;route&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt;, and &lt;code&gt;status_class&lt;/code&gt; (&lt;code&gt;2xx&lt;/code&gt;, &lt;code&gt;4xx&lt;/code&gt;, &lt;code&gt;5xx&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http_request_duration&lt;/code&gt;: Histogram tracking bucketed latency distributions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;downstream_errors_total&lt;/code&gt;: Counter tracking third-party API dependencies (&lt;code&gt;catfact&lt;/code&gt;, &lt;code&gt;dogimg&lt;/code&gt;, &lt;code&gt;joke&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  B. Nested Traces with Downstream Attributes
&lt;/h3&gt;

&lt;p&gt;For distributed tracing, the &lt;code&gt;/data&lt;/code&gt; endpoint fans out requests to multiple external APIs in parallel. To observe downstream degradation without breaking root trace spans, each downstream call is wrapped in a explicit child span:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slo-pack-demo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`downstream &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttributes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;downstream.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http.response.status_code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;downstream.duration_ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;recordDownstreamError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttributes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;downstream.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  C. 100% Trace-Correlated Logs
&lt;/h3&gt;

&lt;p&gt;One of the biggest friction points in debugging alerts is jumping from a metrics spike to log output. By configuring &lt;code&gt;winston&lt;/code&gt; alongside &lt;code&gt;@opentelemetry/instrumentation-winston&lt;/code&gt;, trace context is automatically injected into every stdout log line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"handling /error -&amp;gt; 500"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"slo-pack-demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4bf92f3577b34da6a3ce929d0e0e4736"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"span_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00f067aa0ba902b7"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the SigNoz UI, clicking any log entry instantly renders the exact visual trace waterfall.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Deriving the SLO &amp;amp; Burn Rate Mathematics
&lt;/h2&gt;

&lt;p&gt;Google’s SRE Handbook recommends multi-window, multi-threshold alerting to avoid alert fatigue. Instead of triggering an alert the second error rate hits 0.1%, we measure &lt;strong&gt;how fast the error budget is burning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a 30-day SLO window with a &lt;strong&gt;99.9% Availability Target&lt;/strong&gt;:&lt;br&gt;
$$\text{Allowed Unreliability } (U) = 1 - 0.999 = 0.001 \quad (0.1\%)$$&lt;/p&gt;
&lt;h3&gt;
  
  
  Fast-Burn Alert (Critical)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt;: Catch massive outages that would consume 2% of the total monthly error budget in 1 hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burn Rate Formula&lt;/strong&gt;: 
$$\text{Burn Rate} = \frac{\text{Budget Consumed}}{\text{Time Ratio}} = \frac{0.02}{1 \text{ hour} / 720 \text{ hours}} = 14.4$$&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt;: If the current 1-hour burn rate exceeds &lt;strong&gt;14.4x&lt;/strong&gt;, trigger a &lt;strong&gt;Critical Alert&lt;/strong&gt; immediately.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Slow-Burn Alert (Warning)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal&lt;/strong&gt;: Catch persistent, subtle error leaks that consume 5% of the error budget over 6 hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burn Rate Formula&lt;/strong&gt;:
$$\text{Burn Rate} = \frac{0.05}{6 \text{ hours} / 720 \text{ hours}} = 6.0$$&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt;: If the 6-hour burn rate exceeds &lt;strong&gt;6.0x&lt;/strong&gt;, trigger a &lt;strong&gt;Warning Alert&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These exact mathematical expressions were converted into SigNoz API v2 alert definitions under &lt;code&gt;alerts/fast-burn.json&lt;/code&gt;, &lt;code&gt;alerts/slow-burn.json&lt;/code&gt;, and &lt;code&gt;alerts/latency-slo.json&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. The 3 Custom Dashboards
&lt;/h2&gt;

&lt;p&gt;The pack includes three pre-configured dashboards in &lt;code&gt;dashboards/&lt;/code&gt;:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Service Health Dashboard
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request Throughput&lt;/strong&gt;: Real-time req/sec grouped by status class (&lt;code&gt;2xx&lt;/code&gt; vs &lt;code&gt;5xx&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency Percentiles&lt;/strong&gt;: p50, p90, and p99 derived directly from trace span durations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downstream Error Tracker&lt;/strong&gt;: Isolates third-party API failures from internal app errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. SLO / Error Budget Dashboard
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Availability SLI %&lt;/strong&gt;: Computed in real-time as &lt;code&gt;(1 - (sum(5xx) / sum(total))) * 100&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remaining Error Budget %&lt;/strong&gt;: Gauge chart highlighting budget degradation before breach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burn Rate Gauges&lt;/strong&gt;: Live 14.4x fast-burn and 6.0x slow-burn indicators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency SLI %&lt;/strong&gt;: Percentage of requests completing under the 6-second p99 ceiling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3. Cost-Efficiency &amp;amp; Waste Dashboard
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Waste Ratio %&lt;/strong&gt;: Quantifies wasted compute spent servicing HTTP 5xx responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waste Index&lt;/strong&gt;: A hybrid metric ($Latency \times ErrorRatio$) pointing out high-latency, error-prone endpoints that consume disproportionate server resources.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  4. Automated Verification &amp;amp; Live-Fire Alert Drills
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Automated Test Harness
&lt;/h3&gt;

&lt;p&gt;To ensure the pack remains reliable across environments, I wrote a verification script (&lt;code&gt;scripts/verify-p1.sh&lt;/code&gt;) that queries both the SigNoz REST API and ClickHouse database:&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="nv"&gt;SIGNOZ_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dahunsisamuel1st@gmail.com"&lt;/span&gt; &lt;span class="nv"&gt;SIGNOZ_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt; ./scripts/verify-p1.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== A. Stack &amp;amp; reproducibility (PRD §7.6)
PASS  A1   SigNoz UI + API healthy
PASS  A2   OTLP HTTP ingest endpoint accepting
PASS  A3   demo service /healthz responds
PASS  A4   webhook catcher up
PASS  A5   casting.yaml + lock committed
== B. All three signals w/ required shape
PASS  B1   traces landing (fresh &amp;lt;2m)
PASS  B2   downstream child spans carry custom attrs
PASS  B3   http_requests_total with route/status_class labels
PASS  B4   http_request_duration histogram series present
PASS  B5   downstream_errors_total with downstream label
PASS  B6   metric samples fresh (&amp;lt;2m)
PASS  B7   logs landing (fresh &amp;lt;2m)
PASS  B8   &amp;gt;=95% of app logs trace-correlated
== C. Alert pack
PASS  C1   webhook notification channel registered
PASS  C2   rule 'SLO fast burn' exists w/ severity=critical
PASS  C3   rule 'SLO slow burn' exists w/ severity=warning
PASS  C4   rule 'Latency SLO breach' exists w/ severity=warning
PASS  C5   no rule stuck without state
== D. Paper requirements
PASS  D1   AI_USAGE.md present
PASS  D2   alert payloads committed
PASS  D3   SLO math documented

passed: 21  failed: 0  skipped-sections: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Live-Fire Fault Injection
&lt;/h3&gt;

&lt;p&gt;To test alerting end-to-end without waiting for natural outages, I built environment knobs into the containerized service:&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="c"&gt;# Inject 90% error rate — forces burn rate to ~18x&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;demo-service
&lt;span class="nv"&gt;ERROR_RATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.9 docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; demo-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;Availability SLI&lt;/strong&gt; dropped on the SLO Dashboard.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;SLO Fast-Burn Rule&lt;/strong&gt; turned red (&lt;code&gt;FIRING&lt;/code&gt;) in SigNoz.&lt;/li&gt;
&lt;li&gt;The standalone &lt;strong&gt;Webhook Catcher&lt;/strong&gt; (&lt;code&gt;http://localhost:3002/&lt;/code&gt;) logged the incoming incident JSON payload:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"firing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alerts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"alertname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SLO fast burn — availability error budget"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"slo-pack-demo"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"annotations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"High availability error budget burn rate detected"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resetting the container restores normal operation immediately:&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="nv"&gt;ERROR_RATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.1 &lt;span class="nv"&gt;SLOW_MAX_MS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000 docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; demo-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Lessons Learned &amp;amp; Gotchas
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ClickHouse JSON Ingestion Behavior&lt;/strong&gt;: When querying &lt;code&gt;signoz_logs.distributed_logs_v2&lt;/code&gt;, &lt;code&gt;resources_string['service.name']&lt;/code&gt; must match exact string casing. Using &lt;code&gt;LOWER()&lt;/code&gt; or un-indexed metadata queries can degrade query times during dashboard renders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bash Execution &amp;amp; &lt;code&gt;curl -sf&lt;/code&gt;&lt;/strong&gt;: When automating API provisioning scripts under &lt;code&gt;set -e&lt;/code&gt;, avoid using &lt;code&gt;curl -sf&lt;/code&gt; on authentication endpoints. Standard HTTP 401 responses will cause &lt;code&gt;curl&lt;/code&gt; to exit non-zero silently without outputting SigNoz's JSON error response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trace-Log Context Ingestion&lt;/strong&gt;: OpenTelemetry Winston instrumentation requires standard stdout JSON formatting to reliably extract &lt;code&gt;trace_id&lt;/code&gt; and &lt;code&gt;span_id&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion &amp;amp; Links
&lt;/h2&gt;

&lt;p&gt;Building this pack demonstrated how powerful OpenTelemetry and SigNoz are when all three signals work in unison. Instead of staring at disconnected graphs during an incident, engineering teams get a single pane of glass showing &lt;strong&gt;budget remaining, exact firing alerts, and trace-correlated logs&lt;/strong&gt; within seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/Psalmuel01/signoz-slo-pack" rel="noopener noreferrer"&gt;signoz-slo-pack&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SigNoz Documentation&lt;/strong&gt;: &lt;a href="https://signoz.io/docs" rel="noopener noreferrer"&gt;https://signoz.io/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenTelemetry Documentation&lt;/strong&gt;: &lt;a href="https://opentelemetry.io/docs" rel="noopener noreferrer"&gt;https://opentelemetry.io/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>signoz</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>What a Corrupted ClickHouse Binary Taught Me About Self-Hosting SigNoz</title>
      <dc:creator>Samuel Dahunsi</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:58:33 +0000</pubDate>
      <link>https://dev.to/psalmuel1st/what-a-corrupted-clickhouse-binary-taught-me-about-self-hosting-signoz-15f2</link>
      <guid>https://dev.to/psalmuel1st/what-a-corrupted-clickhouse-binary-taught-me-about-self-hosting-signoz-15f2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Three crash-looping containers, one corrupted Docker image, and a trace waterfall that made the entire detour worthwhile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My SigNoz containers kept crash-looping with &lt;code&gt;CANNOT_PARSE_ELF&lt;/code&gt; and &lt;code&gt;Segmentation fault&lt;/code&gt;. The install guide doesn't mention either error, because neither is SigNoz's fault. Both are what happens when your machine dies mid &lt;code&gt;docker pull&lt;/code&gt;. This is the full trail: what broke, why, and how I got traces flowing anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I was doing this
&lt;/h2&gt;

&lt;p&gt;I wanted to actually understand observability, not just read about it. The plan was simple: self-host SigNoz, point a real app at it, and poke at traces, logs, and dashboards until the concepts clicked.&lt;/p&gt;

&lt;p&gt;Quick vocabulary, since these words get thrown around a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry&lt;/strong&gt; is the data an app emits about what it's doing while it runs, essentially a flight recorder for software.&lt;/li&gt;
&lt;li&gt;It comes in three flavors: &lt;strong&gt;traces&lt;/strong&gt; (the journey of one request through your system), &lt;strong&gt;metrics&lt;/strong&gt; (numbers over time, like requests/sec), and &lt;strong&gt;logs&lt;/strong&gt; (the text your app prints).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenTelemetry (OTel)&lt;/strong&gt; is the vendor-neutral standard for formatting and shipping that data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SigNoz&lt;/strong&gt; receives all of it, stores it, and gives you dashboards, search, and alerts so you can make sense of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My setup: a Mac, Docker Desktop, and SigNoz's newer installer, Foundry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The clean install (this part is easy)
&lt;/h2&gt;

&lt;p&gt;SigNoz has moved off the old &lt;code&gt;docker-compose&lt;/code&gt; instructions to a CLI called Foundry. Three steps:&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="c"&gt;# 1. Install the Foundry CLI (I downloaded and read it before running it)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://signoz.io/foundry.sh &lt;span class="nt"&gt;-o&lt;/span&gt; foundry.sh
bash foundry.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 2. A tiny config file, casting.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Installation&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signoz&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;flavor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compose&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 3. Deploy&lt;/span&gt;
foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; casting.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That generates a real Compose file under &lt;code&gt;pours/deployment/&lt;/code&gt; and starts every container SigNoz needs: ClickHouse (the database), ClickHouse Keeper (coordination), Postgres (metadata), an OTel Collector (the thing your app sends data to), and the SigNoz app itself.&lt;/p&gt;

&lt;p&gt;That part is in the docs. Here's the part that isn't.&lt;/p&gt;

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

&lt;p&gt;My first deploy got interrupted while Docker was still pulling images. When I brought the stack back up, three containers went into a crash-loop. The logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Code: 464. DB::Exception: The ELF '/usr/bin/clickhouse' doesn't have
string table with section names. (CANNOT_PARSE_ELF)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Segmentation fault
Segmentation fault
Segmentation fault
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initdb: error: directory "/var/lib/postgresql/data" exists but is not empty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three different services, three different errors. It looked like SigNoz was badly broken. It wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The diagnosis
&lt;/h2&gt;

&lt;p&gt;The common thread is that all three failures are about &lt;em&gt;half-written files&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Docker Desktop on a Mac doesn't run containers directly on macOS. It runs a hidden Linux VM, and every image, container, and database volume lives on one big virtual disk inside that VM. When Docker (or the whole machine) is killed abruptly instead of shut down cleanly, whatever was mid-write gets left truncated, the same way a file can end up corrupted if you yank a USB stick out while it's still saving.&lt;/p&gt;

&lt;p&gt;Mapping the errors back:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;What actually happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CANNOT_PARSE_ELF&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The ClickHouse binary inside the image was written only partway, so Linux can't parse it as an ELF file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Segmentation fault&lt;/code&gt; (collector)&lt;/td&gt;
&lt;td&gt;Same story: an incomplete binary crashing the instant it runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres &lt;code&gt;directory exists but is not empty&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Postgres got interrupted mid-initialization and left a half-built data folder&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of this is a SigNoz bug. It's what an interrupted write looks like at three different layers of the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;The recovery recipe is the same every time: delete the corrupted artifact and recreate it from scratch.&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="c"&gt;# Stop everything&lt;/span&gt;
docker compose down

&lt;span class="c"&gt;# Remove the corrupted images so they re-pull cleanly&lt;/span&gt;
docker rmi &lt;span class="nt"&gt;-f&lt;/span&gt; clickhouse/clickhouse-server:25.12.5
docker rmi &lt;span class="nt"&gt;-f&lt;/span&gt; signoz/signoz-otel-collector:latest

&lt;span class="c"&gt;# Remove the half-initialized Postgres volume&lt;/span&gt;
docker volume &lt;span class="nb"&gt;rm &lt;/span&gt;signoz-metastore-postgres-0-data

&lt;span class="c"&gt;# Re-pull and verify the binary is actually intact this time&lt;/span&gt;
docker compose pull
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; clickhouse &lt;span class="se"&gt;\&lt;/span&gt;
  clickhouse/clickhouse-server:25.12.5 &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; ClickHouse local version 25.12.5.44 (official build)   ✅&lt;/span&gt;

&lt;span class="c"&gt;# Bring it back up&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last verify step is the one I'd skip in a hurry, and the one I'd regret skipping. Checking the binary before starting the stack turns a 20-minute crash-loop mystery into a five-second yes/no.&lt;/p&gt;

&lt;p&gt;After that, every container came up healthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;signoz-signoz-0                            Up (healthy)
signoz-telemetrystore-clickhouse-0-0       Up (healthy)
signoz-metastore-postgres-0                Up (healthy)
signoz-telemetrykeeper-clickhousekeeper-0  Up (healthy)
signoz-ingester-1                          Up
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SigNoz UI came up at &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;the SigNoz home dashboard showing traces and metrics ingestion both active&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F43zfrg57jvf1p359cs8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F43zfrg57jvf1p359cs8m.png" alt="home dashboard" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting real data in
&lt;/h2&gt;

&lt;p&gt;An empty observability tool is useless, so I grabbed SigNoz's Node.js sample app, which is already OpenTelemetry-instrumented. It has one endpoint, &lt;code&gt;/data&lt;/code&gt;, that calls three public APIs in parallel: a cat fact, a dog photo, and a joke.&lt;/p&gt;

&lt;p&gt;The only change needed was pointing it at my local collector instead of SigNoz Cloud, in &lt;code&gt;tracing.js&lt;/code&gt;. Hitting the endpoint directly confirms what it returns:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;the /data endpoint's JSON response showing catFact, dogImage, and joke fields&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi3n19xj7iggpq9bnqwcz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi3n19xj7iggpq9bnqwcz.png" alt="data endpoint response" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exporterOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://host.docker.internal:4318/v1/traces&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;host.docker.internal&lt;/code&gt; is how a container reaches a service running on the host machine, in this case the OTel Collector listening on port 4318.&lt;/p&gt;

&lt;p&gt;One gotcha worth saving you the hour I lost: on my network, the app's outbound calls kept failing with &lt;code&gt;connect ENETUNREACH&lt;/code&gt; because Node was resolving the APIs to IPv6 addresses my router wasn't routing. The fix was one line in the Compose file to prefer IPv4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_OPTIONS=--dns-result-order=ipv4first&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a build, and a burst of traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 20&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:3000/data&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The feature I liked most: the trace waterfall
&lt;/h2&gt;

&lt;p&gt;This is where tracing earned its keep. A burst of traffic against &lt;code&gt;/data&lt;/code&gt; lands as a flat list first:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;the Traces Explorer list view showing spans from a burst of traffic against /data&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftl5fsnssn78z1s23rkqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftl5fsnssn78z1s23rkqh.png" alt="traces list" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click into one trace and the &lt;code&gt;/data&lt;/code&gt; endpoint fires three API calls at once with &lt;code&gt;Promise.all&lt;/code&gt;, and the trace waterfall shows exactly that: a parent &lt;code&gt;GET /data&lt;/code&gt; span with three child spans running side by side.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;the /data trace waterfall showing a parent span with three parallel child spans&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8h42njhstl8l5lgp2r8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8h42njhstl8l5lgp2r8r.png" alt="trace waterfall" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The insight lands in one glance. Because the three calls are parallel, the total response time isn't their sum, it's whichever single call was slowest. When a request was slow, I could see which of the three APIs dragged it, not guess. That's the whole pitch of distributed tracing, made concrete in one screen.&lt;/p&gt;

&lt;p&gt;The part that genuinely surprised me: I wrote &lt;strong&gt;zero&lt;/strong&gt; tracing code to get this. No manual spans, no timers. &lt;code&gt;getNodeAutoInstrumentations()&lt;/code&gt; wrapped Express and axios automatically and produced the entire waterfall, including each outbound call's URL, method, and status code as span attributes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;a single GET span expanded, showing auto-captured HTTP attributes like http.method, http.url, and http.status_code&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdphjfebnevrdkflp8556.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdphjfebnevrdkflp8556.png" alt="span attributes" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked, what didn't
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Worked:&lt;/strong&gt; traces, immediately and impressively. 218 spans from one burst of traffic, waterfalls rendering with parallel child spans and full HTTP attributes, no custom instrumentation code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Also worked:&lt;/strong&gt; metrics. &lt;code&gt;http.client.duration.bucket&lt;/code&gt; and &lt;code&gt;http.server.duration.bucket&lt;/code&gt; both showed up in the Metrics Explorer with thousands of samples, no extra config beyond what Foundry sets up by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;[the Metrics Explorer showing http.client.duration.bucket and http.server.duration.bucket with 12.1K+ and 3.2K+ samples]&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxo1b47ko1x0224crzdnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxo1b47ko1x0224crzdnu.png" alt="metrics explorer" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Didn't (yet):&lt;/strong&gt; logs. I wired up an OTel log exporter too, but it didn't land in SigNoz on the first pass while traces and metrics did. Traces, logs, and metrics are three separate export pipelines, and getting two working doesn't mean the third is, a lesson I'd rather learn on a toy app than in production. That's my next debugging session.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The big one:&lt;/strong&gt; an abrupt shutdown can corrupt Docker's VM disk, and the fix is always the same: delete the broken image or volume and recreate it. If your machine has ever hung and forced you to hard-quit, this is worth filing away for next time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The install is genuinely three commands. The real learning was in the failure: reading &lt;code&gt;CANNOT_PARSE_ELF&lt;/code&gt;, a segfault, and a Postgres init error, and realizing they were all the same problem wearing different masks. Traces made my toy app's behavior obvious at a glance, and I got there without writing a line of instrumentation. Metrics came through cleanly too. Logs are the unfinished piece, which feels like the right note to end a first-week-with-a-new-tool post on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SigNoz docs: &lt;a href="https://signoz.io/docs" rel="noopener noreferrer"&gt;https://signoz.io/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Foundry install guide: &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;https://signoz.io/docs/install/docker/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenTelemetry: &lt;a href="https://opentelemetry.io" rel="noopener noreferrer"&gt;https://opentelemetry.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SigNoz Node.js sample app: &lt;a href="https://github.com/SigNoz/sample-nodejs-app" rel="noopener noreferrer"&gt;https://github.com/SigNoz/sample-nodejs-app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>signoz</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
