<?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: KHUSH DAVE</title>
    <description>The latest articles on DEV Community by KHUSH DAVE (@khush_dave_18s).</description>
    <link>https://dev.to/khush_dave_18s</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%2F4035234%2F9580353b-4108-4262-a721-b10c4e86539a.jpg</url>
      <title>DEV Community: KHUSH DAVE</title>
      <link>https://dev.to/khush_dave_18s</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khush_dave_18s"/>
    <language>en</language>
    <item>
      <title>Building SitePulse: How I Instrumented My FastAPI App with OpenTelemetry and SigNoz [https://cloud-sentinel-app.vercel.app/]</title>
      <dc:creator>KHUSH DAVE</dc:creator>
      <pubDate>Sat, 18 Jul 2026 11:58:00 +0000</pubDate>
      <link>https://dev.to/khush_dave_18s/building-sitepulse-how-i-instrumented-my-fastapi-app-with-opentelemetry-and-signoz-1c4</link>
      <guid>https://dev.to/khush_dave_18s/building-sitepulse-how-i-instrumented-my-fastapi-app-with-opentelemetry-and-signoz-1c4</guid>
      <description>&lt;p&gt;When I first started building SitePulse—a cloud monitoring dashboard designed to track server status and health metrics—I ran into a classic developer problem. I could easily ping target servers to see if they were "up," but I had zero visibility into my own backend. When my data ingestion endpoints started lagging, looking at standard terminal output was useless. I didn’t just need to know if an error happened; I needed to know exactly where the bottleneck was in the request lifecycle.&lt;/p&gt;

&lt;p&gt;Instead of rewriting my entire logging architecture, I decided to use OpenTelemetry to trace my Python FastAPI backend, sending the data directly to SigNoz. Here is exactly how I set it up in under 30 minutes, and what I learned along the way.&lt;/p&gt;

&lt;p&gt;The Goal: From Black Box to Full Tracing&lt;br&gt;
For SitePulse to be a reliable part of my CloudSentinel suite, the FastAPI backend needs to handle high volumes of health-check data rapidly.&lt;/p&gt;

&lt;p&gt;My objective was simple:&lt;/p&gt;

&lt;p&gt;Automatically instrument all incoming HTTP requests.&lt;/p&gt;

&lt;p&gt;Export those traces to a backend where I could visualize latency spikes.&lt;/p&gt;

&lt;p&gt;Do it without polluting my business logic with custom logging code.&lt;/p&gt;

&lt;p&gt;I chose SigNoz because it natively supports OpenTelemetry (OTel) and gives me both metrics and traces out of the box without the heavy setup of an ELK stack.&lt;/p&gt;

&lt;p&gt;Step 1: Setting Up OpenTelemetry in FastAPI&lt;br&gt;
First, I needed to install the core OpenTelemetry libraries and the specific FastAPI auto-instrumentation package.&lt;/p&gt;

&lt;p&gt;Bash&lt;br&gt;
pip install opentelemetry-api opentelemetry-sdk &lt;br&gt;
pip install opentelemetry-instrumentation-fastapi&lt;br&gt;
pip install opentelemetry-exporter-otlp&lt;br&gt;
Auto-instrumentation is practically magic. Instead of wrapping every single route in a timer, you attach the OTel instrumentor to the FastAPI app instance. Here is the exact main.py configuration I used for SitePulse:&lt;/p&gt;

&lt;p&gt;Python&lt;br&gt;
from fastapi import FastAPI&lt;br&gt;
from opentelemetry import trace&lt;br&gt;
from opentelemetry.sdk.trace import TracerProvider&lt;br&gt;
from opentelemetry.sdk.trace.export import BatchSpanProcessor&lt;br&gt;
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter&lt;br&gt;
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor&lt;/p&gt;

&lt;p&gt;app = FastAPI(title="SitePulse API")&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Initialize the Tracer Provider
&lt;/h1&gt;

&lt;p&gt;provider = TracerProvider()&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Configure the OTLP Exporter to send data to SigNoz
&lt;/h1&gt;

&lt;h1&gt;
  
  
  SigNoz typically listens for OTLP gRPC on port 4317
&lt;/h1&gt;

&lt;p&gt;otlp_exporter = OTLPSpanExporter(endpoint="&lt;a href="http://localhost:4317" rel="noopener noreferrer"&gt;http://localhost:4317&lt;/a&gt;", insecure=True)&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Add the BatchSpanProcessor (batches spans for better performance)
&lt;/h1&gt;

&lt;p&gt;processor = BatchSpanProcessor(otlp_exporter)&lt;br&gt;
provider.add_span_processor(processor)&lt;br&gt;
trace.set_tracer_provider(provider)&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Instrument the FastAPI app
&lt;/h1&gt;

&lt;p&gt;FastAPIInstrumentor.instrument_app(app)&lt;/p&gt;

&lt;p&gt;@app.get("/api/v1/health")&lt;br&gt;
async def check_health():&lt;br&gt;
    # A simple endpoint to test tracing&lt;br&gt;
    return {"status": "operational", "service": "SitePulse"}&lt;br&gt;
Step 2: Routing to SigNoz&lt;br&gt;
For local development, I spun up SigNoz using their official Docker Compose script. The crucial part here is the endpoint="&lt;a href="http://localhost:4317" rel="noopener noreferrer"&gt;http://localhost:4317&lt;/a&gt;" in my Python code. SigNoz’s OTel Collector listens on port 4317 for gRPC traffic.&lt;/p&gt;

&lt;p&gt;Once I started my FastAPI server using Uvicorn (uvicorn main:app --reload), I hit the /api/v1/health endpoint a few times and opened the SigNoz dashboard at &lt;a href="http://localhost:3301" rel="noopener noreferrer"&gt;http://localhost:3301&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Step 3: Finding the Bottleneck&lt;br&gt;
The real value clicked when I looked at a more complex endpoint—the one SitePulse uses to aggregate server metrics.&lt;/p&gt;

&lt;p&gt;By default, the FastAPIInstrumentor captures the overall request time. But because OpenTelemetry automatically propagates context, I could also see how much time was spent on database queries within that single request.&lt;/p&gt;

&lt;p&gt;Looking at the flame graph in SigNoz, it became instantly obvious that an asynchronous database call was blocking the event loop. I didn't have to guess; the trace visually mapped out exactly where the 400ms delay was happening.&lt;/p&gt;

&lt;p&gt;What I Learned (The Gotchas)&lt;br&gt;
If you are doing this for your own project, keep these two things in mind:&lt;/p&gt;

&lt;p&gt;gRPC vs HTTP: Make sure you install opentelemetry-exporter-otlp. There are separate packages for HTTP and gRPC. SigNoz defaults to gRPC on 4317. If you accidentally use the HTTP exporter without changing the port/endpoint, your traces will silently fail to export.&lt;/p&gt;

&lt;p&gt;Auto-instrumentation is a baseline, not the finish line: FastAPIInstrumentor is great for HTTP spans, but if you have complex background tasks or custom functions, you'll still want to create manual spans using tracer.start_as_current_span("my_custom_logic").&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Adding observability to SitePulse didn't require a massive architectural rewrite. With about 15 lines of OpenTelemetry configuration, I went from guessing about API latency to having a real-time, visual breakdown of every request in SigNoz. If you are building a modern web app, skip the print() statements and instrument your code properly from day one.&lt;br&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%2Fmo8eevkh892kaov9k1z8.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%2Fmo8eevkh892kaov9k1z8.png" alt=" " width="799" height="402"&gt;&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%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxq4hgfbnfp7lyozxscy.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%2Flxq4hgfbnfp7lyozxscy.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;br&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%2Fhlo7o3dmdvew5mlw4e98.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%2Fhlo7o3dmdvew5mlw4e98.png" alt=" " width="800" height="386"&gt;&lt;/a&gt;&lt;br&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%2Fbf1erqbkuixi8wam17df.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%2Fbf1erqbkuixi8wam17df.png" alt=" " width="800" height="379"&gt;&lt;/a&gt;&lt;br&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%2Fttb6zfwxs9ffp9srq2bd.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%2Fttb6zfwxs9ffp9srq2bd.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
