<?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: Chethan</title>
    <description>The latest articles on DEV Community by Chethan (@chethanblgs99).</description>
    <link>https://dev.to/chethanblgs99</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%2F3452602%2Fa50749b2-82c1-43e1-826a-dbb3bb5f7ca8.png</url>
      <title>DEV Community: Chethan</title>
      <link>https://dev.to/chethanblgs99</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chethanblgs99"/>
    <language>en</language>
    <item>
      <title>From log.Printf to Trace Context: What I Learned Debugging a Go Checkout Service with OpenTelemetry and SigNoz</title>
      <dc:creator>Chethan</dc:creator>
      <pubDate>Sat, 18 Jul 2026 17:20:53 +0000</pubDate>
      <link>https://dev.to/chethanblgs99/from-logprintf-to-trace-context-what-i-learned-debugging-a-go-checkout-service-with-opentelemetry-5478</link>
      <guid>https://dev.to/chethanblgs99/from-logprintf-to-trace-context-what-i-learned-debugging-a-go-checkout-service-with-opentelemetry-5478</guid>
      <description>&lt;p&gt;&lt;strong&gt;I spent an hour staring at a 500 error in my terminal logs, only to realize the real culprit was hidden in a trace I wasn't looking at. Here is how I learned to stop relying on log.Printf and started using SigNoz and OpenTelemetry to map the full path of a failure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While building a small checkout service simulator, I intentionally introduced database failures every few requests: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;HTTP POST /checkout returned 500&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The difficult question to answer was: &lt;strong&gt;Which request failed? and why did it fail?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the agents of SigNoz hackathon, I have built a Go-based checkout simulator, which I have instrumented with OpenTelemetry to generate high-cardinality requests injected with controlled failures. I used SigNoz to understand which pieces of telemetry actually helped during debugging and how much context was needed to understand the root cause of a failed request.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Find the link to repo here and follow README to reproduce: &lt;a href="https://github.com/chethanm99/checkout-observability-lab" rel="noopener noreferrer"&gt;Checkout-Observability-Lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are the components of my experiment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Go checkout traffic generator&lt;/li&gt;
&lt;li&gt;OpenTelemetry instrumentation&lt;/li&gt;
&lt;li&gt;A local SigNoz deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application simulated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   HTTP POST /checkout

                           |
                           |
                           v

                SQL SELECT clusters_meta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each request generated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a unique user ID&lt;/li&gt;
&lt;li&gt;a unique product ID&lt;/li&gt;
&lt;li&gt;request metadata&lt;/li&gt;
&lt;li&gt;simulated database latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every 15th request intentionally failed with:&lt;br&gt;
&lt;code&gt;database connection timeout on pool allocation&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This was to simulate the situation that engineers would face during debugging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## Environment

OS:
Ubuntu 24.04 LTS running inside WSL2

Container Runtime:
Docker Desktop 4.0.0
Docker Engine 27.5.1

Language:
Go 1.26.0

Telemetry:
OpenTelemetry Go SDK 1.44.0

Observability:
SigNoz self-hosted Docker deployment v0.133.0

Storage:
ClickHouse 25.12.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My local deployment included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SigNoz frontend&lt;/li&gt;
&lt;li&gt;OpenTelemetry collector&lt;/li&gt;
&lt;li&gt;ClickHouse storage&lt;/li&gt;
&lt;li&gt;PostgreSQL metadata store&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application exported telemetry through OTLP gRPC:&lt;br&gt;
&lt;code&gt;localhost:4317&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The SigNoz interface was available at:&lt;br&gt;
&lt;code&gt;localhost:8080&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&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%2Fg2p9lifw0vjew2z8y4do.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%2Fg2p9lifw0vjew2z8y4do.png" alt="Experiment Architecture Diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Go service was responsible for creating spans and attaching the context. The collector handled telemetry ingestion, while ClickHouse stored the data that SigNoz queried for trace exploration.&lt;/p&gt;

&lt;p&gt;Simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Go Checkout Service

                                |
                                |
                                | OTLP gRPC
                                |
                                v

                       OpenTelemetry Collector

                                |
                                |
                                v

                            ClickHouse

                                |
                                |
                                v

                             SigNoz UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The Go application created spans using the OpenTelemetry SDK.&lt;/li&gt;
&lt;li&gt;The OTLP exporter sent those spans to the collector running with SigNoz.&lt;/li&gt;
&lt;li&gt;The collector processed the telemetry and forwarded it to ClickHouse.&lt;/li&gt;
&lt;li&gt;SigNoz then allowed me to explore the generated traces, errors, attributes, and service behavior from one interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating Realistic Traffic
&lt;/h2&gt;

&lt;p&gt;The traffic generator created dynamic request data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;fakeUserID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"usr-%06d-%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;requestID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;fakeProductID&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"prod-%016x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uint64&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;I attached these values as span attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rootSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"user.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;fakeUserID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"product.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;fakeProductID&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;p&gt;As, I wanted every request to carry enough information to investigate individually.&lt;/p&gt;

&lt;p&gt;In the experiments, every request generated a different user ID. If I had attached these values as metric labels, each generated user could have created another unique time series. Instead, I kept this information in traces where it helped with individual request investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the experiment
&lt;/h2&gt;

&lt;p&gt;These are the commands I used to run the experiment:&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;# Start SigNoz locally&lt;/span&gt;
foundryctl cast &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/casting.yaml

&lt;span class="c"&gt;# Verify containers&lt;/span&gt;
docker ps

&lt;span class="c"&gt;# Start traffic generator&lt;/span&gt;
go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running Traffic Generator
&lt;/h2&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%2Fyb0bspa0sqzmhg00s1vg.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%2Fyb0bspa0sqzmhg00s1vg.png" alt="Traffic Generator" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Go simulator generated checkout traffic with injected failures while exporting OpenTelemetry telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  SigNoz Service Overview
&lt;/h2&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%2Fkj7msq5i1kgral72cszq.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%2Fkj7msq5i1kgral72cszq.png" alt="Service Overview" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before diving into individual trace failures, I started with the SigNoz Service Overview. Seeing the Apdex score and latency percentiles (p50, p90, p99) fluctuate in real-time helped me correlate the spikes in my 500 errors with the simulated database load.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unexpected Problem: My Logs Were Missing
&lt;/h2&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%2F5x7l80dew4yop296x5dz.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%2F5x7l80dew4yop296x5dz.png" alt="Found No Events" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I first thought because the application was already exporting OpenTelemetry traces, my existing application logs would somehow appear alongside those traces.&lt;/p&gt;

&lt;p&gt;As the failure, path in my code looked: &lt;br&gt;
&lt;code&gt;log.Printf("[ERROR] Request failed",)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When I opened the failed checkout trace in SigNoz, and expected to find the matching log message.&lt;/p&gt;

&lt;p&gt;Instead, I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the failed trace&lt;/li&gt;
&lt;li&gt;the error status&lt;/li&gt;
&lt;li&gt;the failed database span&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;but no application log entry. This created more confusion as the application clearly printed the error, and the trace clearly showed the failure.&lt;/p&gt;

&lt;p&gt;That is when I found that both logs and traces are separate telemetry signals, and I had to explicitly instrument the application to connect them.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fixing the Missing Context With Span Events
&lt;/h2&gt;

&lt;p&gt;Instead of relying on my existing console logs, I attached structured failure information directly to the trace using span events.&lt;/p&gt;

&lt;p&gt;For the database failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="s"&gt;"database connection timeout on pool allocation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dbSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RecordError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dbSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;codes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&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 recorded the technical failure on the database span. Then I added a checkout request to get the information about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what failed&lt;/li&gt;
&lt;li&gt;where it failed&lt;/li&gt;
&lt;li&gt;which user was affected&lt;/li&gt;
&lt;li&gt;which product was involved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checkout Failure Event
&lt;/h2&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%2Ffrc2sdmym7j7nzcc3v5l.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%2Ffrc2sdmym7j7nzcc3v5l.png" alt="Failed Event" width="799" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The checkout span contained structured failure context using OpenTelemetry span events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Exception Span
&lt;/h2&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%2Fotsoa5wdrlk0hicnsqn0.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%2Fotsoa5wdrlk0hicnsqn0.png" alt="Exception Span" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The database child span captured the underlying exception details separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling from Traces to Dashboards
&lt;/h2&gt;

&lt;p&gt;While diving straight into a single trace is incredibly powerful when you know which transaction broke, finding that is like finding a needle in a haystack during a massive production incident. In a live environment, instead of manually sift through thousands of successful events to find a handful of failures, you need an aggregate macro-view that rings the alarm bells first.&lt;/p&gt;

&lt;p&gt;This is where building an &lt;strong&gt;Error Dashboard&lt;/strong&gt; changes the game. It perfectly bridges the gap between seeing that a system is bleeding and knowing exactly who or what is causing it.&lt;/p&gt;

&lt;p&gt;Using SigNoz's flexible query builder, I set up a custom time-series monitoring panel fueled directly by our ingested Traces data:&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%2F2pgjnr8ljjvfedwzicre.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%2F2pgjnr8ljjvfedwzicre.png" alt="Error Dashboard" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the Dashboards tab on the left sidebar and select + New Dashboard.&lt;/li&gt;
&lt;li&gt;Click + Add Panel and choose Time Series.&lt;/li&gt;
&lt;li&gt;In the query panel dropdown, switch from all Spans to Root Spans (ensuring you only track the entry point of the failure).&lt;/li&gt;
&lt;li&gt;Configure the Query Builder options precisely:
Filter (Where): &lt;code&gt;http.status_code == 500&lt;/code&gt; AND &lt;code&gt;isRoot == 'true'&lt;/code&gt;
Aggregation: &lt;code&gt;count()&lt;/code&gt;
Group By: &lt;code&gt;service.name&lt;/code&gt;, &lt;code&gt;product.id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click Run Query, name your panel &lt;code&gt;Checkout Service - 500 Error Distribution&lt;/code&gt;, and hit Save Changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using SigNoz to Investigate the Failure
&lt;/h2&gt;

&lt;p&gt;After adding structured context, the debugging workflow changed.&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Find error metric

                               ↓

                       Search application logs

                               ↓

                          Find request ID

                               ↓

                           Locate trace

                               ↓

                    Connect information manually
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Find failed checkout trace

                               ↓

                      Inspect checkout span

                               ↓

                       View failure event

                               ↓

                       Open database span

                               ↓

                      Understand root cause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also used SigNoz features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service overview for request rate, latency, and errors&lt;/li&gt;
&lt;li&gt;trace filtering for failed requests&lt;/li&gt;
&lt;li&gt;span attributes for investigating generated identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Experiment Results
&lt;/h2&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%2Fkxgfatrxerfmsnuufmh9.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%2Fkxgfatrxerfmsnuufmh9.png" alt="Experiment Results" width="667" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During my completed run, the simulator generated 1698 requests and injected 113 controlled failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Instrumentation is only the beginning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What didn't work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My first implementation focused on successfully exporting traces. &lt;/li&gt;
&lt;li&gt;I verified that SigNoz received spans from my Go service, but I assumed my existing log.Printf() messages would automatically become visible during trace investigation. That assumption was wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What worked:&lt;/p&gt;

&lt;p&gt;Adding structured information directly to spans changed the workflow. By attaching error details, user identifiers, product identifiers, and failure events to the trace, the transaction carried the context needed for investigation.&lt;/p&gt;

&lt;p&gt;What I would tell my past self:&lt;/p&gt;

&lt;p&gt;Before generating traffic, decide what information you would need during an incident, and attach that context to spans instead of expecting the observability platform to construct it later after the failure occurs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A good observability workflow is about reducing investigation time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What didn't work:&lt;/p&gt;

&lt;p&gt;Before this experiment, I viewed observability mostly as collecting more data and creating more dashboards.&lt;/p&gt;

&lt;p&gt;The first debugging approach still required several manual steps:&lt;/p&gt;

&lt;p&gt;Find the latency or error signal.&lt;br&gt;
Search application logs separately.&lt;br&gt;
Identify the request involved.&lt;br&gt;
Locate the corresponding trace.&lt;br&gt;
Reconstruct the failure path manually.&lt;/p&gt;

&lt;p&gt;The problem was not that the data did not exist. The problem was that the connection between the data sources was missing.&lt;/p&gt;

&lt;p&gt;What worked:&lt;/p&gt;

&lt;p&gt;After moving the failure context into trace events and span attributes, the investigation path became shorter:&lt;/p&gt;

&lt;p&gt;Open failed checkout trace.&lt;br&gt;
Inspect checkout span.&lt;br&gt;
View failure event.&lt;br&gt;
Open database child span.&lt;br&gt;
Identify root cause.&lt;/p&gt;

&lt;p&gt;The biggest improvement was not adding another dashboard. It was reducing the number of places I needed to check.&lt;/p&gt;

&lt;p&gt;What I would tell my past self:&lt;/p&gt;

&lt;p&gt;I spent time thinking observability was mainly about having more dashboards and more data. This experiment changed that view. The goal is not collecting every possible field; the goal is shortening the path from "something failed" to "I know why it failed." Add the context you will need during debugging while you are writing the application, not after an incident has already started.&lt;/p&gt;

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

&lt;p&gt;Moving from log.Printf messages to structured OpenTelemetry events changed the debugging workflow from searching across disconnected information to investigating one connected trace.&lt;/p&gt;

&lt;p&gt;With OpenTelemetry instrumentation and SigNoz running on ClickHouse, I was able to move from manually stitching together logs and traces to investigating failures directly from a single transaction view.&lt;/p&gt;

&lt;p&gt;For readers interested in trying this workflow, these resources are useful starting points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenTelemetry instrumentation patterns: &lt;a href="https://opentelemetry.io/docs/concepts/instrumentation/" rel="noopener noreferrer"&gt;OpenTelemetry Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SigNoz trace exploration and dashboards: &lt;a href="https://signoz.io/docs/userguide%2Ftraces/" rel="noopener noreferrer"&gt;SigNoz Trace Explorer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Building automated workflows using telemetry data: &lt;a href="https://eureka.patsnap.com/report-how-to-implement-automations-in-telemetry-workflows" rel="noopener noreferrer"&gt;Automated Workflows using Telemetry&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>signoz</category>
      <category>opentelemetry</category>
      <category>agentsofhackathon</category>
      <category>wemakedevs</category>
    </item>
    <item>
      <title>Understanding Kubernetes Architecture: A Simple Breakdown</title>
      <dc:creator>Chethan</dc:creator>
      <pubDate>Wed, 03 Dec 2025 16:00:01 +0000</pubDate>
      <link>https://dev.to/chethanblgs99/understanding-kubernetes-architecture-a-simple-breakdown-5aph</link>
      <guid>https://dev.to/chethanblgs99/understanding-kubernetes-architecture-a-simple-breakdown-5aph</guid>
      <description>&lt;p&gt;Understanding the Kubernetes architecture has given me a solid foundation for diving deep into the world of container orchestration🐋. I've synthesized the core components into an easy-to-follow guide. This is the complete breakdown of the Kubernetes architecture☸️👇:&lt;/p&gt;

&lt;p&gt;Kubernetes follows a client-server architecture🛜. It mainly has two components:&lt;/p&gt;

&lt;p&gt;📌Control plane: It is the master node responsible for maintaining the state of the cluster.&lt;br&gt;
📌Data plane: It is a collection of worker nodes that are responsible for running your applications.&lt;/p&gt;

&lt;p&gt;The control plane has the following components:&lt;/p&gt;

&lt;p&gt;📌Kube-apiserver: Exposes the Kubernetes API, serving as the front-end for the Control Plane.&lt;br&gt;
📌Kube-scheduler: Watches for newly created Pods and assigns them to suitable Worker Nodes based on requirements, constraints, etc.&lt;br&gt;
📌 Kube-controller-manager: It is responsible for running various controller processes, such as Replication controllers, Node controllers, etc.&lt;br&gt;
📌Cloud-controller-manager: It integrates Kubernetes with cloud provider APIs to manage cloud resources like persistent volumes.&lt;/p&gt;

&lt;p&gt;The data plane has the following components:&lt;/p&gt;

&lt;p&gt;📌Container runtime: Software responsible for running containers, such as Docker or CRI-O. &lt;br&gt;
📌Kubelet: An agent that runs on each node and communicates with the Control Plane.&amp;nbsp;It ensures that containers are running in Pods.&lt;br&gt;
📌Kube-proxy: It maintains network rules on the nodes, enabling network communication to and from Pods, and facilitating Service discovery.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>containers</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Understanding Linux Permissions: A Beginner-Friendly Guide for DevOps 🚀</title>
      <dc:creator>Chethan</dc:creator>
      <pubDate>Thu, 04 Sep 2025 13:10:08 +0000</pubDate>
      <link>https://dev.to/chethanblgs99/understanding-linux-permissions-a-beginner-friendly-guide-for-devops-3mbj</link>
      <guid>https://dev.to/chethanblgs99/understanding-linux-permissions-a-beginner-friendly-guide-for-devops-3mbj</guid>
      <description>&lt;p&gt;One of the most important concepts that I and many beginners encountered was File Permissions. These topics are super important if you want to become handy with handling Linux files. In this blog, I will deep dive and provide a brief overview of Linux File permissions and help others learn it fast.&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.amazonaws.com%2Fuploads%2Farticles%2F64vr42gxt2ljehb5ej1i.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.amazonaws.com%2Fuploads%2Farticles%2F64vr42gxt2ljehb5ej1i.png" alt=" " width="800" height="984"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📌&lt;strong&gt;Listing File Permissions:&lt;/strong&gt;&lt;br&gt;
To check permissions of a file use: ls -l&lt;br&gt;
Files in Linux have different types:&lt;/p&gt;

&lt;p&gt;1) - → regular file&lt;br&gt;
2) d → directory&lt;br&gt;
3) l → symbolic link (e.g., lrwxrwxrwx 1 root root 7 Aug 26 10:00 lib -&amp;gt; usr/lib)&lt;/p&gt;

&lt;p&gt;In the permissions string (rwxr-xr-x):&lt;br&gt;
1) r → read&lt;br&gt;
2) w → write&lt;br&gt;
3) x → execute&lt;/p&gt;

&lt;p&gt;The string is divided into 3 sets:&lt;br&gt;
1) User (owner)&lt;br&gt;
2) Group&lt;br&gt;
3) Others&lt;/p&gt;

&lt;p&gt;So rwxr-xr-x means: owner can read, write, execute; group can read &amp;amp; execute; others can read &amp;amp; execute.&lt;/p&gt;

&lt;p&gt;🔑&lt;strong&gt;Changing Permissions with chmod:&lt;/strong&gt;&lt;br&gt;
Permissions can be set numerically or symbolically:&lt;/p&gt;

&lt;p&gt;1) Numeric (octal method)&lt;br&gt;
r = 4, w = 2, x = 1&lt;br&gt;
&lt;strong&gt;Eg&lt;/strong&gt;:  To give all permissions to all sets of users: chmod 777 file.txt&lt;/p&gt;

&lt;p&gt;2) Symbolic (using letters) &lt;br&gt;
&lt;strong&gt;Eg&lt;/strong&gt;:&lt;br&gt;
 1) To give read permission to the user: chmod u+r file.txt &lt;br&gt;
2) To give read permissions to both the group and others: chmod go+r file.txt&lt;/p&gt;

&lt;p&gt;To remove permission use '-' : chmod g-r file.txt&lt;/p&gt;

&lt;p&gt;⚙️&lt;strong&gt;Default Permissions with unmask:&lt;/strong&gt;&lt;br&gt;
When a new file is created, its default permissions depend on the umask value.&lt;/p&gt;

&lt;p&gt;Default mask : 022&lt;br&gt;
To change it : umask 021 (This removes read permission from group and execute permission from others)&lt;/p&gt;

&lt;p&gt;🔒&lt;strong&gt;Special Permission Bits:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Linux also supports advanced permission bits:&lt;br&gt;
1) SUID (Set User ID) – executable runs with file owner’s privileges : &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eg&lt;/strong&gt;: chmod u+s file.txt , chmod 4555 file.txt&lt;/p&gt;

&lt;p&gt;2) SGID (Set Group ID) - Here there are two cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On files → runs with file group privileges&lt;/li&gt;
&lt;li&gt;On directories → new files inherit directory’s group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Eg&lt;/strong&gt;: chmod g+s file.txt , chmod 2777 file.txt&lt;/p&gt;

&lt;p&gt;3) Sticky Bit – on directories, prevents deletion of files by anyone except the owner&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eg&lt;/strong&gt; : chmod +t dir , chmod 1755 file.txt&lt;/p&gt;

&lt;p&gt;👥&lt;strong&gt;Change Ownership:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1) Change file owner. &lt;strong&gt;Eg&lt;/strong&gt;: chown newuser filename&lt;br&gt;
2) Change group ownership. &lt;strong&gt;Eg&lt;/strong&gt;:chown :newgroup filename&lt;br&gt;
3) Change both. &lt;strong&gt;Eg&lt;/strong&gt;:chown -R newuser:newgroup director&lt;/p&gt;

&lt;p&gt;To change group: &lt;/p&gt;

&lt;p&gt;1) For a single file. &lt;strong&gt;Eg&lt;/strong&gt;: chgrp newgroup filename&lt;br&gt;
2) For all files in directory. &lt;strong&gt;Eg&lt;/strong&gt;: chgrp -R newgroup directory/&lt;/p&gt;

&lt;p&gt;This was my learning summary on Linux permissions. If you’re also exploring DevOps/Linux, practicing these commands hands-on will help things click much faster. Do share it with your newtork and share your thoughts on this 🙌&lt;/p&gt;

&lt;p&gt;👉 Which of these file permissions do you use the most ?&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>learning</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A Beginner’s Guide to the Linux File System Hierarchy 🚀</title>
      <dc:creator>Chethan</dc:creator>
      <pubDate>Mon, 01 Sep 2025 13:09:51 +0000</pubDate>
      <link>https://dev.to/chethanblgs99/a-beginners-guide-to-the-linux-file-system-hierarchy-7oo</link>
      <guid>https://dev.to/chethanblgs99/a-beginners-guide-to-the-linux-file-system-hierarchy-7oo</guid>
      <description>&lt;p&gt;When I first started exploring Linux, one of the things that confused me most was the file system layout. Unlike Windows, where everything is neatly tucked under C:\, Linux starts with a single root directory /, and from there, everything branches out into different subfolders with very specific purposes.&lt;/p&gt;

&lt;p&gt;At first glance, it can feel overwhelming — why are there so many folders, and what do they all mean? But once you understand the logic behind the structure, navigating Linux becomes a lot more intuitive.&lt;/p&gt;

&lt;p&gt;Let’s take a quick tour of the key directories in the Linux file system 👇&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.amazonaws.com%2Fuploads%2Farticles%2Fo0uy8mc360p6xfcmwkno.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.amazonaws.com%2Fuploads%2Farticles%2Fo0uy8mc360p6xfcmwkno.png" alt=" " width="800" height="963"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/bin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essential binaries needed to boot and run basic commands.&lt;br&gt;
Eg: ls, cp, cat&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/sbin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;System binaries and admin tools, mainly for the root user.&lt;br&gt;
Eg: ip, mount, fdisk&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/lib&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Shared libraries and kernel modules required by /bin and /sbin.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration files for the system and applications.&lt;br&gt;
Eg: passwd, hosts&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/home&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Personal workspace for each user — your familiar "home directory."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/dev&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Device files that act as an interface to hardware.&lt;br&gt;
Eg: sda, sdb&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/root&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Home directory for the root (superuser).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/var&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Variable files like logs, caches, and backups.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/usr&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;User-installed applications, binaries, and source code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/tmp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Temporary files (wiped after reboot, with sticky bit).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/boot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bootloader files required to start the operating system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/proc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Virtual file system exposing kernel and process info.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/sys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Virtual file system for interacting with devices and drivers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runtime process data since the last reboot.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/mnt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Temporary mount point for sysadmins.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/media&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mount point for removable devices like USBs, DVDs, CDs.&lt;/p&gt;

&lt;p&gt;As I’m learning Linux, I’m curious:&lt;br&gt;
👉 Which of these directories do you use or explore the most, and why?&lt;/p&gt;

&lt;p&gt;I’d love to hear your experiences and tips — feel free to share them in the comments! 🙌&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>learning</category>
    </item>
    <item>
      <title>Starting my Tech Journey 💪</title>
      <dc:creator>Chethan</dc:creator>
      <pubDate>Fri, 22 Aug 2025 12:16:45 +0000</pubDate>
      <link>https://dev.to/chethanblgs99/starting-my-tech-journey-4eog</link>
      <guid>https://dev.to/chethanblgs99/starting-my-tech-journey-4eog</guid>
      <description>&lt;p&gt;Hi Folks,&lt;br&gt;
My name is Chethan, a second-year undergrad passionate to learn about &lt;strong&gt;DevOps&lt;/strong&gt; and &lt;strong&gt;Cloud-Native&lt;/strong&gt; tools. Today, I am happy to share that I will be starting my journey where I will be sharing my learnings with you and learn from others as well, and growing as an Engineer. &lt;/p&gt;

&lt;p&gt;Thanks&lt;/p&gt;

</description>
      <category>devops</category>
      <category>devsecops</category>
      <category>softwaredevelopment</category>
      <category>learninpublic</category>
    </item>
  </channel>
</rss>
