<?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>Forem: Adnan Rahić</title>
    <description>The latest articles on Forem by Adnan Rahić (@adnanrahic).</description>
    <link>https://forem.com/adnanrahic</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F11252%2F57b23301-aa8e-4cb7-83a1-5bedfdc663bd.png</url>
      <title>Forem: Adnan Rahić</title>
      <link>https://forem.com/adnanrahic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/adnanrahic"/>
    <language>en</language>
    <item>
      <title>Custom OpenTelemetry Collectors: Build, Run, and Manage at Scale</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Thu, 11 Sep 2025 11:47:15 +0000</pubDate>
      <link>https://forem.com/adnanrahic/custom-opentelemetry-collectors-build-run-and-manage-at-scale-464d</link>
      <guid>https://forem.com/adnanrahic/custom-opentelemetry-collectors-build-run-and-manage-at-scale-464d</guid>
      <description>&lt;p&gt;I tried thinking back to when the last time I read an actual tutorial that did not include a bunch of em (—) dashes, semicolons, normal dashes, and an unnervingly large quantity of the phrases like “XYZ-thing Alert 🚨” and “Exciting News!”.&lt;/p&gt;

&lt;p&gt;Well, hold on to your suspenders folks, here we go again. Part 2 is up and it’s a controversial one. 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you tried building your own, custom, OpenTelemetry Collector distribution?&lt;/strong&gt; Did you like it? 😂&lt;/p&gt;

&lt;p&gt;I bet you’re NOT smiling. Let alone laughing out loud at that statement like I am right now! I tried, and boy did I have a hard time.&lt;/p&gt;

&lt;p&gt;I do have a nice solution to the current norm of building custom OpenTelemetry Collectors, if you have the patience to stick around and read for the next 4 minutes. I’ll do my best to be respectful of your time and cut it down to the bare bones you need to be successful yourself. 🤝&lt;/p&gt;

&lt;h2&gt;
  
  
  As a normal human I find this hard…
&lt;/h2&gt;

&lt;p&gt;There are a few missing steps in the existing resources and docs around using the OpenTelemetry Collector Builder (OCB). I felt like stumbling through a dark forest and barely making it out the other side. The OCB is an &lt;strong&gt;amazing&lt;/strong&gt; tool for Go developers. That’s the kicker though. A lot of us are not seasoned engineers, let alone experienced Go developers.&lt;/p&gt;

&lt;p&gt;That’s why I wanted to write this tutorial. I’ll show you a hands-on guide with the open-source &lt;strong&gt;OpenTelemetry Distribution Builder&lt;/strong&gt; (ODB) from Bindplane. You’ll learn how to build a custom, OpAMP-enabled collector using a &lt;code&gt;manifest.yaml&lt;/code&gt; file and GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building custom OpenTelemetry collectors is a real need
&lt;/h2&gt;

&lt;p&gt;Custom OpenTelemetry Collectors are no longer a niche thing. As more devs run collectors in intricate Kubernetes environments, or in containers in general, even in the edge, trimming down the binary is becoming standard practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why build a custom collector?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The upstream &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib" rel="noopener noreferrer"&gt;OpenTelemetry Collector Contrib&lt;/a&gt; ships with &lt;strong&gt;a lot&lt;/strong&gt; of components. That’s great for getting started, but in production you don’t need all of them. Simply put, more components equals bigger binaries and more attack surface.&lt;/p&gt;

&lt;p&gt;A custom built collector solves that. &lt;strong&gt;You&lt;/strong&gt; define exactly what is needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only receivers, processors, exporters, and extensions you use&lt;/li&gt;
&lt;li&gt;Minimal footprint&lt;/li&gt;
&lt;li&gt;No unnecessary dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The OpenTelemetry Distribution Builder (ODB)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/observIQ/otel-distro-builder" rel="noopener noreferrer"&gt;ODB&lt;/a&gt; is Bindplane’s open-source builder for creating custom collectors. &lt;/p&gt;

&lt;p&gt;You feed it a &lt;code&gt;manifest.yaml&lt;/code&gt; and it gives you binaries and packages for every platform you need.&lt;/p&gt;

&lt;p&gt;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-platform builds: Linux, Windows, macOS, AMD64, ARM64&lt;/li&gt;
&lt;li&gt;Multiple formats: .tar.gz, .zip, .deb, .rpm&lt;/li&gt;
&lt;li&gt;No Go coding, no manual dependency resolution&lt;/li&gt;
&lt;li&gt;OpAMP support (Bindplane-compatible out-of-the-box)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Create a GitHub repo
&lt;/h2&gt;

&lt;p&gt;Start by creating a blank GitHub repo to store the &lt;code&gt;manifest.yaml&lt;/code&gt; and run GitHub Action Workflows.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-1_xdsyd2.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-1_xdsyd2.png" alt="custom-otel-col-1.png" width="800" height="822"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create a new repo in your local env.&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"# otel-distro-builder-github-action"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; README.md
git init
git add README.md
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"first commit"&lt;/span&gt;
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git remote add origin git@github.com:&amp;lt;YOUR_ACCOUNT_NAME&amp;gt;/otel-distro-builder-github-action.git
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-2_h5bfjk.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-2_h5bfjk.png" alt="custom-otel-col-2.png" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repo is now ready to add a &lt;code&gt;manifest.yaml&lt;/code&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-3_k1ct9g.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-3_k1ct9g.png" alt="custom-otel-col-3.png" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2 — Write a &lt;code&gt;manifest.yaml&lt;/code&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;manifest.yaml&lt;/code&gt; is where you define what goes in your collector.&lt;/p&gt;

&lt;p&gt;Here’s a suggested example that I’ve vetted with my colleagues at Bindplane that contribute to the OpenTelemetry project. It’s minimal, but still includes quality-of-life modules like OpAMP support and common processors.&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;dist&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/&amp;lt;YOUR-USERNAME&amp;gt;/my-custom-opentelemetry-distro&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;my-custom-opentelemetry-distro&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Custom-built OpenTelemetry Collector.&lt;/span&gt;
  &lt;span class="na"&gt;output_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./_build&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v0.0.1&lt;/span&gt;
&lt;span class="na"&gt;conf_resolver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default_uri_scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env"&lt;/span&gt;
&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/receiver/nopreceiver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/receiver/otlpreceiver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/receiver/telemetrygeneratorreceiver v1.79.0&lt;/span&gt;
&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/exporter/debugexporter v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/exporter/nopexporter v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/exporter/otlpexporter v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/exporter/otlphttpexporter v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/exporter/googlecloudstorageexporter v1.79.0&lt;/span&gt;
&lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/extension/zpagesextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/asapauthextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/awsproxy v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/basicauthextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/bearertokenauthextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/jaegerencodingextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/otlpencodingextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/zipkinencodingextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/headerssetterextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/httpforwarderextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/jaegerremotesampling v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/dockerobserver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/ecsobserver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/ecstaskobserver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/hostobserver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/k8sobserver v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/oidcauthextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/opampextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/sigv4authextension v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/filestorage v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/dbstorage v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/extension/bindplaneextension v1.79.0&lt;/span&gt;
&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/processor/batchprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/cumulativetodeltaprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatorateprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbytraceprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/logdedupprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricsgenerationprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/probabilisticsamplerprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/redactionprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/remotetapprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/routingprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/datapointcountprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/logcountprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/lookupprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/maskprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/metricextractprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/metricstatsprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/removeemptyvaluesprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/resourceattributetransposerprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/samplingprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/snapshotprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/spancountprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/throughputmeasurementprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/topologyprocessor v1.79.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/processor/unrollprocessor v1.79.0&lt;/span&gt;
&lt;span class="na"&gt;connectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/connector/forwardconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/countconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/datadogconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/exceptionsconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/failoverconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/grafanacloudconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/roundrobinconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/routingconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/servicegraphconnector v0.128.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector v0.128.0&lt;/span&gt;
&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/confmap/provider/envprovider v1.34.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/confmap/provider/fileprovider v1.34.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/confmap/provider/httpprovider v1.34.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/confmap/provider/httpsprovider v1.34.0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gomod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go.opentelemetry.io/collector/confmap/provider/yamlprovider v1.34.0&lt;/span&gt;
&lt;span class="c1"&gt;# When adding a replace, add a comment before it to document why it's needed and when it can be removed&lt;/span&gt;
&lt;span class="na"&gt;replaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# See https://github.com/google/gnostic/issues/262&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github.com/googleapis/gnostic v0.5.6 =&amp;gt; github.com/googleapis/gnostic v0.5.5&lt;/span&gt;
  &lt;span class="c1"&gt;# See https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/12322#issuecomment-1185029670&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 =&amp;gt; github.com/docker/go-connections v0.4.0&lt;/span&gt;
  &lt;span class="c1"&gt;# see https://github.com/mattn/go-ieproxy/issues/45&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github.com/mattn/go-ieproxy =&amp;gt; github.com/mattn/go-ieproxy v0.0.1&lt;/span&gt;
  &lt;span class="c1"&gt;# see https://github.com/openshift/api/pull/1515&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github.com/openshift/api =&amp;gt; github.com/openshift/api v0.0.0-20230726162818-81f778f3b3ec&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;github.com/observiq/bindplane-otel-collector/internal/version =&amp;gt; github.com/observiq/bindplane-otel-collector/internal/version v0.0.0-20250306153219-6fe3f849c29f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💡 The &lt;code&gt;opampextension&lt;/code&gt; is the key — it’s what lets Bindplane discover and manage your collector.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3 — Automate the build with GitHub Actions&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Use the &lt;a href="https://github.com/marketplace/actions/opentelemetry-distribution-builder" rel="noopener noreferrer"&gt;**OpenTelemetry Distribution Builder GitHub Action&lt;/a&gt;** to do the heavy lifting.&lt;/p&gt;

&lt;p&gt;Here’s a &lt;code&gt;.github/workflows/multi.yaml&lt;/code&gt; you can drop in:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Matrixed OpenTelemetry Distribution Build&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v*"&lt;/span&gt; &lt;span class="c1"&gt;# Runs when a version tag is pushed (e.g., v1.0.0)&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Enables manual triggering from the GitHub UI&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;# This is required for creating/modifying releases&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Configure build matrix to run multiple platform builds in parallel&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Define the platforms we want to build for&lt;/span&gt;
        &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;linux/amd64&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;linux/arm64&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;darwin/arm64&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;windows/amd64&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# Map platform identifiers to simpler names for artifact handling&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;
            &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
            &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amd64&lt;/span&gt;
            &lt;span class="na"&gt;artifact_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux-amd64&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/arm64&lt;/span&gt;
            &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
            &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
            &lt;span class="na"&gt;artifact_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux-arm64&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;darwin/arm64&lt;/span&gt;
            &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;darwin&lt;/span&gt;
            &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
            &lt;span class="na"&gt;artifact_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;darwin-arm64&lt;/span&gt;

          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows/amd64&lt;/span&gt;
            &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
            &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amd64&lt;/span&gt;
            &lt;span class="na"&gt;artifact_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows-amd64&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="c1"&gt;# Build the OpenTelemetry distribution for each platform&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;Build and Package&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;observiq/otel-distro-builder@main&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
          &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.arch }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Upload platform-specific artifacts with a unique name&lt;/span&gt;
      &lt;span class="c1"&gt;# These artifacts are available for download in the GitHub Actions UI&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;Upload Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&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;distribution-artifacts-${{ matrix.artifact_name }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ startsWith(matrix.platform, 'linux') &amp;amp;&amp;amp; format('{0}/artifacts/*.deb', github.workspace) || '' }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ startsWith(matrix.platform, 'linux') &amp;amp;&amp;amp; format('{0}/artifacts/*.rpm', github.workspace) || '' }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ startsWith(matrix.platform, 'linux') &amp;amp;&amp;amp; format('{0}/artifacts/*.apk', github.workspace) || '' }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ startsWith(matrix.platform, 'windows') &amp;amp;&amp;amp; format('{0}/artifacts/*.zip', github.workspace) || '' }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ (startsWith(matrix.platform, 'linux') || startsWith(matrix.platform, 'darwin')) &amp;amp;&amp;amp; format('{0}/artifacts/*.tar.gz', github.workspace) || '' }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ format('{0}/artifacts/*.sbom.json', github.workspace) || '' }}&lt;/span&gt;
            &lt;span class="s"&gt;${{ format('{0}/artifacts/*_checksums.txt', github.workspace) || '' }}&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="c1"&gt;# Artifacts are kept for 5 days then automatically deleted&lt;/span&gt;

  &lt;span class="c1"&gt;# Create a single release containing all platform artifacts&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt; &lt;span class="c1"&gt;# Wait for all platform builds to complete&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;# Required permission for creating releases&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Download all platform-specific artifacts into a single directory&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;Download All Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all-artifacts&lt;/span&gt;
          &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;distribution-artifacts-*&lt;/span&gt; &lt;span class="c1"&gt;# Match all our platform-specific artifacts&lt;/span&gt;
          &lt;span class="na"&gt;merge-multiple&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Combine all artifacts into a single directory&lt;/span&gt;

      &lt;span class="c1"&gt;# Create a GitHub Release and attach all platform artifacts&lt;/span&gt;
      &lt;span class="c1"&gt;# This makes all platform builds available for download from the Releases page&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;Create Release&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;softprops/action-gh-release@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all-artifacts/**/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every new version release will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build your custom collector for all platforms&lt;/li&gt;
&lt;li&gt;Package it into .tar.gz, .zip, .deb, and .rpm&lt;/li&gt;
&lt;li&gt;Store them as GitHub Actions artifacts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should have two files created and ready to add to Git.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status

&lt;span class="o"&gt;[&lt;/span&gt;Output]
On branch main
Your branch is up to &lt;span class="nb"&gt;date &lt;/span&gt;with &lt;span class="s1"&gt;'origin/main'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Untracked files:
  &lt;span class="o"&gt;(&lt;/span&gt;use &lt;span class="s2"&gt;"git add &amp;lt;file&amp;gt;..."&lt;/span&gt; to include &lt;span class="k"&gt;in &lt;/span&gt;what will be committed&lt;span class="o"&gt;)&lt;/span&gt;
    .github/
    manifest.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit these changes and push them to your repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .github manifest.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add a manifest and workflow"&lt;/span&gt;
git push origin main
&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-4_uwck0f.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590379%2Fcustom-otel-col-4_uwck0f.png" alt="custom-otel-col-4.png" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new release. Make sure to use the same release tag as you specified in your &lt;code&gt;manifest.yaml&lt;/code&gt;. In the sample &lt;code&gt;manifest.yaml&lt;/code&gt; above I used &lt;code&gt;v0.0.1&lt;/code&gt; which means I need to set the same tag in the release.&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%2Fzvg2jnr8l53hmiowuy2p.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%2Fzvg2jnr8l53hmiowuy2p.png" alt="custom-otel-col-5.png" width="800" height="784"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the release is created, you’ll see it’s initially empty.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590378%2Fcustom-otel-col-5-5_v0pgd5.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590378%2Fcustom-otel-col-5-5_v0pgd5.png" alt="custom-otel-col-5-5.png" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Opening the &lt;code&gt;Actions&lt;/code&gt; will show the build running.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-6_dakogb.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-6_dakogb.png" alt="custom-otel-col-6.png" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it about 5 minutes to complete. Go get a coffee. ☕&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-7_gc2alu.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-7_gc2alu.png" alt="custom-otel-col-7.png" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the builds are done, you’ll see artifacts saved and added to the release.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-9_f0iapa.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-9_f0iapa.png" alt="custom-otel-col-9.png" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4 — Download and run your collector&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let me show you how to run your custom collector in a Linux VM. Grab an artifact from the Release and extract it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://github.com/adnanrahic/otel-distro-builder-github-action/releases/download/v0.0.1/my-custom-opentelemetry-distro_otelcol_v0.0.1_linux_amd64.tar.gz

&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xvzf&lt;/span&gt; my-custom-opentelemetry-distro_otelcol_v0.0.1_linux_amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll also get a skeleton &lt;code&gt;collector_config.yaml&lt;/code&gt; for the custom collector bundled in the tar as well.&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;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Condensed Output]
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; collector_config.yaml
&lt;span class="nt"&gt;-rwxr-xr-x&lt;/span&gt; my-custom-opentelemetry-distro
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; my-custom-opentelemetry-distro_otelcol_v0.0.1_linux_amd64.tar.gz
drwxr-xr-x service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s edit it slightly, and also add Bindplane’s OpAMP configuration by &lt;a href="https://docs.bindplane.com/how-to-guides/connecting-other-opentelemetry-collectors-using-the-opamp-extension" rel="noopener noreferrer"&gt;following this guide&lt;/a&gt;. Paste this into the 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;# To limit exposure to denial of service attacks, change the host in endpoints below from 0.0.0.0 to a specific network interface.&lt;/span&gt;
&lt;span class="c1"&gt;# See https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/security-best-practices.md#safeguards-against-denial-of-service-attacks&lt;/span&gt;

&lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;health_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pprof&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:1777&lt;/span&gt;
  &lt;span class="na"&gt;zpages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:55679&lt;/span&gt;

    &lt;span class="c1"&gt;# Add an OpAMP connection to Bindplane.&lt;/span&gt;
    &lt;span class="c1"&gt;# Use the credentials from your account, and&lt;/span&gt;
    &lt;span class="c1"&gt;# follow the guide from the screenshot below.&lt;/span&gt;
  &lt;span class="na"&gt;opamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;instance_uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;01K42T4MGFFDZMXBY7C5C2APX9&lt;/span&gt; &lt;span class="c1"&gt;# Generated ULID&lt;/span&gt;
    &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;reports_effective_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Bindplane Cloud OpAMP Endpoint&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wss://app.bindplane.com/v1/opamp&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret-Key &amp;lt;YOUR_SECRET_KEY&amp;gt;&lt;/span&gt; 
          &lt;span class="na"&gt;X-Bindplane-Labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;YOUR_LABEL=YOUR_VALUE&amp;gt;&lt;/span&gt;
        &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telemetrygeneratorreceiver/logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;generators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;additional_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foo:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bar'&lt;/span&gt;
          &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logs&lt;/span&gt;
    &lt;span class="na"&gt;payloads_per_second&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4317&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4318&lt;/span&gt;

&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;verbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;
  &lt;span class="na"&gt;nop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

    &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;logs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;telemetrygeneratorreceiver/logs&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;health_check&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pprof&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;zpages&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;opamp&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;**Note:&lt;/em&gt;* If you’re on the Growth or Enterprise plans of Bindplane, and want to connect the collector Bindplane, use the secret key after the &lt;code&gt;-s&lt;/code&gt; and the labels after the &lt;code&gt;-k&lt;/code&gt;. The collector will work as expected with or without connecting to Bindplane. But, you will not get the management capabilities and benefits of OpAMP.*&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%2Fuggp9n7wouzkq7udtg42.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%2Fuggp9n7wouzkq7udtg42.png" alt="custom-otel-col-10.png" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, go ahead and run the collector binary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./my-custom-opentelemetry-distro &lt;span class="nt"&gt;--config&lt;/span&gt; collector_config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll see logs like this show as the terminal output confirming the &lt;code&gt;telemetrygeneratorreceiver&lt;/code&gt; is creating some dummy logs to validate your config is working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2025-09-02T08:44:44.255Z    info    service@v0.128.0/service.go:282 Everything is ready. Begin running and processing data. &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"resource"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"service.instance.id"&lt;/span&gt;: &lt;span class="s2"&gt;"8c4d008a-700d-49d6-b6ed-41cd1e03cf18"&lt;/span&gt;, &lt;span class="s2"&gt;"service.name"&lt;/span&gt;: &lt;span class="s2"&gt;"my-custom-opentelemetry-distro"&lt;/span&gt;, &lt;span class="s2"&gt;"service.version"&lt;/span&gt;: &lt;span class="s2"&gt;"v0.0.1"&lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;

2025-09-02T08:44:44.454Z    info    Logs    &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"resource"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"service.instance.id"&lt;/span&gt;: &lt;span class="s2"&gt;"8c4d008a-700d-49d6-b6ed-41cd1e03cf18"&lt;/span&gt;, &lt;span class="s2"&gt;"service.name"&lt;/span&gt;: &lt;span class="s2"&gt;"my-custom-opentelemetry-distro"&lt;/span&gt;, &lt;span class="s2"&gt;"service.version"&lt;/span&gt;: &lt;span class="s2"&gt;"v0.0.1"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, &lt;span class="s2"&gt;"otelcol.component.id"&lt;/span&gt;: &lt;span class="s2"&gt;"debug"&lt;/span&gt;, &lt;span class="s2"&gt;"otelcol.component.kind"&lt;/span&gt;: &lt;span class="s2"&gt;"exporter"&lt;/span&gt;, &lt;span class="s2"&gt;"otelcol.signal"&lt;/span&gt;: &lt;span class="s2"&gt;"logs"&lt;/span&gt;, &lt;span class="s2"&gt;"resource logs"&lt;/span&gt;: 1, &lt;span class="s2"&gt;"log records"&lt;/span&gt;: 1&lt;span class="o"&gt;}&lt;/span&gt;

2025-09-02T08:44:44.455Z    info    ResourceLog &lt;span class="c"&gt;#0&lt;/span&gt;
Resource SchemaURL: 
ScopeLogs &lt;span class="c"&gt;#0&lt;/span&gt;
ScopeLogs SchemaURL: 
InstrumentationScope  
LogRecord &lt;span class="c"&gt;#0&lt;/span&gt;
ObservedTimestamp: 2025-09-02 08:44:44.253813518 +0000 UTC
Timestamp: 2025-09-02 08:44:44.253813518 +0000 UTC
SeverityText: 
SeverityNumber: Info&lt;span class="o"&gt;(&lt;/span&gt;9&lt;span class="o"&gt;)&lt;/span&gt;
Body: Str&lt;span class="o"&gt;(&lt;/span&gt;foo: bar&lt;span class="o"&gt;)&lt;/span&gt;
Trace ID: 
Span ID: 
Flags: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within seconds, your custom collector will appear in &lt;strong&gt;Bindplane’s Agents&lt;/strong&gt; list.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-12_xz7ojv.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-12_xz7ojv.png" alt="custom-otel-col-12.png" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can open the collector config as well.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-12_xz7ojv.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590380%2Fcustom-otel-col-12_xz7ojv.png" alt="custom-otel-col-13.png" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bindplane is picking up the collector metadata and the config you added as well. The OpAMP Extension currently doesn't support remote configuration, which means you cannot modify the Collector configuration through the Bindplane UI. You can still view the current Collector configuration as YAML on the Collector page, but the "Choose Another Configuration" button will not be available.&lt;/p&gt;

&lt;p&gt;Let me walk you through adding your custom collector to Bindplane and enabling remote configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Remotely manage your custom collector with OpAMP
&lt;/h2&gt;

&lt;p&gt;You can enable remote collector management by adding your custom collector as an Agent Type in Bindplane as outlined in &lt;a href="https://docs.bindplane.com/how-to-guides/using-an-opentelemetry-distribution-with-bindplane" rel="noopener noreferrer"&gt;this docs guide&lt;/a&gt;. Let me walk you through it step-by-step. 🚶‍♀️&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install the Bindplane CLI
&lt;/h3&gt;

&lt;p&gt;The Bindplane CLI lets you manage Bindplane resources including Agent Types. &lt;a href="https://docs.bindplane.com/configuration/cli/installation" rel="noopener noreferrer"&gt;Follow the OS-specific installation steps here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create an API Key &amp;amp; set a &lt;code&gt;default&lt;/code&gt; profile
&lt;/h3&gt;

&lt;p&gt;Create an API Key to access resources in Bindplane with the CLI. &lt;a href="https://docs.bindplane.com/configuration/cli/api-keys" rel="noopener noreferrer"&gt;Follow the steps here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create an Agent Type in Bindplane
&lt;/h3&gt;

&lt;p&gt;In Bindplane, an Agent Type represents an OpenTelemetry Collector Distribution. For example, the BDOT v1 and v2 collectors are both Agent Types.&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bindplane.observiq.com/v1&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;AgentType&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;my-custom-opentelemetry-distro&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My Custom OpenTelemetry Distro&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My custom OpenTelemetry collector distro.&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;repositoryLink&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/&amp;lt;YOUR_GITHUB_USERNAME&amp;gt;/&amp;lt;YOUR_REPO_NAME&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;platformArchSet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;darwin&lt;/span&gt;
      &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
      &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amd64&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
      &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arm64&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
      &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amd64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where you need to be careful. The &lt;code&gt;repositoryLink&lt;/code&gt; needs to match the link of the repo where you ran the GitHub action. In my example it was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://github.com/adnanrahic/otel-distro-builder-github-action
&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590381%2Fcustom-otel-col-15_g1xjee.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590381%2Fcustom-otel-col-15_g1xjee.png" alt="custom-otel-col-15.png" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;metadata.name&lt;/code&gt; value also needs to match the value of &lt;code&gt;dist.name&lt;/code&gt; in your &lt;code&gt;manifest.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Apply the custom Agent Type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bindplane apply &lt;span class="nt"&gt;-f&lt;/span&gt; /path/to/agent/type/file.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, sync the Agent Type to load it into Bindplane.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bindplane &lt;span class="nb"&gt;sync &lt;/span&gt;agent-versions &lt;span class="nt"&gt;--agent-type&lt;/span&gt; my-custom-opentelemetry-distro &lt;span class="nt"&gt;--version&lt;/span&gt; v0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the version matches the release.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Install your custom collector from the Bindplane UI
&lt;/h3&gt;

&lt;p&gt;Now since you’ve added a custom Agent Type and synced a version, you can choose to install it from the Bindplane 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.amazonaws.com%2Fuploads%2Farticles%2Fvxlo3vez3kcheuf0v9ev.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%2Fvxlo3vez3kcheuf0v9ev.png" alt="custom-otel-col-14.png" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since you built it for Mac, Windows, and Linux, you’ll see all three options when selecting platform.&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%2Facizzhnaaa2c9gpl4beu.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%2Facizzhnaaa2c9gpl4beu.png" alt="custom-otel-col-18.png" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I want to install it in my Linux VM, so I’ll select Linux and click next.&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%2Fonxiiglbumbkwji5jfsq.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%2Fonxiiglbumbkwji5jfsq.png" alt="custom-otel-col-17.png" width="800" height="820"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ll get this generic install single-command. Running this in my VM will start my custom collector and hook it up to Bindplane via OpAMP.&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;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSlL&lt;/span&gt; &lt;span class="s1"&gt;'https://raw.githubusercontent.com/observIQ/bindplane-otel-collector/refs/heads/main/scripts/generic-install/install_unix.sh'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; install_unix.sh &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'my-custom-opentelemetry-distro'&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/adnanrahic/otel-distro-builder-github-action'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'wss://app.bindplane.com/v1/opamp'&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'01J06XSD7FVM3CHCQA3823AC2X'&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'0.0.1'&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s1"&gt;'install_id=937a3445-8962-441a-90f0-ee120c67edb7'&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;Output]
Using repository URL: https://github.com/adnanrahic/otel-distro-builder-github-action
Auto-detected package &lt;span class="nb"&gt;type&lt;/span&gt;: deb
Downloading: https://github.com/adnanrahic/otel-distro-builder-github-action/releases/download/v0.0.1/my-custom-opentelemetry-distro_v0.0.1_linux_amd64.deb
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0
100 46.9M  100 46.9M    0     0  38.4M      0  0:00:01  0:00:01 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- 98.4M
Selecting previously unselected package my-custom-opentelemetry-distro.
&lt;span class="o"&gt;(&lt;/span&gt;Reading database ... 76875 files and directories currently installed.&lt;span class="o"&gt;)&lt;/span&gt;
Preparing to unpack .../my-custom-opentelemetry-distro_0.0.1.deb ...
Unpacking my-custom-opentelemetry-distro &lt;span class="o"&gt;(&lt;/span&gt;0.0.1&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up my-custom-opentelemetry-distro &lt;span class="o"&gt;(&lt;/span&gt;0.0.1&lt;span class="o"&gt;)&lt;/span&gt; ...
Created symlink /etc/systemd/system/multi-user.target.wants/my-custom-opentelemetry-distro.service → /lib/systemd/system/my-custom-opentelemetry-distro.service.
Creating supervisor config...
Managing service state...
Starting my-custom-opentelemetry-distro service...
Service is running
Installation &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
Installation directory: /opt/my-custom-opentelemetry-distro
Supervisor config: /opt/my-custom-opentelemetry-distro/supervisor_config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the collector as a &lt;code&gt;systemd&lt;/code&gt; 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="nb"&gt;sudo &lt;/span&gt;systemctl status my-custom-opentelemetry-distro

&lt;span class="o"&gt;[&lt;/span&gt;Output]
● my-custom-opentelemetry-distro.service - An OpenTelemetry Collector service named &lt;span class="s1"&gt;'my-custom-opentelemetry-distro'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
     Loaded: loaded &lt;span class="o"&gt;(&lt;/span&gt;/lib/systemd/system/my-custom-opentelemetry-distro.service&lt;span class="p"&gt;;&lt;/span&gt; enabled&lt;span class="p"&gt;;&lt;/span&gt; preset: enabled&lt;span class="o"&gt;)&lt;/span&gt;
     Active: active &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt; since Tue 2025-09-02 12:36:56 UTC&lt;span class="p"&gt;;&lt;/span&gt; 2min 51s ago
   Main PID: 17005 &lt;span class="o"&gt;(&lt;/span&gt;supervisor&lt;span class="o"&gt;)&lt;/span&gt;
      Tasks: 7 &lt;span class="o"&gt;(&lt;/span&gt;limit: 4681&lt;span class="o"&gt;)&lt;/span&gt;
     Memory: 8.5M
        CPU: 249ms
     CGroup: /system.slice/my-custom-opentelemetry-distro.service
             └─17005 /opt/my-custom-opentelemetry-distro/supervisor &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/my-custom-opentelemetry-distro/supervisor_config.yaml

Sep 02 12:36:56 my-custom-opentelemetry-distro systemd[1]: Started my-custom-opentelemetry-distro.service - An OpenTelemetry Collector service named &lt;span class="s1"&gt;'my-opentelemetry-distro.service - An OpenTelemetry Collector service named '&lt;/span&gt;my-custom-opentelemetry-distro&lt;span class="s1"&gt;'.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the OpAMP connection works via websockets, it’ll update the UI right away and show you the collector running.&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%2Fr0ndug1vrbwozof5oks2.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%2Fr0ndug1vrbwozof5oks2.png" alt="custom-otel-col-20.png" width="800" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Configure and manage your custom collector from the Bindplane UI
&lt;/h3&gt;

&lt;p&gt;You can now create a configuration for the collector and remote push it down. &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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590381%2Fcustom-otel-col-21_cxf7is.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590381%2Fcustom-otel-col-21_cxf7is.png" alt="custom-otel-col-21.png" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Create Configuration&lt;/code&gt; button to create and manage a config in Bindplane and apply it remotely.&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%2Fvibjd3lellv38v09xoxe.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%2Fvibjd3lellv38v09xoxe.png" alt="custom-otel-col-22.png" width="800" height="817"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it a name, select the Agent Type for your custom collector, and select the platform where your custom collector is running. For my example, it’s Linux. Add a Telemetry Generator source.&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%2Fiqwvckl8divb9qx1vgbw.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%2Fiqwvckl8divb9qx1vgbw.png" alt="custom-otel-col-23.png" width="800" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, a Dev Null destination.&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%2F6sof3931d5ofj36se0oe.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%2F6sof3931d5ofj36se0oe.png" alt="custom-otel-col-24.png" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will finalize your config creation. You still need to connect it to your custom collector.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590383%2Fcustom-otel-col-25_jjaeaq.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590383%2Fcustom-otel-col-25_jjaeaq.png" alt="custom-otel-col-25.png" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Add Agents&lt;/code&gt; button. Select your custom collector, and hit save.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590383%2Fcustom-otel-col-26_fts1jk.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590383%2Fcustom-otel-col-26_fts1jk.png" alt="custom-otel-col-26.png" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, you can start a rollout to apply the config remotely.&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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590384%2Fcustom-otel-col-27_yutlnp.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%2Fres.cloudinary.com%2Fdu4nxa27k%2Fimage%2Fupload%2Fv1757590384%2Fcustom-otel-col-27_yutlnp.png" alt="custom-otel-col-27.png" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What’s awesome here is that Bindplane reads the collector’s capabilities directly from the build, so you only see the components you actually included in the manifest.&lt;/p&gt;

&lt;p&gt;Let me show you by adding a new source.&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%2Ftje39k9d1ginlgfm7bmx.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%2Ftje39k9d1ginlgfm7bmx.png" alt="custom-otel-col-28.png" width="800" height="760"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll see which sources are incompatible are which you can use. This is a huge quality-of-life improvement and convenience across your entire team when creating and managing collector configs.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 6 — Iterate with confidence&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;With this setup you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the &lt;code&gt;manifest.yaml&lt;/code&gt; to add or remove modules&lt;/li&gt;
&lt;li&gt;Create a new release&lt;/li&gt;
&lt;li&gt;GitHub Actions builds a new version&lt;/li&gt;
&lt;li&gt;Deploy or upgrade in your environment&lt;/li&gt;
&lt;li&gt;Bindplane instantly manages the updated agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now have a &lt;strong&gt;BYOC (Bring Your Own Collector)&lt;/strong&gt; workflow — fully automated, versioned, and controlled by you.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why this works so well&lt;/strong&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Value&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpAMP out-of-the-box&lt;/td&gt;
&lt;td&gt;Full Bindplane remote config and monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Declarative manifest&lt;/td&gt;
&lt;td&gt;No Go code, no manual dependency resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub-native CI/CD&lt;/td&gt;
&lt;td&gt;Push → Build → Package → Manage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-platform packaging&lt;/td&gt;
&lt;td&gt;Build once, run anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI-aware Bindplane integration&lt;/td&gt;
&lt;td&gt;Only shows supported components&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Future goals
&lt;/h2&gt;

&lt;p&gt;Moving forward I would love to abstract away the &lt;code&gt;manifest.yaml&lt;/code&gt; as well. In an ideal world I would want to give the OpenTelemetry Distro Builder a sample collector config file. It should then be able to create a &lt;code&gt;manifest.yaml&lt;/code&gt; from my config. This process would abstract away everything except for the specific receivers, exporters, extensions, and processors I really need.&lt;/p&gt;

&lt;p&gt;More on this by the end of the year. 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Custom collectors aren’t just for power users anymore. With ODB and GitHub Actions, you can build exactly what you need, package it for every platform, and manage it at scale with Bindplane. All without touching a Go compiler. 🔥&lt;/p&gt;

&lt;p&gt;It’s clean. It’s fast. And it’s production-ready.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>opentelemetry</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Build Resilient Telemetry Pipelines with the OpenTelemetry Collector: High Availability and Gateway Architecture</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Fri, 25 Jul 2025 12:35:38 +0000</pubDate>
      <link>https://forem.com/adnanrahic/how-to-build-resilient-telemetry-pipelines-with-the-opentelemetry-collector-high-availability-and-49ng</link>
      <guid>https://forem.com/adnanrahic/how-to-build-resilient-telemetry-pipelines-with-the-opentelemetry-collector-high-availability-and-49ng</guid>
      <description>&lt;p&gt;Do you remember when humans used to write step-by-step tutorials?&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf662Py8qqh7bAFVVDw65mJrq9Bac4lSLQ_uI8ZWE68xhy5_a473HtVInTitqdm2b4RuwGzncwNQnyVTHQQotGWMgfYKqMUquwGu7IrlFrwM-OO3qYFmapJaPwz6ILNfXKL7vXkgQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf662Py8qqh7bAFVVDw65mJrq9Bac4lSLQ_uI8ZWE68xhy5_a473HtVInTitqdm2b4RuwGzncwNQnyVTHQQotGWMgfYKqMUquwGu7IrlFrwM-OO3qYFmapJaPwz6ILNfXKL7vXkgQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s bring that back. Today you’ll learn how to configure high availability for the OpenTelemetry Collector so you don’t lose telemetry during node failures, rolling upgrades, or traffic spikes. The guide covers both Docker and Kubernetes samples with hands-on demos of configs.&lt;/p&gt;

&lt;p&gt;But first, let’s lay some groundwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to define High Availability (HA) with the OpenTelemetry Collector?
&lt;/h2&gt;

&lt;p&gt;You want to ensure telemetry collection and processing works even if individual Collector instances fail. It’s outlined in three main points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Avoid data loss when exporting to a dead observability backend.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure telemetry continuity during rolling updates or infrastructure failures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable horizontal scalability for load-balancing traces, logs, and metrics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To enable high availability it’s recommended that you use the Agent-Gateway deployment pattern. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Agent Collectors run on every host, container, or node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gateway Collectors are centralized, scalable back-end services receiving telemetry from Agent Collectors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each layer can be scaled independently and horizontally.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf-s6Pyq4XLsbHr4xnJ9_lIYX22sn2FFfgPn6N-nt1vn6WpX-krQhmlESURE2Q4_GX9B92-j85IQyF-NDJa0ymu2bzVd3SkZskX8gepv3gkWvljQvk0PzUPS2Epou8Ey4B_rbViQA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf-s6Pyq4XLsbHr4xnJ9_lIYX22sn2FFfgPn6N-nt1vn6WpX-krQhmlESURE2Q4_GX9B92-j85IQyF-NDJa0ymu2bzVd3SkZskX8gepv3gkWvljQvk0PzUPS2Epou8Ey4B_rbViQA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please note, an Agent Collector and Gateway Collector is essentially the same binary. They’re completely identical. The ONLY difference is WHERE it is running. Think of it this way.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An Agent Collector&lt;/strong&gt; runs close to the workload–in the context of Kubernetes it could be a sidecar, or a deployment for every namespace–or for Docker, a service alongside your app in the docker-compose.yaml. This would tend to mean the dev team will own this instance of the Collector.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A Gateway Collector&lt;/strong&gt; is a central (standalone) operation of the collector–think a standalone Collector in a specific namespace or even a dedicated Kubernetes cluster–typically owned by the platform team. This is the final step of the telemetry pipeline letting the platform team enforce policies like filtering logs, sampling traces, dropping metrics, before sending it to an observability backend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s an awesome explanation on &lt;a href="https://stackoverflow.com/questions/73802116/gateway-vs-agent-opentelemetry-collector-deployment-on-kubernetes" rel="noopener noreferrer"&gt;StackOverflow&lt;/a&gt;. Yes, it’s still a thing. No, not everything is explained by AI. 😂&lt;/p&gt;

&lt;p&gt;To satisfy all high availability I’ll walk you through how to configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple Collector Instances.&lt;/strong&gt; Each instance is capable of handling the full workload with redundant storage for temporary data buffering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A Load Balancer.&lt;/strong&gt; It’ll distribute incoming telemetry data and maintain consistent routing. Load balancers also support automatic failover if a collector becomes unavailable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shared Storage.&lt;/strong&gt; Persistent storage for collector state and configuration management.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it’s time to get our hands dirty with some hands-on coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Agent-Gateway High Availability (HA) with the OpenTelemetry Collector
&lt;/h2&gt;

&lt;p&gt;Let me first explain this concept by using Docker and visualize it with Bindplane. This architecture is transferable and usable for any type of Linux or Windows VM setup as well. More about Kubernetes further below.&lt;/p&gt;

&lt;p&gt;There are three options you can use. Either using a &lt;a href="https://opentelemetry.io/docs/collector/deployment/gateway/#nginx-as-an-out-of-the-box-load-balancer" rel="noopener noreferrer"&gt;load balancer like Nginx&lt;/a&gt; or Traefik. Or, using the &lt;a href="https://opentelemetry.io/docs/collector/deployment/gateway/#load-balancing-exporter" rel="noopener noreferrer"&gt;loadbalancing exporter&lt;/a&gt; that’s available in the Collector. Finally, if you’re fully committed to a containerized environment, use native load balancing in Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nginx Load Balancer
&lt;/h3&gt;

&lt;p&gt;The Nginx option is the simpler, out-of-the-box solution.&lt;/p&gt;

&lt;p&gt;I’ll set up the architecture with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Three Gateway Collectors in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One Nginx load balancer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One Agent Collector configured to generate telemetry (app simulation)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeezt57L7eebMutLMwC9s5aGItPDE2nl0LZncw3M4XqPJZPuWz6ZgQG3pC9VRgmhTReTzkbdg9fVX34KaAGAqbKfoMkb09sPcx0-0hBlhw76GKwOB8fOFQMEqPDFC1CN1MbR00-Ng%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeezt57L7eebMutLMwC9s5aGItPDE2nl0LZncw3M4XqPJZPuWz6ZgQG3pC9VRgmhTReTzkbdg9fVX34KaAGAqbKfoMkb09sPcx0-0hBlhw76GKwOB8fOFQMEqPDFC1CN1MbR00-Ng%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="988"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This structure is the bare-bones minimum you’ll end up using. Note that you'll end up using three separate services for the gateway collectors. The reason behind this is that each collector needs to have its own separate &lt;code&gt;file_storage&lt;/code&gt; path to store data in the persistent queue. In Docker, this means you need to make sure each container gets a unique volume. Let me explain how that works. &lt;/p&gt;

&lt;p&gt;Copy the content below into a &lt;code&gt;docker-compose.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3.8'

volumes:
  gw1-storage:      # persistent queue for gateway-1
  gw2-storage:      # persistent queue for gateway-2
  gw3-storage:      # persistent queue for gateway-3
  telgen-storage:   # persistent queue for telemetry generator
  external-gw-storage: # persistent queue for external gateway

services:
  # ────────────── GATEWAYS (3×) ──────────────
  gw1:
    image: ghcr.io/observiq/bindplane-agent:1.79.2
    container_name: gw1
    hostname: gw1
    command: ["--config=/etc/otel/config/config.yaml"]
    volumes:
      - ./config:/etc/otel/config
      - gw1-storage:/etc/otel/storage  # 60 GiB+ queue
    environment:
      OPAMP_ENDPOINT: "wss://app.bindplane.com/v1/opamp"   # point to your Bindplane server
      OPAMP_SECRET_KEY: "&amp;lt;secret&amp;gt;"
      OPAMP_LABELS: ephemeral=true
      MANAGER_YAML_PATH: /etc/otel/config/gw1-manager.yaml
      CONFIG_YAML_PATH: /etc/otel/config/config.yaml
      LOGGING_YAML_PATH: /etc/otel/config/logging.yaml

  gw2:
    image: ghcr.io/observiq/bindplane-agent:1.79.2
    container_name: gw2
    hostname: gw2
    command: ["--config=/etc/otel/config/config.yaml"]
    volumes:
      - ./config:/etc/otel/config
      - gw2-storage:/etc/otel/storage  # 60 GiB+ queue
    environment:
      OPAMP_ENDPOINT: "wss://app.bindplane.com/v1/opamp"   # point to your Bindplane server
      OPAMP_SECRET_KEY: "&amp;lt;secret&amp;gt;"
      OPAMP_LABELS: ephemeral=true
      MANAGER_YAML_PATH: /etc/otel/config/gw2-manager.yaml
      CONFIG_YAML_PATH: /etc/otel/config/config.yaml
      LOGGING_YAML_PATH: /etc/otel/config/logging.yaml
    
  gw3:
    image: ghcr.io/observiq/bindplane-agent:1.79.2
    container_name: gw3
    hostname: gw3
    command: ["--config=/etc/otel/config/config.yaml"]
    volumes:
      - ./config:/etc/otel/config
      - gw3-storage:/etc/otel/storage  # 60 GiB+ queue
    environment:
      OPAMP_ENDPOINT: "wss://app.bindplane.com/v1/opamp"   # point to your Bindplane server
      OPAMP_SECRET_KEY: "&amp;lt;secret&amp;gt;"
      OPAMP_LABELS: ephemeral=true
      MANAGER_YAML_PATH: /etc/otel/config/gw3-manager.yaml
      CONFIG_YAML_PATH: /etc/otel/config/config.yaml
      LOGGING_YAML_PATH: /etc/otel/config/logging.yaml

  # ────────────── OTLP LOAD-BALANCER ──────────────
  otlp-lb:
    image: nginx:1.25-alpine
    volumes:
      - ./nginx-otlp.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP/JSON
    depends_on: [gw1, gw2, gw3]

  # ────────────── TELEMETRY GENERATOR ──────────────
  telgen:
    image: ghcr.io/observiq/bindplane-agent:1.79.2
    container_name: telgen
    hostname: telgen
    command: ["--config=/etc/otel/config/config.yaml"]
    volumes:
      - ./config:/etc/otel/config
      - telgen-storage:/etc/otel/storage  # 60 GiB+ queue
    environment:
      OPAMP_ENDPOINT: "wss://app.bindplane.com/v1/opamp"   # point to your Bindplane server
      OPAMP_SECRET_KEY: "&amp;lt;secret&amp;gt;"
      OPAMP_LABELS: ephemeral=true
      MANAGER_YAML_PATH: /etc/otel/config/telgen-manager.yaml
      CONFIG_YAML_PATH: /etc/otel/config/config.yaml
      LOGGING_YAML_PATH: /etc/otel/config/logging.yaml

  # ────────────── EXTERNAL GATEWAY ──────────────
  external-gw:
    image: ghcr.io/observiq/bindplane-agent:1.79.2
    container_name: external-gw
    hostname: external-gw
    command: ["--config=/etc/otel/config/external-gw-config.yaml"]
    volumes:
      - ./config:/etc/otel/config
      - external-gw-storage:/etc/otel/storage  # 60 GiB+ queue
    environment:
      OPAMP_ENDPOINT: "wss://app.bindplane.com/v1/opamp"   # point to your Bindplane server
      OPAMP_SECRET_KEY: "&amp;lt;secret&amp;gt;"
      OPAMP_LABELS: ephemeral=true
      MANAGER_YAML_PATH: /etc/otel/config/external-gw-manager.yaml
      CONFIG_YAML_PATH: /etc/otel/config/external-gw-config.yaml
      LOGGING_YAML_PATH: /etc/otel/config/logging.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open your Bindplane instance and click the Install Agent button.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfn_YKusSoaliryk41rAU1fVA_JrjeWe7MFy7FwQ6EyG7N85Rqz9zHVB2Ug2KNQoo51nzUj1qQ61n-xWTABRnm24WbhxCfkFF0fSAeT5TbCRBBrkU8BhjdASCtG2k_Zu3uoUVHYZg%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfn_YKusSoaliryk41rAU1fVA_JrjeWe7MFy7FwQ6EyG7N85Rqz9zHVB2Ug2KNQoo51nzUj1qQ61n-xWTABRnm24WbhxCfkFF0fSAeT5TbCRBBrkU8BhjdASCtG2k_Zu3uoUVHYZg%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="829"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the platform to Linux, since I’m demoing this with Docker, and hit next.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXctzthy2_5kGf-A_w4JOpLjmLQ6XptbtW_TM3aBS7nLwtSt6h-8lsoovOPQRo8Bm7sXwRWI7cYDBEvm0IuGEjll7MOKeGe4Npj2IVH9Ku6TscaPZUP8eGop6NcXrUsA800mQLO_qw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXctzthy2_5kGf-A_w4JOpLjmLQ6XptbtW_TM3aBS7nLwtSt6h-8lsoovOPQRo8Bm7sXwRWI7cYDBEvm0IuGEjll7MOKeGe4Npj2IVH9Ku6TscaPZUP8eGop6NcXrUsA800mQLO_qw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This screen now shows the environment variables you'll need to replace in the &lt;code&gt;docker-compose.yaml&lt;/code&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcZm5gnqAVnJUmqFrHoWyNmFnPh51GThiaYwLtyawqdumxhJMi5fz91JcJbQUYUERZnIJeiHFBFyitXqP0RqE5M6bYd-a8beF6ktnScI-Dp9-OiVVbj5VOHE0d6OyjIVsI8nrUZMw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcZm5gnqAVnJUmqFrHoWyNmFnPh51GThiaYwLtyawqdumxhJMi5fz91JcJbQUYUERZnIJeiHFBFyitXqP0RqE5M6bYd-a8beF6ktnScI-Dp9-OiVVbj5VOHE0d6OyjIVsI8nrUZMw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and replace the &lt;code&gt;OPAMP_SECRET_KEY&lt;/code&gt;with your own secret key from Bindplane. If you’re using a self-hosted instance of Bindplane, replace your &lt;code&gt;OPAMP_ENDPOINT&lt;/code&gt;as well. Use the values after &lt;code&gt;-e&lt;/code&gt; and &lt;code&gt;-s&lt;/code&gt; which represent the endpoint and secret.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;nginx-otlp.conf&lt;/code&gt; file for the load balancer.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker_processes auto;
events { worker_connections 1024; }

stream {
  upstream otlp_grpc {
    server gw1:4317 max_fails=3 fail_timeout=15s;
    server gw2:4317 max_fails=3 fail_timeout=15s;
    server gw3:4317 max_fails=3 fail_timeout=15s;
  }
  server {
    listen 4317;            # gRPC
    proxy_pass otlp_grpc;
    proxy_connect_timeout 1s;
    proxy_timeout 30s;
  }
}

http {
  upstream otlp_http {
    server gw1:4318 max_fails=3 fail_timeout=15s;
    server gw2:4318 max_fails=3 fail_timeout=15s;
    server gw3:4318 max_fails=3 fail_timeout=15s;
  }
  server {
    listen 4318;            # HTTP/JSON
    location / {
      proxy_pass http://otlp_http;
      proxy_next_upstream error timeout http_502 http_503 http_504;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a &lt;code&gt;./config&lt;/code&gt; directory in the same root directory as your docker-compose.yaml, and create 3 files.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; config/
    config.yaml
    telgen-config.yaml
    logging.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Paste this basic config into the &lt;code&gt;config.yaml&lt;/code&gt; and &lt;code&gt;telgen-config.yaml&lt;/code&gt; for the BDOT Collector to have a base config to start. I’ll then configure it with Bindplane.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receivers:
  nop:
processors:
  batch:
exporters:
  nop:
service:
  pipelines:
    metrics:
      receivers: [nop]
      processors: [batch]
      exporters: [nop]
  telemetry:
    metrics:
      level: none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And, a base setup for the logging.yaml.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output: stdout
level: info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Start the Docker Compose services.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Jump into Bindplane and create three configurations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;telgen&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;otlp-lb-gw&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;external-gw&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXesZDlHq7y4MGpIUpMtch8b0GXf8Pm57aFw_v2eMLebZJpPcTHav13oM7pcQBogYN15lNF5cYVWtdSCLZ3TzVAGcJALCIDojVCb6UNhgEufFZUZw036QL6jVJF9AjI9_DQVNDUlyg%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXesZDlHq7y4MGpIUpMtch8b0GXf8Pm57aFw_v2eMLebZJpPcTHav13oM7pcQBogYN15lNF5cYVWtdSCLZ3TzVAGcJALCIDojVCb6UNhgEufFZUZw036QL6jVJF9AjI9_DQVNDUlyg%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;telgen&lt;/strong&gt; configuration has a Telemetry Generator source.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXetxAFjijBCHrqudqo6Nx5ptFQs9K6wUgCShmP4u6PC_pS8JoNDVN41o0VdLI27EzA50DPQhFNL0K5v5wmKdNbKanHG8x5-hMwTImgcZui3euEss-UXfWJPtHdLuRBmSmDzgRM6_w%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXetxAFjijBCHrqudqo6Nx5ptFQs9K6wUgCShmP4u6PC_pS8JoNDVN41o0VdLI27EzA50DPQhFNL0K5v5wmKdNbKanHG8x5-hMwTImgcZui3euEss-UXfWJPtHdLuRBmSmDzgRM6_w%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, an OTLP destination.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfTUPjp5dK7prmmnhAP1UzjWcTrXbIeET2W7fknXcKGhPfcTrUKrJJM74bEfuGrAXIWsQAYxF6f_zX5COWorrAROU30fxl0de3URQfvS_d83CwBoKWVa05LSmq6jx3s7hWlhD-qbw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfTUPjp5dK7prmmnhAP1UzjWcTrXbIeET2W7fknXcKGhPfcTrUKrJJM74bEfuGrAXIWsQAYxF6f_zX5COWorrAROU30fxl0de3URQfvS_d83CwBoKWVa05LSmq6jx3s7hWlhD-qbw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OTLP destination is configured to send telemetry to the otlp-lb hostname, which is the hostname for the Nginx load balancer I’m running in Docker Compose.&lt;/p&gt;

&lt;p&gt;Next, the &lt;strong&gt;otlp-lb-gw&lt;/strong&gt; configuration has an OTLP source that listens on 0.0.0.0 and ports 4317 and 4318.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeJ-mtywLKRCOh45XFSbxLcDMSift4tKlICOYdqNNLs52hQllKcQ6EjUn6CQeeLGAkH-CdH3BcXaqyVye6R5eGmU1s_SFEwmW5AAkExU2_7yHDbqrIBVMTwy3xwx-i55Cf0FIBa_g%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeJ-mtywLKRCOh45XFSbxLcDMSift4tKlICOYdqNNLs52hQllKcQ6EjUn6CQeeLGAkH-CdH3BcXaqyVye6R5eGmU1s_SFEwmW5AAkExU2_7yHDbqrIBVMTwy3xwx-i55Cf0FIBa_g%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The destination is also OTLP, but instead sending to the &lt;strong&gt;external-gw&lt;/strong&gt; hostname.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXek5G8ftb-qCfzHBaxRjLAEwAwoROqQkSPjlaqTIrpdVkUuDmmJi16Ln45Q5p7Pk-H8Dwhqn1JIkeyXaNjKvYlYxGUb9k8ghV2OaI-MY1yFtoD1K3SBu_uMGKvPm3avOIKXAu_9TQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXek5G8ftb-qCfzHBaxRjLAEwAwoROqQkSPjlaqTIrpdVkUuDmmJi16Ln45Q5p7Pk-H8Dwhqn1JIkeyXaNjKvYlYxGUb9k8ghV2OaI-MY1yFtoD1K3SBu_uMGKvPm3avOIKXAu_9TQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the external-gw configuration is again using an identical OTLP source.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcs84JO2gQO32TEBNpY2bgtjLz3cFvVk5Gb4bnCNichKm60_Ahl9pW2L06bpRfRZrTNb-gsinFovNdU5r2d0HfLthvwZ_4iqGbgLbsJ05qMVUCQw64dMQVeKQlz5f6t6PWnrvcMdA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcs84JO2gQO32TEBNpY2bgtjLz3cFvVk5Gb4bnCNichKm60_Ahl9pW2L06bpRfRZrTNb-gsinFovNdU5r2d0HfLthvwZ_4iqGbgLbsJ05qMVUCQw64dMQVeKQlz5f6t6PWnrvcMdA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, a Dev Null destination.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdjW_PsGUOZ4sbgV3wpjGTDpQmrCSWZv-EcrkMB8hijFBPIIGEBDX4FTNogQBp8WnYgc1tVNiUp5Ydq-94eDM_afidRMab2arkEnpgpzZzvhd_Bx9g_7g92wn49aNBgbjxGluL_ZQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdjW_PsGUOZ4sbgV3wpjGTDpQmrCSWZv-EcrkMB8hijFBPIIGEBDX4FTNogQBp8WnYgc1tVNiUp5Ydq-94eDM_afidRMab2arkEnpgpzZzvhd_Bx9g_7g92wn49aNBgbjxGluL_ZQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup enables you to drop in whatever destination you want in the list of destinations for the external-gw configuration. Go wild! 😂&lt;/p&gt;

&lt;p&gt;If you open the processor node for the Dev Null destination, you’ll see logs flowing through the load balancer. &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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcH-2WUot1eiW-vQNfFo2954OlF2Snf_t-oIgXekc7XP19Xz8fAAMj2pgn2dIOFyocxWdxPu9SXlpuwOU3EwNEAmgeZGd7SDCDy7tJYoS6lmZFzkTW08Hhdxajf9abLxJTerj5gdQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcH-2WUot1eiW-vQNfFo2954OlF2Snf_t-oIgXekc7XP19Xz8fAAMj2pgn2dIOFyocxWdxPu9SXlpuwOU3EwNEAmgeZGd7SDCDy7tJYoS6lmZFzkTW08Hhdxajf9abLxJTerj5gdQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While in the &lt;strong&gt;otlp-lb-gw&lt;/strong&gt; configuration, if you open a processor node, you’ll see evenly distributed load across all three collectors.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeCNzO1TP_0_PaPjVKj8oZleRuC9WBpWjzfbIWjLEatrHWFicF9y17vEQH9OSL3qghk0FOtGy0NqHaT4_igIhC8DCZGYV0euocJ-cg16osje5Ukm1jf3mtioQ75_bI3FVFBBYw3pQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeCNzO1TP_0_PaPjVKj8oZleRuC9WBpWjzfbIWjLEatrHWFicF9y17vEQH9OSL3qghk0FOtGy0NqHaT4_igIhC8DCZGYV0euocJ-cg16osje5Ukm1jf3mtioQ75_bI3FVFBBYw3pQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s how you load balance telemetry across multiple collectors with Nginx.&lt;/p&gt;

&lt;p&gt;If you would rather apply these configs via the Bindplane CLI, &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/tree/main/docker-nginx" rel="noopener noreferrer"&gt;get the files on GitHub, here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Load Balancing Exporter
&lt;/h3&gt;

&lt;p&gt;The second option is to use the &lt;a href="https://opentelemetry.io/docs/collector/deployment/gateway/#load-balancing-exporter" rel="noopener noreferrer"&gt;dedicated &lt;code&gt;loadbalancing&lt;/code&gt; exporter in the collector&lt;/a&gt;. With this exporter you can specify multiple downstream collectors that will receive the telemetry traffic equally.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfh5o14JJXuMeqNRD_kcN4PTqNJE7ghhgprm_xO_kUOz2wP5tOJqIg1E0bZJ7x3C6vUiLcHfUMqumrFxtyYZ8JLLe74f89zEhE9NnUKZxmB2AY71bhaIF6XZyFFK-dXqAhEseGlnA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfh5o14JJXuMeqNRD_kcN4PTqNJE7ghhgprm_xO_kUOz2wP5tOJqIg1E0bZJ7x3C6vUiLcHfUMqumrFxtyYZ8JLLe74f89zEhE9NnUKZxmB2AY71bhaIF6XZyFFK-dXqAhEseGlnA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="902"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One quick note before about the load balancing exporter. You don’t always need it. Its main job is to &lt;a href="https://opentelemetry.io/docs/collector/deployment/gateway/#load-balancing-exporter" rel="noopener noreferrer"&gt;make sure spans from the same trace stick together&lt;/a&gt; and get routed to the same backend collector. That’s super useful for distributed tracing with sampling. But if you’re just shipping logs and metrics, or even traces without fancy sampling rules, you can probably skip it and stick with Nginx.&lt;/p&gt;

&lt;p&gt;I’ll set up the architecture just as I did above but with yet another collector instead the Nginx load balancer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Three Gateway Collectors in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One Gateway Collector using the loadbalancing exporter&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One Agent Collector configured to generate telemetry (app simulation)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This behaves identical to an Nginx load balancer. However this requires one less step and less configuration overhead. No need to configure and run Nginx, manage specific Nginx files, instead run one more instance of the collector and use a trusty collector config.yaml that you’re already familiar with.&lt;/p&gt;

&lt;p&gt;The drop in replacement for the use case above is as follows. In the &lt;code&gt;docker-compose.yaml&lt;/code&gt; replace the &lt;code&gt;otlp-lb&lt;/code&gt; Nginx service with another collector named &lt;code&gt;lb&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:


# ...

  lb:
    image: ghcr.io/observiq/bindplane-agent:1.79.2
    container_name: lb
    hostname: lb
    command: ["--config=/etc/otel/config/lb-config.yaml"]
    volumes:
      - ./config:/etc/otel/config
      - lb-storage:/etc/otel/storage
    ports:
      - "4317:4317"   # OTLP gRPC - external endpoint
      - "4318:4318"   # OTLP HTTP/JSON - external endpoint
    environment:
      OPAMP_ENDPOINT: "wss://app.bindplane.com/v1/opamp"
      OPAMP_SECRET_KEY: "01JFJGVKWHQ1SPQVDGZEHVA995"
      OPAMP_LABELS: ephemeral=true
      MANAGER_YAML_PATH: /etc/otel/config/lb-manager.yaml
      CONFIG_YAML_PATH: /etc/otel/config/lb-config.yaml
      LOGGING_YAML_PATH: /etc/otel/config/logging.yaml
    depends_on: [gw1, gw2, gw3]

# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a base &lt;code&gt;lb-config.yaml&lt;/code&gt; for this collector instance in the &lt;code&gt;./config&lt;/code&gt; directory. Bindplane will update this remotely once you add a destination for the loadbalancing exporter.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receivers:
  nop:
processors:
  batch:
exporters:
  nop:
service:
  pipelines:
    metrics:
      receivers: [nop]
      processors: [batch]
      exporters: [nop]
  telemetry:
    metrics:
      level: none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Go ahead and restart Docker Compose. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose down
docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will start the new &lt;code&gt;lb&lt;/code&gt; collector. In Bindplane, go ahead and create a new configuration called &lt;code&gt;lb&lt;/code&gt; and add an OTLP source that listens on 0.0.0.0 and ports 4317 and 4318.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdBQz-SxaC45RNgV1n30jBgicpnF6cO4pQGtgzlFI3TCSkeyn_ZRt5qacCfYU022VHRAbTSHZcRhKFbwhafo2kzpwyd1GrDG36iKwwwoPqM7FcFVPxiBtNwlIgFgWbqhXVat5tt%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdBQz-SxaC45RNgV1n30jBgicpnF6cO4pQGtgzlFI3TCSkeyn_ZRt5qacCfYU022VHRAbTSHZcRhKFbwhafo2kzpwyd1GrDG36iKwwwoPqM7FcFVPxiBtNwlIgFgWbqhXVat5tt%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, create a custom destination and paste the &lt;code&gt;loadbalancing&lt;/code&gt; exporter configuration in the input field.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loadbalancing:
    protocol:
      otlp:
        tls:
          insecure: true
        timeout: 30s
        retry_on_failure:
          enabled: true
          initial_interval: 5s
          max_elapsed_time: 300s
          max_interval: 30s
        sending_queue:
          enabled: true
          num_consumers: 10
          queue_size: 5000
    resolver:
      static:
        hostnames:
          - gw1:4317
          - gw2:4317
          - gw3:4317
&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeP2t78EAJW1Zh2MXOSfIlAV9vnEJ9ZBQpWxM2WUQ1H8ByS2FfiAzdRPETKNlr19MuwAmAYTHZY8SgQ5UFMqThlzgNj5g3XIDUq9quMuJ0gyG2oWZqXK7-jEyiPEg6yGGiXU896%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeP2t78EAJW1Zh2MXOSfIlAV9vnEJ9ZBQpWxM2WUQ1H8ByS2FfiAzdRPETKNlr19MuwAmAYTHZY8SgQ5UFMqThlzgNj5g3XIDUq9quMuJ0gyG2oWZqXK7-jEyiPEg6yGGiXU896%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the hostnames correlate to the hostnames of the gateway collectors configured in Docker Compose. Save this configuration and roll it out to the new &lt;code&gt;lb&lt;/code&gt; collector. Opening the &lt;code&gt;gw&lt;/code&gt; configuration in Bindplane and selecting a processor node, you’ll see the telemetry flowing through all 3 gateway collector instances.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcOvZlU840MzOAcbg0GchCV0fy0CD976O_QiOQhqv1xYmq-4QlrTcCPnZDV17qTej88c3OSjGQ4hKvM__RtihOeTvYMNGy8xYVnFt0cDFq6bdYPHXhHKMJKzcLrURcL4jXu9-Ry-A%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcOvZlU840MzOAcbg0GchCV0fy0CD976O_QiOQhqv1xYmq-4QlrTcCPnZDV17qTej88c3OSjGQ4hKvM__RtihOeTvYMNGy8xYVnFt0cDFq6bdYPHXhHKMJKzcLrURcL4jXu9-Ry-A%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll see an even nicer split by seeing the telemetry throughput across all collectors in the Agents view.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcoeOjEISGNLoX_mfhZUqMhow_igfUG0_MsdPMrJGvflzCy3RJ-BUMe--3OG6pc_qDHkXK6noM_eKURWZIscZF4K1DVpdyr6Ge4Rd3QJMEnIQJZE3O8nIX0O6WdKeChh2-X9G862Q%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcoeOjEISGNLoX_mfhZUqMhow_igfUG0_MsdPMrJGvflzCy3RJ-BUMe--3OG6pc_qDHkXK6noM_eKURWZIscZF4K1DVpdyr6Ge4Rd3QJMEnIQJZE3O8nIX0O6WdKeChh2-X9G862Q%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;lb&lt;/strong&gt; and &lt;strong&gt;external-gw&lt;/strong&gt; are reporting the same throughput with the three gateway collectors load balancing traffic equally.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://opentelemetry.io/docs/collector/deployment/gateway/#load-balancing-exporter" rel="noopener noreferrer"&gt;loadbalancing exporter&lt;/a&gt; is behaving like a drop-in replacement for Nginx. I would call that a win. Less configuration overhead, fewer moving parts, and no need to learn specific Nginx configs. Instead, focus only on the collector.&lt;/p&gt;

&lt;p&gt;To get this sample up-and-running quickly, apply these configs via the Bindplane CLI, &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/tree/main/docker-loadbalancing-exporter" rel="noopener noreferrer"&gt;get the files on GitHub, here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since you now have a good understanding of how to configure OpenTelemetry Collector  infrastructure for high availability, let's move into details about resilience specifically. &lt;/p&gt;

&lt;h2&gt;
  
  
  Building Resilience into Your Collector
&lt;/h2&gt;

&lt;p&gt;When it comes to resilience, features like &lt;strong&gt;retry logic, persistent queues, and batching&lt;/strong&gt; should be handled in the &lt;strong&gt;Agent Collectors&lt;/strong&gt;. These are the instances sitting closest to your workloads; they’re most at risk of losing data if something goes wrong. The Agent’s job is to collect, buffer, and forward telemetry reliably, even when the backend is flaky or slow.&lt;/p&gt;

&lt;p&gt;How you configure the OpenTelemetry collector for resilience to avoid losing telemetry during network issues or telemetry backend outages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Batching&lt;/strong&gt; groups signals before export, improving efficiency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retry&lt;/strong&gt; ensures failed exports are re-attempted. For critical workloads, increase &lt;code&gt;max_elapsed_time&lt;/code&gt; to tolerate longer outages—but be aware this will increase the buffer size on disk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Persistent Queue&lt;/strong&gt; stores retries on disk, protecting against data loss if the Collector crashes. You can configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Number of consumers&lt;/em&gt;&lt;/strong&gt; – how many parallel retry workers run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Queue size&lt;/em&gt;&lt;/strong&gt; – how many batches are stored&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Persistence&lt;/em&gt;&lt;/strong&gt; – enables disk buffering for reliability&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Retry &amp;amp; Persistent Queue
&lt;/h3&gt;

&lt;p&gt;Luckily enough for you, Bindplane handles both retries and the persistent queue out of the box for OTLP exporters.&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;strong&gt;telgen&lt;/strong&gt; configuration. This is the collector we’re running in agent-mode simulating a bunch of telemetry traffic.&lt;/p&gt;

&lt;p&gt;In the telgen-config.yaml, you'll see OTLP exporter is configured with both the persistent queue and retries.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exporters:
    otlp/lb:
        compression: gzip
        endpoint: gw:4317
        retry_on_failure:
            enabled: true
            initial_interval: 5s
            max_elapsed_time: 300s
            max_interval: 30s
        sending_queue:
            enabled: true
            num_consumers: 10
            queue_size: 5000
            storage: file_storage/lb
        timeout: 30s
        tls:
            insecure: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is because the advanced settings for every OTLP exporter in Bindplane have this default configuration enabled.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc72tjin5I68b0vGQXZbmGRQDYmA8IQ8qbwyObWuhlSpZgTuaVwkI1RyJIETVERnW6bEl-zmUMKhxR4oHI7ix3TXBONpS2lkWU33T3fxCVosTWeQGD8Fi4hysux2UhCdcsfsq_qSw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc72tjin5I68b0vGQXZbmGRQDYmA8IQ8qbwyObWuhlSpZgTuaVwkI1RyJIETVERnW6bEl-zmUMKhxR4oHI7ix3TXBONpS2lkWU33T3fxCVosTWeQGD8Fi4hysux2UhCdcsfsq_qSw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The persistent queue directory here is the storage directory that we configured by creating a volume in Docker. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# docker-compose.yaml 

...

volumes:
  gw1-storage:      # persistent queue for gateway-1
  gw2-storage:      # persistent queue for gateway-2
  gw3-storage:      # persistent queue for gateway-3
  telgen-storage:   # persistent queue for telemetry generator
  lb-storage:    # persistent queue for load-balancing gateway
  external-gw-storage: # persistent queue for external gateway

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Bindplane then automatically configures a storage extension in the config and enables it like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# telgen-config.yaml 

...
extensions:
    file_storage/lb:
        compaction:
            directory: ${OIQ_OTEL_COLLECTOR_HOME}/storage
            on_rebound: true
        directory: ${OIQ_OTEL_COLLECTOR_HOME}/storage
service:
    extensions:
        - file_storage/lb
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that the &lt;code&gt;OIQ_OTEL_COLLECTOR_HOME&lt;/code&gt; environment variable actually is mapped to the &lt;code&gt;/etc/otel&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Now your telemetry pipeline becomes resilient and HA-ready with data persistence to survive restarts, persistent queue buffering to handle temporary outages, and failover recovery to prevent data loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batching
&lt;/h3&gt;

&lt;p&gt;Batching is a whole other story, because you need to add a processor on the processor node for it to be enabled before connecting it to the destination. &lt;/p&gt;

&lt;p&gt;Agent-mode collectors should batch telemetry before sending it to the gateway collector. The OTLP receiver on the gateway side will receive batches and forward them to your telemetry backend of choice.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;telgen&lt;/strong&gt; configuration, click a processor node and add a batch processor.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf6emSTjc9thPdpN3y5yIdXT5M7YeRx53riuSzRYdQE7G0u5f42uY0FiVV8A9Yasl08L5z2JRXTGsPo9qdqCAoG0hgasHO82xhYyYQsh5nJWwz0PLeuiEmuKWkaF_jmWBkqIX5eaA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf6emSTjc9thPdpN3y5yIdXT5M7YeRx53riuSzRYdQE7G0u5f42uY0FiVV8A9Yasl08L5z2JRXTGsPo9qdqCAoG0hgasHO82xhYyYQsh5nJWwz0PLeuiEmuKWkaF_jmWBkqIX5eaA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This config will send a batch of telemetry signals every 200ms regardless of the size. Or, it will send a batch of the size 8192 regardless of the timeout. Applying this processor in Bindplane will generate a config like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# telgen-config.yaml 

...

processors:
    batch/lb: null
    batch/lb-0__processor0:
        send_batch_max_size: 0
        send_batch_size: 8192
        timeout: 200ms

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Kubernetes-native load balancing with HorizontalPodAutoscaler
&lt;/h2&gt;

&lt;p&gt;Finally, after all the breakdowns, explanations, and diagrams, it’s time to show you what it would look like in the wild with a simple Kubernetes sample.&lt;/p&gt;

&lt;p&gt;Using Kubernetes is the preferred architecture suggested by the Bindplane team and the OpenTelemetry community. K8s will maximize the benefits you get with Bindplane as well.&lt;/p&gt;

&lt;p&gt;I’ll set up the architecture with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;One Agent-mode Collector running per node on the K8s cluster configured to generate telemetry (app simulation)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A Gateway Collector Deployment&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using a HorizontalPodAutoscaler scaling from 2 to 10 pods&lt;/li&gt;
&lt;li&gt;And a ClusterIP service&lt;/li&gt;
&lt;li&gt;Configured with persistent storage, sending queue, and retry&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An external Gateway Collector running on another cluster acting as a mock telemetry backend&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily enough getting all the K8s YAML manifests for the collectors is all point-and-click from the Bindplane UI. However, you need to build the configurations first, before applying the collectors to your K8s cluster.&lt;/p&gt;

&lt;p&gt;For the sake of simplicity l’ll show how to spin up two K8s clusters with &lt;a href="https://kind.sigs.k8s.io/" rel="noopener noreferrer"&gt;kind&lt;/a&gt;, and use them in this demo.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind create cluster --name kind-2
kind create cluster --name kind-1
# make sure you set the context to the kind-1 cluster first
kubectl config use-context kind-kind-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, jump into Bindplane and create three configurations for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;telgen-kind-1&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;gw-kind-1&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;external-gw-kind-2&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeanLg1JOJMNBBXsBuyn_ooaBuXQH8Id6TdZXS9bL1QhPT_GKEiygbuFt4zpDQ4JZT0-LFHSK-ZlyiT75k9DMBB8AVgNyIQbhho3HbgWdF209x8p7a1sBYsRfCtzzkshgvkXbvhPw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeanLg1JOJMNBBXsBuyn_ooaBuXQH8Id6TdZXS9bL1QhPT_GKEiygbuFt4zpDQ4JZT0-LFHSK-ZlyiT75k9DMBB8AVgNyIQbhho3HbgWdF209x8p7a1sBYsRfCtzzkshgvkXbvhPw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;telgen-kind-1&lt;/strong&gt; configuration has a Custom source with a &lt;code&gt;telemetrygeneratorreceiver&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telemetrygeneratorreceiver:
        generators:
            - additional_config:
                body: 127.0.0.1 - - [30/Jun/2025:12:00:00 +0000] \"GET /index.html HTTP/1.1\" 200 512
                severity: 9
              type: logs
        payloads_per_second: 1
&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc-LZuY_TOqimPQLPzUOy0-fYVT1Vfv5QJscX4Zfm4ktmr7ViwKVjcd7hnwSdzm-16aSSwnbBsnzJAmQeSvGC-BhsCcKn-gKrOK1Ps3FqNvF2dTck3hMa0JgwXomNQP0ipuhA63%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXc-LZuY_TOqimPQLPzUOy0-fYVT1Vfv5QJscX4Zfm4ktmr7ViwKVjcd7hnwSdzm-16aSSwnbBsnzJAmQeSvGC-BhsCcKn-gKrOK1Ps3FqNvF2dTck3hMa0JgwXomNQP0ipuhA63%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, a Bindplane Gateway destination.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: This is identical to any OTLP destination.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdTPqRpRijEMhq7MKSHrqYFM9_FSF1nryvbukTwlhE_B0FrchnHOInswZBodEEmOabn7fNr8SPmRS0wGKTKqK-X4sVlmDLT7gRL7_hji074_lvOT0V9gUCoYVM22HZam_JI7yNT%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdTPqRpRijEMhq7MKSHrqYFM9_FSF1nryvbukTwlhE_B0FrchnHOInswZBodEEmOabn7fNr8SPmRS0wGKTKqK-X4sVlmDLT7gRL7_hji074_lvOT0V9gUCoYVM22HZam_JI7yNT%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="929"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Bindplane Gateway destination is configured to send telemetry to the &lt;code&gt;bindplane-gateway-agent.bindplane-agent.svc.cluster.local&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;hostname, which is the hostname for the Bindplane Gateway Collector service in Kubernetes that you’ll start in a second.&lt;/p&gt;

&lt;p&gt;The final step for this configuration is to click a processor node and add a batch processor.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf6emSTjc9thPdpN3y5yIdXT5M7YeRx53riuSzRYdQE7G0u5f42uY0FiVV8A9Yasl08L5z2JRXTGsPo9qdqCAoG0hgasHO82xhYyYQsh5nJWwz0PLeuiEmuKWkaF_jmWBkqIX5eaA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf6emSTjc9thPdpN3y5yIdXT5M7YeRx53riuSzRYdQE7G0u5f42uY0FiVV8A9Yasl08L5z2JRXTGsPo9qdqCAoG0hgasHO82xhYyYQsh5nJWwz0PLeuiEmuKWkaF_jmWBkqIX5eaA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, the &lt;strong&gt;gw-kind-1&lt;/strong&gt; configuration has a Bindplane Gateway source that listens on 0.0.0.0 and ports 4317 and 4318.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcJnp5O0ub82RNBJhpE5hTTgWNJayK3siFEWq3Bu9I0_HX6dqO3UMD9W0RyKLW7FpsEi_zZK89Uxv8Wz1crjlH1jhheDs0sqUgNN-RapvUI-T9TW5_-KFylSgNHF8EDR95z6YJ18Q%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcJnp5O0ub82RNBJhpE5hTTgWNJayK3siFEWq3Bu9I0_HX6dqO3UMD9W0RyKLW7FpsEi_zZK89Uxv8Wz1crjlH1jhheDs0sqUgNN-RapvUI-T9TW5_-KFylSgNHF8EDR95z6YJ18Q%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1005"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The destination is OTLP, and sending telemetry to the IP address (172.18.0.2) of the external gateway running on the second K8s cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;: This might differ for your clusters. If you are using&lt;/em&gt; &lt;strong&gt;&lt;em&gt;kind,&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;like I am in this demo, the IP will be 172.18.0.2.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfJZMuWYLxfPxQxczC21OtV1Jk6ohX3_WqngBXsIQKrk6bPKh8QWYOCcaPnDlL7xqMpzL080Zepb84dfKYYFRytKvjp7O5bVz5beQ2iI9E5WP_3l1eSq079sxyprpa86IVekE_Rzg%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfJZMuWYLxfPxQxczC21OtV1Jk6ohX3_WqngBXsIQKrk6bPKh8QWYOCcaPnDlL7xqMpzL080Zepb84dfKYYFRytKvjp7O5bVz5beQ2iI9E5WP_3l1eSq079sxyprpa86IVekE_Rzg%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1076"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;external-gw-kind-2&lt;/strong&gt; configuration is again using an OTLP source.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcs84JO2gQO32TEBNpY2bgtjLz3cFvVk5Gb4bnCNichKm60_Ahl9pW2L06bpRfRZrTNb-gsinFovNdU5r2d0HfLthvwZ_4iqGbgLbsJ05qMVUCQw64dMQVeKQlz5f6t6PWnrvcMdA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcs84JO2gQO32TEBNpY2bgtjLz3cFvVk5Gb4bnCNichKm60_Ahl9pW2L06bpRfRZrTNb-gsinFovNdU5r2d0HfLthvwZ_4iqGbgLbsJ05qMVUCQw64dMQVeKQlz5f6t6PWnrvcMdA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, a Dev Null destination.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdjW_PsGUOZ4sbgV3wpjGTDpQmrCSWZv-EcrkMB8hijFBPIIGEBDX4FTNogQBp8WnYgc1tVNiUp5Ydq-94eDM_afidRMab2arkEnpgpzZzvhd_Bx9g_7g92wn49aNBgbjxGluL_ZQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdjW_PsGUOZ4sbgV3wpjGTDpQmrCSWZv-EcrkMB8hijFBPIIGEBDX4FTNogQBp8WnYgc1tVNiUp5Ydq-94eDM_afidRMab2arkEnpgpzZzvhd_Bx9g_7g92wn49aNBgbjxGluL_ZQ%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="1260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/tree/main/k8s-loadbalancing" rel="noopener noreferrer"&gt;use the Bindplane CLI and these resources&lt;/a&gt; to apply all the configurations in one go without having to do it manually in the UI.&lt;/p&gt;

&lt;p&gt;With the configurations created, you can install collectors easily by getting manifest files from your Bindplane account. Navigate to the install agents UI in Bindplane and select a Kubernetes environment. Use the &lt;strong&gt;Node&lt;/strong&gt; platform and &lt;strong&gt;telgen-kind-1&lt;/strong&gt; configuration.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdZ1JbFmWY1gKSoxp_G3WIycXz8MxcLOwpevEKwjYakmdPzxr-p8UkJHMUn-_0JZJcm-rGTc5ALCWAiK38JD6-_WiTs7Oewx8to7wvQA90e8xIOKDXkiK7k_EImf7QHn7KPMaho%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdZ1JbFmWY1gKSoxp_G3WIycXz8MxcLOwpevEKwjYakmdPzxr-p8UkJHMUn-_0JZJcm-rGTc5ALCWAiK38JD6-_WiTs7Oewx8to7wvQA90e8xIOKDXkiK7k_EImf7QHn7KPMaho%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1555" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking next will show a manifest file for you to apply in the cluster.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe1XVpWtfttEEOrPQyRzFVx8a13ZYNATHJ-Skd9o6_-hNIfegMvLtrNPK72N7XS8ykdwh3Hjh0jR7f07Cm9YWVIBBzQOCVzcyr09i8bEap6jp1oEpY2u9-Qty5TYck59XB4r-BM6w%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXe1XVpWtfttEEOrPQyRzFVx8a13ZYNATHJ-Skd9o6_-hNIfegMvLtrNPK72N7XS8ykdwh3Hjh0jR7f07Cm9YWVIBBzQOCVzcyr09i8bEap6jp1oEpY2u9-Qty5TYck59XB4r-BM6w%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1489" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save this file as &lt;code&gt;node-agent-kind-1.yaml&lt;/code&gt;. Check out below what a sample of it looks like. Or, see the &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/blob/main/k8s-loadbalancing/node-agent-kind-1.yaml" rel="noopener noreferrer"&gt;file in GitHub, here&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
  name: bindplane-agent
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
  name: bindplane-agent
  namespace: bindplane-agent
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: bindplane-agent
  labels:
    app.kubernetes.io/name: bindplane-agent
rules:
- apiGroups:
  - ""
  resources:
  - events
  - namespaces
  - namespaces/status
  - nodes
  - nodes/spec
  - nodes/stats
  - nodes/proxy
  - pods
  - pods/status
  - replicationcontrollers
  - replicationcontrollers/status
  - resourcequotas
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - daemonsets
  - deployments
  - replicasets
  - statefulsets
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - daemonsets
  - deployments
  - replicasets
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - batch
  resources:
  - jobs
  - cronjobs
  verbs:
  - get
  - list
  - watch
- apiGroups:
    - autoscaling
  resources:
    - horizontalpodautoscalers
  verbs:
    - get
    - list
    - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: bindplane-agent
  labels:
    app.kubernetes.io/name: bindplane-agent
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: bindplane-agent
subjects:
- kind: ServiceAccount
  name: bindplane-agent
  namespace: bindplane-agent
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
  name: bindplane-node-agent
  namespace: bindplane-agent
spec:
  ports:
  - appProtocol: grpc
    name: otlp-grpc
    port: 4317
    protocol: TCP
    targetPort: 4317
  - appProtocol: http
    name: otlp-http
    port: 4318
    protocol: TCP
    targetPort: 4318
  selector:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: node
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: node
  name: bindplane-node-agent-headless
  namespace: bindplane-agent
spec:
  clusterIP: None
  ports:
  - appProtocol: grpc
    name: otlp-grpc
    port: 4317
    protocol: TCP
    targetPort: 4317
  - appProtocol: http
    name: otlp-http
    port: 4318
    protocol: TCP
    targetPort: 4318
  selector:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: node
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: bindplane-node-agent-setup
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: node
  namespace: bindplane-agent
data:
  # This script assumes it is running in /etc/otel.
  setup.sh: |
    # Configure storage/ emptyDir volume permissions so the
    # manager configuration can ge written to it.
    chown 10005:10005 storage/

    # Copy config and logging configuration files to storage/
    # hostPath volume if they do not already exist.
    if [ ! -f storage/config.yaml ]; then
      echo '
      receivers:
        nop:
      processors:
        batch:
      exporters:
        nop:
      service:
        pipelines:
          metrics:
            receivers: [nop]
            processors: [batch]
            exporters: [nop]
        telemetry:
          metrics:
            level: none
      ' &amp;gt; storage/config.yaml
    fi
    if [ ! -f storage/logging.yaml ]; then
      echo '
      output: stdout
      level: info
      ' &amp;gt; storage/logging.yaml
    fi
    chown 10005:10005 storage/config.yaml storage/logging.yaml
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: bindplane-node-agent
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: node
  namespace: bindplane-agent
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: bindplane-agent
      app.kubernetes.io/component: node
  template:
    metadata:
      labels:
        app.kubernetes.io/name: bindplane-agent
        app.kubernetes.io/component: node
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/path: /metrics
        prometheus.io/port: "8888"
        prometheus.io/scheme: http
        prometheus.io/job-name: bindplane-node-agent
    spec:
      serviceAccount: bindplane-agent
      initContainers:
        - name: setup
          image: busybox:latest
          securityContext:
            # Required for changing permissions from
            # root to otel user in emptyDir volume.
            runAsUser: 0
          command: ["sh", "/setup/setup.sh"]
          volumeMounts:
            - mountPath: /etc/otel/config
              name: config
            - mountPath: /storage
              name: storage
            - mountPath: "/setup"
              name: setup
      containers:
        - name: opentelemetry-collector
          image: ghcr.io/observiq/bindplane-agent:1.80.1
          imagePullPolicy: IfNotPresent
          securityContext:
            readOnlyRootFilesystem: true
            # Required for reading container logs hostPath.
            runAsUser: 0
          ports:
            - containerPort: 8888
              name: prometheus
          resources:
            requests:
              memory: 200Mi
              cpu: 100m
            limits:
              memory: 200Mi
          env:
            - name: OPAMP_ENDPOINT
              value: wss://app.bindplane.com/v1/opamp
            - name: OPAMP_SECRET_KEY
              value: &amp;lt;secret&amp;gt;
            - name: OPAMP_AGENT_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: OPAMP_LABELS
              value: configuration=telgen-kind-1,container-platform=kubernetes-daemonset,install_id=0979c5c2-bd7a-41c1-89b8-2c16441886ab
            - name: KUBE_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            # The collector process updates config.yaml
            # and manager.yaml when receiving changes
            # from the OpAMP server.
            #
            # The config.yaml is persisted by saving it to the
            # hostPath volume, allowing the agent to continue
            # running after restart during an OpAMP server outage.
            #
            # The manager configuration must be re-generated on
            # every startup due to how the bindplane-agent handles
            # manager configuration. It prefers a manager config file
            # over environment variables, meaning it cannot be
            # updated using environment variables, if it is persisted).
            - name: CONFIG_YAML_PATH
              value: /etc/otel/storage/config.yaml
            - name: MANAGER_YAML_PATH
              value: /etc/otel/config/manager.yaml
            - name: LOGGING_YAML_PATH
              value: /etc/otel/storage/logging.yaml
          volumeMounts:
            - mountPath: /etc/otel/config
              name: config
            - mountPath: /run/log/journal
              name: runlog
              readOnly: true
            - mountPath: /var/log
              name: varlog
              readOnly: true
            - mountPath: /var/lib/docker/containers
              name: dockerlogs
              readOnly: true
            - mountPath: /etc/otel/storage
              name: storage
      volumes:
        - name: config
          emptyDir: {}
        - name: runlog
          hostPath:
            path: /run/log/journal
        - name: varlog
          hostPath:
            path: /var/log
        - name: dockerlogs
          hostPath:
            path: /var/lib/docker/containers
        - name: storage
          hostPath:
            path: /var/lib/observiq/otelcol/container
        - name: setup
          configMap:
            name: bindplane-node-agent-setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In short, this manifest deploys the BDOT Collector as a &lt;strong&gt;DaemonSet&lt;/strong&gt; on every node, using &lt;strong&gt;OpAMP&lt;/strong&gt; to receive config from Bindplane. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;RBAC to read Kubernetes objects (pods, nodes, deployments, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services to expose OTLP ports (4317 gRPC, 4318 HTTP)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An init container to bootstrap a config to start the collector which will be replaced by the &lt;strong&gt;telgen-kind-1&lt;/strong&gt; configuration once started&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Persistent hostPath storage for retries and disk buffering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus annotations for metrics scraping&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your file will include the correct &lt;code&gt;OPAMP_ENDPOINT&lt;/code&gt;, &lt;code&gt;OPAMP_SECRET_KEY&lt;/code&gt;, and &lt;code&gt;OPAMP_LABELS.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and apply this manifest to the first k8s cluster.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config use-context kind-kind-1
kubectl apply -f node-agent-kind-1.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, install another collector in the K8s cluster, but now choose a Gateway and the &lt;strong&gt;gw-kind-1&lt;/strong&gt; configuration.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeEGBgMI2M2sfGyYVFQDF2qY1AsxogeiJOkCPy_LEvqbWMX-U9Nyemtf8Gg4rRDzqgo2_auBlrvYy2MViE-tojeAV2_okhIFhBszQeRmXj7zbz49U6GdcDlu8NbN3PfQtbSZ9bhgA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeEGBgMI2M2sfGyYVFQDF2qY1AsxogeiJOkCPy_LEvqbWMX-U9Nyemtf8Gg4rRDzqgo2_auBlrvYy2MViE-tojeAV2_okhIFhBszQeRmXj7zbz49U6GdcDlu8NbN3PfQtbSZ9bhgA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1559" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll get a manifest file to apply again, but this time a deployment. Save it as &lt;code&gt;gateway-collector-kind-1.yaml&lt;/code&gt;. &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/blob/main/k8s-loadbalancing/gateway-collector-kind-1.yaml" rel="noopener noreferrer"&gt;Here’s what it looks like in GitHub&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeme4JUQz0-9hHrpSy1s8W-LW2KY82KT0X2H8_bV7Ir4s0zwCWlrszCTmj0wT1cd1kHfTrK_Em9reC9a2ScmI-bJVIWCmTudNivT5oNLmDkp9yuzHl3aT_y32CD_LomtXLeYcNDzA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXeme4JUQz0-9hHrpSy1s8W-LW2KY82KT0X2H8_bV7Ir4s0zwCWlrszCTmj0wT1cd1kHfTrK_Em9reC9a2ScmI-bJVIWCmTudNivT5oNLmDkp9yuzHl3aT_y32CD_LomtXLeYcNDzA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1403" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the full manifest as a deployment with a horizontal pod autoscaler.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
  name: bindplane-agent
---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
  name: bindplane-agent
  namespace: bindplane-agent
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
  name: bindplane-gateway-agent
  namespace: bindplane-agent
spec:
  ports:
  - appProtocol: grpc
    name: otlp-grpc
    port: 4317
    protocol: TCP
    targetPort: 4317
  - appProtocol: http
    name: otlp-http
    port: 4318
    protocol: TCP
    targetPort: 4318
  - appProtocol: tcp
    name: splunk-tcp
    port: 9997
    protocol: TCP
    targetPort: 9997
  - appProtocol: tcp
    name: splunk-hec
    port: 8088
    protocol: TCP
    targetPort: 8088
  selector:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
  name: bindplane-gateway-agent-headless
  namespace: bindplane-agent
spec:
  clusterIP: None
  ports:
  - appProtocol: grpc
    name: otlp-grpc
    port: 4317
    protocol: TCP
    targetPort: 4317
  - appProtocol: http
    name: otlp-http
    port: 4318
    protocol: TCP
    targetPort: 4318
  selector:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bindplane-gateway-agent
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
  namespace: bindplane-agent
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: bindplane-agent
      app.kubernetes.io/component: gateway
  template:
    metadata:
      labels:
        app.kubernetes.io/name: bindplane-agent
        app.kubernetes.io/component: gateway
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/path: /metrics
        prometheus.io/port: "8888"
        prometheus.io/scheme: http
        prometheus.io/job-name: bindplane-gateway-agent
    spec:
      serviceAccount: bindplane-agent
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                topologyKey: kubernetes.io/hostname
                labelSelector:
                  matchExpressions:
                    - key: app.kubernetes.io/name
                      operator: In
                      values:  [bindplane-agent]
                    - key: app.kubernetes.io/component
                      operator: In
                      values: [gateway]
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000000000
        runAsGroup: 1000000000
        fsGroup: 1000000000
        seccompProfile:
          type: RuntimeDefault
      initContainers:
        - name: setup-volumes
          image: ghcr.io/observiq/bindplane-agent:1.80.1
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000000000
            runAsGroup: 1000000000
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop:
                - ALL
          command:
            - 'sh'
            - '-c'
            - |
              echo '
              receivers:
                nop:
              processors:
                batch:
              exporters:
                nop:
              service:
                pipelines:
                  metrics:
                    receivers: [nop]
                    processors: [batch]
                    exporters: [nop]
                telemetry:
                  metrics:
                    level: none
              ' &amp;gt; /etc/otel/storage/config.yaml
              echo '
              output: stdout
              level: info
              ' &amp;gt; /etc/otel/storage/logging.yaml
          resources:
            requests:
              memory: 200Mi
              cpu: 100m
            limits:
              memory: 200Mi
          volumeMounts:
            - mountPath: /etc/otel/storage
              name: bindplane-gateway-agent-storage
      containers:
        - name: opentelemetry-container
          image: ghcr.io/observiq/bindplane-agent:1.80.1
          imagePullPolicy: IfNotPresent
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000000000
            runAsGroup: 1000000000
            readOnlyRootFilesystem: true
            allowPrivilegeEscalation: false
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop:
                - ALL
          resources:
            requests:
              memory: 500Mi
              cpu: 250m
            limits:
              memory: 500Mi
          ports:
            - containerPort: 8888
              name: prometheus
          env:
            - name: OPAMP_ENDPOINT
              value: wss://app.bindplane.com/v1/opamp
            - name: OPAMP_SECRET_KEY
              value: &amp;lt;secret&amp;gt;
            - name: OPAMP_AGENT_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: OPAMP_LABELS
              value: configuration=gw-kind-1,container-platform=kubernetes-gateway,install_id=51dbe4d2-83d2-45c0-ab4a-e0c127a59649
            - name: KUBE_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            # The collector process updates config.yaml
            # and manager.yaml when receiving changes
            # from the OpAMP server.
            - name: CONFIG_YAML_PATH
              value: /etc/otel/storage/config.yaml
            - name: MANAGER_YAML_PATH
              value: /etc/otel/config/manager.yaml
            - name: LOGGING_YAML_PATH
              value: /etc/otel/storage/logging.yaml
          volumeMounts:
          - mountPath: /etc/otel/storage
            name: bindplane-gateway-agent-storage
          - mountPath: /etc/otel/config
            name: config
      volumes:
        - name: config
          emptyDir: {}
        - name: bindplane-gateway-agent-storage
          emptyDir: {}
      # Allow exporters to drain their queue for up to
      # five minutes.
      terminationGracePeriodSeconds: 500
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: bindplane-gateway-agent
  namespace: bindplane-agent
spec:
  maxReplicas: 10
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: bindplane-gateway-agent
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here’s a breakdown of what this manifest does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creates a dedicated namespace and service account&lt;/strong&gt; for the Bindplane Gateway Collector (bindplane-agent).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Defines two Kubernetes services&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A standard ClusterIP service for OTLP (gRPC/HTTP) and Splunk (TCP/HEC) traffic.&lt;/li&gt;
&lt;li&gt;A headless service for direct pod discovery, useful in peer-to-peer setups.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Deploys the Bindplane Agent as a scalable Deployment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs the OpenTelemetry Collector image.&lt;/li&gt;
&lt;li&gt;Bootstraps basic config via an initContainer.&lt;/li&gt;
&lt;li&gt;Secure runtime with strict securityContext settings.&lt;/li&gt;
&lt;li&gt;Prometheus annotations enable metrics scraping.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Auto-scales the collector horizontally&lt;/strong&gt; using an HPA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scales between 2 and 10 replicas based on CPU utilization.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Uses OpAMP&lt;/strong&gt; to receive remote config and updates from Bindplane.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mounts ephemeral storage&lt;/strong&gt; for config and persistent queue support using emptyDir.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Your file will include the correct &lt;code&gt;OPAMP_ENDPOINT&lt;/code&gt;, &lt;code&gt;OPAMP_SECRET_KEY&lt;/code&gt;, and &lt;code&gt;OPAMP_LABELS.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Apply it in the first k8s cluster.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f gateway-collector-kind-1.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, create an identical Gateway Collector as above but use the &lt;strong&gt;external-gw-kind-2&lt;/strong&gt; configuration.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf2R8EUsgLOO17lItXlctx504JAfVH43vcNGi8KLihvS1Dyi-guLSFEf6OTLHK3ApyqXksh37Bt50MNZ34BWlL2iZ9OVl7uJ7OMxk3TGAjzPMPZDtfpYT59DARdS3_eT5YLFdJP%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf2R8EUsgLOO17lItXlctx504JAfVH43vcNGi8KLihvS1Dyi-guLSFEf6OTLHK3ApyqXksh37Bt50MNZ34BWlL2iZ9OVl7uJ7OMxk3TGAjzPMPZDtfpYT59DARdS3_eT5YLFdJP%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1556" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll get a manifest file to apply again, but this time apply it in your second cluster. Save it as &lt;code&gt;gateway-collector-kind-2.yaml&lt;/code&gt;. &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/blob/main/k8s-loadbalancing/gateway-collector-kind-2.yaml" rel="noopener noreferrer"&gt;Here’s what it looks like in GitHub&lt;/a&gt;. I won’t bother showing you the manifest YAML since it will be identical to the one above.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl config use-context kind-kind-2
kubectl apply -f gateway-collector-kind-2.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, to expose this Gateway Collector’s service and enable OTLP traffic from cluster 1 to cluster 2 I’ll use this NodePort service called &lt;code&gt;gateway-nodeport-service.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: bindplane-gateway-agent-nodeport
  namespace: bindplane-agent
  labels:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
spec:
  type: NodePort
  ports:
  - name: otlp-grpc
    port: 4317
    targetPort: 4317
    nodePort: 30317
    protocol: TCP
  - name: otlp-http
    port: 4318
    targetPort: 4318
    nodePort: 30318
    protocol: TCP
  selector:
    app.kubernetes.io/name: bindplane-agent
    app.kubernetes.io/component: gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And, apply it with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f gateway-nodeport-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Your final setup will look like this.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcj4Lejv1vj01PrT65PHZtI6LasCIW-IfHNSCPVghEdejU7_l2yBHR7qc89wqrh12_TOwXXP_PEg4fPN7vvFS1oliXzqAeOwpcOCq6e9Fplm1Unbj9W3vz2W1acoq_Zpfq3rIGocA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXcj4Lejv1vj01PrT65PHZtI6LasCIW-IfHNSCPVghEdejU7_l2yBHR7qc89wqrh12_TOwXXP_PEg4fPN7vvFS1oliXzqAeOwpcOCq6e9Fplm1Unbj9W3vz2W1acoq_Zpfq3rIGocA%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="882"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One Agent-mode collector sending telemetry traffic via a horizontally scaled Gateway-mode collector to an external Gateway running in a separate cluster. This can be any other telemetry backend of your choice.&lt;/p&gt;

&lt;p&gt;You’ll have 5 collectors running in total.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdc_RcY9GAFpLWXJp4mHVTz_3FZT1CcxjvsHGBE5RTAm-AyxoK1MLJh3WhW3c5h1isZpvxP4Ck-7F9OQ451q0TzUuTK3P1_-2pjtXptLQBXmyLNNha34WxQl_KgMKBsP4iQvN1O%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdc_RcY9GAFpLWXJp4mHVTz_3FZT1CcxjvsHGBE5RTAm-AyxoK1MLJh3WhW3c5h1isZpvxP4Ck-7F9OQ451q0TzUuTK3P1_-2pjtXptLQBXmyLNNha34WxQl_KgMKBsP4iQvN1O%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, 3 configurations, where two of them will be scaled between 2 and 10 collector pods.&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf734ui3OWWNoiaRh0lv4_x2cDbNV91gP6U9wBwdffBOH-J4Lh8uv1gxe6HRyjLO2_nqg2rbfQceTKlNTB-UzmGJG851IoT0cqj1VQhAimf9LqA-15AF9yq5IdOGuX6uQ2HTPC7bw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXf734ui3OWWNoiaRh0lv4_x2cDbNV91gP6U9wBwdffBOH-J4Lh8uv1gxe6HRyjLO2_nqg2rbfQceTKlNTB-UzmGJG851IoT0cqj1VQhAimf9LqA-15AF9yq5IdOGuX6uQ2HTPC7bw%3Fkey%3DSNFg6wIEkaldv4Ub0dUGcA" width="1600" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get this sample up-and-running quickly, apply these configs via the Bindplane CLI, &lt;a href="https://github.com/observIQ/high-availability-agent-gateway-opentelemetry-collector/blob/main/k8s-loadbalancing/README.md" rel="noopener noreferrer"&gt;get the files on GitHub, here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;At the end of the day, high availability for the OpenTelemetry Collector means one thing: &lt;strong&gt;don’t lose telemetry when stuff breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You want things to keep working when a telemetry backend goes down, a node restarts, or you’re pushing out updates. That’s why the Agent-Gateway pattern exists. That’s why we scale horizontally. That’s why we use batching, retries, and persistent queues.&lt;/p&gt;

&lt;p&gt;Set it up once, and sleep better knowing your pipeline won’t fall over at the first hiccup. Keep signals flowing. No drops. No drama.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to give Bindplane a try? Spin up a free instance of&lt;/em&gt; &lt;a href="https://app.bindplane.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Bindplane Cloud&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and hit the ground running right away.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>kubernetes</category>
      <category>docker</category>
      <category>observability</category>
    </item>
    <item>
      <title>Bindplane Launch Week 1: The Daily Releases Begin June 2 🪂</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Wed, 28 May 2025 16:25:43 +0000</pubDate>
      <link>https://forem.com/adnanrahic/bindplane-launch-week-1-the-daily-releases-begin-june-2-56a</link>
      <guid>https://forem.com/adnanrahic/bindplane-launch-week-1-the-daily-releases-begin-june-2-56a</guid>
      <description>&lt;p&gt;In 2024 we rewired the way teams build telemetry pipelines. Since the beginning of 2025 we’ve helped customers shave millions off telemetry storage bills with smart filtering and data reduction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next week we’re stepping on the gas—five straight mornings of brand-new Bindplane feature releases, each landing at 10:30 a.m. ET.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What to Expect&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We don’t want to spoil the surprise, so we're keeping the details sealed until launch. Surprises are half the fun, right!? For now, we’ll give you a teaser of what to expect.&lt;/p&gt;

&lt;p&gt;Every new release removes friction you’ve grown to accept—whether that’s tangled integrations, scale ceilings, or late-night config gymnastics. Come back each day, or watch the daily live stream on LinkedIn: &lt;a href="https://www.linkedin.com/company/bindplane" rel="noopener noreferrer"&gt;@Bindplane&lt;/a&gt;, and follow the hashtag &lt;code&gt;#bindplanelaunchweek&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Monday: Telemetry Routing&lt;/strong&gt;
&lt;/h3&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfm4Z7qg7KHFeu5OeR3XIwGPfWKLyI0KE-_LuEadDNvoGb4oV5gvml-1vhJmWSDrx6ZVPRUE63OPTETo35HI61Poc3gUD8_IbrP5TSO98zkzmU7dfFu8QBeBC1dAf-ZLKmVddyWmw%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfm4Z7qg7KHFeu5OeR3XIwGPfWKLyI0KE-_LuEadDNvoGb4oV5gvml-1vhJmWSDrx6ZVPRUE63OPTETo35HI61Poc3gUD8_IbrP5TSO98zkzmU7dfFu8QBeBC1dAf-ZLKmVddyWmw%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" alt="Telemetry Routing" width="524" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monday, June 2nd 2025: 10:30 a.m. ET&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bindplane’s philosophy has always been rooted in enabling you to receive telemetry from any source, process it efficiently, and deliver it to any destination. You’ll see the launch of brand new integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for a blog post, a short video demo, and a live stream on Monday morning.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Tuesday: Custom Collectors&lt;/strong&gt;
&lt;/h3&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdqfNhW0decYMcs84HZFwoZNl91ui6cAesR6jWtPS9eG1ssBog3dpgoO1IP87HdGbQn-IaojWGMnpjhRF8lUZDOEMATMItqpkmVhBfeR0IfVP6FWxXAFTgSWuZvlIOM44RfmwchRg%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdqfNhW0decYMcs84HZFwoZNl91ui6cAesR6jWtPS9eG1ssBog3dpgoO1IP87HdGbQn-IaojWGMnpjhRF8lUZDOEMATMItqpkmVhBfeR0IfVP6FWxXAFTgSWuZvlIOM44RfmwchRg%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" alt="custom collectors" width="600" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tuesday, June 3rd 2025: 10:30 a.m. ET&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The point of OpenTelemetry has been to give you a choice. Yet, most observability vendors still insist you run their collector. We’re removing that last point of friction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check in on Tuesday for a blog post, a short video demo, and a live stream.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Wednesday: Scale&lt;/strong&gt;
&lt;/h3&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfLYQ_krKy2Qo4-QpclraEXRwSMr-XE7ILnavZODNRSX6QpQGX2SCRGswBmwooEjMVoQ6dZebO04iWqkHbe4zYVgLSDe42LyBU4WV6iUnJEIGD1KTfMVHCJfqz4SsNc7xuEsGs4MA%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfLYQ_krKy2Qo4-QpclraEXRwSMr-XE7ILnavZODNRSX6QpQGX2SCRGswBmwooEjMVoQ6dZebO04iWqkHbe4zYVgLSDe42LyBU4WV6iUnJEIGD1KTfMVHCJfqz4SsNc7xuEsGs4MA%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" alt="scale" width="735" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wednesday, June 4th 2025: 10:30 a.m. ET&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think “order-of-magnitude scale,” not incremental tuning. Your telemetry pipelines are about to feel limitless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Come back on Wednesday for a blog post, a short video demo, and a live stream.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Thursday: Easy Processing&lt;/strong&gt;
&lt;/h3&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfCe_VUQ-Z86Lwy9LiwqfZoTJq3RaCK9R8w9M5H-cFpnXEyo-2aJq2g0cuuYpJICV05sSQI88HeUjd7Z9y4Zjaue1XOHXAXDrq8uUCqHR1c5P7Ovj4Csy4N9Lu-GnlVvlztWuJKVg%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXfCe_VUQ-Z86Lwy9LiwqfZoTJq3RaCK9R8w9M5H-cFpnXEyo-2aJq2g0cuuYpJICV05sSQI88HeUjd7Z9y4Zjaue1XOHXAXDrq8uUCqHR1c5P7Ovj4Csy4N9Lu-GnlVvlztWuJKVg%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" alt="processing" width="498" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thursday, June 5th 2025: 10:30 a.m. ET&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do you spend hours building and configuring processors? Most work revolves around &lt;strong&gt;recreating the same processor bundles&lt;/strong&gt; in multiple processor nodes. What if Bindplane did that for you?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll have a blog post, a short video demo, and a live stream ready for you on Thursday morning.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Friday: A…Hi?&lt;/strong&gt;
&lt;/h3&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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdDb4M-3crw6uEjjQWO48WUkqOE_PePwXgNh_E-V98EkZdtQuUTmze524I2I6gUYY968bFXYoF2VP_IFpltEmAURTCCIO88f6LLFtJilX_JDNgzRvV04m4m2EwDAxujku9Y68BJhw%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" 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%2Flh7-rt.googleusercontent.com%2Fdocsz%2FAD_4nXdDb4M-3crw6uEjjQWO48WUkqOE_PePwXgNh_E-V98EkZdtQuUTmze524I2I6gUYY968bFXYoF2VP_IFpltEmAURTCCIO88f6LLFtJilX_JDNgzRvV04m4m2EwDAxujku9Y68BJhw%3Fkey%3DkEkxMLaGQ9WR6r4F3eSE2A" alt="secret surprise" width="662" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Friday, June 6th 2025: 10:30 a.m. ET&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A finale that thinks for itself—buzzwords included, hype justified. 😎&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re putting a ribbon on the Launch Week with a final blog post, video demo, and live stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to Join&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inbox:&lt;/strong&gt; A blog post announcement with a demo video will hit your email inbox every morning at 10:30 a.m. ET. &lt;a href="https://bindplane.com/contact" rel="noopener noreferrer"&gt;Sign up for the newsletter here&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Social:&lt;/strong&gt; Follow @Bindplane on &lt;a href="https://www.linkedin.com/company/bindplane" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; &amp;amp; &lt;a href="https://x.com/bindplane" rel="noopener noreferrer"&gt;X&lt;/a&gt; for teasers and to join the live streams.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live:&lt;/strong&gt; Live streams will start at 11 a.m. ET each day. Jump into the daily stream, ask questions, and watch feature demos in real time.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clear your calendar, charge the coffee mug, and get ready for a week with five new feature releases. &lt;strong&gt;Launch Week starts Monday, June 2nd—see you there.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>news</category>
      <category>showdev</category>
      <category>opentelemetry</category>
      <category>observability</category>
    </item>
    <item>
      <title>End-to-End Observability with Grafana LGTM Stack</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Fri, 11 Oct 2024 14:37:47 +0000</pubDate>
      <link>https://forem.com/kubeshop/end-to-end-observability-with-grafana-lgtm-stack-1h6o</link>
      <guid>https://forem.com/kubeshop/end-to-end-observability-with-grafana-lgtm-stack-1h6o</guid>
      <description>&lt;p&gt;Ensuring your applications are running smoothly requires more than just monitoring. It demands comprehensive visibility into every aspect of your system, from logs and metrics to traces. This is where end-to-end observability comes into play. It's about connecting the dots between different parts of your system to get a complete picture of how everything is performing.&lt;/p&gt;

&lt;p&gt;In this blog post, we'll dive into setting up end-to-end observability using Grafana and its associated tools, including OpenTelemetry, Prometheus, Loki, and Tempo. Let's break down each component and see how they fit together to provide a robust observability solution.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/kubeshop/tracetest/tree/main/examples/lgtm-end-to-end-observability-testing" rel="noopener noreferrer"&gt;View the full sample code for the observability stack you'll build, here.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is End-to-End Observability, And Why Do We Need It?
&lt;/h2&gt;

&lt;p&gt;Before we dive into the setup, it's important to understand what end-to-end observability means and why it's essential. End-to-end observability provides a unified view of your entire system, including traces, metrics, and logs. It helps you monitor and debug your applications more effectively by correlating different types of data to understand the overall health of your system.&lt;/p&gt;

&lt;p&gt;With Grafana playing a key role in visualizing and analyzing this data, we can bring everything together. Grafana's ability to integrate with multiple data sources allows us to correlate logs, metrics, and traces in one unified platform, making troubleshooting and maintaining your system easier. The architecture diagram below gives a clear view of how these components fit together to provide end-to-end observability.&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%2Fgiw60ht9979qvg67b6dp.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%2Fgiw60ht9979qvg67b6dp.png" width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what is happening:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The application is instrumented using OpenTelemetry for metrics and traces and Winston-Loki for logging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The application sends logs to Loki, metrics to Prometheus, and trace data to Tempo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tempo forwards traces to the Tracetest Agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Grafana is used to visualize the logs (from Loki), metrics (from Prometheus), and traces (from Tempo), providing a unified observability dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, the Tracetest Agent syncs the data to the Tracetest UI for trace-based testing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But how do we get all this data into Grafana? The first step is to instrument your application. This means directly embedding tracing, logging, and metrics functionality into your code. By doing this, you'll generate the metrics required to monitor the system and identify any performance bottlenecks or issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Setting Up Instrumentation in Your Application
&lt;/h2&gt;

&lt;p&gt;The first step in setting up end-to-end observability is to instrument your application. This involves adding tracing, logging, and metrics SDKs or libraries to your application code.&lt;/p&gt;

&lt;p&gt;For this tutorial, you'll set up a simple &lt;a href="http://expressjs.com/en/starter/hello-world.html" rel="noopener noreferrer"&gt;Express server&lt;/a&gt; in your root directory in &lt;strong&gt;index.js&lt;/strong&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8081&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Example app listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic web application; while it doesn't include observability yet, let's imagine this is your real-world app or service that you've built.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Adding Metrics to Your Application
&lt;/h3&gt;

&lt;p&gt;Metrics gives you insights into the behavior and performance of your application by tracking things like response times, error rates, or even custom-defined metrics like the number of requests handled.&lt;/p&gt;

&lt;p&gt;In observability, metrics help identify trends or issues in real-time, allowing you to react quickly to problems. For this, we'll use OpenTelemetry, a popular open-source observability framework, to instrument our application for collecting metrics.&lt;/p&gt;

&lt;p&gt;To start collecting metrics, create a new file, &lt;strong&gt;meter.js&lt;/strong&gt;, which will handle generating and exporting the metrics to Prometheus, a time-series database commonly used for metrics storage and querying.&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;// meter.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MeterProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PrometheusExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/exporter-prometheus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/resources&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SemanticResourceAttributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/semantic-conventions&lt;/span&gt;&lt;span class="dl"&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;prometheusExporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrometheusExporter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9464&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/metrics&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prometheus scrape endpoint: http://localhost:9464/metrics&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;meterProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MeterProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SemanticResourceAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_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;hello-world-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;meterProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addMetricReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prometheusExporter&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;meter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;meterProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello-world-meter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;meter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding this &lt;strong&gt;meter.js&lt;/strong&gt; file to our application, we're setting the groundwork to collect and export metrics. Prometheus will now be able to scrape these metrics and store them for visualization and alerting later in Grafana.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Adding Logging to Your Application
&lt;/h3&gt;

&lt;p&gt;Logs are a key component of observability, providing real-time information about the events happening inside your application. They help you trace errors, track user activity, and understand the execution flow. By centralizing your logs, you can more easily search, filter, and correlate them with other observability data like metrics and traces.&lt;/p&gt;

&lt;p&gt;To manage logs, you'll use Winston, a popular logging library for Node.js, along with the Winston-Loki transport, which sends logs directly to a Loki server. Loki is a log aggregation system designed to work alongside tools like Prometheus, providing a scalable way to collect and query logs.&lt;/p&gt;

&lt;p&gt;Create a &lt;strong&gt;logger.js&lt;/strong&gt; file to handle logging and send the logs to a Loki server.&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;// logger.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;winston&lt;/span&gt;&lt;span class="dl"&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;LokiTransport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;winston-loki&lt;/span&gt;&lt;span class="dl"&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;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createLogger&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;winston&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&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="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LokiTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;host&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://loki:3100&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Loki URL&lt;/span&gt;
&lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loki-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;json&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="na"&gt;batching&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="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, import the meter and logger in index.js to add logs and metrics to the application.&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&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;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logger&lt;/span&gt;&lt;span class="dl"&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;meter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./meter&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Define a custom metric (e.g., a request counter)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requestCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCounter&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_requests&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Counts HTTP requests&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="c1"&gt;// Middleware to increment the counter on every request and log the visited URL&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="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Received request for &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;url&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="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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="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;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="err"&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;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// Simulate some work&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nf"&gt;setTimeout&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="err"&gt; &lt;/span&gt; &lt;span class="err"&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, World!&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start the server&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8081&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="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Server is running on http://localhost:8081&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;h3&gt;
  
  
  3. Adding Traces to Your Application
&lt;/h3&gt;

&lt;p&gt;Traces are a critical part of understanding how requests flow through your application. They give you the ability to track requests from the moment they enter the system to when they leave, allowing you to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The services that were called.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The time it took for each of the services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The places where bottlenecks may be occurring.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially important for debugging performance issues and understanding distributed systems.&lt;/p&gt;

&lt;p&gt;To create and collect these traces, we'll use the OpenTelemetry SDK along with auto-instrumentation, which automatically instruments Node.js modules to start generating traces without you having to manually add trace code everywhere.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;strong&gt;tracer.js&lt;/strong&gt; to set up OpenTelemetry in your application.&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;// tracer.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;opentelemetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/auto-instrumentations-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OTLPTraceExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/exporter-trace-otlp-http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ConsoleSpanExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-trace-node&lt;/span&gt;&lt;span class="dl"&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;dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&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;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;traceExporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OTLPTraceExporter&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="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="na"&gt;instrumentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;strong&gt;.env&lt;/strong&gt; file to load the &lt;code&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/code&gt; environment variable.&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_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://tempo:4318/v1/traces"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this code is in place, and &lt;code&gt;tracer.js&lt;/code&gt; is preloaded when running your index.js, every request to your Node.js app will automatically generate traces, thanks to OpenTelemetry's auto-instrumentation. These traces are collected and sent to an OTLP-compatible backend, which is Tempo in this case.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Containerize the Application
&lt;/h3&gt;

&lt;p&gt;To run your application in a container, create a &lt;strong&gt;Dockerfile&lt;/strong&gt; in your root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt;  8081&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build the image of your application with the command &lt;code&gt;docker build -t &amp;lt;dockerhub-username&amp;gt;/tracetest-app .&lt;/code&gt; in the root directory and push it to DockerHub with &lt;code&gt;docker push &amp;lt;dockerhub-username&amp;gt;/tracetest-app&lt;/code&gt;&lt;br&gt;
Also, add a new script in your &lt;strong&gt;package.json&lt;/strong&gt; to automatically require &lt;strong&gt;tracer.js&lt;/strong&gt; when running the server.&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="nl"&gt;"scripts"&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;"index-with-tracer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node -r ./tracer.js index.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a &lt;strong&gt;docker-compose.yml&lt;/strong&gt; and add the the code below to run the application in the container.&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;  app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;your-dockerhub-uername&amp;gt;/tracetest-app&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run index-with-tracer&lt;/span&gt;
&lt;span class="na"&gt;    ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;      -  "8081:8081"&lt;/span&gt;
&lt;span class="na"&gt;    environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;      -  OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}&lt;/span&gt;
&lt;span class="na"&gt;    depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;      tempo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_started&lt;/span&gt;
&lt;span class="na"&gt;      tracetest-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_started&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, you have successfully instrumented and containerized your application. Let's focus on setting up the Prometheus, Loki, and Tempo servers to collect all the data and visualize it in Grafana.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Setting up Prometheus for the Node.js Application
&lt;/h2&gt;

&lt;p&gt;Prometheus is an open-source monitoring and alerting toolkit designed for reliability and scalability. It collects metrics from various sources, storing them in a time-series database, and providing a powerful query language (PromQL) for analysis.&lt;/p&gt;

&lt;p&gt;In the context of observability, Prometheus is used to gather metrics from your applications and infrastructure, which can then be visualized and analyzed to gain insights into system performance and health.&lt;/p&gt;

&lt;p&gt;To integrate Prometheus with your Node.js application, follow these steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create the Prometheus Configuration File
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;prometheus.yml&lt;/strong&gt; file specifies how Prometheus should discover and scrape metrics from your application. Here's the configuration:&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;# prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hello-world-app'&lt;/span&gt;
&lt;span class="na"&gt;    static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;app:9464'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Metrics exposed on port 9464&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines the target endpoints where Prometheus will look for metrics. &lt;code&gt;host.docker.internal&lt;/code&gt; refers to your local Docker host, and &lt;code&gt;9464&lt;/code&gt; is the port on which your Node.js application exposes the metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Run Prometheus in a Docker Container
&lt;/h3&gt;

&lt;p&gt;To run the Prometheus server in a container, add the below service in your &lt;strong&gt;docker-compose.yml&lt;/strong&gt;.&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;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus&lt;/span&gt;
&lt;span class="na"&gt;  volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;    -  ./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
&lt;span class="na"&gt;  ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;    -  "9090:9090"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;image&lt;/code&gt; specifies the Docker image to use for Prometheus and &lt;code&gt;volumes&lt;/code&gt; mounts your local &lt;strong&gt;prometheus.yml&lt;/strong&gt; file into the container at &lt;code&gt;etc/prometheus/prometheus.yml&lt;/code&gt;. This file contains the configuration Prometheus will use and &lt;code&gt;ports&lt;/code&gt; maps port 9090 on the Docker container to port 9090 on your host machine. This makes the Prometheus web UI accessible at &lt;code&gt;localhost:9090&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use Docker Compose to start the Prometheus server: &lt;code&gt;docker-compose up&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Verifying Metrics Collection
&lt;/h3&gt;

&lt;p&gt;Open your browser and navigate to &lt;code&gt;http://localhost:9090/graph&lt;/code&gt; to access the Prometheus web UI. Here, you can execute queries to verify that Prometheus is successfully scraping and storing metrics.&lt;/p&gt;

&lt;p&gt;For instance, running the query &lt;code&gt;http_requests_total&lt;/code&gt; will display metrics related to the number of HTTP requests processed by your application.&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%2Fm2yliabd2tbyhqd2skuc.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%2Fm2yliabd2tbyhqd2skuc.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Prometheus configured, you now have a monitoring tool that collects and stores metrics from your Node.js application. Now, let's configure Loki to get logs from your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Setting up Loki for the Node.js Application
&lt;/h2&gt;

&lt;p&gt;Logs are vital for diagnosing issues and understanding how your application behaves in different scenarios. They offer detailed insights into system events, errors, and application flow, making them indispensable for effective troubleshooting and performance monitoring. Loki is a log aggregation system designed to work seamlessly with Grafana. It collects and stores logs, enabling powerful querying and visualization through Grafana.&lt;/p&gt;

&lt;p&gt;To set up Loki to collect logs from your Node.js application, follow these steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add Loki to Your Docker Compose Configuration
&lt;/h3&gt;

&lt;p&gt;Extend your &lt;strong&gt;docker-compose.yml&lt;/strong&gt; file to include Loki. This allows you to run Loki as a container alongside your Prometheus instance:&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;loki&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/loki:2.9.10&lt;/span&gt;
&lt;span class="na"&gt;    ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;      -  "3100:3100"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;grafana/loki:2.9.10&lt;/code&gt; is the official Loki image from Grafana and the port &lt;code&gt;3100&lt;/code&gt; on the Docker container is mapped to the port &lt;code&gt;3100&lt;/code&gt; on your host machine. This allows you to access Loki's API and check its status.&lt;/p&gt;

&lt;p&gt;With Loki added to your Docker Compose configuration, restart your containers to include Loki.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Verify Loki's is Running
&lt;/h3&gt;

&lt;p&gt;Loki does not have a traditional web UI for interacting with logs, so you'll need to check its status and ensure it's running correctly by accessing its metrics endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;http://localhost:3100/metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint will display raw metrics data related to Loki, confirming that Loki is up and running. The metrics here are used internally by Grafana to visualize logs and track Loki's performance.&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%2Fuxtow6fiqqdpijdpay4i.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%2Fuxtow6fiqqdpijdpay4i.png" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With logs being collected and stored by Loki, you can use Grafana to visualize these logs, helping you gain deeper insights into your application's behavior and troubleshoot issues more effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Setting up Tempo for the Node.js Application
&lt;/h2&gt;

&lt;p&gt;While metrics tell you how your application is performing and logs show what is happening, traces help answer why something is going wrong by showing the detailed flow of requests through your system. Tempo is designed to collect and store these traces.&lt;/p&gt;

&lt;p&gt;To ensure traces from your Node.js application are captured, configure Tempo to accept traces exported by the OpenTelemetry. Tempo will act as a bridge, collecting traces from your app, which will be fetched by Grafana and the Tracetest Agent for further testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a Tempo Configuration File
&lt;/h3&gt;

&lt;p&gt;Ensure that Tempo is set up with the appropriate storage configuration in the &lt;strong&gt;tempo.yaml&lt;/strong&gt; file to handle the incoming traces.&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;stream_over_http_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;http_listen_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;log_level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;

&lt;span class="na"&gt;query_frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;duration_slo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;throughput_bytes_slo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.073741824e+09&lt;/span&gt;
  &lt;span class="na"&gt;trace_by_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;duration_slo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;

&lt;span class="na"&gt;distributor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;  receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    jaeger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;      protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        thrift_http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        thrift_binary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        thrift_compact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    zipkin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;      protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;        grpc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;    opencensus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;ingester&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;max_block_duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt; 

&lt;span class="na"&gt;compactor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;  compaction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;block_retention&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;

&lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;  trace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
&lt;span class="na"&gt;    wal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/tempo/wal&lt;/span&gt;
&lt;span class="na"&gt;    local&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/tempo/blocks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add Tempo to Your Docker Compose
&lt;/h3&gt;

&lt;p&gt;In the &lt;strong&gt;docker-compose.yml&lt;/strong&gt;, add Tempo in the services and map the local port &lt;code&gt;3200&lt;/code&gt; of your machine with &lt;code&gt;80&lt;/code&gt; of the Docker container since that is the default port of Tempo.&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;init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;tempoImage&lt;/span&gt;  &lt;span class="s"&gt;grafana/tempo:latest&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chown"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10001:10001"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/tempo"&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s"&gt;./tempo-data:/var/tempo&lt;/span&gt;

&lt;span class="na"&gt;tempo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*tempoImage&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-config.file=/etc/tempo.yaml"&lt;/span&gt;  &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s"&gt;./tempo.yaml:/etc/tempo.yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s"&gt;./tempo-data:/var/tempo&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;14268:14268"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3200:80"&lt;/span&gt;  &lt;span class="c1"&gt;# tempo&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9095:9095"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4417:4317"&lt;/span&gt; &lt;span class="c1"&gt;# otlp grpc&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4418:4318"&lt;/span&gt; &lt;span class="c1"&gt;# otlp http&lt;/span&gt;
  &lt;span class="s"&gt;  -  "9411:9411"&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt;  &lt;span class="s"&gt;init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The init container runs a command to change the ownership of the &lt;code&gt;/var/tempo&lt;/code&gt; directory to user &lt;code&gt;10001&lt;/code&gt;, and the Tempo service uses the &lt;code&gt;grafana/tempo&lt;/code&gt; image to start Tempo with the specified configuration file, exposing multiple ports for tracing protocols.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Enabling Observability in Grafana
&lt;/h2&gt;

&lt;p&gt;With Prometheus, Loki, and Tempo set up, it's time to integrate them into Grafana for visualization.&lt;/p&gt;

&lt;p&gt;Begin with adding Prometheus as a data source in Grafana. Go to &lt;code&gt;localhost:3000&lt;/code&gt; where Grafana is running. On the login page, enter &lt;code&gt;admin&lt;/code&gt; in both username and password.&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%2Fb05i0ix9gg6ja11knpb8.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%2Fb05i0ix9gg6ja11knpb8.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next page, you can update your password or skip it to continue with the default one.&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%2F2aemmhqkuyz36gmbw9kd.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%2F2aemmhqkuyz36gmbw9kd.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Adding a Data Source in Grafana
&lt;/h3&gt;

&lt;p&gt;After logging in, go to the Data Sources, click "Add new Data Source" and search for Prometheus to configure it.&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%2F5y6zlq34j8zllnoqskux.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%2F5y6zlq34j8zllnoqskux.png" width="800" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the "Connection" section enter the Prometheus server URL as &lt;code&gt;http://prometheus:9090&lt;/code&gt;. Keep the other settings as default.&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%2Fxwpqlby00h6rox6raf7i.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%2Fxwpqlby00h6rox6raf7i.png" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down and click the "Save &amp;amp; test" button to verify the connection between Prometheus and Grafana.&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%2Frmax2z9ojnum0k26rwi7.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%2Frmax2z9ojnum0k26rwi7.png" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, search for Loki in the new Data Sources to configure it. In the connection URL of Loki, enter &lt;code&gt;http://loki:3100&lt;/code&gt; and verify it by clicking "Save &amp;amp; test".&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%2Fsvxs0n4xjq0s3z4qpu61.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%2Fsvxs0n4xjq0s3z4qpu61.png" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add Tempo as the final Data Source. Configure its Connection URL as &lt;code&gt;http://tempo:80&lt;/code&gt;. Finally, verify the server connection by clicking on "Save &amp;amp; test".&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%2Fown5nemy30ts26zsbi1g.jpg" 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%2Fown5nemy30ts26zsbi1g.jpg" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding these integrations allows you to create dashboards that visualize metrics, logs, and traces all in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Setting up Grafana for Visualization
&lt;/h3&gt;

&lt;p&gt;Go to the Dashboards tab to create dashboards in Grafana to visualize your metrics, logs, and traces. For example, you might create a dashboard that shows HTTP request rates, info logs, and trace latencies.&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%2Fy2hv3x25sqj638tcwqre.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%2Fy2hv3x25sqj638tcwqre.png" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next panel, click on "Add visualization" to add panels of Prometheus, Loki and Tempo.&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%2Fq5rdsnll3ioghh78mxtb.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%2Fq5rdsnll3ioghh78mxtb.png" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the available data sources, select Prometheus to configure its panel. In the query section, select &lt;code&gt;http_request_total&lt;/code&gt; and run the query to get a time series graph of total HTTP requests on the application. Click the "Save" button to save its configuration and get the panel on the dashboard.&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%2F6eiohobjf4tu1l325dzi.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%2F6eiohobjf4tu1l325dzi.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the dashboard, you will see the panel showing the total HTTP requests on the application in the form of a time series graph. Now, select "Visualization" on the "Add" dropdown to add Loki in the similar way.&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%2F9ido8jnrhpf0mp2g8sb9.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%2F9ido8jnrhpf0mp2g8sb9.png" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Data Source, select Loki, select job in the filter, and table as the visualization in the top right corner. Finally, apply the changes to save the panel.&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%2Fkixyoxpinu35lddcxadi.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%2Fkixyoxpinu35lddcxadi.png" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar to Loki, add one more panel, select the Data Source as Tempo and visualization as Table to get a proper view of all the traces generated in the application.&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%2Frsqym0jz97a18h5hcagu.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%2Frsqym0jz97a18h5hcagu.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all the three panels created, you can resize and adjust their position on the Grafana dashboard according to your requirements.&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%2Ff0qajxw7mswsumuo7e02.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%2Ff0qajxw7mswsumuo7e02.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. End-to-End Testing Using Trace-Based Testing with Tracetest
&lt;/h2&gt;

&lt;p&gt;Once you have your observability stack up and running, the next step is ensuring everything works as expected. But how can you test if all the traces, logs, and metrics you're collecting are not only being captured correctly but also providing meaningful insight? That's where Tracetest comes in. It's designed to take your end-to-end testing to the next level by leveraging trace-based testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Sign up to Tracetest
&lt;/h3&gt;

&lt;p&gt;Let us start by signing up to Tracetest. Go to the Tracetest &lt;a href="https://app.tracetest.io/?flow=ed6bcf4b-b36e-406e-b21b-66681981e68f" rel="noopener noreferrer"&gt;Sign Up&lt;/a&gt; page and log in with your Google or GitHub account.&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%2F71okxvxb6giijplsnok8.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%2F71okxvxb6giijplsnok8.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new organization in your Tracetest account.&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%2F04gch5bmuzrsc9cut2x9.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%2F04gch5bmuzrsc9cut2x9.png" width="522" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the organization, a new environment must be created as well.&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%2Flmsz2iw9eid6lr0ijxmu.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%2Flmsz2iw9eid6lr0ijxmu.png" width="522" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set up the Tracetest Agent
&lt;/h3&gt;

&lt;p&gt;Return to your &lt;code&gt;docker-compose.yml&lt;/code&gt; file, and add a service to run the Tracetest Agent in a container.&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;tracetest-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubeshop/tracetest-agent&lt;/span&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;TRACETEST_API_KEY=${TRACETEST_TOKEN}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;TRACETEST_ENVIRONMENT_ID=${TRACETEST_ENVIRONMENT_ID}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find &lt;a href="https://app.tracetest.io/retrieve-token" rel="noopener noreferrer"&gt;your &lt;code&gt;TRACETEST_TOKEN&lt;/code&gt; and &lt;code&gt;TRACETEST_ENVIRONMENT_ID&lt;/code&gt;, here&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.amazonaws.com%2Fuploads%2Farticles%2F3zaqw949qt7ca0p64cx6.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%2F3zaqw949qt7ca0p64cx6.png" width="747" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, update the &lt;strong&gt;.env&lt;/strong&gt; file in the root directory and add the values of &lt;code&gt;TRACETEST_API_KEY&lt;/code&gt; and &lt;code&gt;TRACETEST_ENVIRONMENT_ID&lt;/code&gt; you copied from &lt;code&gt;https://app.tracetest.io/retrieve-token&lt;/code&gt;&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;TRACETEST_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-tracetest-organization-token&amp;gt;"&lt;/span&gt;
&lt;span class="nv"&gt;TRACETEST_ENVIRONMENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-environment-id&amp;gt;"&lt;/span&gt;

&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://tempo:4318/v1/traces"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the Docker Compose file again with &lt;code&gt;docker-compose up&lt;/code&gt; to run the Tracetest Agent with the rest of the services.&lt;/p&gt;

&lt;h3&gt;
  
  
  3 . Ingest the Traces from Tempo to Tracetest
&lt;/h3&gt;

&lt;p&gt;In the Settings of Tracetest UI, go to the "Trace Ingestion" tab and select Tempo as your tracing backend. Enable trace ingestion, Select connection type as &lt;code&gt;Http&lt;/code&gt;, and enter &lt;code&gt;http://tempo:80&lt;/code&gt; as the URL. Finally, click "Test Connection" and "Save" to test the connection with Tempo and save the backend configuration.&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%2Fmv5w4ic38glqgmilmx8u.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%2Fmv5w4ic38glqgmilmx8u.png" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also apply it with the CLI. First, configure the Tracetest CLI in your Terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tracetest configure &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;your-tracetest-organization-token&amp;gt; &lt;span class="nt"&gt;--environment&lt;/span&gt; &amp;lt;your-environment-id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a file for the connection to Tempo.&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;# tracetest-trace-ingestion.yaml&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DataStore&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;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;current&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;Grafana Tempo&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tempo&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;tempo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://tempo:80&lt;/span&gt;
      &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run this command to apply it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tracetest apply datastore &lt;span class="nt"&gt;-f&lt;/span&gt; tracetest-trace-ingestion.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now trigger a test with a &lt;code&gt;GET&lt;/code&gt; request on &lt;code&gt;http://app:8081&lt;/code&gt; where the application is running.&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%2F508vsen9t08dhectx125.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%2F508vsen9t08dhectx125.png" width="800" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the Trace tab to get a flow of your traces in the application.&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%2Fyqyeel5xrsnrlk9ghok7.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%2Fyqyeel5xrsnrlk9ghok7.png" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Automate the Tests with Tracetest CLI
&lt;/h3&gt;

&lt;p&gt;Now, let us see how to automate these tests. Go to the Automate tab and follow the CLI configuration steps to automate the testing using your command line.&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%2Fw7php440ohvubr5euvwo.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%2Fw7php440ohvubr5euvwo.png" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can download the &lt;code&gt;untitled.yaml&lt;/code&gt;, rename it to &lt;code&gt;tracetest-test.yaml&lt;/code&gt;, and add assertions to the file in yaml format to execute the &lt;code&gt;tracetest run test --file tracetest-test.yaml --output pretty&lt;/code&gt; command and automate the tests.&lt;/p&gt;

&lt;p&gt;For example, you can add assertions in the configuration file as given below.&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&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;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eKmofseIR&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;Untitled&lt;/span&gt;
&lt;span class="na"&gt;  trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
&lt;span class="na"&gt;    httpRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
&lt;span class="na"&gt;      Url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://app:8081&lt;/span&gt;
&lt;span class="na"&gt;      headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;span[tracetest.span.type  =  "http"]&lt;/span&gt;
      &lt;span class="s"&gt;# the assertions define the checks to be run. In this case, all&lt;/span&gt;
      &lt;span class="s"&gt;# http spans will be checked for a status code = &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;
&lt;span class="na"&gt;      - assertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;http.status_code  =  &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trigger the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tracetest run &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; ./tracetest-test.yaml &lt;span class="nt"&gt;--output&lt;/span&gt; pretty

✔ RunGroup: &lt;span class="c"&gt;#v52GDwRHR (https://app.tracetest.io/organizations/_HDptBgNg/environments/ttenv_6da9b4f817b8b9df/run/v52GDwRHR)\&lt;/span&gt;
Summary: 1 passed, 0 failed, 0 pending&lt;span class="se"&gt;\&lt;/span&gt;
  ✔ Untitled &lt;span class="o"&gt;(&lt;/span&gt;https://app.tracetest.io/organizations/_HDptBgNg/environments/ttenv_6da9b4f817b8b9df/test/mMtWIwgNg/run/5/test&lt;span class="o"&gt;)&lt;/span&gt; - trace &lt;span class="nb"&gt;id&lt;/span&gt;: 4362a098956f9bf8790fc4b37e1ad99f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's how Tracetest helps you with your End-to-End Observability by testing all generated traces and automating the end-to-end testing pipeline by leveraging telemetry data.&lt;/p&gt;

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

&lt;p&gt;End-to-end observability is critical for managing complex applications effectively. By integrating Grafana with Prometheus, Loki, and Tempo, you gain a comprehensive view of your system's performance, logs, and traces. This setup helps not only monitor but also debug and optimize your applications.&lt;/p&gt;

&lt;p&gt;With Tracetest, you can further ensure the reliability of your system through proactive testing. As you saw today, implementing these observability practices allows for a more resilient and maintainable system, providing insights that are crucial for modern DevOps practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q. What are the stages of end-to-end testing?
&lt;/h3&gt;

&lt;p&gt;End-to-end testing typically involves planning, where test cases and scenarios are defined; setup, which includes configuring the environment and integrating necessary tools; execution, where the test cases are run to simulate real-world user interactions; and validation, where results are analyzed to ensure the system behaves as expected. In the context of trace-based testing, this also includes inspecting traces to verify internal processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q. Is Prometheus a visualization tool?
&lt;/h3&gt;

&lt;p&gt;No, Prometheus is not a visualization tool. It's primarily a metrics storage and monitoring system. It collects, stores, and queries time-series data. However, Prometheus integrates well with visualization tools like Grafana, which can be used to create dashboards and visualizations for the metrics stored in Prometheus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q. What is "trace" in "trace-based testing"?
&lt;/h3&gt;

&lt;p&gt;A trace in testing represents the journey of a request or transaction through various services in a system. In distributed systems, traces help track the flow of requests across different components, making it easier to identify bottlenecks, errors, or latency issues in the entire process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q. What is Grafana and Prometheus?
&lt;/h3&gt;

&lt;p&gt;Grafana is an open-source visualization platform that creates interactive dashboards for monitoring metrics, logs, and traces. Prometheus, on the other hand, is a metrics collection and storage tool. Together, they form a powerful monitoring solution, with Prometheus gathering the data and Grafana visualizing it for better insights and troubleshooting.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>observability</category>
      <category>monitoring</category>
      <category>traces</category>
    </item>
    <item>
      <title>Trace-based Testing With OpenTelemetry: Using Tracetest with OpenTelemetry</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Tue, 17 Sep 2024 10:29:07 +0000</pubDate>
      <link>https://forem.com/kubeshop/trace-based-testing-with-opentelemetry-using-tracetest-with-opentelemetry-4l30</link>
      <guid>https://forem.com/kubeshop/trace-based-testing-with-opentelemetry-using-tracetest-with-opentelemetry-4l30</guid>
      <description>&lt;p&gt;Working with distributed systems can feel like a circus act. Trying to keep a dozen spinning plates in the air is hard—one slight misstep and everything can come crashing down.&lt;/p&gt;

&lt;p&gt;Traditional testing methods are great for catching straightforward issues, but they often miss the nuanced problems that pop up when microservices start interacting with each other in unexpected ways. This is where trace-based testing comes in. By using distributed tracing in your observability tool, you’re not just checking if the end result is correct—you’re ensuring that every step in your application’s workflow is valid, from start to end.&lt;/p&gt;

&lt;p&gt;This tutorial covers four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basics of distributed tracing.&lt;/li&gt;
&lt;li&gt;Differences between tracing and logging.&lt;/li&gt;
&lt;li&gt;Introduction to OpenTelemetry, a set of observability APIs, SDKs, and tools used to instrument, generate, collect, and export telemetry data.&lt;/li&gt;
&lt;li&gt;Introduction to Tracetest, a tool for trace-based testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you work with microservices, cloud-native applications, mobile apps, serverless, or monolithic architectures, you’ll learn how to level up your testing strategy and ensure your systems perform at their best.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Distributed Tracing?
&lt;/h2&gt;

&lt;p&gt;Distributed tracing maps how a single request moves through your application’s services.&lt;/p&gt;

&lt;p&gt;Here’s a real-life example. When you request a product on an e-commerce platform, distributed tracing shows each step of the transaction, from the server’s HTTP request to database queries to the final page render. It’s crucial for understanding the system in real time, helping you spot bottlenecks or issues.&lt;/p&gt;

&lt;p&gt;You might wonder—how is this different from logging? They both show what your system is doing. Their differences are key to effective monitoring and debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracing vs Logging
&lt;/h2&gt;

&lt;p&gt;Tracing provides a detailed journey of a request while logging captures specific moments. Logs are essential for debugging but don’t offer the whole picture. Tracing ties these moments together, creating a complete story that makes pinpointing the root cause of issues easier without sifting through scattered logs.&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%2F5bgomcdzu80hxdr9s2yk.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%2F5bgomcdzu80hxdr9s2yk.png" width="800" height="759"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This detailed visibility offered by tracing naturally extends into testing strategies. Just as tracing connects the dots in monitoring, it also plays a critical role in testing workflows. This leads us to the comparison between traditional and trace-based testing, where the ability to see the entire process becomes invaluable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional Testing vs. Trace-based Testing
&lt;/h2&gt;

&lt;p&gt;In traditional testing, you check if the output of a function or microservice matches the expected result. However, this can often miss issues like race conditions, performance bottlenecks, or incorrect service interactions.&lt;/p&gt;

&lt;p&gt;Trace-based testing thoroughly validates workflows, ensuring not only the accuracy of the final output but also that each step is executed as intended. This is especially crucial in microservices environments, where interdependencies can make issues more difficult to detect.&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%2Ffz70g6spy79slzwvkhct.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%2Ffz70g6spy79slzwvkhct.png" width="800" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we dive into how to leverage trace-based testing in your own systems, having the right tools at your disposal is crucial. That’s where OpenTelemetry comes into play. As one of the most widely adopted open-source projects, it provides a unified framework and software development kit (SDK) for collecting, processing, and exporting traces, metrics, and logs from your applications. With OpenTelemetry, you can easily instrument your code to generate the telemetry data you need to see how every core component of your system is performing and interacting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry is the open-source Swiss army knife for observability, providing tools to collect and export the three pillars of observability—traces, logs, and metrics from your applications. Whether you’re dealing with a sprawling microservices architecture or a monolithic app, OpenTelemetry integrates seamlessly, allowing you to monitor and troubleshoot easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  Instrumentation in OpenTelemetry
&lt;/h3&gt;

&lt;p&gt;Instrumentation is adding code to your application to generate telemetry data. With OpenTelemetry, you can instrument your services to automatically generate traces and metrics. This can be done manually by adding specific code or automatically by using OpenTelemetry's auto-instrumentation libraries, which inject the necessary tracing libraries into your application.&lt;/p&gt;

&lt;p&gt;Let’s look at how to generate this telemetry data with OpenTelemetry using auto-instrumentation. We’ll be using a basic E-commerce application in Node.js for this tutorial. Create an Express server in &lt;code&gt;app.js&lt;/code&gt;. Refer to this &lt;a href="https://expressjs.com/en/starter/hello-world.html" rel="noopener noreferrer"&gt;Express Guide&lt;/a&gt; to get started.&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&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;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ecommerce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Routes&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="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="o"&gt;=&amp;gt;&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome to the E-commerce App!&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="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;gt;&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;result&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;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM products&lt;/span&gt;&lt;span class="dl"&gt;'&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&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="nx"&gt;message&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="c1"&gt;// Start the server&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running on http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the server on your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server will start running on port 5000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Server running on http://localhost:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this tutorial, I’ll showcase a basic Node.js server, but in your case, it can be a complex application in Java or a Kubernetes cluster with multiple microservices. It does not matter which tech stack you use since OpenTelemetry supports all the major programming languages like Java, .Net, Ruby, Python, Go, Rust, C++, and JavaScript.&lt;/p&gt;

&lt;p&gt;Let’s enable the instrumentation in the server by creating the file &lt;code&gt;instrumentation.js&lt;/code&gt; and adding the auto-instrumentation code to the server:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodeSDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ConsoleSpanExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-trace-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/auto-instrumentations-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;PeriodicExportingMetricReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ConsoleMetricExporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-metrics&lt;/span&gt;&lt;span class="dl"&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;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;traceExporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;metricReader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PeriodicExportingMetricReader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ConsoleMetricExporter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;instrumentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s a concise breakdown of the code above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Import the necessary OpenTelemetry components for tracing and metrics.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;traceExporter&lt;/code&gt;, which uses &lt;code&gt;ConsoleSpanExporter&lt;/code&gt; to print trace data to the console.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;metricReader&lt;/code&gt;, which uses &lt;code&gt;PeriodicExportingMetricReader&lt;/code&gt; with &lt;code&gt;ConsoleMetricExporter&lt;/code&gt; to print metrics to the console periodically.&lt;/li&gt;
&lt;li&gt;Enable automatic instrumentation for standard Node.js modules for tracing.&lt;/li&gt;
&lt;li&gt;Start the SDK using &lt;code&gt;sdk.start()&lt;/code&gt; function, which initializes and begins collecting and exporting traces and metrics based on the configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the required dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;express &lt;span class="se"&gt;\&lt;/span&gt;
 @opentelemetry/sdk-node &lt;span class="se"&gt;\&lt;/span&gt;
  @opentelemetry/api &lt;span class="se"&gt;\&lt;/span&gt;
  @opentelemetry/auto-instrumentations-node &lt;span class="se"&gt;\&lt;/span&gt;
  @opentelemetry/sdk-metrics &lt;span class="se"&gt;\&lt;/span&gt;
  @opentelemetry/sdk-trace-node &lt;span class="se"&gt;\&lt;/span&gt;
  dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, run your application as you normally would, but use the &lt;code&gt;--require&lt;/code&gt; flag to load the instrumentation before the application code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--require&lt;/span&gt; ./instrumentation.js app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a while, you should see the spans and metrics printed in the console by &lt;code&gt;ConsoleSpanExporter&lt;/code&gt; and &lt;code&gt;ConsoleMetricExporter&lt;/code&gt;.&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="err"&gt;resource:&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="err"&gt;attributes:&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="err"&gt;'service.name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'unknown_service:C:/Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Files/nodejs/node.exe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'telemetry.sdk.language':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'nodejs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'telemetry.sdk.name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'opentelemetry'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'telemetry.sdk.version':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.pid':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17668&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.executable.name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.executable.path':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'C:\\Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Files\\nodejs\\node.exe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.command_args':&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="err"&gt;'C:\\Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Files\\nodejs\\node.exe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;'--require'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;'./instrumentation.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;'C:\\Users\\Public\\Infrasity\\Tracetest\\e-commerce-app\\server\\app.js'&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="err"&gt;'process.runtime.version':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mf"&gt;20.10&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.runtime.name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'nodejs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.runtime.description':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Node.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.command':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'C:\\Users\\Public\\Infrasity\\Tracetest\\e-commerce-app\\server\\app.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'process.owner':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'host.name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'MSI'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;'host.arch':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'amd&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="err"&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;span class="err"&gt;traceId:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;97975&lt;/span&gt;&lt;span class="err"&gt;dbd&lt;/span&gt;&lt;span class="mi"&gt;81381&lt;/span&gt;&lt;span class="err"&gt;c&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="err"&gt;bae&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="err"&gt;dafbff&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;bf&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;parentId:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;traceState:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'fs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;statSync'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;id:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;eab&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;d&lt;/span&gt;&lt;span class="mi"&gt;5343483&lt;/span&gt;&lt;span class="err"&gt;d'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;kind:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;timestamp:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1724234720014000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;128.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;attributes:&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="err"&gt;status:&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="err"&gt;code:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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="err"&gt;events:&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="err"&gt;links:&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;Now that we’ve set up tracing with OpenTelemetry, you’ve got a solid tool for capturing how your system operates, like seeing where requests slow down or fail. However, while OpenTelemetry shows you the data, it falls short in the following cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Built-In Testing&lt;/strong&gt; - OpenTelemetry provides traces but doesn’t test if your app functions as expected. It might show a slow request but won’t check its impact on the user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of Workflow Validation&lt;/strong&gt; - It logs individual steps but doesn’t confirm if the entire process is correct, like logging an API call without verifying the follow-up actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Assertion Validation&lt;/strong&gt; - It records data but doesn’t check it against specific criteria. For example, it logs a database query but won’t validate the result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misses Workflow Impact&lt;/strong&gt; - It identifies issues but doesn’t assess their impact on the overall system, such as detecting a delay without evaluating downstream effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Automated Trace-based Testing&lt;/strong&gt; - It captures trace data but doesn’t automate testing to verify if each trace meets expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s where Tracetest steps in. It allows you to create tests based on the traces collected, validating not just the final outcomes but every step in the workflow. This helps you catch issues like incorrect service interactions or performance bottlenecks that might not be obvious with OpenTelemetry alone. By integrating Tracetest, you ensure your application operates smoothly in production, addressing potential problems before they impact users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Tracetest for Trace-based Testing
&lt;/h2&gt;

&lt;p&gt;Tracetest is a testing tool based on OpenTelemetry that allows you to test your distributed application. It will enable you to use data from distributed traces generated by OpenTelemetry to validate and assert if your application has the desired behavior defined by your test definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Tracetest work?
&lt;/h3&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%2F0cb4sx1fpw2qaux47g2x.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%2F0cb4sx1fpw2qaux47g2x.png" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the architecture diagram above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Node.js server is instrumented to send traces to the OpenTelemetry Collector.&lt;/li&gt;
&lt;li&gt;The Tracetest agent triggers tests and fetches the traces from the collector.&lt;/li&gt;
&lt;li&gt;Finally, the agent synchronizes the test data back with Tracetest for analysis and validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process enables continuous monitoring and testing of distributed systems using tracing data.&lt;/p&gt;

&lt;p&gt;With a clear understanding of how Tracetest works, let's proceed to set it up in your system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Tracetest
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up to &lt;a href="https://app.tracetest.io" rel="noopener noreferrer"&gt;app.tracetest.io&lt;/a&gt; and follow the get started docs to set up the Tracetest agent.&lt;/li&gt;
&lt;li&gt;Create a new environment on the platform in the &lt;strong&gt;Environment&lt;/strong&gt; tab.
&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%2Feinn11z1co7llsqck8d5.png" width="800" height="219"&gt;
&lt;/li&gt;
&lt;li&gt;Name your Environment and click on "Create."
&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%2F055r1nciclfedj3e1l6p.png" width="800" height="303"&gt;
&lt;/li&gt;
&lt;li&gt;After creating the environment, go to the &lt;strong&gt;Settings&lt;/strong&gt; tab and follow the steps on the page to run the Tracetest Agent in your local environment.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tracetest start &lt;span class="nt"&gt;--api-key&lt;/span&gt; &amp;lt;your-api-key&amp;gt; &lt;span class="nt"&gt;--environment&lt;/span&gt; &amp;lt;your-env&amp;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;Version: v1.4.3

Tracetest start launches a lightweight agent. It enables you to run tests and collect traces with Tracetest.
Once started, Tracetest Agent exposes OTLP ports 4317 and 4318 to ingest traces via gRCP and HTTP.

 INFO  Running &lt;span class="k"&gt;in &lt;/span&gt;desktop mode...
 INFO  Starting Agent with name MSI...
Agent is started! Leave the terminal open so tests can be run and traces gathered from this environment.
You can:
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Open Tracetest &lt;span class="k"&gt;in &lt;/span&gt;a browser to this environment
  &lt;span class="o"&gt;(&lt;/span&gt;Experimental&lt;span class="o"&gt;)&lt;/span&gt; Open Dashboard
  Stop this agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that your Tracetest Agent is successfully running, it can fetch all the traces generated by your services and sync them with Tracetest. Let’s start creating actual tests on Tracetest using these traces!&lt;/p&gt;

&lt;h3&gt;
  
  
  Trace-based Testing in a Node.js application using Tracetest
&lt;/h3&gt;

&lt;p&gt;Let us use the same &lt;code&gt;app.js&lt;/code&gt; we created when learning instrumentation in OpenTelemetry.&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&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;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ecommerce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Routes&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="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="o"&gt;=&amp;gt;&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome to the E-commerce App!&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="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;gt;&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;result&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;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM products&lt;/span&gt;&lt;span class="dl"&gt;'&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&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="nx"&gt;message&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="c1"&gt;// Start the server&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;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running on http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change &lt;code&gt;instrumentation.js&lt;/code&gt; with the following code:&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;opentelemetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/auto-instrumentations-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OTLPTraceExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/exporter-trace-otlp-grpc&lt;/span&gt;&lt;span class="dl"&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;dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&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;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is passed into "new OTLPTraceExporter" automatically&lt;/span&gt;
    &lt;span class="na"&gt;traceExporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OTLPTraceExporter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;instrumentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
    &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quick-start-nodejs&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="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file. Get your Tracetest token and environment ID and add the following environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="c"&gt;# Get the required information here: https://app.tracetest.io/retrieve-token&lt;/span&gt;
  &lt;span class="nv"&gt;TRACETEST_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-tracetest-token&amp;gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;TRACETEST_ENVIRONMENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-tracetest-env-id&amp;gt;"&lt;/span&gt;

  &lt;span class="c"&gt;# GRPC&lt;/span&gt;
  &lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:4317/"&lt;/span&gt;

  &lt;span class="c"&gt;# or, use HTTP&lt;/span&gt;
  &lt;span class="c"&gt;# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://tracetest-agent:4318/v1/traces"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, run your application as you normally would, but use the &lt;code&gt;--require&lt;/code&gt; flag to load the instrumentation before the application code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--require&lt;/span&gt; ./instrumentation.js app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will automatically push the traces generated by OpenTelemetry auto-instrumentation to &lt;code&gt;http://localhost:4317/&lt;/code&gt;, where the Tracetest Agent is running.&lt;/p&gt;

&lt;p&gt;Ensure you run the Node.js application and the Tracetest Agent in the same network so the Tracetest Agent can ingest traces successfully.&lt;/p&gt;

&lt;p&gt;Now, return to Tracetest to validate and test the application with distributed traces. In the “Run your first test” section, choose the trigger as HTTP and click on continue.&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%2Fyje25z0i2i2tjo6o81y1.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%2Fyje25z0i2i2tjo6o81y1.png" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next screen, enter the URL of your server and click on “Run.”&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%2Fj5jgp6za9zyug7dgtyrt.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%2Fj5jgp6za9zyug7dgtyrt.png" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you go to the "Trace" tab in Tracetest, you’ll see a list of spans, which are individual units of work within a trace. Each span represents a specific operation or step in your application's workflow, such as an HTTP request, a database query, or a function execution.&lt;/p&gt;

&lt;p&gt;Tracetest simplifies the view of these spans by organizing them in a clear, hierarchical structure, making it easy to follow the sequence of operations.&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%2Fqekwrdme4wr7bokxlfc8.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%2Fqekwrdme4wr7bokxlfc8.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switch to the "Test" tab to start adding tests on every trace span. Click the "Add Test Spec" button.&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%2Fg6qz2gpwxkq3755it8ko.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%2Fg6qz2gpwxkq3755it8ko.png" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s add a test to validate the server's response. Start by selecting the "Tracetest trigger" span, which represents the initial action in your test. In the "Add Assertions" section, choose the attribute &lt;code&gt;attr.response.body&lt;/code&gt;. This allows you to check specific details of the server's response. Enter the expected value, which in our case is &lt;code&gt;[]&lt;/code&gt;, since the server returns an empty array.&lt;/p&gt;

&lt;p&gt;Give the test spec a name and save it. Once saved, Tracetest will apply this spec to the test run whenever you trigger it, checking if the application behaves as expected according to the criteria you’ve set.&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%2Fi5m82f0brim4tozmw3tq.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%2Fi5m82f0brim4tozmw3tq.png" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the test again and verify if the test spec passed or failed.&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%2F6m46faqmrb3ytb3soxy2.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%2F6m46faqmrb3ytb3soxy2.png" width="800" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, let's create tests for an API. Select an API trace and add assertions to verify key aspects of the API's behavior. For example, you might check that the method used is "GET". You can also validate the route to ensure it contains the expected data or matches a specific format. By setting these assertions, you ensure that the API behaves as intended and meets your functional requirements.&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%2Fnsxpnqpr2kt1e7q4xdj3.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%2Fnsxpnqpr2kt1e7q4xdj3.png" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the test spec and run tests again:&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%2F2txa87a3kjxy4v5sxe5h.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%2F2txa87a3kjxy4v5sxe5h.png" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above image, you can see that one of the assertions failed the test because the expected value &lt;code&gt;localhost:5001&lt;/code&gt; does not match the actual value &lt;code&gt;localhost:5000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s create assertions for database connection next.&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%2Fhpihv1jntjwvhhbupma6.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%2Fhpihv1jntjwvhhbupma6.png" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, add tests for database queries by selecting the trace that corresponds to your database operations. Focus on specific spans related to the database interactions, such as queries or transactions.&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%2Fix4dj2mp34ya1gzbizwu.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%2Fix4dj2mp34ya1gzbizwu.png" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the test after creating all the test specs to know how many tests pass and how many fail.&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%2Fa908bi4a1itttkbteg9w.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%2Fa908bi4a1itttkbteg9w.png" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have successfully tested the application in Tracetest using OpenTelemetry traces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: Trace-Based Testing for Easier Validation
&lt;/h2&gt;

&lt;p&gt;Today, you learned about how trace-based testing with OpenTelemetry and Tracetest can simplify how you validate complex systems. Tracing offers a complete view of a request’s journey, far beyond what logs can provide. By adding Tracetest to your workflow, you’re not just monitoring—you’re testing every step of your application’s process.&lt;/p&gt;

&lt;p&gt;While adopting trace-based testing might seem like a shift, the advantages are huge. With Tracetest, you’re not only ensuring system reliability but also gaining confidence that everything works as intended before hitting production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is OpenTelemetry used for?
&lt;/h3&gt;

&lt;p&gt;OpenTelemetry is an open-source observability framework used to collect, process, and export telemetry data (traces, metrics, and logs) from applications. It helps developers and operations teams monitor the performance of distributed systems, troubleshoot issues, and gain insights into how their applications are functioning in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between OpenTelemetry and Prometheus?
&lt;/h3&gt;

&lt;p&gt;OpenTelemetry and Prometheus serve different but complementary purposes in observability. OpenTelemetry is focused on collecting and exporting traces, metrics, and logs from applications, providing a unified way to instrument and observe systems.&lt;br&gt;
On the other hand, Prometheus is a monitoring and alerting tool designed explicitly for metrics collection and querying. While OpenTelemetry can generate and export metrics, Prometheus specializes in scraping and storing these metrics for monitoring purposes.&lt;br&gt;
They can be used together, with OpenTelemetry generating the metrics and Prometheus storing and analyzing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the “trace” in trace-based testing?
&lt;/h3&gt;

&lt;p&gt;In testing, a trace refers to a record of the execution path of a request or transaction as it moves through different components of a system. Tracing helps in understanding the flow of data, identifying bottlenecks, and ensuring that each step in the workflow is executed correctly. In trace-based testing, these traces are used to validate that not only the final output but each part of the system’s process works as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Tracetest Yourself!
&lt;/h2&gt;

&lt;p&gt;Last, but not least, do you want to learn more about Tracetest and what it brings to the table? Check the &lt;a href="https://docs.tracetest.io" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and try it out by &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;signing up&lt;/a&gt; today!&lt;/p&gt;

&lt;p&gt;Also, please feel free to join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;, give &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;Tracetest a star on GitHub&lt;/a&gt;, or schedule a &lt;a href="https://calendly.com/ken-kubeshop/tracetest-walkthrough" rel="noopener noreferrer"&gt;time to chat 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>monitoring</category>
      <category>logging</category>
      <category>tracing</category>
    </item>
    <item>
      <title>Trace-Based Tests with GraphQL in Action!</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Tue, 10 Sep 2024 11:42:57 +0000</pubDate>
      <link>https://forem.com/kubeshop/trace-based-tests-with-graphql-in-action-7l</link>
      <guid>https://forem.com/kubeshop/trace-based-tests-with-graphql-in-action-7l</guid>
      <description>&lt;h1&gt;
  
  
  Trace-based Tests with GraphQL in Action 💣
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/events/7237397975470112768/comments/" rel="noopener noreferrer"&gt;&lt;em&gt;Join us for the GraphQL and Playwright test trigger webinar on September 11th!&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; is a query language for APIs and a runtime for fulfilling those queries with your existing data. It offers a clear, comprehensive description of your API's data, allowing clients to request exactly what they need—nothing more, nothing less. This approach simplifies API evolution and enables powerful developer tools.&lt;/p&gt;

&lt;p&gt;Wouldn't it be great if Tracetest had a GraphQL trigger for testing GraphQL APIs?&lt;/p&gt;

&lt;p&gt;Our community certainly thinks so. In fact, three community members have requested this feature in recent months.&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%2Fvwlbraj8cz1zli7bap41.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%2Fvwlbraj8cz1zli7bap41.png" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Tracetest GraphQL Trigger 🔥
&lt;/h2&gt;

&lt;p&gt;Great news! The team has just released a brand-new GraphQL trigger type.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://app.tracetest.io/organizations/ttorg_2179a9cd8ba8dfa5/invites/CB01dP6IR/accept" rel="noopener noreferrer"&gt;Join our demo environment to try it yourself!&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/running-tests-with-tracetest-graphql-pokeshop" rel="noopener noreferrer"&gt;Or, check out the guided example to run it in your own environment.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can now effortlessly test GraphQL APIs using OpenTelemetry and trace-based testing. This feature is a natural addition to your existing HTTP and gRPC API tests.&lt;/p&gt;

&lt;p&gt;This update enables you to run tests using GraphQL queries, expanding your API testing capabilities.&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%2Fh89tgl7go0df5fwwyrh0.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%2Fh89tgl7go0df5fwwyrh0.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have two options: upload your own schema or use schema introspection. With the latter, Tracetest automatically fetches the schema from your API URL.&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%2Fedkhx9n1odcvh1n4jp7u.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%2Fedkhx9n1odcvh1n4jp7u.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, you can create test specs just as you would with other triggers. To streamline the creation and execution process and take advantage of any supported integrations. Navigate to the Automate tab to download or copy the definition file and the trigger script.&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%2Fusw9depin0xpovatk3u7.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%2Fusw9depin0xpovatk3u7.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing GraphQL APIs
&lt;/h2&gt;

&lt;p&gt;Running trace-based tests against GraphQL APIs has never been simpler. With this new trigger you can finally get the same test coverage for your GraphQL APIs as for your existing HTTP and gRPC APIs.&lt;/p&gt;

&lt;p&gt;There are 4 steps to create and run a GraphQL trigger test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a test with the GraphQL trigger 🕵️&lt;/li&gt;
&lt;li&gt;Add your GraphQL API endpoint 🔗&lt;/li&gt;
&lt;li&gt;Use the GraphQL Schema Introspection 🔍&lt;/li&gt;
&lt;li&gt;Add a GraphQL query ❓&lt;/li&gt;
&lt;li&gt;Hit run 🏃&lt;/li&gt;
&lt;li&gt;Add test specs 🧪&lt;/li&gt;
&lt;li&gt;Profit 🤑&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let me walk you through it all step-by-step.&lt;/p&gt;

&lt;p&gt;To run a test with the GraphQL trigger, click the GraphQL trigger type when creating a new test.&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%2Fmm2183dbi1gt57j069df.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%2Fmm2183dbi1gt57j069df.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add your GraphQL API endpoint. Let’s use the Pokeshop demo as a sample.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://demo-pokeshop.tracetest.io/graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click the &lt;code&gt;Use GraphQL Introspection&lt;/code&gt; button on the &lt;code&gt;Schema&lt;/code&gt; tab to load the GraphQL schema.&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%2Fvlcjhd1v4kf0wm20li75.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%2Fvlcjhd1v4kf0wm20li75.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to the &lt;code&gt;Query&lt;/code&gt; tab. Let’s add a mutation with two fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;create&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="n"&gt;createPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Charizard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flying"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;isFeatured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;imageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://img.pokemondb.net/artwork/large/charizard.jpg"&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;isFeatured&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;imageUrl&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="n"&gt;importPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&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="n"&gt;id&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;This will trigger the test and show you a trace response to start building test specs. The kicker here is that the two fields in the mutation will show two flows in the trace response.&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%2Fg3cuk87jmabhsll1mngi.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%2Fg3cuk87jmabhsll1mngi.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see both the &lt;code&gt;create&lt;/code&gt; and &lt;code&gt;import&lt;/code&gt; flow in the trace.&lt;/p&gt;

&lt;p&gt;To validate the GraphQL API, you can create a test spec like this:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;span[tracetest.span.type="http" name="POST /graphql" http.method="POST"]&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;Validate route is "/graphql" and data is valid.&lt;/span&gt;
  &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attr:http.route   =   "/graphql"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attr:http.response.body =&lt;/span&gt;
      &lt;span class="s"&gt;'{"data":{"createPokemon":{"id":224318,"name":"Charizard","isFeatured":false,"type":"flying","imageUrl":"https://img.pokemondb.net/artwork/large/charizard.jpg"},"importPokemon":{"id":38}}}'&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.amazonaws.com%2Fuploads%2Farticles%2Fo5pxm56op9sj0i4yyn8u.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%2Fo5pxm56op9sj0i4yyn8u.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also create specific test specs for the async flows in the Pokeshop demo like validating the RabbitMQ queue and the async API triggered behind the RabbitMQ queue.&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%2Fb0ytnuu8fo0yxqw6z3k5.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%2Fb0ytnuu8fo0yxqw6z3k5.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, what’s more exciting is that you can validate database interactions. In this sample you can validate both the &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;create&lt;/code&gt; database calls in the same test. Here’s the validation for the &lt;code&gt;create&lt;/code&gt; flow.&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%2F9r4821lgx3rm30lrj4nd.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%2F9r4821lgx3rm30lrj4nd.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, by selecting the database span below the RabbitMQ queue, you can validate the &lt;code&gt;import&lt;/code&gt; flow as well.&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%2Fdsi6fnvoixvpfpg6sjul.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%2Fdsi6fnvoixvpfpg6sjul.png" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like that, in a few minutes, you’ve created a GraphQL API test that validates two flows with multiple database interactions, async APIs and a message queue. If that isn’t magic, I don’t know what it. 🪄&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL API Trigger with the Playwright Engine and Test Observability
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;Tracetest&lt;/a&gt;, you can use the new GraphQL trigger alongside any other feature. Whether you want &lt;a href="https://docs.tracetest.io/concepts/tests" rel="noopener noreferrer"&gt;Tests&lt;/a&gt; as part of a &lt;a href="https://docs.tracetest.io/concepts/test-suites" rel="noopener noreferrer"&gt;Test Suite&lt;/a&gt;, plan to use them with the &lt;a href="https://tracetest.io/blog/tracetest-monitors-trace-based-testing-meets-synthetic-monitoring" rel="noopener noreferrer"&gt;Synthetic Monitor Framework&lt;/a&gt;, or aim to include them in &lt;a href="https://docs.tracetest.io/ci-cd-automation/github-actions-pipeline-with-secrets" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;—it's all possible.&lt;/p&gt;

&lt;p&gt;A game-changing feature would be combining tests using both the GraphQL trigger and &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/running-tests-with-tracetest-playwright-engine" rel="noopener noreferrer"&gt;Playwright engine trigger&lt;/a&gt; into a &lt;a href="https://docs.tracetest.io/concepts/run-groups" rel="noopener noreferrer"&gt;Run Group&lt;/a&gt;. Then, add these to a &lt;a href="https://docs.tracetest.io/concepts/monitors" rel="noopener noreferrer"&gt;Synthetic Monitor&lt;/a&gt; and run them on a schedule to continuously validate your applications in real time!&lt;/p&gt;

&lt;h2&gt;
  
  
  Try This in Tracetest yourself!
&lt;/h2&gt;

&lt;p&gt;Check out the &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/running-tests-with-tracetest-graphql-pokeshop" rel="noopener noreferrer"&gt;GraphQL trigger documentation&lt;/a&gt; to get started with your own tests. We're eager to hear your thoughts on this first version of the GraphQL Trigger!&lt;/p&gt;

&lt;p&gt;Last, but not least, do you want to learn more about Tracetest and what it brings to the table? Check the &lt;a href="https://docs.tracetest.io" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and try it out by &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;signing up&lt;/a&gt; today!&lt;/p&gt;

&lt;p&gt;Also, please feel free to join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;, give &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;Tracetest a star on GitHub&lt;/a&gt;, or schedule a &lt;a href="https://calendly.com/ken-kubeshop/tracetest-walkthrough" rel="noopener noreferrer"&gt;time to chat 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>testing</category>
      <category>opentelemetry</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Announcing Tracetest Enterprise On-Prem Solution</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Mon, 19 Aug 2024 14:30:48 +0000</pubDate>
      <link>https://forem.com/kubeshop/announcing-tracetest-enterprise-on-prem-solution-4mkj</link>
      <guid>https://forem.com/kubeshop/announcing-tracetest-enterprise-on-prem-solution-4mkj</guid>
      <description>&lt;p&gt;We are thrilled to announce the release of the &lt;a href="https://tracetest.io/get-started" rel="noopener noreferrer"&gt;on-prem enterprise self-hosted Tracetest&lt;/a&gt;! You can now get access to our cloud-based managed Tracetest experience in your own infrastructure. This new offering provides the same robust features and user-friendly experience as our cloud-based solution, now tailored for enterprises that require on-prem deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Hosted Tracetest: Ultimate Control and Customization
&lt;/h2&gt;

&lt;p&gt;Our SaaS solution has empowered countless developers with seamless and efficient trace-based testing. However, we understand that organizations have specific compliance, security, and performance needs that sometimes require on-prem solutions.&lt;/p&gt;

&lt;p&gt;The on-prem enterprise version addresses these requirements by providing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Security&lt;/strong&gt;: Your data remains within your organization's infrastructure, providing an extra layer of security and compliance with internal policies and regulations. This is crucial for organizations in highly regulated industries or those with specific data residency requirement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customization Options&lt;/strong&gt;: Tailor the software to meet your specific requirements. Set up deep customization to suit your unique needs, whether it's integrating with legacy systems, tweaking the testing environment, or configuring specific security protocols.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Control Over Updates&lt;/strong&gt;: Manage when and how updates are applied.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Which Tracetest Version is Right for You?
&lt;/h2&gt;

&lt;p&gt;Choosing between the &lt;a href="https://app.tracetest.io" rel="noopener noreferrer"&gt;Managed Cloud&lt;/a&gt; and the &lt;a href="https://tracetest.io/get-started" rel="noopener noreferrer"&gt;Self-Hosted On-Prem version&lt;/a&gt; of Tracetest comes down to your organization's specific needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go with Managed Cloud&lt;/strong&gt; if you prioritize ease of use, rapid scalability, and prefer not to manage infrastructure or updates. This option is perfect for organizations looking for a seamless, low-maintenance solution that can quickly adapt to changing needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opt for Self-Hosted On-Prem&lt;/strong&gt; if you require full control over your environment, need to comply with strict data  regulations, or want to deeply customize your testing setup. This version is ideal for large or security-focused organizations with unique needs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;If you're ready to take control of your testing infrastructure with the On-Prem version of Tracetest, we're here to help you every step of the way. We're committed to making your onboarding to the on-prem enterprise version as smooth as possible! We’ll create a dedicated Slack channel and assign a support team to assist you with deployment, configuration, PoC, and ongoing support to ensure that you are up and running as fast as possible.&lt;/p&gt;

&lt;p&gt;Please reach out to our team for a &lt;a href="https://tracetest.io/on-prem-installation" rel="noopener noreferrer"&gt;personalized demo and pricing information&lt;/a&gt; or contact our founder Ken at &lt;a href="//mailto:ken@kubeshop.io"&gt;ken@kubeshop.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>testing</category>
      <category>devops</category>
      <category>tracetest</category>
    </item>
    <item>
      <title>Tracetest Monitors: Trace-based testing meets Synthetic Monitoring 🔥</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Wed, 26 Jun 2024 11:42:05 +0000</pubDate>
      <link>https://forem.com/kubeshop/tracetest-monitors-trace-based-testing-meets-synthetic-monitoring-2f0f</link>
      <guid>https://forem.com/kubeshop/tracetest-monitors-trace-based-testing-meets-synthetic-monitoring-2f0f</guid>
      <description>&lt;p&gt;Are you ready to get your mind blown!? 🧠💥&lt;/p&gt;

&lt;p&gt;Trace-based synthetic monitoring is here! You can now create “Monitors” for Tracetest tests and test suites.&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%2F1fxav1yec82whzzh2n80.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%2F1fxav1yec82whzzh2n80.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tracetest Monitors is a framework for creating scheduled runs of tests and test suites, and getting alerted when they fail. With native support for webhooks, you can choose how to integrate with you favorite alerting tools!&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%2Fnzrz6gaj0aku7e6y22w5.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%2Fnzrz6gaj0aku7e6y22w5.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’re dedicated to user experience and giving customers the easiest way of automating test runs without needing external CI tools. We want to give you the best possible tooling to embrace the “test in production” mindset too! Let’s be honest, we all do it! 😎&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://app.tracetest.io/organizations/ttorg_2179a9cd8ba8dfa5/invites/invite_760904a64b4b9dc9/accept" rel="noopener noreferrer"&gt;&lt;em&gt;Join our demo environment to try it out yourself!!&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem — Scheduled Testing is Not as Simple as it Sounds
&lt;/h2&gt;

&lt;p&gt;Test observability and trace-based testing has become a staple in the OpenTelemetry community. Having the power of distributed tracing at your fingertips when troubleshooting tests and writing assertions is immense. It’s highly advocated by the OpenTelemetry contributors in the official demo repository. While they, and all our other customers, are rolling their own automation with CI, to get the full benefit of trace-based testing, we’ve noticed it can often be a nuisance.&lt;/p&gt;

&lt;p&gt;Configuring automated trace-based testing across multiple environments, using several tests, with a set timeframe of, let’s say, every 15 minutes, is not as simple as it sounds. You’d “just” need a CI tool of sorts, like GitHub Actions, or Jenkins. You’d maybe even settle for “just” creating a Kubernetes Job and defining it to run as a cron.&lt;/p&gt;

&lt;p&gt;All of this is not as simple as using the word “just”.&lt;/p&gt;

&lt;p&gt;Let’s normalize that “just” is relative and can’t apply the same to me, you, or someone else.&lt;/p&gt;

&lt;p&gt;DevOps work like this can get exponentially more complex the more moving parts you introduce. A lot of the time SREs can get overloaded. That’s why shifting left and enabling the entire engineering team to perform synthetic testing in production, and across all your environments, is so powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea — The Beginning of Synthetic Testing
&lt;/h2&gt;

&lt;p&gt;We introduced the &lt;code&gt;Automate&lt;/code&gt; tab back in July of 2023 including this &lt;a href="https://tracetest.io/blog/github-actions-observability-slack-synthetic-api-tests" rel="noopener noreferrer"&gt;guide about simulating synthetic monitoring with GitHub Actions&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.amazonaws.com%2Fuploads%2Farticles%2Fwnhfgpoahn0lr0dhznqt.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%2Fwnhfgpoahn0lr0dhznqt.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Making trace-based synthetic monitoring a reality has been a dream since then. But, waking up from dreams takes you back to reality. Back in July of last year we were hyper-focused on releasing Tracetest Open Beta and making Tracetest widely available for our users as a cloud-based managed platform.&lt;/p&gt;

&lt;p&gt;We’ve grown and matured since then. Finally, reaching a point where adding scheduled runs with Monitors has become reality!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution — Trace-based Synthetic Monitoring
&lt;/h2&gt;

&lt;p&gt;The first-ever native synthetic monitoring tool for trace-based testing is live! Create a Monitor to run tests and test suites on a schedule.&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%2Fwmaa9gknqpaoy5wqsxl9.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%2Fwmaa9gknqpaoy5wqsxl9.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Get alerted via webhooks by integrating with your favorite alerting tools.&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%2Fuoqhosr8ls4hrlw8jg35.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%2Fuoqhosr8ls4hrlw8jg35.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monitors leverage the existing Runs and Run Groups resources in Tracetest and build on top of it by enabling scheduling and alerting. Every Monitor you define will be presented as a Run Group with an additional tag using the specific name of the &lt;code&gt;Monitor&lt;/code&gt;.  Here you can see a list of Run Groups that include two Run Groups labeled as a &lt;code&gt;Monitor&lt;/code&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.amazonaws.com%2Fuploads%2Farticles%2Fgcezlf2itciapyz246mx.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%2Fgcezlf2itciapyz246mx.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting the &lt;code&gt;#c85ea8f8-805d-4064-86ea-1aad2074e9c9&lt;/code&gt; Run Group shows which tests are part of the &lt;code&gt;Monitor&lt;/code&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.amazonaws.com%2Fuploads%2Farticles%2Fetb6sv4ff2bbnd2na6kr.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%2Fetb6sv4ff2bbnd2na6kr.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Filtering Runs by the &lt;code&gt;Monitor&lt;/code&gt; tag is also available in the Runs view.&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%2Fzom5b0oigogecw92s6yi.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%2Fzom5b0oigogecw92s6yi.png" alt="Image description" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Releasing Monitors as a Synthetic Monitoring feature has been a long-standing dream, and it has finally arrived. This is the culmination of almost a year of planning and laying the groundwork to launch it for you! Now you can finally get the full benefit of test observability and trace-based testing and easy-to-use test automation for both production and pre-production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Start Using Tracetest Synthetic Monitoring?
&lt;/h2&gt;

&lt;p&gt;Make sure to use Tracetest &lt;code&gt;v.1.3.1&lt;/code&gt; and above. There are no other requirements. Click on the &lt;code&gt;Monitors&lt;/code&gt; tab, click &lt;code&gt;Create&lt;/code&gt; and have fun!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What’s Next?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First and foremost, we welcome your feedback on the initial version of Tracetest Monitors!&lt;/p&gt;

&lt;p&gt;We recognize that there are big opportunities for improvement. Ensuring that the features we're developing meet the community's needs is our priority!&lt;/p&gt;

&lt;p&gt;Last, but not least, do you want to learn more about Tracetest and what it brings to the table? Check the &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/running-tracetest-with-lightstep/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and try it out by &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;signing up&lt;/a&gt; it today!&lt;/p&gt;

&lt;p&gt;Also, please feel free to join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;, give &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;Tracetest a star on GitHub&lt;/a&gt;, or schedule a &lt;a href="http://calendly.com/ken-kubeshop/otel-user-interview-w-tracetest" rel="noopener noreferrer"&gt;time to chat 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>testing</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Using Env Vars to Include &amp; Exclude OpenTelemetry Node.js Libraries</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Tue, 11 Jun 2024 11:58:07 +0000</pubDate>
      <link>https://forem.com/kubeshop/using-env-vars-to-include-exclude-opentelemetry-nodejs-libraries-6ke</link>
      <guid>https://forem.com/kubeshop/using-env-vars-to-include-exclude-opentelemetry-nodejs-libraries-6ke</guid>
      <description>&lt;p&gt;OpenTelemetry's auto-instrumentation for Node.js is the open standard for tracing your applications. The default setting includes a wide range of events, some of which might not be meaningful or relevant to you. This can create a lot of noise in your distributed traces, making it harder to identify the insights that matter.&lt;/p&gt;

&lt;p&gt;To run Node.js with OpenTelemetry auto instrumentation, you'll need to &lt;a href="https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/metapackages/auto-instrumentations-node#usage-auto-instrumentation" rel="noopener noreferrer"&gt;install the Node.js auto instrumentation module&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @opentelemetry/api
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @opentelemetry/auto-instrumentations-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable auto instrumentation by requiring this module using the &lt;a href="https://nodejs.org/api/cli.html#-r---require-module" rel="noopener noreferrer"&gt;--require flag&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--require&lt;/span&gt; &lt;span class="s1"&gt;'@opentelemetry/auto-instrumentations-node/register'&lt;/span&gt; app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Node application is encapsulated in a complex run script, you can also set it via an environment variable before running Node.&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;env &lt;/span&gt;&lt;span class="nv"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--require @opentelemetry/auto-instrumentations-node/register"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module is highly configurable using environment variables. Many aspects of the auto instrumentation's behavior can be configured for your needs. But, it’s sometimes hard to understand which instrumentations are most useful for your application. This might include HTTP events, database events, or other types of events depending on the nature of your application.&lt;/p&gt;

&lt;p&gt;For instance, file system (&lt;code&gt;fs&lt;/code&gt;) events and TCP (&lt;code&gt;net&lt;/code&gt;) events are included in the auto-instrumentation, but these might not be useful in every application. Luckily, OpenTelemetry provides ways to customize the instrumentation to suit your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable/Disable Auto Instrumentations with Env Vars
&lt;/h2&gt;

&lt;p&gt;One common problem is the inclusion of file system spans, which might not be relevant to all applications. Disabling these can help to streamline your data and make it easier to interpret.&lt;/p&gt;

&lt;p&gt;By default, all &lt;a href="https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/metapackages/auto-instrumentations-node#supported-instrumentations" rel="noopener noreferrer"&gt;supported Instrumentations&lt;/a&gt; are enabled. With the &lt;code&gt;OTEL_NODE_ENABLED_INSTRUMENTATIONS&lt;/code&gt; environment variable, you can enable certain instrumentations by providing a comma-separated list of the packages without the &lt;code&gt;@opentelemetry/instrumentation-&lt;/code&gt; prefix.&lt;/p&gt;

&lt;p&gt;To enable only &lt;a href="https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-instrumentation-http" rel="noopener noreferrer"&gt;&lt;code&gt;@opentelemetry/instrumentation-http&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/opentelemetry-instrumentation-express" rel="noopener noreferrer"&gt;&lt;code&gt;@opentelemetry/instrumentation-express&lt;/code&gt;&lt;/a&gt; you can run this command below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_NODE_ENABLED_INSTRUMENTATIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http,express"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable/Disable Auto Instrumentations Programmatically
&lt;/h2&gt;

&lt;p&gt;You can also initialize OpenTelemetry and configure the auto instrumentations programmatically.&lt;/p&gt;

&lt;p&gt;Custom configuration for each auto instrumentations package can be passed to the &lt;code&gt;getNodeAutoInstrumentations&lt;/code&gt; function. You provide an object with the name of the instrumentation as a key and its configuration as the value.&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;opentelemetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/sdk-node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/auto-instrumentations-node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OTLPTraceExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/exporter-trace-otlp-grpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/resources&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SemanticResourceAttributes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/semantic-conventions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodeTracerProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/sdk-trace-node&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BatchSpanProcessor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/sdk-trace-base&lt;/span&gt;&lt;span class="dl"&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;dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&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;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SemanticResourceAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_NAME&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quick-start-nodejs-manual-instrumentation&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="nx"&gt;SemanticResourceAttributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SERVICE_VERSION&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&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="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodeTracerProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resource&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;exporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OTLPTraceExporter&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;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&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;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;traceExporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instrumentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/instrumentation-fs&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="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/instrumentation-net&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="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quick-start-nodejs-manual-instrumentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out this example in a code sample, &lt;a href="https://github.com/kubeshop/tracetest/blob/main/examples/quick-start-nodejs-manual-instrumentation/tracing.otel.grpc.js" rel="noopener noreferrer"&gt;here on GitHub&lt;/a&gt;. There’s a runnable sample app you can start with Docker Compose to see it for yourself. Here’s what the trace will look like without the noisy &lt;code&gt;fs&lt;/code&gt; and &lt;code&gt;net&lt;/code&gt; events.&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%2Fmcbw76604o4myebyf969.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%2Fmcbw76604o4myebyf969.png" alt="excluded libs" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For more info you can also refer to the &lt;a href="https://opentelemetry.io/docs/zero-code/js/configuration/#excluding-instrumentation-libraries" rel="noopener noreferrer"&gt;OpenTelemetry docs&lt;/a&gt;. It includes a section on excluding certain instrumentation libraries.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;When using Node.js with OpenTelemetry auto instrumentation, you need to understand which auto instrumentations are most useful for your application. This might include HTTP events, database events, or other types of events that you need to make your app reliable.&lt;/p&gt;

&lt;p&gt;You can enable specific instrumentations from environment variables, but also configure them programmatically. Other options you get by default include setting the OTLP endpoint, headers, and much more. See the list below.&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;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otlp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http/protobuf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_EXPORTER_OTLP_COMPRESSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-endpoint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key=your-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_EXPORTER_OTLP_TRACES_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key=your-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;service.namespace=my-namespace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_NODE_RESOURCE_DETECTORS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;env,host,os,serviceinstance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_NODE_ENABLED_INSTRUMENTATIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http,express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="nx"&gt;NODE_OPTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--require @opentelemetry/auto-instrumentations-node/register&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In conclusion, the Node.js OpenTelemetry auto instrumentation is a lifesaver and includes a lot of useful instrumentations by default. However, it’s up to you to customize the instrumentations to suit your application. By enabling only relevant instrumentations, you can reduce the noise in your tracing, making it easier to gain valuable insights.&lt;/p&gt;

&lt;p&gt;Interested in proactively using traces instead of just reactively troubleshooting in production? Check out the Tracetest &lt;a href="https://docs.tracetest.io/getting-started/installation" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and give it a try by &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;signing up today&lt;/a&gt;. Tracetest enables you to add test observability to all your existing tests. It integrates with &lt;a href="https://docs.tracetest.io/tools-and-integrations/playwright" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;, &lt;a href="https://docs.tracetest.io/tools-and-integrations/cypress" rel="noopener noreferrer"&gt;Cypress&lt;/a&gt;, &lt;a href="https://docs.tracetest.io/tools-and-integrations/k6" rel="noopener noreferrer"&gt;k6&lt;/a&gt;, &lt;a href="https://docs.tracetest.io/tools-and-integrations/artillery-plugin" rel="noopener noreferrer"&gt;Artillery&lt;/a&gt;, and can run tests against &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/running-tracetest-without-a-trace-data-store" rel="noopener noreferrer"&gt;APIs (HTTP/gRPC)&lt;/a&gt;, &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-kafka-go-api-with-opentelemetry-tracetest" rel="noopener noreferrer"&gt;message queues like Kafka&lt;/a&gt;, and much more!&lt;/p&gt;

&lt;p&gt;Also, please feel free to join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack Community&lt;/a&gt;, give &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;Tracetest a star on GitHub&lt;/a&gt;, or schedule a &lt;a href="https://calendly.com/ken-kubeshop/45min" rel="noopener noreferrer"&gt;time to chat 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>opentelemetry</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Tracetest + Artillery Launch Week Recap 💥</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Fri, 19 Apr 2024 15:13:50 +0000</pubDate>
      <link>https://forem.com/kubeshop/tracetest-artillery-launch-week-recap-565b</link>
      <guid>https://forem.com/kubeshop/tracetest-artillery-launch-week-recap-565b</guid>
      <description>&lt;p&gt;This week was Tracetest’s first-ever Launch Week. We’ve been working on a major integration with &lt;a href="https://www.artillery.io/" rel="noopener noreferrer"&gt;Artillery&lt;/a&gt; for the last month and our team is beyond excited to share it with you all!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s a launch week?
&lt;/h2&gt;

&lt;p&gt;Each day this week, we’ve published new educational content on how to quickly get started with trace-based performance testing. This included docs guides, video tutorials, code samples, a blog post, and a webinar with live demos.&lt;/p&gt;

&lt;p&gt;We’re not the first company to run a launch week. Shoutout to &lt;a href="https://supabase.com/blog/tags/launch-week" rel="noopener noreferrer"&gt;Supabase for starting the hype around launch weeks&lt;/a&gt;. However, they’re a great way to give a huge new product update the time in the spotlight it deserves. And, give both the Artillery and Tracetest teams praise for releasing amazing new features!&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened this week?
&lt;/h2&gt;

&lt;p&gt;We shared daily updates on our &lt;a href="https://www.linkedin.com/company/tracetest" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; and &lt;a href="https://twitter.com/tracetest_io" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; pages, as well as in our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;community&lt;/a&gt;. In addition, we collaborated with our friends from Artillery on a blog post and live stream, complete with live demos. Lastly, we compiled this recap, making it easier for you to catch up on everything in one place.&lt;/p&gt;

&lt;p&gt;Without further ado, here’s what we did this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monday - Day 1: Artillery + Tracetest Integration Quick Starts
&lt;/h2&gt;

&lt;p&gt;We've announced a quick start guide for using the Artillery and Tracetest integration, including a runnable code example that you can start using in seconds!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.tracetest.io/tools-and-integrations/artillery-plugin" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kubeshop/tracetest/tree/main/examples/quick-start-artillery" rel="noopener noreferrer"&gt;Code Example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, there's a video tutorial by &lt;a href="https://www.linkedin.com/in/%F0%9F%87%B2%F0%9F%87%BD-oscar-rafael-reyes-gaucin-8aa843a8/" rel="noopener noreferrer"&gt;Oscar&lt;/a&gt; that provides a step-by-step guide on how to use the integration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VElGmu27y-I" rel="noopener noreferrer"&gt;‍View the full video on YouTube, here.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tuesday - Day 2: Artillery + Tracetest Integration Announcement
&lt;/h2&gt;

&lt;p&gt;We were excited to see our friends at Artillery publish the official integration announcement in their blog.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.artillery.io/blog/announcing-tracetest-integration" rel="noopener noreferrer"&gt;Read the full blog post, here.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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%2Fp9jedzl7q36rvqxfptlr.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%2Fp9jedzl7q36rvqxfptlr.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1713449996/Blogposts/artillery-announcement/og_uvhgtb.png" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wednesday - Day 3: Performance Testing with Distributed Tracing using Artillery, Playwright, and Tracetest
&lt;/h2&gt;

&lt;p&gt;Day 3 was a banger! We sat down with our friends Bernardo and Ines from Artillery and discussed everything from performance testing to using distributed tracing with OpenTelemetry for end-to-end testing. Oscar and Ines covered hands-on code demos of the Artillery + Tracetest integration, but also demoed how to use Playwright too!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4kfiZgB3FvQ" rel="noopener noreferrer"&gt;‍View the full video on YouTube, here.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thursday - Day 4: Enhancing the Artillery Playwright engine with Tracetest for observability-driven testing
&lt;/h2&gt;

&lt;p&gt;Day 4 was packed with guides and examples from the webinar collaboration! It includes a docs quick start and example code for using the Artillery + Playwright + Tracetest for trace-based performance testing with end-to-end visibility.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/running-playwright-performance-tests-with-artillery-and-tracetest" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kubeshop/tracetest/tree/main/examples/quick-start-artillery-playwright" rel="noopener noreferrer"&gt;Code Example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Friday - Day 5: Recap‍
&lt;/h2&gt;

&lt;p&gt;Today is the last day of the Launch Week. We’re dedicating today to a recap of everything the community has learned during the week and compiling all learning resources in one place.&lt;/p&gt;

&lt;p&gt;We’re so proud to have had the chance to collaborate with the lovely folks at Artillery. Thanks to &lt;a href="https://www.linkedin.com/in/inesfazlic/" rel="noopener noreferrer"&gt;Ines&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/ringlum/" rel="noopener noreferrer"&gt;Bernardo&lt;/a&gt;, and &lt;a href="https://www.linkedin.com/in/hveldstra/" rel="noopener noreferrer"&gt;Hassy&lt;/a&gt; for their hard work and awesome presentation skills!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;Would you like to learn more about what Tracetest and Artillery can solve?&lt;/p&gt;

&lt;h3&gt;
  
  
  Try out Artillery
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.artillery.io/docs/get-started/first-test" rel="noopener noreferrer"&gt;Follow this guide&lt;/a&gt; to try out running an example performance test.&lt;/li&gt;
&lt;li&gt;Check out &lt;a href="https://www.artillery.io/docs/cloud/get-started" rel="noopener noreferrer"&gt;this example&lt;/a&gt; to use &lt;a href="https://app.artillery.io/login" rel="noopener noreferrer"&gt;Artillery Cloud&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try out Tracetest
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check the &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes" rel="noopener noreferrer"&gt;guides in the docs&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Try it out by &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;signing up today&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, please feel free to join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack Community&lt;/a&gt;, give &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;Tracetest a star on GitHub&lt;/a&gt;, or schedule a &lt;a href="https://calendar.app.google/8qHAomjQZDKKDARd8" rel="noopener noreferrer"&gt;time to chat 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>testing</category>
      <category>devops</category>
    </item>
    <item>
      <title>Observability at KubeCon + CloudNativeCon Europe 2024 in Paris</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Tue, 26 Mar 2024 14:58:45 +0000</pubDate>
      <link>https://forem.com/kubeshop/observability-at-kubecon-cloudnativecon-europe-2024-in-paris-1117</link>
      <guid>https://forem.com/kubeshop/observability-at-kubecon-cloudnativecon-europe-2024-in-paris-1117</guid>
      <description>&lt;p&gt;Greetings from KubeCon + CloudNativeCon Europe! It was a wild and awesome ride last week! &lt;a href="https://www.linkedin.com/in/ken-hamric-016b1420/" rel="noopener noreferrer"&gt;Ken&lt;/a&gt; and I had a blast at both the Co-Located Observability Day on Tuesday, as well as during the main conference! I have a lot to share. Here’s a recap of how the observability community has grown in Europe since last year.&lt;/p&gt;

&lt;h2&gt;
  
  
  KubeCon + CloudNativeCon Venue
&lt;/h2&gt;

&lt;p&gt;KubeCon + CloudNativeCon Europe 2024 was in Paris the week of March 18th. I thought it would be interesting to recap some of the events from the Tracetest team’s perspective with an eye towards Observability.&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%2Fdc8dqavq8h4f0pnjnb4i.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%2Fdc8dqavq8h4f0pnjnb4i.png" alt="header" width="800" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, the venue &lt;a href="https://maps.app.goo.gl/gWXq6EaZzMnWfsMZ6?g_st=ic" rel="noopener noreferrer"&gt;Paris Expo Porte de Versailles&lt;/a&gt;, was beautiful. Our team had a booth on the main floor, but also attended the co-located Observability Day event prior to the main conference. &lt;/p&gt;

&lt;p&gt;The Observability Day got two rooms this year! Finally! Here’s what the main room looked like at 8:30 before it got packed to the brim when the talks started. Absolutely massive!&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%2F89v72r19u6uhfpdmtng7.jpg" 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%2F89v72r19u6uhfpdmtng7.jpg" alt="kc2" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability Day and OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;Let me start off by discussing Observability Day. The agenda was packed with informative sessions and discussions that shed light on the latest trends and advancements in observability.&lt;/p&gt;

&lt;p&gt;The CNCF finally provided a second room to have more sessions and accommodate the number of interested people!&lt;/p&gt;

&lt;h3&gt;
  
  
  Opening session by OpenTelemetry community committee members
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/austinlparker/" rel="noopener noreferrer"&gt;Austin Parker&lt;/a&gt;, Honeycomb, &lt;a href="https://colocatedeventseu2024.sched.com/event/1YGT9/observability-day-welcome-project-updates-eduardo-silva-fluentbit-austin-parker-honeycombio" rel="noopener noreferrer"&gt;started off the co-located event&lt;/a&gt;. He covered OpenTelemetry project updates and future goals.&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%2Fres.cloudinary.com%2Fdjwdcmwdz%2Fimage%2Fupload%2Fv1711373649%2FConferences%2Fkubecon-paris-2024%2Fmin%2Fkc3-min_wju2zv.jpg" 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%2Fres.cloudinary.com%2Fdjwdcmwdz%2Fimage%2Fupload%2Fv1711373649%2FConferences%2Fkubecon-paris-2024%2Fmin%2Fkc3-min_wju2zv.jpg" alt="kc3" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most notable were updates to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stability&lt;/li&gt;
&lt;li&gt;Events &amp;amp; RUM&lt;/li&gt;
&lt;li&gt;Continuous profiling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since last Observability Day 2023 in Chicago, 4 more languages have been marked stable!&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%2F1p491d7s9q5doyn3kwbm.jpg" 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%2F1p491d7s9q5doyn3kwbm.jpg" alt="kc4" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Events will be optimized for real-user monitoring!&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%2Fnvwh5e6d0ioqpdzvddyn.jpg" 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%2Fnvwh5e6d0ioqpdzvddyn.jpg" alt="kc5" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Profiling will be added this year!&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%2Fhf0o048jzl85yx3hllx1.jpg" 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%2Fhf0o048jzl85yx3hllx1.jpg" alt="kc6" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the biggest bombshell is that OpenTelemetry has applied for graduation in the CNCF.&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%2F5v0fgiz0f2x3hrj61788.jpg" 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%2F5v0fgiz0f2x3hrj61788.jpg" alt="kc7" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/edsiper/" rel="noopener noreferrer"&gt;Eduardo Silva&lt;/a&gt;, Chronosphere, then took the stage to talk about updates happening in the world of FluentBit.&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%2Fzclv4p0jnkvsttqp8oc6.jpg" 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%2Fzclv4p0jnkvsttqp8oc6.jpg" alt="kc8" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He covered latest updates and the roadmap for 2024.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/pavolloffay/" rel="noopener noreferrer"&gt;Pavol Loffay&lt;/a&gt; (Red Hat) and &lt;a href="https://www.linkedin.com/in/jkowall/" rel="noopener noreferrer"&gt;Jonah Kowall&lt;/a&gt; (Aiven) covered exciting updates to Jaeger.&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%2F7hzqe0ytwwsvcq0vg281.jpg" 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%2F7hzqe0ytwwsvcq0vg281.jpg" alt="kc9" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The roadmap looks awesome!&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%2Fymhv852h8trfppn453fl.jpg" 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%2Fymhv852h8trfppn453fl.jpg" alt="kc10" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As do the v2 goals!&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%2Fdjxljzvcl07uhohaat1r.jpg" 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%2Fdjxljzvcl07uhohaat1r.jpg" alt="kc11" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was a great segue into the more technical presentations of the day.&lt;/p&gt;

&lt;p&gt;We did catch all the sessions at Observability Day, but I want to share some of my favorites with you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hey OpenTelemetry, where’s my error!?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://colocatedeventseu2024.sched.com/#" rel="noopener noreferrer"&gt;Dude, Where’s My Error?: How OpenTelemetry Records Errors, and Why It Does It Like That&lt;/a&gt; — The session presented by &lt;a href="https://www.linkedin.com/in/reese-lee/" rel="noopener noreferrer"&gt;Reese Lee, New Relic&lt;/a&gt; &amp;amp; &lt;a href="https://www.linkedin.com/in/adrianavillela/" rel="noopener noreferrer"&gt;Adriana Villela, ServiceNow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;They emphasized the importance of tracking errors and understanding system functionality for user satisfaction. They used a demo to showcase OTel's error logging, metadata-enhanced troubleshooting, and the difference between errors and exceptions. The session explained error visualization across different backends and the effect of different span kinds on error reporting.&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%2Fres.cloudinary.com%2Fdjwdcmwdz%2Fimage%2Fupload%2Fv1711374384%2FConferences%2Fkubecon-paris-2024%2Fmin%2Fkc12-min_vit2qf.jpg" 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%2Fres.cloudinary.com%2Fdjwdcmwdz%2Fimage%2Fupload%2Fv1711374384%2FConferences%2Fkubecon-paris-2024%2Fmin%2Fkc12-min_vit2qf.jpg" alt="kc12" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to think about overhead?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://colocatedeventseu2024.sched.com/#" rel="noopener noreferrer"&gt;How to Think About Instrumentation Overhead&lt;/a&gt; — &lt;a href="https://www.linkedin.com/in/jasonplumb/" rel="noopener noreferrer"&gt;Jason Plumb (Splunk)&lt;/a&gt; did an amazing talk on instrumentation overhead in an observability context. Novice observability practitioners who were previously overly obsessed with performance had approached instrumentation with skepticism due to concerns about latency degradation or resource consumption.&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%2F430ba3xvh245zqruwjfq.jpg" 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%2F430ba3xvh245zqruwjfq.jpg" alt="kc13" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The causes of overhead were covered, illustrating why it is difficult to measure and predict. Jason also showcased practical techniques for understanding overhead in one's environment and strategies for coping with it were presented.&lt;/p&gt;

&lt;h3&gt;
  
  
  FluentBit vs. OpenTelemetry Collector
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://colocatedeventseu2024.sched.com/#" rel="noopener noreferrer"&gt;Telemetry Showdown: Fluent Bit Vs. OpenTelemetry Collector - a Comprehensive Benchmark Analysis&lt;/a&gt; —  &lt;a href="https://www.linkedin.com/in/hrexed/" rel="noopener noreferrer"&gt;Henrik Rexed&lt;/a&gt;, Dynatrace.&lt;/p&gt;

&lt;p&gt;In a push to standardize observability practices, the cloud-native community has embraced OpenTelemetry, offering a unified framework for metrics, logs, and traces. In the past log processing relied on agents like Fluentd, evolving into FluentBit. With FluentBit's recent expansion to support additional signals and the rise of OpenTelemetry Collector, a question arises: "Which is the superior choice for performance?"&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%2Fs7zdbleye5hj1ftlnt3w.jpg" 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%2Fs7zdbleye5hj1ftlnt3w.jpg" alt="kc14" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Henrik explained the performance differences, ease of use, compatibility, and more!&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving Serverless monitoring at Google
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://colocatedeventseu2024.sched.com/event/1YFhh/monitoring-serverless-workloads-with-opentelemetry-and-prometheus-ridwan-sharif-google" rel="noopener noreferrer"&gt;Monitoring Serverless Workloads with OpenTelemetry and Prometheus&lt;/a&gt; - &lt;a href="https://www.linkedin.com/in/ridwanmsharif/" rel="noopener noreferrer"&gt;Ridwan Sharif&lt;/a&gt;, Google.&lt;/p&gt;

&lt;p&gt;In this presentation Ridwan explained the challenge with observability and serverless platforms. To enable OpenTelemetry tracing and Prometheus-based metrics in Google's Cloud Run they had to implement new updates to sidecars in Cloud Run.&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%2Fres.cloudinary.com%2Fdjwdcmwdz%2Fimage%2Fupload%2Fv1711374564%2FConferences%2Fkubecon-paris-2024%2Fmin%2Fkc15-min_k4gbqf.jpg" 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%2Fres.cloudinary.com%2Fdjwdcmwdz%2Fimage%2Fupload%2Fv1711374564%2FConferences%2Fkubecon-paris-2024%2Fmin%2Fkc15-min_k4gbqf.jpg" alt="kc15" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This enabled flushing push-based OpenTelemetry traces before containers were killed. But also scraping with Prometheus. This presentation explains how these improvements to Cloud Run enabled the use of open-source observability tooling to improve serverless observability.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a good OpenTelemetry mindset?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://colocatedeventseu2024.sched.com/event/1YFjB/shift-into-an-observability-mindset-with-opentelemetry-daniel-gomez-blanco-skyscanner" rel="noopener noreferrer"&gt;Shift Into an Observability Mindset with OpenTelemetry&lt;/a&gt; - &lt;a href="https://www.linkedin.com/in/danielgblanco86/" rel="noopener noreferrer"&gt;Daniel Gomez Blanco&lt;/a&gt;, Skyscanner.&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%2F5b2qsszxso9kgql6uhto.jpg" 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%2F5b2qsszxso9kgql6uhto.jpg" alt="kc16" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Daniel covered an amazing topic. Embracing observability in your organization.&lt;/p&gt;

&lt;p&gt;To do so, you must shift your mindset to rethink monitoring and debugging practices. This means moving to OpenTelemetry and communicating the value of OpenTelemetry as the ideal tool for the job both today and moving forward. Daniel also covered adoption best practices as well. Can't wait to get the recording and re-watch this amazing talk.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to build dynamic OpenTelemetry Collector configs?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://colocatedeventseu2024.sched.com/event/1YFk6/opamp-in-action-user-configurable-observability-pipelines-srikanth-chekuri-signoz" rel="noopener noreferrer"&gt;OpAMP in Action: User Configurable Observability Pipelines&lt;/a&gt; - &lt;a href="https://www.linkedin.com/in/tinyidly/" rel="noopener noreferrer"&gt;Srikanth Chekuri&lt;/a&gt;, SigNoz.&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%2Fea19w6tfe8cs8acuj0bo.jpg" 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%2Fea19w6tfe8cs8acuj0bo.jpg" alt="kc17" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observability pipelines using OpenTelemetry Collector can't be configured dynamically. This means you need to restart the collector when the config is changed. With OpAMP you can bypass this to build dynamic user configurable observability pipelines!&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability Vendors at KubeCon
&lt;/h2&gt;

&lt;p&gt;Moving on, let's explore the diverse range of observability vendors present at KubeCon in Paris this year. I counted 29 vendors who set up booths, each offering unique solutions and perspectives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tracetest.io/" rel="noopener noreferrer"&gt;Tracetest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://honeycomb.io/" rel="noopener noreferrer"&gt;Honeycomb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.datadoghq.com/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opensearch.org/" rel="noopener noreferrer"&gt;OpenSearch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tyk.io/" rel="noopener noreferrer"&gt;Tyk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dynatrace.com/" rel="noopener noreferrer"&gt;Dynatrace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://newrelic.com/" rel="noopener noreferrer"&gt;New Relic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.splunk.com/" rel="noopener noreferrer"&gt;Splunk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/" rel="noopener noreferrer"&gt;Elastic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://edgedelta.com/" rel="noopener noreferrer"&gt;Edge Delta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chronosphere.io/" rel="noopener noreferrer"&gt;Chronosphere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.toCalyptia"&gt;Calyptia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://victoriametrics.com/" rel="noopener noreferrer"&gt;Victoria Metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://logz.io/" rel="noopener noreferrer"&gt;Logz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluentbit.io/" rel="noopener noreferrer"&gt;Fluentbit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lumigo.io/" rel="noopener noreferrer"&gt;Lumigo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://coralogix.com/" rel="noopener noreferrer"&gt;Coralogix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://axiom.co/" rel="noopener noreferrer"&gt;Axiom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.stackstate.com/" rel="noopener noreferrer"&gt;StackState&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sentry.io/welcome/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dash0.com/" rel="noopener noreferrer"&gt;Dash0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.highlight.io/" rel="noopener noreferrer"&gt;Highlight&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.manageengine.com/" rel="noopener noreferrer"&gt;ManageEngine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kloudfuse.com/" rel="noopener noreferrer"&gt;Kloudfuse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://axoflow.com/" rel="noopener noreferrer"&gt;Axoflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.groundcover.com/" rel="noopener noreferrer"&gt;Groundcover&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As promised, I have compiled a collection of photos to give you a glimpse into the vibrant atmosphere and impressive setups.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=g58GB_YVbM8" rel="noopener noreferrer"&gt;Check out the video on YouTube, here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Interviewing OpenTelemetry Maintainers, Approvers, and Contributors
&lt;/h2&gt;

&lt;p&gt;I had the chance to talk to OpenTelemetry community members about their experience and work in the community. The interviews include &lt;a href="https://www.linkedin.com/in/julianocosta89/" rel="noopener noreferrer"&gt;Juliano Costa&lt;/a&gt; (OpenTelemetry Demo maintainer), &lt;a href="https://www.linkedin.com/in/kaylareopelle/" rel="noopener noreferrer"&gt;Kayla Reopelle&lt;/a&gt; (OpenTelemetry Ruby Approver), and &lt;a href="https://www.linkedin.com/in/adrianavillela/" rel="noopener noreferrer"&gt;Adriana Villela&lt;/a&gt; (OpenTelemetry Contributor).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=vgBKuSMa5J0" rel="noopener noreferrer"&gt;View the video with OpenTelemetry Demo Maintainer Juliano Costa, here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for a complete case study with the OpenTelemetry maintainers!&lt;/p&gt;

&lt;h2&gt;
  
  
  Fostering a Sense of Community
&lt;/h2&gt;

&lt;p&gt;In the world of observability, it's super cool to see how vendors, even while competing, come together like old friends. It’s awesome to witness these bonds and collabs, giving the whole scene a cozy, tight-knit vibe.&lt;/p&gt;

&lt;p&gt;Need proof? Check out how folks from ServiceNow and New Relic teamed up for sessions, not to mention how Google engineers are committed to open-source observability!&lt;/p&gt;

&lt;p&gt;And then there’s us at Tracetest, playing it cool like Switzerland in the midst of OpenTelemetry. We're all about bridging gaps, working with everyone to bring trace-based testing into the spotlight. It's all about fostering that spirit of unity and teamwork!&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do we go from now?
&lt;/h2&gt;

&lt;p&gt;Now once we're all back from KubeCon in Paris, let’s continue the discussion and try contributing even more to the OpenTelemetry project. The OpenTelemetry project has filed for graduation in the CNCF.&lt;/p&gt;

&lt;p&gt;Our team has been active in the last year, with numerous PRs, both code contributions and content for the OpenTelemetry blog. I am hoping we can ramp up even more and help the developer community embrace OpenTelemetry, making it easier than ever to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you think?
&lt;/h2&gt;

&lt;p&gt;I’ve shared my thoughts and experiences from KubeCon. Let me hear your ideas as well! What topics would you like to see covered? Feel free to reach out and share your suggestions!&lt;/p&gt;

&lt;p&gt;Do you want us to contribute to something specific in the OpenTelemetry project?&lt;br&gt;
Do you need a specific feature in Tracetest to help you work with OpenTelemetry?&lt;br&gt;
There are no bad ideas!&lt;/p&gt;

&lt;p&gt;I’m eager to hear your thoughts. Feel free to reach out to me any way you prefer! I am active on LinkedIn daily and happy to answer questions or just chat about OTel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/adnanrahic/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tracetest.io/contact" rel="noopener noreferrer"&gt;Contact Form&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  See you at KubeCon North America in Salt Lake City!
&lt;/h2&gt;

&lt;p&gt;To wrap it up, KubeCon was a super exciting experience. The observability vendors showed off their skills, and the awesome sense of community was all about working together and contributing to OpenTelemetry.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post, share it with your friends!&lt;/p&gt;

&lt;p&gt;Feel free to connect with us on &lt;a href="https://www.linkedin.com/company/tracetest" rel="noopener noreferrer"&gt;social media&lt;/a&gt;, join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;, and give us a &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;⭐ on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>community</category>
    </item>
    <item>
      <title>Crafting Observable Cloudflare Workers with OpenTelemetry</title>
      <dc:creator>Adnan Rahić</dc:creator>
      <pubDate>Wed, 14 Feb 2024 12:46:57 +0000</pubDate>
      <link>https://forem.com/kubeshop/crafting-observable-cloudflare-workers-with-opentelemetry-1ocm</link>
      <guid>https://forem.com/kubeshop/crafting-observable-cloudflare-workers-with-opentelemetry-1ocm</guid>
      <description>&lt;p&gt;Serverless architectural patterns struggle with visibility. They’re difficult to troubleshoot in production and complex to test across development and staging environments including integration tests.&lt;/p&gt;

&lt;p&gt;Today you’ll learn how to gain insight into your development lifecycle and test critical flows while building production-ready &lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt; that interact with a &lt;a href="https://www.cloudflare.com/developer-platform/d1/" rel="noopener noreferrer"&gt;D1 database&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This includes adding:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;Distributed tracing with OpenTelemetry&lt;/a&gt; for troubleshooting.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://tracetest.io/features" rel="noopener noreferrer"&gt;Trace-based testing with Tracetest&lt;/a&gt; for integration testing and testing staging/production deployments.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why? So you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test what you usually can’t!&lt;/strong&gt; Apply assertions against the Cloudflare Worker runtime, external API calls, and a serverless database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Troubleshoot failed tests, with traces.&lt;/strong&gt; Use OpenTelemetry-based distributed traces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop the blame game.&lt;/strong&gt; With a view of the entire flow, from upstream API to database, quickly determine the cause of the failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gain better observability!&lt;/strong&gt; As tracing data from instrumentation is used to build trace-based tests, developers will want to add more insightful and meaningful instrumentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you’re done with the tutorial, you’ll have configured &lt;strong&gt;testing Cloudflare Workers in live staging and production deployments&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fl1vhxt03rghc460qwzhn.gif" 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%2Fl1vhxt03rghc460qwzhn.gif" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707486040/Blogposts/testing-cloudflare-workers/ezgif.com-optimize_2_tvoauz.gif" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If you’re eager to start, &lt;a href="https://github.com/kubeshop/tracetest/tree/main/examples/testing-cloudflare-workers" rel="noopener noreferrer"&gt;clone the example from GitHub&lt;/a&gt; and get a &lt;a href="https://docs.tracetest.io/concepts/cloud-agent" rel="noopener noreferrer"&gt;Tracetest Agent public URL&lt;/a&gt; and &lt;a href="https://docs.tracetest.io/concepts/environment-tokens" rel="noopener noreferrer"&gt;Token&lt;/a&gt; after signing up at &lt;a href="https://app.tracetest.io" rel="noopener noreferrer"&gt;&lt;code&gt;app.tracetest.io&lt;/code&gt;&lt;/a&gt;. Sign up for a Cloudflare account on&lt;/em&gt; &lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;&lt;code&gt;dash.cloudflare.com/&lt;/code&gt;&lt;/a&gt;&lt;em&gt;. Update the values in &lt;code&gt;wrangler.toml&lt;/code&gt;, deploy your Cloudflare Worker, and run tests against deployed code! &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-cloudflare-workers-with-opentelemetry-tracetest" rel="noopener noreferrer"&gt;Read the quick start instructions, here&lt;/a&gt;.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/kubeshop/tracetest.git
&lt;span class="nb"&gt;cd &lt;/span&gt;tracetest/examples/testing-cloudflare-workers
&lt;span class="c"&gt;# Install modules and npx if you haven't already&lt;/span&gt;
npm i npx &lt;span class="nt"&gt;-g&lt;/span&gt;
npm i
&lt;span class="c"&gt;# Sign in to Cloudflare&lt;/span&gt;
npx wrangler login
&lt;span class="c"&gt;# Set the &amp;lt;TRACETEST_URL&amp;gt; in wrangler.toml&lt;/span&gt;
npx wrangler d1 create testing-cloudflare-workers
&lt;span class="c"&gt;# Set the &amp;lt;YOUR_DATABASE_ID&amp;gt; from the command above in wrangler.toml&lt;/span&gt;
npx wrangler d1 execute testing-cloudflare-workers &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./schema.sql
&lt;span class="c"&gt;# Deploy Cloudflare Worker&lt;/span&gt;
npm run deploy
&lt;span class="c"&gt;# Run tests!&lt;/span&gt;
tracetest run &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ./test/test-api.prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What are Cloudflare Workers?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt; are Cloudflare’s answer to AWS Lambda. They let you deploy serverless code instantly across the globe and are blazing fast. You write code and deploy it to cloud environments without the need for traditional infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Cloudflare Dev Tools and Create the Boilerplate
&lt;/h2&gt;

&lt;p&gt;There are three prerequisites to get started. You might’ve figured already but let me outline them below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up for a Cloudflare account.&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;npm&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;Install Node.js.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You create a new Worker project with the &lt;a href="https://www.npmjs.com/package/create-cloudflare" rel="noopener noreferrer"&gt;&lt;code&gt;create-cloudflare-cli&lt;/code&gt;&lt;/a&gt; also called C3. It’s a command-line tool designed to help you setup and deploy Workers to Cloudflare.&lt;/p&gt;

&lt;p&gt;Open a terminal window and run C3 to create your Worker project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create cloudflare@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will prompt you to install C3, and lead you through setup. Let’s set up a basic worker.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Name your new Worker directory &lt;code&gt;pokemon-api&lt;/code&gt; because I’ll demo how to fetch Pokemon from an external API.&lt;/li&gt;
&lt;li&gt;Select &lt;code&gt;"Hello World" script&lt;/code&gt; as the type of application you want to create.&lt;/li&gt;
&lt;li&gt;Answer &lt;code&gt;yes&lt;/code&gt; to using TypeScript.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You will be asked if you would like to deploy the project to Cloudflare. Go ahead and select &lt;code&gt;yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You will be asked to authenticate, if not logged in already, and your project will be deployed to the Cloudflare global network.&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%2Fktlczsmf20n69y3iou3q.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%2Fktlczsmf20n69y3iou3q.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707486765/Blogposts/testing-cloudflare-workers/screely-1707486760350_x0bnrq.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open your Cloudflare Dashboard and see that indeed you have deployed the &lt;code&gt;Hello World&lt;/code&gt; Worker.&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%2F4yy90k2v8wwalptgow4b.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%2F4yy90k2v8wwalptgow4b.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707486752/Blogposts/testing-cloudflare-workers/screely-1707486747011_t3vl63.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In your project directory, C3 has generated the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;wrangler.toml&lt;/code&gt;: Your &lt;a href="https://developers.cloudflare.com/workers/wrangler/configuration/#sample-wranglertoml-configuration" rel="noopener noreferrer"&gt;Wrangler&lt;/a&gt; configuration file. The Workers command-line interface, &lt;a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/" rel="noopener noreferrer"&gt;Wrangler&lt;/a&gt;, allows you to &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#init" rel="noopener noreferrer"&gt;create&lt;/a&gt;, &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#dev" rel="noopener noreferrer"&gt;test&lt;/a&gt;, and &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#deploy" rel="noopener noreferrer"&gt;deploy&lt;/a&gt; your Workers projects. C3 will install Wrangler in projects by default.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.js&lt;/code&gt; (in &lt;code&gt;/src&lt;/code&gt;): A minimal &lt;code&gt;'Hello World!'&lt;/code&gt; Worker written in &lt;a href="https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/" rel="noopener noreferrer"&gt;ES module&lt;/a&gt; syntax.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package.json&lt;/code&gt;: A minimal Node dependencies configuration file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package-lock.json&lt;/code&gt;: Refer to &lt;a href="https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json" rel="noopener noreferrer"&gt;&lt;code&gt;npm&lt;/code&gt; documentation on &lt;code&gt;package-lock.json&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node_modules&lt;/code&gt;: Refer to &lt;a href="https://docs.npmjs.com/cli/v7/configuring-npm/folders#node-modules" rel="noopener noreferrer"&gt;&lt;code&gt;npm&lt;/code&gt; documentation &lt;code&gt;node_modules&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a Cloudflare D1 Database
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.cloudflare.com/developer-platform/d1/" rel="noopener noreferrer"&gt;Cloudflare D1 Database&lt;/a&gt; is a serverless SQL database built on SQLite. It offers a native serverless architecture, a SQL-based dialect, and built-in JSON parsing and querying functions. With D1, you can easily deploy and maintain a database of any size, using a familiar query language and benefiting from features like point-in-time recovery and cost-effective pricing.&lt;/p&gt;

&lt;p&gt;Start by creating a D1 database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler d1 create testing-cloudflare-workers

&lt;span class="o"&gt;[&lt;/span&gt;Output]

✅ Successfully created DB &lt;span class="s1"&gt;'testing-cloudflare-workers'&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;region EEUR
Created your database using D1&lt;span class="s1"&gt;'s new storage backend. The new storage backend is not yet recommended for production workloads, but backs up your data via point-in-time
restore.

[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "testing-cloudflare-workers"
database_id = "&amp;lt;your_database_id&amp;gt;"

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the &lt;code&gt;d1_databases&lt;/code&gt; block to your &lt;code&gt;wrangler.toml&lt;/code&gt; file.&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="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;D1&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;d1_databases&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"DB"&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;
&lt;span class="n"&gt;database_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"testing-cloudflare-workers"&lt;/span&gt;
&lt;span class="n"&gt;database_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"&amp;lt;your_database_id&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a schema that defines a Pokemon table. Call it &lt;code&gt;schema.sql&lt;/code&gt; and put it in the root directory.&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;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;Pokemon&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;createdAt&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure D1 locally and in your Cloudflare account.&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;# Local&lt;/span&gt;
npx wrangler d1 execute testing-cloudflare-workers &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./schema.sql

&lt;span class="c"&gt;# Deployed&lt;/span&gt;
npx wrangler d1 execute testing-cloudflare-workers &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./schema.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally use the &lt;code&gt;DB&lt;/code&gt; binding to query the database.&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;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// If you set another name in wrangler.toml as the value for 'binding',&lt;/span&gt;
  &lt;span class="c1"&gt;// replace "DB" with the variable name you defined.&lt;/span&gt;
  &lt;span class="nl"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;D1Database&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’re ready to start building!&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Wrangler CLI to Develop Workers
&lt;/h2&gt;

&lt;p&gt;The Workers command-line interface, &lt;a href="https://developers.cloudflare.com/workers/wrangler/install-and-update/" rel="noopener noreferrer"&gt;Wrangler&lt;/a&gt;, allows you to &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#init" rel="noopener noreferrer"&gt;create&lt;/a&gt;, &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#dev" rel="noopener noreferrer"&gt;test&lt;/a&gt;, and &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#deploy" rel="noopener noreferrer"&gt;deploy&lt;/a&gt; your Workers projects. C3 will install Wrangler in projects by default.&lt;/p&gt;

&lt;p&gt;After you have created your first Worker, run the &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#dev" rel="noopener noreferrer"&gt;&lt;code&gt;wrangler dev&lt;/code&gt;&lt;/a&gt; command in the project directory to start a local server for developing your Worker. This will allow you to test your Worker locally during development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler dev

&lt;span class="o"&gt;[&lt;/span&gt;output]
 ⛅️ wrangler 3.22.1
&lt;span class="nt"&gt;-------------------&lt;/span&gt;
✔ Would you like to &lt;span class="nb"&gt;help &lt;/span&gt;improve Wrangler by sending usage metrics to Cloudflare? … no
Your choice has been saved &lt;span class="k"&gt;in &lt;/span&gt;the following file: ../../../../Library/Preferences/.wrangler/metrics.json.

  You can override the user level setting &lt;span class="k"&gt;for &lt;/span&gt;a project &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;wrangler.toml&lt;span class="sb"&gt;`&lt;/span&gt;:

   - to disable sending metrics &lt;span class="k"&gt;for &lt;/span&gt;a project: &lt;span class="sb"&gt;`&lt;/span&gt;send_metrics &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
   - to &lt;span class="nb"&gt;enable &lt;/span&gt;sending metrics &lt;span class="k"&gt;for &lt;/span&gt;a project: &lt;span class="sb"&gt;`&lt;/span&gt;send_metrics &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
⎔ Starting &lt;span class="nb"&gt;local &lt;/span&gt;server...
&lt;span class="o"&gt;[&lt;/span&gt;wrangler:inf] Ready on http://localhost:8787
&lt;span class="o"&gt;[&lt;/span&gt;wrangler:inf] GET / 200 OK &lt;span class="o"&gt;(&lt;/span&gt;5ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;wrangler:inf] GET /favicon.ico 200 OK &lt;span class="o"&gt;(&lt;/span&gt;1ms&lt;span class="o"&gt;)&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have not used Wrangler before, it will try to open your web browser to authenticate with your Cloudflare account. If you have issues with this step or you do not have access to a browser interface, refer to the &lt;a href="https://developers.cloudflare.com/workers/wrangler/commands/#login" rel="noopener noreferrer"&gt;&lt;code&gt;wrangler login&lt;/code&gt;&lt;/a&gt; documentation for more information.&lt;/p&gt;

&lt;p&gt;You will now be able to go to &lt;a href="http://localhost:8787/" rel="noopener noreferrer"&gt;&lt;code&gt;http://localhost:8787&lt;/code&gt;&lt;/a&gt; to see your Worker running. Any changes you make to your code will trigger a rebuild, and reloading the page will show you the up-to-date output of your Worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloudflare Worker Boilerplate Code
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;src/index.ts&lt;/code&gt; file contains the Worker code. This file will be triggered when hitting the &lt;code&gt;/&lt;/code&gt; endpoint of your Worker.&lt;/p&gt;

&lt;p&gt;Think of it as a tiny Node.js server. You can do all kinds of cool things here. Create routers, listen for different HTTP methods like &lt;code&gt;POST&lt;/code&gt; etc, fetch external APIs, store data in databases, and even trigger other Workers!&lt;/p&gt;

&lt;p&gt;Open up the &lt;code&gt;src/index.ts&lt;/code&gt;. You’ll see boilerplate code.&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="cm"&gt;/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run `npm run dev` in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run `npm run deploy` to publish your worker
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/&lt;/span&gt;
    &lt;span class="c1"&gt;// MY_KV_NAMESPACE: KVNamespace;&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/&lt;/span&gt;
    &lt;span class="c1"&gt;// MY_DURABLE_OBJECT: DurableObjectNamespace;&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/&lt;/span&gt;
    &lt;span class="c1"&gt;// MY_BUCKET: R2Bucket;&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/&lt;/span&gt;
    &lt;span class="c1"&gt;// MY_SERVICE: Fetcher;&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/&lt;/span&gt;
    &lt;span class="c1"&gt;// MY_QUEUE: Queue;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s edit it to instead create a Pokemon directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Cloudflare Worker Code
&lt;/h2&gt;

&lt;p&gt;You’ll create an import flow. Send the ID of a Pokemon to the Cloudflare Worker, it handles getting the Pokemon info from an external API and stores it in the D1 database.&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%2F1rpu0jip7swjsr5hmt48.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%2F1rpu0jip7swjsr5hmt48.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707484941/Blogposts/testing-cloudflare-workers/cf-work-tt-2_zcwwjd.png" width="800" height="689"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Cloudflare Worker you’ll create will be accessible at the URL &lt;code&gt;http://localhost:8787/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To return JSON data, you’ll use the &lt;code&gt;return Response.json(...)&lt;/code&gt; method. Async/Await flows are enabled by default as well!&lt;/p&gt;

&lt;p&gt;Here’s an example of a POST request with a GET request to an external API from within the Cloudflare Worker and then inserting the data into D1. It’s a common point-of-failure that is hard to troubleshoot and test.&lt;/p&gt;

&lt;p&gt;You’ll configure the Worker to listen for a POST request on path &lt;code&gt;/api/pokemon&lt;/code&gt;. And, add an optional query parameter called &lt;code&gt;id&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;D1Database&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO Pokemon (name) VALUES (?) RETURNING *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&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="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM Pokemon WHERE id = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatPokeApiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;response&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="kd"&gt;const&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;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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="nx"&gt;id&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="c1"&gt;// Import a Pokemon&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&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;requestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://pokeapi.co/api/v2/pokemon/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;queryId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestUrl&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;resPokemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;formatPokeApiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;addedPokemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resPokemon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&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="nx"&gt;addedPokemon&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello Worker!&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="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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&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="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go ahead and run the Worker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;http://localhost:8787/api/pokemon&lt;/code&gt; to see the response from your Cloudflare Worker.&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="err"&gt;curl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-X&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8787/api/pokemon?id=1"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"success"&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="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"served_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"v3-prod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.4586&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"changes"&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="nl"&gt;"last_row_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"changed_db"&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="nl"&gt;"size_after"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"rows_read"&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="nl"&gt;"rows_written"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"bulbasaur"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-02-06 17:54:52"&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;To validate that the Pokemon has been added, you can also run this command to query the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler d1 execute testing-cloudflare-workers &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"SELECT * FROM Pokemon"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redeploy the Worker via Wrangler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Preview your Worker at &lt;code&gt;&amp;lt;YOUR_WORKER&amp;gt;.&amp;lt;YOUR_SUBDOMAIN&amp;gt;.workers.dev&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Troubleshooting with OpenTelemetry and Distributed Tracing
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry libraries for Node.js are stable and include auto-instrumentation.&lt;/p&gt;

&lt;p&gt;Luckily for you and me, &lt;a href="https://github.com/evanderkoogh" rel="noopener noreferrer"&gt;Erwin van der Koogh&lt;/a&gt; ****wrote an awesome package for wrapping the OpenTelemetry libraries in Cloudflare Workers. It’s called &lt;a href="https://github.com/evanderkoogh/otel-cf-workers" rel="noopener noreferrer"&gt;&lt;code&gt;otel-cf-workers&lt;/code&gt;&lt;/a&gt; and it is beautiful! 🤩 — Let’s be awesome to each other and give him as many ⭐ as possible on GitHub!&lt;/p&gt;

&lt;p&gt;To be able to use the OpenTelemetry library at all you have to add the Node.js compatibility flag in your &lt;code&gt;wrangler.toml&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;compatibility_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"nodejs_compat"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, install the node modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="se"&gt;\&lt;/span&gt;
  @opentelemetry/api &lt;span class="se"&gt;\&lt;/span&gt;
  @microlabs/otel-cf-workers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need the &lt;code&gt;@opentelemetry/api&lt;/code&gt; module to create custom spans, while the &lt;code&gt;@microlabs/otel-cf-workers&lt;/code&gt; module is the OpenTelemetry wrapper.&lt;/p&gt;

&lt;p&gt;Next up, add OpenTelemetry to your code.&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SpanStatusCode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ResolveConfigFn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@microlabs/otel-cf-workers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tracer&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;pokemon-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;D1Database&lt;/span&gt;
    &lt;span class="nx"&gt;TRACETEST_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO Pokemon (name) VALUES (?) RETURNING *&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&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="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM Pokemon WHERE id = ?;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatPokeApiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&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;response&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="kd"&gt;const&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;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;

    &lt;span class="c1"&gt;// Add manual instrumentation&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;getActiveSpan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&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="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;setStatus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpanStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pokemon fetched successfully!&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="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pokemon.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="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pokemon.id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&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="nx"&gt;id&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&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="c1"&gt;// Import a Pokemon&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/pokemon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;searchParams&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&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;requestUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://pokeapi.co/api/v2/pokemon/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;queryId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestUrl&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;resPokemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;formatPokeApiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Add manual instrumentation&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startActiveSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;D1: Add Pokemon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="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;addedPokemon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addPokemon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resPokemon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&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;setStatus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SpanStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pokemon added successfully!&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="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pokemon.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;addedPokemon&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&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="nx"&gt;addedPokemon&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello Worker!&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="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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&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="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;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResolveConfigFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_trigger&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;exporter&lt;/span&gt;&lt;span class="p"&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRACETEST_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&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="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&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;pokemon-api&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me explain what’s happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’re importing the &lt;code&gt;@opentelemetry/api&lt;/code&gt; and &lt;code&gt;@microlabs/otel-cf-workers&lt;/code&gt; modules to enable OpenTelemetry tracing.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@microlabs/otel-cf-workers&lt;/code&gt; module contains the &lt;code&gt;instrument&lt;/code&gt; and &lt;code&gt;ResolveConfigFn&lt;/code&gt; functions. You’ll use them to wrap the Cloudflare Worker and automatically generate traces.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;const tracer = trace.getTracer('pokemon-api')&lt;/code&gt; will instantiate a tracer object for you to create new spans in your code. You’ll do this to create a trace for the D1 insert operation. As you can see in the &lt;code&gt;handler&lt;/code&gt; I’ve now wrapped the &lt;code&gt;addPokemon&lt;/code&gt; function with a &lt;code&gt;tracer.startActiveSpan('D1: Add Pokemon'...)&lt;/code&gt; trace span.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;formatPokeApiResponse&lt;/code&gt; function now contains manual instrumentation to add span attributes for the external API request. This is to validate the external Pokemon API works as expected.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;url: env.TRACETEST_URL&lt;/code&gt; in the &lt;code&gt;ResolveConfigFn&lt;/code&gt; function sets the Tracetest Agent URL where you send traces to. I’ll walk you through creating tests further down where you’ll also add environment variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By configuring your Cloudflare Workers with the following settings, you can enable production observability and emit distributed traces. But what if you could also incorporate testing?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic of Trace-based Testing for Serverless Architectures
&lt;/h2&gt;

&lt;p&gt;Testing serverless architectures has long been a challenge due to limited visibility. However, observability through distributed traces is now providing a solution to this problem.&lt;/p&gt;

&lt;p&gt;Trace-based testing takes uses existing OpenTelemetry traces as test specifications. This allows you to validate the behavior and performance of your distributed services and serverless functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tracetest.io/features" rel="noopener noreferrer"&gt;Tracetest&lt;/a&gt; is a trace-based testing tool for building integration tests in minutes using &lt;a href="https://opentelemetry.io/docs/getting-started/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; traces. You can build test specs against trace data at every point of a request transaction.&lt;/p&gt;

&lt;p&gt;To get started with Tracetest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You’ll need to &lt;a href="https://docs.tracetest.io/getting-started/installation#install-the-tracetest-cli" rel="noopener noreferrer"&gt;download the CLI&lt;/a&gt; for your operating system.&lt;/li&gt;
&lt;li&gt;And, &lt;a href="https://app.tracetest.io/" rel="noopener noreferrer"&gt;sign up for an account&lt;/a&gt;. Go ahead and do that now.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The CLI is bundled with Tracetest Agent that triggers your application and collects responses and traces for new tests. &lt;a href="https://docs.tracetest.io/concepts/agent" rel="noopener noreferrer"&gt;Learn more in the docs here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me walk you through creating tests across your staging and production deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Cloudflare Workers in Staging and Production
&lt;/h2&gt;

&lt;p&gt;Since I want to keep environments separate I’ll use the &lt;a href="https://developers.cloudflare.com/workers/wrangler/environments/" rel="noopener noreferrer"&gt;environments feature in Wrangler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create a new environment in Tracetest. Select to run the Tracetest Agent in the cloud.&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%2Fwo8e66dkw6nl40pbbhhs.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%2Fwo8e66dkw6nl40pbbhhs.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707398359/Blogposts/testing-cloudflare-workers/screely-1707398345750_qky7xa.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenTelemetry will be selected as the default tracing backend. You’ll find the OTLP endpoint to send traces to.&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%2Fc31x985tjuukx1rpkztf.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%2Fc31x985tjuukx1rpkztf.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707398551/Blogposts/testing-cloudflare-workers/screely-1707398542397_rquan3.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the HTTP URL and paste it in the &lt;code&gt;wrangler.toml&lt;/code&gt; using a new section called &lt;code&gt;[env.prod]&lt;/code&gt; . Make sure to append &lt;code&gt;v1/traces&lt;/code&gt;  to the end of the Tracetest URL. Make sure to use the &lt;code&gt;database_id&lt;/code&gt; that you generated at the beginning of the tutorial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# Production&lt;/span&gt;
&lt;span class="nn"&gt;[env.prod]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pokemon-api"&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/index.ts"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2023-12-18"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"nodejs_compat"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;workers_dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;d1_databases&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;database_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"testing-cloudflare-workers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;database_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;YOUR_DATABASE_ID&amp;gt;"&lt;/span&gt; &lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;[env.prod.vars]&lt;/span&gt;
&lt;span class="py"&gt;TRACETEST_URL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://&amp;lt;YOUR_TRACETEST_AGENT_URL&amp;gt;.tracetest.io:443/v1/traces"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;TRACETEST_URL&lt;/code&gt; here is where the Tracetest Agent is running. Currently, in the cloud in your Tracetest account. To reference it in your Cloudflare Worker you define it in the &lt;code&gt;interface Env&lt;/code&gt; and again set it in the &lt;code&gt;exporter&lt;/code&gt;  section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// [...]&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;TRACETEST_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="c1"&gt;// [...]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// [...]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResolveConfigFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_trigger&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;exporter&lt;/span&gt;&lt;span class="p"&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="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TRACETEST_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&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="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&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;Deploy the Cloudflare Worker to the &lt;code&gt;prod&lt;/code&gt; environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler deploy &lt;span class="nt"&gt;--env&lt;/span&gt; prod

&lt;span class="o"&gt;[&lt;/span&gt;Output]
 ⛅️ wrangler 3.27.0
&lt;span class="nt"&gt;-------------------&lt;/span&gt;
Your worker has access to the following bindings:
- D1 Databases:
  - DB: testing-cloudflare-workers &lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;YOUR_DATABASE_ID&amp;gt;&lt;span class="o"&gt;)&lt;/span&gt;
- Vars:
  - TRACETEST_URL: &lt;span class="s2"&gt;"https://agent-(redacted)..."&lt;/span&gt;
Total Upload: 169.02 KiB / &lt;span class="nb"&gt;gzip&lt;/span&gt;: 38.56 KiB
Uploaded pokemon-api &lt;span class="o"&gt;(&lt;/span&gt;3.79 sec&lt;span class="o"&gt;)&lt;/span&gt;
Published pokemon-api &lt;span class="o"&gt;(&lt;/span&gt;1.72 sec&lt;span class="o"&gt;)&lt;/span&gt;
  https://pokemon-api.&amp;lt;YOUR_ACCOUNT&amp;gt;.workers.dev
Current Deployment ID: 6e5333b9-29de-4a83-84c5-dc582218bdba
&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.amazonaws.com%2Fuploads%2Farticles%2F1klpie0edifbvyrnskdt.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%2F1klpie0edifbvyrnskdt.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707902022/Blogposts/testing-cloudflare-workers/screely-1707901972696_enht9d.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you see the &lt;code&gt;TRACETEST_URL&lt;/code&gt;  environment variable in your Cloudflare account.&lt;/p&gt;

&lt;p&gt;Move back to Tracetest and use the Cloudflare Worker production URL to trigger a test.&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%2Fq89q197en319zbxkk1ve.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%2Fq89q197en319zbxkk1ve.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707399254/Blogposts/testing-cloudflare-workers/screely-1707399248575_nqluym.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switch to the &lt;code&gt;Trace&lt;/code&gt; tab to see the full preview of the distributed trace.&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%2Fnkppzrl8w8jcmthqqayi.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%2Fnkppzrl8w8jcmthqqayi.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707328469/Blogposts/testing-cloudflare-workers/screely-1707328464017_hlvbgn.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here you can add test specs to validate that the external HTTP request does not fail and that the D1 database import worked as expected. You can also check for cold starts!&lt;/p&gt;

&lt;p&gt;Click the &lt;code&gt;Test&lt;/code&gt; tab and add some test specs. First create a test spec to validate the function invocation was not a cold start.&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%2F078eup7t0wiwry9qmemo.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%2F078eup7t0wiwry9qmemo.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707328545/Blogposts/testing-cloudflare-workers/screely-1707328536955_tpjvd9.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then make sure the external API request always returns status 200.&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%2F509okgrbene6oveqrgdj.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%2F509okgrbene6oveqrgdj.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707328622/Blogposts/testing-cloudflare-workers/screely-1707328615649_zfbhwd.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, validate that the Pokemon that was added to the D1 database matched what the external API fetched.&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%2Ft2mgut3qey9pin9xj20m.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%2Ft2mgut3qey9pin9xj20m.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707328716/Blogposts/testing-cloudflare-workers/screely-1707328710073_z3yva0.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the test specs.&lt;/p&gt;

&lt;p&gt;You now see all test specs passing since the external HTTP request is valid, the invocation was not a cold start, and the Pokemon name matches what you were expecting!&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%2F9z3efdu0jf4c9alxfc4t.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%2F9z3efdu0jf4c9alxfc4t.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707328781/Blogposts/testing-cloudflare-workers/screely-1707328776280_uo8b3t.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All this enabled by OpenTelemetry tracing and Tracetest! What’s also awesome is that these tests are stored in your Tracetest account and you can revisit them and run the same tests again every time you run your development environment!&lt;/p&gt;

&lt;p&gt;This is awesome for testing deployments while developing Cloudflare Workers, but also in &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-cloudflare-workers-with-opentelemetry-tracetest#testing-the-cloudflare-worker-locally" rel="noopener noreferrer"&gt;pre-merge testing&lt;/a&gt; and &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-cloudflare-workers-with-opentelemetry-tracetest#integration-testing-the-cloudflare-worker" rel="noopener noreferrer"&gt;integration testing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let me explain how to enable automation next. Check out the &lt;code&gt;Automate&lt;/code&gt; tab.&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%2Fk6gbnz2m7lnoshbdf5w9.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%2Fk6gbnz2m7lnoshbdf5w9.png" alt="https://res.cloudinary.com/djwdcmwdz/image/upload/v1707399688/Blogposts/testing-cloudflare-workers/screely-1707399677427_uwtr38.png" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every test you create can be expressed with YAML. I know you love YAML, quit complaining! 😄&lt;/p&gt;

&lt;p&gt;With this test definition you can trigger the same test via the CLI either locally or in any CI pipeline of you choice.&lt;/p&gt;

&lt;p&gt;To try it locally, create a directory called &lt;code&gt;test&lt;/code&gt; in the root directory.&lt;/p&gt;

&lt;p&gt;Paste this into a file called &lt;code&gt;test-api.prod.yaml&lt;/code&gt;.&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&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;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WMGTfM2Sg&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;Test API Prod&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;httpRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://pokemon-api.&amp;lt;YOUR_URL&amp;gt;.workers.dev/api/pokemon?id=13&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
  &lt;span class="na"&gt;specs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;span[tracetest.span.type="faas" name="POST" faas.trigger="http"]&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;Validate cold start&lt;/span&gt;
    &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attr:faas.coldstart = "false"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;span[tracetest.span.type=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;GET:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pokeapi.co&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&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;Validate external API.&lt;/span&gt;
    &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attr:http.response.status_code = &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;span[tracetest.span.type=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;general&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;D1:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Add&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Pokemon&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&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;Validate Pokemon name.&lt;/span&gt;
    &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attr:pokemon.name = "weedle"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since you already have the Tracetest CLI installed, running it is as simple as one command. You can copy the command for your environment in the Automate tab in Tracetest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tracetest configure &lt;span class="nt"&gt;--organization&lt;/span&gt; &amp;lt;YOUR_ORG&amp;gt; &lt;span class="nt"&gt;--environment&lt;/span&gt; &amp;lt;YOUR_ENV&amp;gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
tracetest run &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; ./test/test-api.prod.yaml &lt;span class="nt"&gt;--required-gates&lt;/span&gt; test-specs &lt;span class="nt"&gt;--output&lt;/span&gt; pretty

&lt;span class="o"&gt;[&lt;/span&gt;Output]
SUCCESS  Successfully configured Tracetest CLI
✘ Test API Prod &lt;span class="o"&gt;(&lt;/span&gt;https://app.tracetest.io/organizations/&amp;lt;YOUR_ORG&amp;gt;/environments/&amp;lt;YOUR_ENV&amp;gt;/test/WMGTfM2Sg/run/1/test&lt;span class="o"&gt;)&lt;/span&gt; - trace &lt;span class="nb"&gt;id&lt;/span&gt;: 59775e06cd96ee0a3973fa924fcf587a
    ✘ Validate cold start
        ✘ &lt;span class="c"&gt;#2cff773d8ea49f9c&lt;/span&gt;
            ✘ attr:faas.coldstart &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"false"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;https://app.tracetest.io/organizations/&amp;lt;YOUR_ORG&amp;gt;/environments/&amp;lt;YOUR_ENV&amp;gt;/test/WMGTfM2Sg/run/1/test?selectedAssertion&lt;span class="o"&gt;=&lt;/span&gt;0&amp;amp;selectedSpan&lt;span class="o"&gt;=&lt;/span&gt;2cff773d8ea49f9c&lt;span class="o"&gt;)&lt;/span&gt;
    ✔ Validate external API.
        ✔ &lt;span class="c"&gt;#d01b92c183b45433&lt;/span&gt;
            ✔ attr:http.response.status_code &lt;span class="o"&gt;=&lt;/span&gt; 200 &lt;span class="o"&gt;(&lt;/span&gt;200&lt;span class="o"&gt;)&lt;/span&gt;
    ✔ Validate Pokemon name.
        ✔ &lt;span class="c"&gt;#12443dd73de11a68&lt;/span&gt;
            ✔ attr:pokemon.name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"weedle"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;weedle&lt;span class="o"&gt;)&lt;/span&gt;

    ✘ Required gates
        ✘ test-specs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s cool is you can follow the link and open the particular test in Tracetest and view it once it’s saved in the cloud. &lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-cloudflare-workers-with-opentelemetry-tracetest#integration-testing-the-cloudflare-worker" rel="noopener noreferrer"&gt;Here’s a guide on using this pattern for integration testing&lt;/a&gt; Cloudflare Workers in the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Deployment Testing
&lt;/h2&gt;

&lt;p&gt;In conclusion, today you learned how to craft production-ready Cloudflare Workers. You now know how to develop, troubleshoot, and test Cloudflare Workers in staging and production. You started from a boilerplate, built an import flow, integrated OpenTelemetry for distributed tracing, and used trace-based testing for integration and deployment testing.&lt;/p&gt;

&lt;p&gt;Want more? Jump over to the docs to learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-cloudflare-workers-with-opentelemetry-tracetest#testing-the-cloudflare-worker-locally" rel="noopener noreferrer"&gt;Testing Cloudflare Workers in your local development environment.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.tracetest.io/examples-tutorials/recipes/testing-cloudflare-workers-with-opentelemetry-tracetest#integration-testing-the-cloudflare-worker" rel="noopener noreferrer"&gt;Integration testing Cloudflare Workers for CI pipelines.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you get stuck along the tutorial, feel free to check out the &lt;a href="https://github.com/kubeshop/tracetest/examples/testing-cloudflare-workers" rel="noopener noreferrer"&gt;example app in the GitHub repo, here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Stay tuned for the next part of this series coming soon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 2&lt;/strong&gt;: Learn how to configure production troubleshooting and testing in Cloudflare Workers by using observability tools like Grafana and Jaeger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would you like to learn more about Tracetest and what it brings to the table? Visit the Tracetest &lt;a href="https://docs.tracetest.io/getting-started/installation" rel="noopener noreferrer"&gt;docs&lt;/a&gt; and try it out by &lt;a href="https://tracetest.io/download" rel="noopener noreferrer"&gt;downloading&lt;/a&gt; it today!&lt;/p&gt;

&lt;p&gt;Also, please feel free to join our &lt;a href="https://dub.sh/tracetest-community" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;, give &lt;a href="https://github.com/kubeshop/tracetest" rel="noopener noreferrer"&gt;Tracetest a star on GitHub&lt;/a&gt;, or schedule a &lt;a href="https://calendly.com/ken-kubeshop/45min" rel="noopener noreferrer"&gt;time to chat 1:1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>serverless</category>
      <category>tutorial</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
