<?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: Mohammad Mudassir</title>
    <description>The latest articles on DEV Community by Mohammad Mudassir (@mohammad_mudassir_8dc0d0f).</description>
    <link>https://dev.to/mohammad_mudassir_8dc0d0f</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%2F4034397%2F7af6834c-2ab3-4399-91f3-a8d3a4a42571.png</url>
      <title>DEV Community: Mohammad Mudassir</title>
      <link>https://dev.to/mohammad_mudassir_8dc0d0f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohammad_mudassir_8dc0d0f"/>
    <language>en</language>
    <item>
      <title>My First 3 Hours With SigNoz</title>
      <dc:creator>Mohammad Mudassir</dc:creator>
      <pubDate>Fri, 17 Jul 2026 20:34:42 +0000</pubDate>
      <link>https://dev.to/mohammad_mudassir_8dc0d0f/my-first-3-hours-with-signoz-1d86</link>
      <guid>https://dev.to/mohammad_mudassir_8dc0d0f/my-first-3-hours-with-signoz-1d86</guid>
      <description>&lt;p&gt;It was 8:14 PM and I had one goal: run &lt;code&gt;docker compose up&lt;/code&gt;, the command every tutorial swears by, and get SigNoz running. Three hours later I had live traces, a dashboard, and an alert going off because I made it go off. Along the way I hit a deprecated install path, a query builder that rejected my filter, and an alert that happily fired on completely healthy traffic.&lt;/p&gt;

&lt;p&gt;Here's the one thing I'd tell you up front: &lt;strong&gt;a single broken request can show up as four "errors" in SigNoz.&lt;/strong&gt; I didn't know that, and it quietly wrecked my first alert. Keep it in mind the whole way through.&lt;/p&gt;

&lt;h2&gt;
  
  
  My exact setup
&lt;/h2&gt;

&lt;p&gt;A couple of the problems below only happen on specific machines, so:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chip&lt;/td&gt;
&lt;td&gt;Apple M3 (arm64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;macOS 26.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;16 GB (7.65 GB given to Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;28.0.4, Compose v2.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;3.10.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Hour 1: the install that wasn't &lt;code&gt;docker compose up&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;My first problem happened before I typed a single SigNoz command. &lt;code&gt;docker info&lt;/code&gt; gave me &lt;em&gt;"Cannot connect to the Docker daemon."&lt;/em&gt; Docker Desktop wasn't running. Started it, moved on.&lt;/p&gt;

&lt;p&gt;Then the plan: clone the repo, run compose.&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 &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;-b&lt;/span&gt; main https://github.com/SigNoz/signoz.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I went hunting for &lt;code&gt;deploy/docker/docker-compose.yaml&lt;/code&gt;, the file basically every guide points you to. It wasn't there. The &lt;code&gt;deploy/&lt;/code&gt; folder had three files, and the README explained why:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The &lt;code&gt;install.sh&lt;/code&gt; script and the &lt;code&gt;docker-compose&lt;/code&gt; manifests have been deprecated. SigNoz now installs and runs through &lt;strong&gt;Foundry&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was the most useful thing I learned all night, and it's buried under a pile of outdated search results: as of 2026, &lt;code&gt;docker compose up&lt;/code&gt; is no longer how you run SigNoz. The compose file still exists. A tool called Foundry just generates it for you.&lt;/p&gt;

&lt;p&gt;Rather than guess, I followed the official docs at &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;signoz.io/docs/install/docker&lt;/a&gt;. Foundry is an open source CLI (&lt;code&gt;foundryctl&lt;/code&gt;) that "runs your self-hosted observability stack as code." Install it:&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://signoz.io/foundry.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I read the script first (worth doing with any &lt;code&gt;curl | bash&lt;/code&gt;). It installs to &lt;code&gt;~/.local/bin&lt;/code&gt;, no sudo. Then a config file:&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="c1"&gt;# 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;p&gt;I ran &lt;code&gt;foundryctl forge -f casting.yaml&lt;/code&gt; first, just to see what it builds, and there was my compose file at &lt;code&gt;pours/deployment/compose.yaml&lt;/code&gt;. Reading it was worth the detour, because the 2026 stack looks nothing like the old tutorials: the UI and query engine are one &lt;code&gt;signoz/signoz&lt;/code&gt; container on port 8080, telemetry comes in through a &lt;code&gt;signoz-otel-collector&lt;/code&gt; ingester on 4317/4318, storage is &lt;code&gt;clickhouse-server:25.12.5&lt;/code&gt;, and coordination is now &lt;strong&gt;ClickHouse Keeper, not Zookeeper&lt;/strong&gt;. If a guide mentions Zookeeper, it's out of date.&lt;/p&gt;

&lt;p&gt;Then the real command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&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;First run pulls every image and went from nothing to a healthy stack in 3 minutes and 9 seconds. Then it tried to scare me: &lt;code&gt;docker ps -a&lt;/code&gt; showed two containers as &lt;code&gt;Exited (0)&lt;/code&gt;. My first reaction was "something crashed." It didn't. The migrator and init containers are one-shot jobs that run once, set up the schema, and stop. Exit code 0 means it worked. Leave them alone.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl localhost:8080&lt;/code&gt; returned a 200. I made an admin account, and there it was.&lt;/p&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%2F04rjzgvcwuefjewst3nt.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%2F04rjzgvcwuefjewst3nt.png" alt="img1" width="800" height="462"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;"You're not sending any data yet." True. SigNoz is running, but it's got nothing to show until you feed it.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Hour 2: sending real data with OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;I wrote the smallest app that would still produce an interesting trace. A Flask &lt;code&gt;/rolldice&lt;/code&gt; endpoint that calls a downstream &lt;code&gt;/roll&lt;/code&gt; on itself over HTTP, with random latency and a deliberate 15% error rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.py (trimmed)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rolldice.manual&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/roll&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;roll&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;          &lt;span class="c1"&gt;# real latency
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                      &lt;span class="c1"&gt;# real errors, ~15%
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the die fell off the table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/rolldice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rolldice&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rolldice.handler&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;player.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/roll&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dice.value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For instrumentation I used OpenTelemetry's zero-code agent, with commands pulled straight from &lt;a href="https://opentelemetry.io/docs/zero-code/python/" rel="noopener noreferrer"&gt;opentelemetry.io/docs/zero-code/python&lt;/a&gt; instead of memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second command scans what you've installed and pulls matching instrumentation, in my case Flask and requests. Then you run the app through the agent:&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;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rolldice &lt;span class="se"&gt;\&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:4318 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http/protobuf &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;otlp &lt;span class="se"&gt;\&lt;/span&gt;
opentelemetry-instrument python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where macOS got me. Flask defaults to port 5000, and on macOS that's quietly owned by AirPlay Receiver. My app looked like it started but nothing answered. I moved it to 5001 and it was fine.&lt;/p&gt;

&lt;p&gt;I fired 40 requests. The logs showed no export errors, which is a good sign but not proof anything arrived, so I checked ClickHouse directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;signoz-telemetrystore-clickhouse-0-0 clickhouse-client &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"SELECT count() FROM signoz_traces.distributed_signoz_index_v3 WHERE serviceName='rolldice'"&lt;/span&gt;
&lt;span class="c"&gt;# 161&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;161 spans from 40 requests, so about 4 spans each. Remember that, it's the whole point of this post.&lt;/p&gt;

&lt;p&gt;Within seconds, &lt;code&gt;rolldice&lt;/code&gt; showed up under Services with real APM numbers I never configured.&lt;/p&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%2Fl79d1rv0mnd1m3t1l9pr.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%2Fl79d1rv0mnd1m3t1l9pr.png" alt="img3" width="800" height="175"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;P99 of 411 ms and a 16.10% error rate, auto-computed from my spans. The 16% lines up with the ~15% I put in the code, which told me the pipeline was real.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The payoff: my first trace
&lt;/h2&gt;

&lt;p&gt;I opened Traces, filtered to &lt;code&gt;service.name = rolldice&lt;/code&gt;, and clicked into one request. This is where it finally made sense.&lt;/p&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%2Fira2sf6ibbsbgy35nmov.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%2Fira2sf6ibbsbgy35nmov.png" alt="img6" width="798" height="173"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One request, four nested spans. &lt;code&gt;GET /rolldice&lt;/code&gt; calls my &lt;code&gt;rolldice.handler&lt;/code&gt;, which makes an outbound &lt;code&gt;GET&lt;/code&gt;, which hits &lt;code&gt;GET /roll&lt;/code&gt;. You can see exactly where the time goes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Clicking the &lt;code&gt;rolldice.handler&lt;/code&gt; span showed the attributes I set in code, now searchable in the UI.&lt;/p&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%2Fpft9jaln8ccaj38wby2a.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%2Fpft9jaln8ccaj38wby2a.png" alt="img8" width="800" height="233"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;&lt;code&gt;dice.value: 1&lt;/code&gt; and &lt;code&gt;player.name: "bob"&lt;/code&gt;, straight from my code.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  My first dashboard, and the error that stopped me
&lt;/h2&gt;

&lt;p&gt;I wanted a P99 latency chart. Time Series panel, data source Traces, &lt;code&gt;p99(duration_nano)&lt;/code&gt;, group by &lt;code&gt;name&lt;/code&gt;. In the filter box I typed what felt obvious, &lt;code&gt;service.name rolldice&lt;/code&gt;, and got two red errors:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;invalid_input&lt;/strong&gt;: Found 2 errors while parsing the search expression&lt;br&gt;
&lt;code&gt;full text search is not supported&lt;/code&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%2Fj0tlft2i1tui6iuv03b0.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%2Fj0tlft2i1tui6iuv03b0.png" alt="img9" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd given it a key and a value but no operator between them. Without the &lt;code&gt;=&lt;/code&gt;, it tried to treat &lt;code&gt;rolldice&lt;/code&gt; as a free text search, which the trace query builder doesn't do. Type the key, pick it from the dropdown, choose &lt;code&gt;=&lt;/code&gt;, then the value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;service.name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'rolldice'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F56t4isfepj3sdafq9c5a.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%2F56t4isfepj3sdafq9c5a.png" alt="img10" width="800" height="619"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The y-axis is in nanoseconds because the field is &lt;code&gt;duration_nano&lt;/code&gt;, so 400,000,000 is 400 ms. &lt;code&gt;GET /roll&lt;/code&gt; sits on top, which is where my &lt;code&gt;time.sleep&lt;/code&gt; lives.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Hour 3: my first alert, and why it screamed at healthy traffic
&lt;/h2&gt;

&lt;p&gt;I built a trace based alert: &lt;code&gt;service.name = 'rolldice'&lt;/code&gt;, &lt;code&gt;count()&lt;/code&gt;, notify when it goes above 10 in the last 10 minutes.&lt;/p&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%2Flxkrozsvwabjgw2b3uo9.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%2Flxkrozsvwabjgw2b3uo9.png" alt="img11" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The preview was wrong immediately. The count line sat around 20, permanently above my threshold of 10. This alert would fire forever, on traffic that was fine. The reason: &lt;code&gt;count()&lt;/code&gt; with no filter counts every span, successes included. I only cared about errors, so I added the flag (in my version it's &lt;code&gt;has_error&lt;/code&gt;, snake case):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rolldice'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;has_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The baseline dropped under the line. Now to trip it on command, I hit the app with a burst:&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;seq &lt;/span&gt;1 300 | xargs &lt;span class="nt"&gt;-P&lt;/span&gt; 25 &lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="o"&gt;{}&lt;/span&gt; curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null http://localhost:5001/rolldice
&lt;span class="c"&gt;# 250 OK, 50 errors, in about 3 seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SigNoz evaluates on an interval, so a minute later the rule flipped.&lt;/p&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%2Fmzvemfmt35e4vqfc4ta0.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%2Fmzvemfmt35e4vqfc4ta0.png" alt="img14" width="800" height="171"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Firing. My first alert, set off by my own burst.&lt;/em&gt;&lt;/p&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%2Fbin474rjkghlvve97io6.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%2Fbin474rjkghlvve97io6.png" alt="img15" width="800" height="381"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Two bursts, two spikes well over the red line, calm baseline in between.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The thing that actually clicked: error spans are not failed requests
&lt;/h2&gt;

&lt;p&gt;Something still bugged me. Even with the error filter, my "calm" baseline was 8 to 20 error spans a minute, far more than a low error rate should give. So I asked ClickHouse to break it down by span name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;signoz_traces&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;distributed_signoz_index_v3&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'rolldice'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hasError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&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;GET /rolldice     137
rolldice.handler  137
GET               137
GET /roll         137
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it was. One failed request produces four error spans, because the failure propagates up the whole trace: the downstream &lt;code&gt;/roll&lt;/code&gt; throws, then the requests client span, then my handler, then the root. So 137 failed requests became 548 error spans. My alert's numbers were inflated four times over, which is exactly why the "calm" baseline kept bumping the threshold.&lt;/p&gt;

&lt;p&gt;That "4 spans per request" number from earlier is why. The fix is to count what you actually mean, failed requests, by pinning the filter to the root span:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rolldice'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;has_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'GET /rolldice'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the chart is honest: a flat baseline of 2 to 4, and my two bursts as clean spikes.&lt;/p&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%2F5dq7erx9hh1069vyuyzn.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%2F5dq7erx9hh1069vyuyzn.png" alt="img16" width="800" height="443"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same alert, one extra filter. Now "count" means "failed requests," and the baseline sits where it should.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An alert is only as good as the query behind it. Counting every span in a broken trace tells you how deep your traces are, not how many requests failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell myself before I started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker compose up&lt;/code&gt; is not how you run SigNoz anymore. Use Foundry, or &lt;code&gt;foundryctl forge&lt;/code&gt; if you still want the raw compose file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Exited (0)&lt;/code&gt; is not a crash. The migrator and init containers run once and stop.&lt;/li&gt;
&lt;li&gt;On macOS, skip port 5000. AirPlay owns it. Use 5001.&lt;/li&gt;
&lt;li&gt;In the query builder, always put an operator between key and value.&lt;/li&gt;
&lt;li&gt;Confirm your data landed, don't just assume it sent. One &lt;code&gt;clickhouse-client&lt;/code&gt; count saved me a lot of guessing.&lt;/li&gt;
&lt;li&gt;Count requests, not spans. One broken request can create several error spans, so filter to the root span when your metric means "requests."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Three hours, one deprecated command I didn't expect, a few small mistakes, and one lesson about spans versus requests that I'll carry into every alert I write. SigNoz got me from an empty screen to a firing alert faster than I thought, and most of the friction was the useful kind that teaches you how the thing works underneath.&lt;/p&gt;

&lt;p&gt;If you're just starting: use the official docs at &lt;a href="https://signoz.io/docs/install/docker/" rel="noopener noreferrer"&gt;signoz.io/docs/install/docker&lt;/a&gt;, check your OpenTelemetry setup against &lt;a href="https://opentelemetry.io/docs/zero-code/python/" rel="noopener noreferrer"&gt;opentelemetry.io&lt;/a&gt;, and when something looks weird, go straight to ClickHouse. The answer is usually one query away.&lt;/p&gt;

&lt;p&gt;If your errors are different from mine, good, that's the fun part.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>signoz</category>
    </item>
  </channel>
</rss>
