<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ivan Kurchenko</title>
    <description>The latest articles on DEV Community by Ivan Kurchenko (@ivankurchenko).</description>
    <link>https://dev.to/ivankurchenko</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%2F56799%2F094c7530-5564-4910-8e84-ef2352badf2c.png</url>
      <title>DEV Community: Ivan Kurchenko</title>
      <link>https://dev.to/ivankurchenko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ivankurchenko"/>
    <language>en</language>
    <item>
      <title>Building decoupled monitoring with OpenTelemetry</title>
      <dc:creator>Ivan Kurchenko</dc:creator>
      <pubDate>Mon, 26 Feb 2024 05:42:00 +0000</pubDate>
      <link>https://dev.to/ivankurchenko/building-decoupled-monitoring-with-opentelemetry-5f41</link>
      <guid>https://dev.to/ivankurchenko/building-decoupled-monitoring-with-opentelemetry-5f41</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;, as the documentation says, is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;OpenTelemetry, also known as OTel, is a vendor-neutral open source Observability framework for instrumenting, generating, collecting, and exporting telemetry data such as traces, metrics, and logs.&lt;br&gt;
In further text, we will refer to OpenTelemetry as OTEL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It covers a wide range of observability aspects: application instrumentation, telemetry data processing, and publishing to other telemetry backends.&lt;br&gt;
Although OpenTelemetry excels in these areas, it's designed to integrate with, rather than replace, telemetry storage and visualization tools.&lt;br&gt;&lt;br&gt;
There are plenty of other systems that provide those capabilities, such as commercial APMs like Datadog or open-source tools like Zipkin. &lt;/p&gt;

&lt;p&gt;To cover all these processes, OTEL provides a number of components to build a complete observability system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/languages/" rel="noopener noreferrer"&gt;Language APIs &amp;amp; SDKs&lt;/a&gt; — a set of consistent SDKs for many popular languages and respective frameworks. 
Along with instrumentation that allows to automatic collect telemetry data for most common cases like HTTP request handling or database querying.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;Collector&lt;/a&gt; - OpenTelemetry Collector is a piece of infrastructure software that essentially provides capabilities to build an &lt;a href="https://en.wikipedia.org/wiki/Extract,_transform,_load" rel="noopener noreferrer"&gt;ETL&lt;/a&gt; process for all kinds of telemetry data. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although software components are crucial, it is also important to mention the basis on which it is built on top of.&lt;br&gt;
In particular, I'd like to mention two specifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/specs/otlp/" rel="noopener noreferrer"&gt;Open Telemetry Protocol&lt;/a&gt; - this is a gRPC, push-based protocol. Own protocol is one of the OTELs pillars that allow it to be vendor-neutral. This protocol covers all transportation of all sorts of telemetry data that &lt;a href="https://opentelemetry.io/docs/concepts/signals/" rel="noopener noreferrer"&gt;OTEL handles&lt;/a&gt;: baggage, metrics, traces, and logs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/specs/semconv/" rel="noopener noreferrer"&gt;Semantic conversions&lt;/a&gt; - set of standard notions for telemetry data. For instance, any HTTP framework instrumentation needs to produce the same metrics with the same attributes as described in &lt;a href="https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestduration" rel="noopener noreferrer"&gt;HTTP Sever Metrics&lt;/a&gt; part of the specification.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will not do a deep inside these specs, but it is worth briefly mentioning them for better understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  System under monitoring
&lt;/h2&gt;

&lt;p&gt;To showcase the capabilities of OpenTelemetry, let's first build a simple service to monitor.&lt;br&gt;
This is going to be a simple HTTP REST service for online shop product management, that provides CRUD API for simple products with name, description, and price.&lt;br&gt;
To make it a bit more interesting, let's also add full-text search capabilities, so we can have more complex telemetry data.&lt;br&gt;
Product service architecture can be described with the following diagram:&lt;br&gt;
&lt;a href="https://media.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%2Fnxog3w8rg4wqmipdbyhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fnxog3w8rg4wqmipdbyhk.png" alt="System under monitoring"&gt;&lt;/a&gt;&lt;br&gt;
And its API examples:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;

# Create product. Response body example for 200 OK:
# {
#  "id": 3,
#  "name": "Test product",
#  "description": "Test Description",
#  "price": 100
#}
POST localhost:8080/api/product
Content-Type: application/json

{
  "name": "Test product",
  "description": "Test Description",
  "price": 100.00
}

###
# Get product. Response body example for 200 OK:
# {
#  "id": 1,
#  "name": "Test product",
#  "description": "Test Description",
#  "price": 100
# }
GET localhost:8080/api/product/1

###
# Search product. # Response body example for 200 OK:
# [
#    {
#        "id": 1,
#        "name": "Test product",
#        "description": "Test Description",
#        "price": 100
#    }
# ]
GET localhost:8080/api/product?query=Test

###
# Delete product. Response body for 200 OK is empty.
DELETE localhost:8080/api/product/1


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

&lt;/div&gt;

&lt;p&gt;To produce some telemetry data out of application, we need to simulate traffic.&lt;br&gt;
For this, we will have &lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/main/traffic.sh" rel="noopener noreferrer"&gt;a simple script&lt;/a&gt;&lt;br&gt;
to create, read, search and delete 100 products sequentially.&lt;/p&gt;

&lt;p&gt;Although This service is implemented in Java and Spring Boot, please, bear in mind that approaches shown further can be applied to any language, framework or other software that leverages OpenTelemetry to produce telemetry signals.   &lt;/p&gt;

&lt;h2&gt;
  
  
  Direct publishing
&lt;/h2&gt;

&lt;p&gt;We have an application that we want to monitor. In the simplest case, we can plug the instrumentation and supply configuration to send telemetry data directly to backends.&lt;br&gt;
The diagram shows how OpenTelemetry instrumentation directly sends metrics to Prometheus and traces to Jaeger:&lt;br&gt;
&lt;a href="https://media.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%2Ff7toincoe7nm5y5rlp1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ff7toincoe7nm5y5rlp1o.png" alt="Direct publishing"&gt;&lt;/a&gt;&lt;br&gt;
As it is shown in the diagram, OpenTelemetry instrumentation can directly send metrics to &lt;a href="https://prometheus.io" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; and traces to &lt;a href="https://www.jaegertracing.io" rel="noopener noreferrer"&gt;Jaeger&lt;/a&gt;.&lt;br&gt;
In the case of Java or any other JVM language, we can pick the default &lt;a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation" rel="noopener noreferrer"&gt;OpenTelemetry Java agent&lt;/a&gt; and use in the service Docker like so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="c"&gt;# Download the OpenTelemetry Java agent for instrumentation. &lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; OTEL_VERSION=v2.0.0&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/&lt;span class="nv"&gt;$OTEL_VERSION&lt;/span&gt;/opentelemetry-javaagent.jar

&lt;span class="c"&gt;#...&lt;/span&gt;

&lt;span class="c"&gt;# Add java agnet to enable telemetry instrumentation.&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java","-javaagent:opentelemetry-javaagent.jar","-jar","/usr/app/products-service.jar"]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Some details are omitted. Full Dockerfile can be found at &lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/main/java/Dockerfile" rel="noopener noreferrer"&gt;the GitHub repository&lt;/a&gt;.&lt;br&gt;
Once the instrumentation is plugged, it can be configured with the following environment variables to expose &lt;code&gt;9094&lt;/code&gt; port to scrape metrics by Prometheus and send traces to Jaeger:  &lt;/p&gt;

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

&lt;span class="nv"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;products_service
&lt;span class="nv"&gt;OTEL_METRICS_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;prometheus
&lt;span class="nv"&gt;OTEL_EXPORTER_PROMETHEUS_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9094
&lt;span class="nv"&gt;OTEL_EXPORTER_PROMETHEUS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0
&lt;span class="nv"&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;otlp
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://jaeger:4318


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

&lt;/div&gt;

&lt;p&gt;You can find in the GitHub repository &lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/master/docker-compose/setup-direct-publishing.yaml" rel="noopener noreferrer"&gt;Docker compose file&lt;/a&gt; to run this setup.&lt;br&gt;
Let run the whole setup and run the script to simulate traffic. Firstly, to check metrics in Prometheus open &lt;a href="http://localhost:9090/graph" rel="noopener noreferrer"&gt;http://localhost:9090/graph&lt;/a&gt; in your browser.&lt;br&gt;
We can use the following PromQL query to calculate average response latency: &lt;code&gt;sum(http_server_request_duration_seconds_sum) / sum(http_server_request_duration_seconds_count)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can observe something like this in the following screenshot:&lt;br&gt;
&lt;a href="https://media.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%2F8bxtkvip44zeo37ho32g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F8bxtkvip44zeo37ho32g.png" alt="Prometheus"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To check the trace data, open Jaeger at &lt;a href="http://localhost:16686" rel="noopener noreferrer"&gt;http://localhost:16686&lt;/a&gt;, and search for traces for Service &lt;code&gt;products_service&lt;/code&gt; and Operation &lt;code&gt;POST /api/product&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media.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%2Fs08gllenhtcj4qzsdqwc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fs08gllenhtcj4qzsdqwc.png" alt="Jaeger"&gt;&lt;/a&gt;&lt;br&gt;
We can open the traces to check details and inner spans:&lt;br&gt;
&lt;a href="https://media.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%2F60igoama6u0t4o7r2xgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F60igoama6u0t4o7r2xgn.png" alt="Jaeger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing to a collector
&lt;/h2&gt;

&lt;p&gt;Now we have a telemetry infrastructure for the Products Service up and running. However, the world is evolving over time and &lt;br&gt;
there might be a need to migrate to other monitoring systems or add new ones. In case of a single application, it might be not a big deal, but in case of a big system with many services, this might be a serious challenge.&lt;br&gt;
This is one of the cases when OpenTelemetry Collector comes to the rescue. It can be used to decouple the application from the monitoring infrastructure and provide a single point of configuration for all telemetry data.&lt;br&gt;
OpenTelemetry collector works with OTLP protocol, mentioned in the introduction, and can be configured to export telemetry data to various backends.&lt;br&gt;
A collector usually consists of the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/configuration/#receivers" rel="noopener noreferrer"&gt;Receivers&lt;/a&gt; - describe how to receive telemetry data (e.g. extract);&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/configuration/#processors" rel="noopener noreferrer"&gt;Processors&lt;/a&gt; - describe how to process telemetry data (e.g. transform);&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/configuration/#exporters" rel="noopener noreferrer"&gt;Exporters&lt;/a&gt; - describe how to export telemetry data (e.g. load).;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/configuration/#exporters" rel="noopener noreferrer"&gt;Connectors&lt;/a&gt; - describe how to connect telemetry data pipelines. Not covered here;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/configuration/#extensions" rel="noopener noreferrer"&gt;Extensions&lt;/a&gt; - describe how to extend the collector with additional capabilities. Not covered here;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So to decouple the application from the monitoring infrastructure, this collector needs to do is to receive telemetry data from the application and send metrics to the Prometheus and traces to Jaeger as before.&lt;br&gt;
In terms of components, it means that the collector needs to have OTLP receiver and exporters for Prometheus and Jaeger.&lt;/p&gt;

&lt;p&gt;Such a setup might look like the following diagram:&lt;br&gt;
&lt;a href="https://media.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%2Fphelakzrr53wxrisnhsz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fphelakzrr53wxrisnhsz.png" alt="Publishing via collector"&gt;&lt;/a&gt;&lt;br&gt;
This can be achieved with the following configuration:&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;receivers&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;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4418&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;otlphttp&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;http://jaeger:4318&lt;/span&gt;

  &lt;span class="na"&gt;prometheus&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:9094&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;products_service&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;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;otlphttp&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;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;prometheus&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;which later can be supplied to the collector as a configuration file for instance in a Docker compose:&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;otel-collector&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;otel/opentelemetry-collector-contrib:0.93.0&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel-collector&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;./mount/otel-collector-config-prometheus-jaeger.yaml:/etc/otelcol-contrib/config.yaml&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;9094:9094"&lt;/span&gt; &lt;span class="c1"&gt;# Prometheus http exporter&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:4418"&lt;/span&gt; &lt;span class="c1"&gt;# OTLP http receiver&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;After this, we need to update the application configuration to send telemetry data to the collector:&lt;/p&gt;

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

&lt;span class="nv"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;products_service
&lt;span class="nv"&gt;OTEL_METRICS_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;otlp
&lt;span class="nv"&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;otlp
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://otel-collector:4418


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

&lt;/div&gt;

&lt;p&gt;You can find in the GitHub repository &lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/master/docker-compose/setup-direct-publishing.yaml" rel="noopener noreferrer"&gt;Docker compose file&lt;/a&gt; to run this setup.&lt;br&gt;
After running the setup, you can run the script to simulate traffic again and observe the same results as in the previous section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending monitoring system
&lt;/h2&gt;

&lt;p&gt;Since we introduced OpenTelemetry Collector to the infrastructure, it is easier to extend the monitoring system with new backends.&lt;br&gt;
For instance, we might want to have a single place to view and analyze all telemetry data. Some sort of Application Monitoring System (APM), such as commercial solutions like Datadog or open source ones like Grafana.&lt;br&gt;
Grafana OSS provides a set of tools to build a complete observability system also known as &lt;a href="https://grafana.com/go/observabilitycon/2022/lgtm-scale-observability-with-mimir-loki-and-tempo/" rel="noopener noreferrer"&gt;LGTM stack&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://grafana.com/docs/loki/latest/" rel="noopener noreferrer"&gt;Loki&lt;/a&gt; - a logs storage;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grafana.com/docs/grafana/latest/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; - a tool for visualization and analysis;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grafana.com/docs/tempo/latest/" rel="noopener noreferrer"&gt;Tempo&lt;/a&gt; - a traces storage;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grafana.com/docs/mimir/latest/" rel="noopener noreferrer"&gt;Mimir&lt;/a&gt; - a metrics storage;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another crucial component in this stack is &lt;a href="https://grafana.com/docs/agent/latest/" rel="noopener noreferrer"&gt;Grafana Agent&lt;/a&gt; which is responsible for many things like collecting telemetry data and sending it to the backends.&lt;br&gt;
In our case, this agent can be configured to receive telemetry data from the OpenTelemetry Collector and send it to LGTM stack components.&lt;br&gt;
Such setup can be shown in the following diagram:&lt;br&gt;
&lt;a href="https://media.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%2Fcvafpqwl1wlx98ot9pgo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fcvafpqwl1wlx98ot9pgo.png" alt="Extended system"&gt;&lt;/a&gt;&lt;br&gt;
To achieve this, we need to add a new exporter to the OpenTelemetry Collector configuration:&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;receivers&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;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4418&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;otlphttp/jaeger&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;http://jaeger:4318&lt;/span&gt;

  &lt;span class="na"&gt;otlphttp/grafana-agent&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;http://grafana-agent:4318&lt;/span&gt;

  &lt;span class="na"&gt;prometheus&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:9094&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;products_service&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;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;otlphttp/jaeger&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;otlphttp/grafana-agent&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;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;prometheus&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;otlphttp/grafana-agent&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;otlp&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;otlphttp/grafana-agent&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As you can see, we configured a new exporter &lt;code&gt;otlphttp/grafana-agent&lt;/code&gt; that is added in &lt;code&gt;service&lt;/code&gt; section to the &lt;code&gt;traces&lt;/code&gt;, &lt;code&gt;metrics&lt;/code&gt;, and &lt;code&gt;logs&lt;/code&gt; pipelines.&lt;br&gt;
Aside note here: pay attention to exporter names as they should be unique and follow the pattern &lt;code&gt;{protocol}/{name}&lt;/code&gt;. The exporter's name can be omitted if there is only one exporter for the protocol.&lt;br&gt;
For the sake of simplicity, we omitted the configuration of Grafana Agent and LGTM stack components, but you can find it in the GitHub repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/master/docker-compose/setup-otel-collector-publishing-extended.yaml" rel="noopener noreferrer"&gt;Docker compose file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/master/docker-compose/mount/grafana-agent.river" rel="noopener noreferrer"&gt;Grafana Agent configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/main/docker-compose/mount/grafana-mimir.yaml" rel="noopener noreferrer"&gt;Mimir configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/main/docker-compose/mount/grafana-tempo.yaml" rel="noopener noreferrer"&gt;Tempo configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/main/docker-compose/mount/grafana-loki.yaml" rel="noopener noreferrer"&gt;Loki configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/main/docker-compose/mount/grafana-datasources.yaml" rel="noopener noreferrer"&gt;Grafana configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After running the setup, you can run the script to simulate traffic again.&lt;br&gt;
To check the metrics in Grafana, open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, go to the "Explore" section, and choose "Mimir" at the dropdown. We can use a similar PrompQL query for testing: &lt;code&gt;sum(products_service_http_server_request_duration_seconds_sum) / sum(products_service_http_server_request_duration_seconds_count)&lt;/code&gt;&lt;br&gt;
The resulting graph could look something like this:&lt;br&gt;
&lt;a href="https://media.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%2Fhbrmpk5q321l3y8re9km.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhbrmpk5q321l3y8re9km.png" alt="Mimir"&gt;&lt;/a&gt;&lt;br&gt;
In the same Grafana, we can find traces in Tempo. Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, go to the "Explore" section, and choose "Tempo" at the dropdown. You can search for traces for "Resource Service Name" equal to &lt;code&gt;products_service&lt;/code&gt; and "Span Name" &lt;code&gt;POST /api/product&lt;/code&gt;.&lt;br&gt;
In the result search list, you can pick any trace to see details and inner spans. The resulting trace could look something like this:&lt;br&gt;
&lt;a href="https://media.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%2Fhi1qwn9utj1jup8ugh4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fhi1qwn9utj1jup8ugh4n.png" alt="Tempo"&gt;&lt;/a&gt;&lt;br&gt;
Now we can check logs in Loki. Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;, go to the "Explore" section, and choose "Loki" at the dropdown. &lt;br&gt;
Let's search log lines containing &lt;code&gt;Creating product&lt;/code&gt; message that is produced by the product service on product creation. The resulting log lines could look something like this:&lt;br&gt;
&lt;a href="https://media.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%2Ftpqvaadgp0j6wqvpgena.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Ftpqvaadgp0j6wqvpgena.png" alt="Loki"&gt;&lt;/a&gt;&lt;br&gt;
We extended the monitoring infrastructure without touching the application, only by changing the OpenTelemetry Collector configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Processing telemetry data
&lt;/h2&gt;

&lt;p&gt;As it was mentioned before, OpenTelemetry Collector can receive, process and export telemetry data. It was shown how to configure it to receive and export telemetry data, hence it is worth mentioning how to process it.&lt;/p&gt;

&lt;p&gt;OpenTelemetry provides a number of processors which provides reach possibilities for telemetry filtering, dimension modifications, batching, etc. Please, see for more details the following &lt;a href="https://opentelemetry.io/docs/collector/configuration/#processors" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Products Service implements a simple health check API used by Docker to verify whether the application is ready to serve traffic. This is &lt;code&gt;GET /health&lt;/code&gt; endpoint, that writes &lt;code&gt;Health check API invoked!&lt;/code&gt; log on every call. This API is not interesting for monitoring, and we might want to filter it out.&lt;br&gt;
&lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/filterprocessor" rel="noopener noreferrer"&gt;Filter processor&lt;/a&gt; is a perfect fit for this task.&lt;/p&gt;

&lt;p&gt;Also lets imaging that there might be multiple environments, and we want to add an attribute to all telemetry data to distinguish them. &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/attributesprocessor" rel="noopener noreferrer"&gt;Attributes Processor&lt;/a&gt; can help with the requirement.&lt;/p&gt;

&lt;p&gt;We can add these processors to the OpenTelemetry Collector configuration in the following way:&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;receivers&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;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0:4418&lt;/span&gt;

&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Filter HTTP spans to server for `GET /health` requests because of spam.&lt;/span&gt;
  &lt;span class="na"&gt;filter/exclude-health-api-traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;error_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&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;span&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;attributes["http.route"]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/health"'&lt;/span&gt;

  &lt;span class="c1"&gt;# Filter logs for `GET /health` requests logs because of spam.&lt;/span&gt;
  &lt;span class="na"&gt;filter/exclude-health-api-logs&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;exclude&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;match_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;regexp&lt;/span&gt;
        &lt;span class="na"&gt;bodies&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;.*Health&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;invoked!.*'&lt;/span&gt;

  &lt;span class="c1"&gt;# Add environment attribute to all telemetry signals.&lt;/span&gt;
  &lt;span class="na"&gt;attributes/add-environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;actions&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;environment&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;development&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;insert&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;otlphttp/jaeger&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;http://jaeger:4318&lt;/span&gt;

  &lt;span class="na"&gt;otlphttp/grafana-agent&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;http://grafana-agent:4318&lt;/span&gt;

  &lt;span class="na"&gt;prometheus&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:9094&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;products_service&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="s"&gt;otlp&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="s"&gt;filter/exclude-health-api-traces&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attributes/add-environment&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="s"&gt;otlphttp/jaeger&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otlphttp/grafana-agent&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="s"&gt;otlp&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="s"&gt;attributes/add-environment&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="s"&gt;prometheus&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otlphttp/grafana-agent&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="s"&gt;otlp&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="s"&gt;filter/exclude-health-api-logs&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;attributes/add-environment&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="s"&gt;otlphttp/grafana-agent&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;You can find it in the GitHub repository &lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring/blob/master/docker-compose/setup-otel-collector-publishing-extended-process.yaml" rel="noopener noreferrer"&gt;Docker compose file&lt;/a&gt; to run this setup.&lt;br&gt;
After running the setup, you can run the script to simulate traffic again.&lt;br&gt;
To check whether logs for &lt;code&gt;GET /health&lt;/code&gt; requests are filtered out, open Loki in Grafana and search for log lines containing &lt;code&gt;Health check API invoked!&lt;/code&gt; message. You should not see any log lines containing this message:&lt;br&gt;
&lt;a href="https://media.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%2F1wh3suv7uj3b1jd5ql7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F1wh3suv7uj3b1jd5ql7b.png" alt="Loki"&gt;&lt;/a&gt;&lt;br&gt;
In Tempo, you can search for traces using span tags &lt;code&gt;http.route&lt;/code&gt; equals to &lt;code&gt;/health&lt;/code&gt; and you should not see any traces for this route:&lt;br&gt;
&lt;a href="https://media.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%2Fgsi2tvzt7zofx5vtacgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fgsi2tvzt7zofx5vtacgz.png" alt="Tempo"&gt;&lt;/a&gt;&lt;br&gt;
Along with this, you can check logs in to see that there is a new &lt;code&gt;environment&lt;/code&gt; label with &lt;code&gt;development&lt;/code&gt; value:&lt;br&gt;
&lt;a href="https://media.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%2Fbd85lgce0ubh85haov1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fbd85lgce0ubh85haov1r.png" alt="Loki"&gt;&lt;/a&gt;&lt;br&gt;
And pick any trace to see the same new &lt;code&gt;environment&lt;/code&gt; tag:&lt;br&gt;
&lt;a href="https://media.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%2Fwfdvge7ef4xx68qs5jhf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwfdvge7ef4xx68qs5jhf.png" alt="Tempo"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;OpenTelemetry is a great framework to build a complete observability system. &lt;br&gt;
Although it has a pretty wide number of tools, bear in mind that some of them are in the "alpha" or "beta" stage and subject to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/IvannKurchenko/blog-opentelemetry-building-decoupled-monitoring" rel="noopener noreferrer"&gt;GitHub repository with all the code&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OTEL collector documentation&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor#recommended-processors" rel="noopener noreferrer"&gt;OTEL recommended Processors&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/ecosystem/registry/" rel="noopener noreferrer"&gt;OTEL Registry&lt;/a&gt; - find instrumentation for your language or framework and any other components;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opentelemetry.io/docs/languages/java/automatic/spring-boot/" rel="noopener noreferrer"&gt;Spring Boot instrumentation&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c" rel="noopener noreferrer"&gt;Introducing native support for OpenTelemetry in Jaeger&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@gleydsoncavalcanti/sending-traces-with-the-grafana-agent-for-grafana-tempo-4092b25c35d0" rel="noopener noreferrer"&gt;Sending Traces with the Grafana Agent for Grafana Tempo&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/grafana/intro-to-mltp" rel="noopener noreferrer"&gt;Introduction to Metrics, Logs, Traces and Profiles in Grafana&lt;/a&gt;;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opentelemetry</category>
      <category>grafana</category>
      <category>observability</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
