<?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: Tom Weiss</title>
    <description>The latest articles on DEV Community by Tom Weiss (@tomweiss).</description>
    <link>https://dev.to/tomweiss</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%2F629933%2F65aa3719-8524-4539-907f-f08c82f51448.png</url>
      <title>DEV Community: Tom Weiss</title>
      <link>https://dev.to/tomweiss</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomweiss"/>
    <language>en</language>
    <item>
      <title>Distributed Tracing for Kafka with OpenTelemetry in Python</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Mon, 05 Sep 2022 07:35:03 +0000</pubDate>
      <link>https://dev.to/aspecto/distributed-tracing-for-kafka-with-opentelemetry-in-python-279m</link>
      <guid>https://dev.to/aspecto/distributed-tracing-for-kafka-with-opentelemetry-in-python-279m</guid>
      <description>&lt;p&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%2F3vsxg2vj8yoa6xebjqmn.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%2F3vsxg2vj8yoa6xebjqmn.png" alt="OpenTelemetry Kafka and Python"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, I will cover Apache Kafka, OpenTelemetry, and how they play out together with practical examples in Python from 0 to 1.&lt;/p&gt;

&lt;p&gt;You will learn how to enable OpenTelemetry tracing in Python to generate spans and visualize traces for various Kafka operations.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;  What is Apache Kafka?
&lt;/li&gt;
&lt;li&gt;  What is OpenTelemetry?
&lt;/li&gt;
&lt;li&gt;  OpenTelemetry Traces
&lt;/li&gt;
&lt;li&gt;  What are OpenTelemetry Instrumentations?
&lt;/li&gt;
&lt;li&gt;  How to Use Kafka with OpenTelemetry in Python
&lt;/li&gt;
&lt;li&gt;  Visualizing OpenTelemetry Tracing Data
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This guide has two parts:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A “hello world” kind of intro to OpenTelemetry and Kafka in Python in which you will end up seeing the outputs in a console.&lt;/li&gt;
&lt;li&gt; In the second part, you will learn to visualize traces to troubleshoot your microservices.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To generate spans for the different Kafka operations (e.g., consume and produce), we will use OpenTelemetry libraries.&lt;/p&gt;

&lt;p&gt;If you’re already familiar with Kafka and OpenTelemetry, feel free to jump right to the  &lt;a href="https://www.aspecto.io/blog/distributed-tracing-for-kafka-with-opentelemetry-in-python/#How-to-Use-Kafka-with-OpenTelemetry-in-Python" rel="noopener noreferrer"&gt;practical&lt;/a&gt;  part of this guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Apache Kafka? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Kafka is an open-source distributed event stream platform, where we can send events to topics and consume them. You can learn more about Kafka  &lt;a href="https://kafka.apache.org/intro" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OpenTelemetry? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In a world where microservices are becoming a standard architecture and distributed messaging systems like Kafka play a core part in enabling the independent microservices to communicate, the world needed a way to visualize and troubleshoot the complexity of a distributed architecture.&lt;/p&gt;

&lt;p&gt;Led by the CNCF (Cloud Native Computing Foundation), OpenTelemetry is an open-source project, a set of APIs and SDKs, that allows us to collect, export, and generate traces, logs, and metrics.&lt;/p&gt;

&lt;p&gt;With OpenTelemetry, we gather data from the events happening within the different components in our systems (including all sorts of message brokers like Kafka), which ultimately help us understand our software’s performance and behavior and visualize our microservices architecture.&lt;/p&gt;

&lt;p&gt;For this specific guide, we will focus on traces, you can learn more about OpenTelemetry  &lt;a href="https://www.aspecto.io/blog/what-is-opentelemetry-the-infinitive-guide/" rel="noopener noreferrer"&gt;in this friendly guide for developers&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenTelemetry Traces &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A trace tracks the progression of a single request, as it is handled by the different microservices/ moving parts.&lt;/p&gt;

&lt;p&gt;The atomic unit of work in a trace is called a span, for example, a call to a database, external service, send message to Kafka topic, etc.&lt;/p&gt;

&lt;p&gt;Each trace contains a collection of 1 or more spans and more information like how much time the entire trace took to complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aspecto.io%2Fwp-content%2Fuploads%2F2022%2F03%2FSpans-Diagram-edited.jpg" 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%2Fwww.aspecto.io%2Fwp-content%2Fuploads%2F2022%2F03%2FSpans-Diagram-edited.jpg" alt="OpenTelemetry Tracing Spans and Traces"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What are OpenTelemetry Instrumentations? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;So now you know what traces and spans mean, but how do you create them?&lt;/p&gt;

&lt;p&gt;That can be done using the SDKs provided to us by OpenTelemetry, which bundles useful instrumentations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s an instrumentation?&lt;/strong&gt;  A piece of code that is responsible for creating spans and traces and sending them to some backend, for later visualizing them.&lt;/p&gt;

&lt;p&gt;OpenTelemetry contains instrumentations for a lot of different libraries, including ones for Kafka.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use Kafka with OpenTelemetry in Python &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;By now you should have all the theory you need, so let’s start writing some code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This guide assumes you are running Kafka in your local (if you don’t,  &lt;a href="https://www.aspecto.io/blog/how-to-achieve-end-to-end-microservices-visibility-in-asyn-messaging-with-opentelemetry/" rel="noopener noreferrer"&gt;visit this quick guide)&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Practical Part
&lt;/h3&gt;

&lt;p&gt;First, let’s install the relevant libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;
&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt;
&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;instrumentation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, let’s add our tracing.py file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.instrumentation.kafka&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KafkaInstrumentor&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;simple_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;simple_processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;KafkaInstrumentor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is responsible for using OpenTelemetry SDKs to enable the creation of spans and traces.&lt;/p&gt;

&lt;p&gt;Notice we have a console span exporter which means we will be sending the spans to the console output.&lt;/p&gt;

&lt;p&gt;As you can see we use the KafkaInstrumentor to create spans for Kafka-related operations, like consume and produce.&lt;/p&gt;

&lt;p&gt;This is all the OpenTelemetry code we need to create Kafka spans.&lt;/p&gt;

&lt;p&gt;Let’s create the producer file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;instrument&lt;/span&gt;
&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KafkaProducer&lt;/span&gt;
&lt;span class="n"&gt;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bootstrap_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="n"&gt;producer&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foobar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;some_message_bytes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And create the consumer file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;instrument&lt;/span&gt;
&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KafkaConsumer&lt;/span&gt;
&lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KafkaConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foobar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bootstrap_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that both files have these 2 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;instrument&lt;/span&gt;
&lt;span class="nf"&gt;instrument&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 the calling of the instrument function and enables the creation of the spans when we run the consumer and producer files using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we use console exporter – we can see the spans of the consumer and producer on the console output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foobar receive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x436c0e9807b2f61b4a72bc63e5205954&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;span_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0xdda310172f7297cd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SpanKind.CONSUMER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x711a6a5d5bb977d8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-03-02T10:40:36.695519Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-03-02T10:40:36.720864Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNSET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attributes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kafka&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.destination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foobar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.kafka.partition&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry.sdk.language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry.sdk.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opentelemetry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry.sdk.version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.9.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_service&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foobar send&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x436c0e9807b2f61b4a72bc63e5205954&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;span_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0x711a6a5d5bb977d8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trace_state&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SpanKind.PRODUCER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-03-02T10:40:36.609135Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2022-03-02T10:40:36.610410Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UNSET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attributes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kafka&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.destination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;foobar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging.url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;localhost:9092&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry.sdk.language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry.sdk.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opentelemetry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;telemetry.sdk.version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.9.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service.name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown_service&lt;/span&gt;&lt;span class="sh"&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;But I have a feeling that you want to get a better visualization than just a console log. If you do – read on 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing OpenTelemetry Tracing Data &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;For this article, I will be using  &lt;a href="https://www.aspecto.io/" rel="noopener noreferrer"&gt;Aspecto&lt;/a&gt;  to visualize my traces. You can follow along by  &lt;a href="https://www.aspecto.io/pricing/" rel="noopener noreferrer"&gt;quickly creating a free account&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Aspecto has built-in support for visualizing async messaging platforms like Kafka so it fits perfectly with this guide’s purpose.&lt;/p&gt;

&lt;p&gt;Once you’re signed in, you get an API key and all you need to do is change a few lines of code, environment variables and install some packages. I’ll be showing you how to do that below.&lt;/p&gt;

&lt;p&gt;The end result should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aspecto.io%2Fwp-content%2Fuploads%2F2022%2F03%2Fimage2-1.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%2Fwww.aspecto.io%2Fwp-content%2Fuploads%2F2022%2F03%2Fimage2-1.png" alt="Single trace view. OpenTelemetry tracing for Kafka operations in the Aspecto Platform. "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.aspecto.io%2Fwp-content%2Fuploads%2F2022%2F03%2Fimage3.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%2Fwww.aspecto.io%2Fwp-content%2Fuploads%2F2022%2F03%2Fimage3.png" alt="Payload view. OpenTelemetry tracing for Kafka operations in the Aspecto Platform. "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let’s install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;otlp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;proto&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;grpc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let’s modify our tracing.py file so that it uses the otlp exporter instead of the console exporter, to send data to Aspecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;ConsoleSpanExporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.instrumentation.kafka&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;KafkaInstrumentor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;opentelemetry.exporter.otlp.proto.grpc.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="n"&gt;simple_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
   &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;simple_processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="nc"&gt;KafkaInstrumentor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;instrument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the consumer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;consumer&lt;/span&gt; &lt;span class="n"&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;otelcol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspecto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4317&lt;/span&gt; &lt;span class="n"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;YOUR_ASPECTO_API_KEY_HERE&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the producer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;producer&lt;/span&gt; &lt;span class="n"&gt;OTEL_EXPORTER_OTLP_TRACES_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;otelcol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aspecto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4317&lt;/span&gt; &lt;span class="n"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Authorization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;YOUR_ASPECTO_API_KEY_HERE&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it! When you enter the Aspecto platform and click on Trace Search, after a minute or two you should be able to see your traces.&lt;/p&gt;

&lt;p&gt;Feel free to click on the chat button and contact us, we’d be glad to help you figure everything out and answer any questions you may have.&lt;/p&gt;

&lt;p&gt;P.S. If you prefer to use an open-source to visualize your traces, check out Jaeger Tracing. Follow this  &lt;a href="https://www.aspecto.io/blog/getting-started-with-opentelemetry-python/" rel="noopener noreferrer"&gt;guide to learn how to use OpenTelemetry to send traces to Jaeger.&lt;/a&gt;  Keep in mind that it takes a bit more time to set up the required environment.&lt;/p&gt;

&lt;p&gt;Hope this has been a useful guide for you, feel free to reach out if you have any questions!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Distributed Tracing for Kafka with OpenTelemetry in Node</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Tue, 30 Aug 2022 09:10:17 +0000</pubDate>
      <link>https://dev.to/aspecto/distributed-tracing-for-kafka-with-opentelemetry-in-node-4g7d</link>
      <guid>https://dev.to/aspecto/distributed-tracing-for-kafka-with-opentelemetry-in-node-4g7d</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--75p_5ao---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0un92gsqkc65n95yykuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--75p_5ao---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0un92gsqkc65n95yykuy.png" alt="Distributed Tracing for Kafka with OpenTelemetry in Node" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this guide, you will learn how to run OpenTelemetry in Node to generate spans for different Kafka operations (e.g., consume and produce) and ultimately visualize your traces. We will also touch on the basics of Kafka, OpenTelemetry, distributed tracing, and how they all play out together.&lt;/p&gt;

&lt;p&gt;If you’re already familiar with Kafka and OpenTelemetry, feel free to jump right to the  &lt;a href="https://www.aspecto.io/blog/distributed-tracing-for-kafka-with-opentelemetry-in-node/#How-to-use-Kafka-with-OpenTelemetry-in-Node"&gt;practical&lt;/a&gt;  part of this guide.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;  What is Apache Kafka
&lt;/li&gt;
&lt;li&gt;  What is OpenTelemetry
&lt;/li&gt;
&lt;li&gt;  What is Distributed Tracing
&lt;/li&gt;
&lt;li&gt;  What is OpenTelemetry Instrumentation
&lt;/li&gt;
&lt;li&gt;  How to use Kafka with OpenTelemetry in Node
&lt;/li&gt;
&lt;li&gt;  Visualizing OpenTelemetry Traces
&lt;/li&gt;
&lt;li&gt;  TroubleShooting OpenTelemetry Node Installation Issues
&lt;/li&gt;
&lt;li&gt;  Additional OpenTelemetry Resources
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Apache Kafka? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Kafka is an open-source distributed event stream platform, where we can send events to topics and consume them. You can learn more about Kafka  &lt;a href="https://kafka.apache.org/intro"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OpenTelemetry? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry is an open-source project, a collection of APIs and SDKs. Led by the CNCF (Cloud Native Computing Foundation, same foundation responsible for Kubernetes), it allows us to collect, export, and generate traces, logs, and metrics (also known as the three pillars of observability).&lt;/p&gt;

&lt;p&gt;In a distributed software world, messaging systems (like Kafka) are key to enabling communication between independent microservices, making it easier to build complex systems.&lt;/p&gt;

&lt;p&gt;But understanding the path each message has gone through, where it goes, why, and when is as critical as it is complicated.&lt;/p&gt;

&lt;p&gt;Users need a way to visualize and troubleshoot the complexity of their distributed architecture.&lt;/p&gt;

&lt;p&gt;OpenTelemetry is our tool to collect data from the different transactions within our services and components (including various message brokers like Kafka) and understand our software’s performance and behavior.&lt;/p&gt;

&lt;p&gt;For this specific guide, we will focus on distributed traces, and you can take a deeper dive into OpenTelemetry  &lt;a href="https://www.aspecto.io/blog/what-is-opentelemetry-the-infinitive-guide/"&gt;in this short guide&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Distributed tracing defines spans and traces. A trace tracks the progression of requests across the services, message brokers, and other components in our system.&lt;/p&gt;

&lt;p&gt;A trace is a tree of spans. A span is a characterization of an activity/process that occurred between our services. For example, a call to a database, external service, sending messages to a Kafka topic, etc.&lt;/p&gt;

&lt;p&gt;Traces inform us of the length of each request, which components and services it interacted with, the latency introduced during each step, and other valuable information about the nature of that activity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DqhiOeqz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/Spans-Diagram-edited-1024x682.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DqhiOeqz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/Spans-Diagram-edited-1024x682.jpg" alt="OpenTelemetry Tracing Spans and Traces" width="880" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is OpenTelemetry Instrumentation? &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Instrumentation&lt;/strong&gt;  is a piece of code responsible for generating spans (which make up traces) and sending them to a dedicated backend of your choice. Later, depending on the tool you’re using, the end goal is to visualize them and leverage that visualization for troubleshooting our system.&lt;/p&gt;

&lt;p&gt;We do that by using the SDKs provided by OpenTelemetry, which bundles useful instrumentations.&lt;/p&gt;

&lt;p&gt;OpenTelemetry includes instrumentations for various libraries in different languages, including a Kafka instrumentation responsible for creating Kafka spans.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use Kafka with OpenTelemetry in Node &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;By now you should have all the theory you need, so let’s start writing some code. A note before we begin – This guide assumes you are running Kafka in your local machine – If you don’t,  &lt;a href="https://www.aspecto.io/blog/how-to-achieve-end-to-end-microservices-visibility-in-asyn-messaging-with-opentelemetry/"&gt;visit this quick guide&lt;/a&gt;  (it shows how to run Kafka with docker in the beginning).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Practical Part
&lt;/h3&gt;

&lt;p&gt;First, let’s create a brand new Express app &amp;amp; install relevant packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;generator&lt;/span&gt; &lt;span class="nx"&gt;kafkaotel&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;kafkajs&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;opentelemetry&lt;/span&gt;&lt;span class="sr"&gt;/exporter-collecto&lt;/span&gt;&lt;span class="err"&gt;r
&lt;/span&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;opentelemetry&lt;/span&gt;&lt;span class="sr"&gt;/auto-instrumentations-nod&lt;/span&gt;&lt;span class="err"&gt;e
&lt;/span&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;opentelemetry&lt;/span&gt;&lt;span class="sr"&gt;/sdk-node @opentelemetry/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;instrumentation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;kafkajs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a tracing file that would create spans and traces for Kafka operations (like produce and consume). For this we use the opentelemetry kafkajs  &lt;a href="https://www.npmjs.com/package/opentelemetry-instrumentation-kafkajs"&gt;instrumentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* tracing.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;KafkaJsInstrumentation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-instrumentation-kafkajs&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="nx"&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="c1"&gt;// Require dependencies&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="nx"&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="nx"&gt;tracerProvider&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;NodeTracerProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="c1"&gt;// be sure to disable old plugin&lt;/span&gt;
 &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;kafkajs&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="na"&gt;path&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-plugin-kafkajs&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;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="nx"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;tracerProvider&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="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracing&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="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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;KafkaJsInstrumentation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
     &lt;span class="c1"&gt;// see kafkajs instrumentation docs for available configuration&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="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&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;Let’s update package.json to require the tracing file so that opentelemetry would be able to instrument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node --require './tracing.js' ./bin/www&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s update our main endpoint to produce and consume Kafka messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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="nx"&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Router&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;Kafka&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;kafkajs&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;kafka&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;Kafka&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="na"&gt;brokers&lt;/span&gt;&lt;span class="p"&gt;:&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:9092&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;sendKafkaMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;producer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;messages&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;value&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 KafkaJS user!&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;await&lt;/span&gt; &lt;span class="nx"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disconnect&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;consumeMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;consumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kafka&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;groupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fromBeginning&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;eachMessage&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;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;partition&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="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
       &lt;span class="na"&gt;value&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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="p"&gt;}&lt;/span&gt;
&lt;span class="cm"&gt;/* GET home page. */&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sendKafkaMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;consumeMessages&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;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index&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;title&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="p"&gt;});&lt;/span&gt;
&lt;span class="kr"&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;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run npm start and run go to localhost:3000 in your favorite browser.&lt;/p&gt;

&lt;p&gt;Then you can see the producer span in the console like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;003cd8ece100e4bed2d05867d3a98dc0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-topic&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;c83af592be261547&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1646386704369183&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28633&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kafka&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.destination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.destination_kind&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;code&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;events&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;And also the consumer span:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;003cd8ece100e4bed2d05867d3a98dc0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;c83af592be261547&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-topic&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;875ea3c9e2b0dbdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1646386726625784&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;562&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;kafka&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.destination&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.destination_kind&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;topic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messaging.operation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;code&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;events&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;And that’s that! You now know how to use Kafka and OpenTelemetry together, and can create spans for Kafka operations that happen in your microservices.&lt;/p&gt;

&lt;p&gt;So far we only printed outputs to our console. But OpenTelemetry has a lot to offer in terms of visualization, so let’s see how you can achieve that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualizing OpenTelemetry Traces in Node &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;For the purposes of this guide, I chose to use  &lt;a href="https://www.aspecto.io/"&gt;Aspecto&lt;/a&gt;  as my visualization tool. This is because Aspecto provides built-in support for visualizing messaging systems like Kafka (and, of course, any other part of our microservice architectures).&lt;/p&gt;

&lt;p&gt;Before jumping in, you can try it yourself with the &lt;a href="https://www.aspecto.io/pricing/"&gt;free-forever plan&lt;/a&gt; that has no limited features.&lt;/p&gt;

&lt;p&gt;Give this &lt;a href="https://app.aspecto.io/play/search"&gt;Live Playground&lt;/a&gt; a try to get a better idea.&lt;/p&gt;

&lt;p&gt;Here’s how it would look at the end of the process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mAvxfka0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/image1-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mAvxfka0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/image1-1.png" alt="Kafka message with OpenTelemetry in Node" width="396" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also click on a specific node and see tracing data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gwC3RJKv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/image4-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gwC3RJKv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/image4-1.png" alt="Aspecto UI seeing OpenTelemetry tracing data in Node" width="857" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can follow along by  &lt;a href="https://www.aspecto.io/pricing/"&gt;creating a free account&lt;/a&gt;, then, obtain your Aspecto API key, and modify your tracing.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;NodeTracerProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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="nx"&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SimpleSpanProcessor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CollectorTraceExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-collector&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;registerInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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/instrumentation&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;KafkaJsInstrumentation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-instrumentation-kafkajs&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;provider&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;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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&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;kafka-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// service name is required&lt;/span&gt;
 &lt;span class="p"&gt;}),&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="nx"&gt;register&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="nx"&gt;addSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CollectorTraceExporter&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://otelcol.aspecto.io/v1/trace&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Aspecto API-Key is required&lt;/span&gt;
       &lt;span class="na"&gt;Authorization&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;ASPECTO_API_KEY&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="nx"&gt;registerInstrumentations&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="c1"&gt;// add other instrumentations here for packages your app uses&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;KafkaJsInstrumentation&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;Then run the app with your Aspecto api key, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ASPECTO_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;YOUR_API_KEY&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tracing.js&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="nx"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;www&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you go to localhost:3000 with a browser, it would produce and consume a Kafka message and send corresponding spans to aspecto. Allow a minute or 2 and you could see it under the trace search in aspecto, just like the above picture shows.&lt;/p&gt;

&lt;p&gt;If you want to instrument libraries other than Kafka, update your tracing file like this(using the node auto instrumentations library that enables a  &lt;a href="https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node"&gt;variety of instrumentations&lt;/a&gt;  by default):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;NodeTracerProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;Resource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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="nx"&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SimpleSpanProcessor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CollectorTraceExporter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-collector&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;registerInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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/instrumentation&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;KafkaJsInstrumentation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-instrumentation-kafkajs&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="nx"&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="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="nx"&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&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;kafka-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// service name is required&lt;/span&gt;
 &lt;span class="p"&gt;}),&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="nx"&gt;register&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="nx"&gt;addSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CollectorTraceExporter&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://otelcol.aspecto.io/v1/trace&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Aspecto API-Key is required&lt;/span&gt;
       &lt;span class="na"&gt;Authorization&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;ASPECTO_API_KEY&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="nx"&gt;registerInstrumentations&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="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;KafkaJsInstrumentation&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;But note: for Node, it’s recommended to use the  &lt;a href="https://docs.aspecto.io/v1/send-tracing-data-to-aspecto/aspecto-sdk/nodejs"&gt;Aspecto SDK&lt;/a&gt;  which uses OpenTelemetry under the hood but also supports collecting actual payloads.&lt;/p&gt;

&lt;p&gt;If you take the exact same service from scratch – without all the OpenTelemetry installations (make sure uninstall them to avoid any version conflicts), and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;aspecto&lt;/span&gt;&lt;span class="sr"&gt;/opentelemetr&lt;/span&gt;&lt;span class="err"&gt;y
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the following code at the top of your app.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&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;@aspecto/opentelemetry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
 &lt;span class="na"&gt;aspectoAuth&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;ASPECTO_API_KEY&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you can even see the payload of the Kafka message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3OkETvsU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/image3-1-1024x476.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3OkETvsU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/03/image3-1-1024x476.png" alt="Payload of the Kafka message with OpenTelemetry in Node" width="880" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P.S. If you prefer to use an open-source to visualize your traces, you can try Jaeger Tracing. You can follow this  &lt;a href="https://www.aspecto.io/blog/how-to-deploy-jaeger-on-aws-a-comprehensive-step-by-step-guide/"&gt;guide on deploying Jaeger on AWS&lt;/a&gt;  and make sure to scroll to the part on sending traces to Jaeger with the OpenTelemetry SDK (In NodeJS). Keep in mind that it may take a bit more time to set up the required environment.&lt;/p&gt;

&lt;p&gt;If you’re not familiar with Jaeger and get a deeper understanding of it, we recommend visiting this  &lt;a href="https://www.aspecto.io/blog/jaeger-tracing-the-ultimate-guide/"&gt;guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope this has been a useful guide for you to learn how to use OpenTelemetry in Node to generate traces for Kafka operations. Feel free to  &lt;a href="https://twitter.com/thetomzach"&gt;reach out&lt;/a&gt;  if you have any questions!&lt;/p&gt;

&lt;h2&gt;
  
  
  TroubleShooting OpenTelemetry Node Installation Issues &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;It is not uncommon to encounter several issues when trying to install OpenTelemetry. More specifically, trying to install OpenTelemetry in your Node application and not seeing any traces or some expected spans are missing.&lt;/p&gt;

&lt;p&gt;To help you avoid these common pitfalls, we recommend going over this  &lt;a href="https://www.aspecto.io/blog/checklist-for-troubleshooting-opentelemetry-nodejs-tracing-issues/"&gt;in-depth checklist of issues and solutions&lt;/a&gt;. We cover the most common mistakes and show you how to solve them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional OpenTelemetry Resources &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you want to go deeper into learning about OpenTelemetry, visit  &lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/"&gt;The OpenTelemetry Bootcamp video library&lt;/a&gt;. It’s a free and vendor-neutral, in-depth course that will help you master OpenTelemetry. We cover everything from the very basics to security and deploying in production.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>OpenTelemetry Collector: A Friendly Guide for Devs</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Wed, 24 Aug 2022 13:19:53 +0000</pubDate>
      <link>https://dev.to/aspecto/opentelemetry-collector-a-friendly-guide-for-devs-41jh</link>
      <guid>https://dev.to/aspecto/opentelemetry-collector-a-friendly-guide-for-devs-41jh</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1bcQPhcz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j1mpmsgf50s2hy3qx1x7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1bcQPhcz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j1mpmsgf50s2hy3qx1x7.png" alt="Jaeger Nirvana Album Cover" width="880" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this guide, you will learn everything you need to know about the OpenTelemetry Collector.&lt;/p&gt;

&lt;p&gt;Before reading about it myself, the collector felt like a complex beast. But as I was learning, I realized it was not so complicated after all.&lt;/p&gt;

&lt;p&gt;In a nutshell, the collector receives telemetry data and sends it to wherever you configure it. It can also manipulate the data as you see fit. These are the key concepts you need to understand.&lt;/p&gt;

&lt;p&gt;We will later go through a practical example where we configure the collector and visualize data.&lt;/p&gt;

&lt;p&gt;Now that you know this, we can further elaborate.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;How does the OpenTelemetry Collector work?&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  Receivers
&lt;/li&gt;
&lt;li&gt;  Processors
&lt;/li&gt;
&lt;li&gt;  Exporters
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Architecture of Running an OpenTelemetry Collector&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  Gateway
&lt;/li&gt;
&lt;li&gt;  Agent
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;OpenTelemetry Collector Deployment Methods&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Configuring OpenTelemetry Collector Gateway with Jaeger for Visualization&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Configuring Your Collector to Send Data to an Observability Vendor&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;OpenTelemetry Collector Extensions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How does the OpenTelemetry Collector work?&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Images speak louder than words. So here’s one that’d help you figure this one out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vjIJhvaq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/OpenTelemetry-Collector-Architecture-1024x773.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vjIJhvaq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/OpenTelemetry-Collector-Architecture-1024x773.jpg" alt="OpenTelemetry-Collector-Architecture" width="880" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So what is happening here?&lt;/p&gt;

&lt;p&gt;Looking at data ingestion to the OTel collector, you should know you can send logs, metrics, and traces using the OTLP protocol, which supports those data types.&lt;/p&gt;

&lt;p&gt;The OpenTelemetry collector has three main components:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Receivers&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As their name suggests, they are in charge of receiving spans from our span emitting applications. We can use a native OTEL SDK to create spans and export them to the receiver that listens for calls in a specified port on the collector.&lt;/p&gt;

&lt;p&gt;We can configure our receivers to accept both gRPC and HTTP protocols.&lt;/p&gt;

&lt;p&gt;For a list of receivers, you can use  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Processors&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The processor’s goal is to enable us to manipulate the data the collector receives right before we export it to our DB or backend. Common use cases:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Sampling&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Assume we have a lot of data we do not actually want to store in our backend. We can define a processor responsible for implementing the sampling logic and deciding which spans would be sent to the backend/DB.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note – the collector still receives all traces using the receiver, but then the processor comes and picks the relevant ones.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Data alteration&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Say you want to remove sensitive data before it reaches the distributed tracing backend (to avoid tokens or other info from leaking).&lt;/p&gt;

&lt;p&gt;You could use a processor to do this for you, but note that this would be a processor that has to be marked as such that is able to mutate your data, thus causing the creation of a copy of your entire data for this processor. Meaning, that every processor marked as  &lt;strong&gt;&lt;em&gt;mutatesData: true&lt;/em&gt;&lt;/strong&gt;  would work with a copy of the entire data as it received it.&lt;/p&gt;

&lt;p&gt;That is not a problem in most cases, but you should be aware of it. It is called data ownership, and you can read more about it  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/README.md#exclusive-ownership"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You could also use this to add attributes (for example, calculate new ones based on others).&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Exporting metrics from spans&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Using the Span Metrics Processor, we can aggregate data from spans into metrics and then export it to a relevant backend. For example, this processor could gather data about the HTTP path and response status code, enabling you to see which routes have the most 500 errors. You can read more about this  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/spanmetricsprocessor"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You could take a look at the  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor"&gt;OTEL Collector Contrib&lt;/a&gt;  to see a list of additional processors you can use.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Exporters&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The goal of the exporters is to take the telemetry data as it is represented in the collector (OTEL data), convert it to a different format when needed (like Jaeger), and then send it to the endpoint you define. The sending part is done using the OTLP format, over either HTTP or gRPC.&lt;/p&gt;

&lt;p&gt;You can export directly to Elasticsearch, Jaeger, Zipkin, and other vendors to enable distributed services visualization. Visit the  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter"&gt;OTEL Collector Contrib&lt;/a&gt;  to see a list of additional exporters you can use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why should you use the OpenTelemetry collector?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From the capabilities described above, you can understand that the collector is helpful in the following scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; You want to control the data leaving your company&lt;/li&gt;
&lt;li&gt; You want to convert data before it leaves for another system&lt;/li&gt;
&lt;li&gt; You want to add abilities you would not otherwise have, e.g., extracting metrics from spans and other data manipulations.&lt;/li&gt;
&lt;li&gt; You want to send your data to multiple destinations as easily as adding a few lines of configuration (like different vendors)&lt;/li&gt;
&lt;li&gt; You want to control the sampling levels yourself (even though some vendors like Aspecto do let you do this without the need to set up your own collector).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But all this goodness comes with a price: you must know that maintaining a collector has its own complexities to take care of, like security, infrastructure management &amp;amp; cost (which could go up or down depending on your needs), etc.&lt;/p&gt;

&lt;p&gt;If you want to go about it by yourself, the next part talks about the architecture of running an OTEL collector.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Architecture of Running an OpenTelemetry Collector&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The collector binary has two modes of running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  As an Agent&lt;/li&gt;
&lt;li&gt;  As a Gateway&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Gateway&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;When we use the collector as a Gateway, it is run as a standalone machine independent from any other services or serves as a central point at which the telemetry data of an entire distributed architecture comes in.&lt;/p&gt;

&lt;p&gt;It provides more advanced capabilities than the agent, such as tail-based sampling (in which the collector only exports spans that have errors, for example).&lt;/p&gt;

&lt;p&gt;It can also help with API token management and reduce the number of egress points required to send data.&lt;/p&gt;

&lt;p&gt;You should know that each collector instance is independent. This means that if you want to scale, you could set up a “gateway cluster” with various collector gateway instances behind a load balancer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Agent&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The agent runs at the same host as your app server (whether it runs as a container or not).&lt;/p&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It can add insightful information about the host (IP, hostname, etc.).&lt;/li&gt;
&lt;li&gt;  Receives the data faster since there’s no DNS resolving to do (it’s just localhost).&lt;/li&gt;
&lt;li&gt;  Offload responsibilities otherwise belonging to the app like batching, compression, retry, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After doing all the above, it would send the data to the Gateway Collector. For more information about all the above, visit  &lt;a href="https://opentelemetry.io/docs/collector/getting-started/"&gt;Getting Started | OpenTelemetry Collector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that you’re familiar with the two modes of operation of the collector, we can talk about  &lt;a href="https://www.aspecto.io/blog/opentelemetry-deployment-methods-sdk-and-collector/"&gt;deployment&lt;/a&gt;  methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;OpenTelemetry Collector Deployment Methods&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I would suggest two options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
All the microservices of your app interact directly with a single collector instance as a collector gateway:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QpSxqW4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/Collector-Deployment-Methods-Option-1-1024x491.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QpSxqW4l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/Collector-Deployment-Methods-Option-1-1024x491.jpg" alt="Collector Deployment Methods" width="880" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Each microservice writes to an agent running at the same host as the microservice. Then, the agent writes to a central collector gateway:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--APi3f8NM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/All-OpenTelemetry-Collector-agent-writes-to-a-central-collector-gateway-1024x491.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--APi3f8NM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/All-OpenTelemetry-Collector-agent-writes-to-a-central-collector-gateway-1024x491.jpg" alt="OpenTelemetry Collector agent writes to a central collector gateway" width="880" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite the benefits of using the agent mode (listed above),  &lt;em&gt;I would only recommend this approach when you’ve reached a level of maturity with your app and OpenTelemetry&lt;/em&gt;, as maintaining it introduces infrastructure overhead. In the beginning, you could probably do just as well with the simple single collector mode.&lt;/p&gt;

&lt;p&gt;And that’s the reason why I will be showing a practical example of the first option (gateway).&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Configuring OpenTelemetry Collector Gateway with Our App and Jaeger for Visualization&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here’s a diagram of what we will be building:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S0TjDlPu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/All-Configuring-OTEL-Collector-Gateway-with-Our-App-and-Jaeger-for-Visualization-1024x773.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S0TjDlPu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/All-Configuring-OTEL-Collector-Gateway-with-Our-App-and-Jaeger-for-Visualization-1024x773.jpg" alt="Configuring OTEL Collector Gateway with Our App and Jaeger for Visualization" width="880" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What we want to achieve: we run a collector and Jaeger using docker-compose, set up OTLP receivers, add some data using a processor, and export to Jaeger for visualization.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up the collector gateway
&lt;/h3&gt;

&lt;p&gt;First, let’s set up a configuration file for our collector.&lt;/p&gt;

&lt;p&gt;Collector-gateway.yaml:&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="nx"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4318&lt;/span&gt;
     &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4317&lt;/span&gt;
&lt;span class="nx"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
 &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
       &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
       &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;insert&lt;/span&gt;
&lt;span class="nx"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;loglevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;
 &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;14250&lt;/span&gt;
   &lt;span class="nx"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;pprof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1888&lt;/span&gt;
 &lt;span class="nx"&gt;zpages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;55679&lt;/span&gt;
&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pprof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zpages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="nx"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;traces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="nx"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="nx"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;batch&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="nx"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;So what do we see in the file above?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, we set up OTLP receivers and told our collector to add HTTP &amp;amp; gRPC endpoints at ports 4318 &amp;amp; 4317.&lt;/p&gt;

&lt;p&gt;Then, we set up a  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor/batchprocessor"&gt;batch processor&lt;/a&gt;  that batches up the spans together and every 1 second sends the batch forward. In production, you would want more than 1 second, but I set this here to 1 second for instant feedback in Jaeger.&lt;/p&gt;

&lt;p&gt;Then we add another processor that inserts a new attribute on each span that passed through it. The key: “test.key”, the value: “test-value”.&lt;/p&gt;

&lt;p&gt;After that, we define two exporters: one to log everything to the console and the other to export the spans to Jaeger.&lt;/p&gt;

&lt;p&gt;We do it insecurely now, but you would not want to do that for production purposes.&lt;/p&gt;

&lt;p&gt;Jaeger-all-in-one is the container’s name in docker-compose. You will see its setup below.&lt;/p&gt;

&lt;p&gt;Ignore the extensions part, for now (we will discuss it later).&lt;/p&gt;

&lt;p&gt;The service is the orchestrator of all the above. We set up a tracing pipeline (though you also could add a metrics/logs one). In our tracing pipeline, we tell the collector to use all the definitions from above.&lt;/p&gt;

&lt;p&gt;Now that we have the collector config ready – let’s use it in the collector.&lt;/p&gt;

&lt;p&gt;Docker-compose.yaml:&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="nx"&gt;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;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Jaeger&lt;/span&gt;
 &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jaegertracing&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;
   &lt;span class="nx"&gt;ports&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="s2"&gt;16686:16686&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;14268&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;14250&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
 &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Collector&lt;/span&gt;
 &lt;span class="nx"&gt;collector&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;otel&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;collector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.29&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
   &lt;span class="nx"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;collector&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;etc&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;collector&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yaml&lt;/span&gt;
   &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--config=/etc/collector-gateway.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="nx"&gt;ports&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="s2"&gt;1888:1888&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;   &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;pprof&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;13133:13133&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4317:4317&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;        &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;OTLP&lt;/span&gt; &lt;span class="nx"&gt;gRPC&lt;/span&gt; &lt;span class="nx"&gt;receiver&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4318:4318&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;        &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;OTLP&lt;/span&gt; &lt;span class="nx"&gt;HTTP&lt;/span&gt; &lt;span class="nx"&gt;receiver&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;55670:55679&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;zpages&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt;
   &lt;span class="nx"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we configure Jaeger to run in the relevant UI and data collection ports and call it Jaeger-all-in-one, as mentioned before.&lt;/p&gt;

&lt;p&gt;Then, we define a collector-gateway container to use the otel collector binary and map the volume so that the container would take the otel config file we created above and use it.&lt;/p&gt;

&lt;p&gt;The command below is where the collector takes that file from within the container.&lt;/p&gt;

&lt;p&gt;Then you can see the ports for each receiver/extension and the dependency on the existence of the jaeger container.&lt;/p&gt;

&lt;p&gt;Now let’s run with the following command:&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="nx"&gt;docker&lt;/span&gt; &lt;span class="nx"&gt;compose&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to start sending data to the collector and then see it from Jaeger.&lt;/p&gt;

&lt;p&gt;Let’s set up a simple nodejs-express app with OpenTelemetry enabled for this.&lt;/p&gt;

&lt;p&gt;Let’s use an express-generator to generate our initial 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="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;generator&lt;/span&gt; &lt;span class="nx"&gt;otel&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;collector&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure should have been automatically created for you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--df5RZcsc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/image8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--df5RZcsc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/image8.png" alt="express-generator file" width="484" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s install the relevant dependencies:&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="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;opentelemetry&lt;/span&gt;&lt;span class="sr"&gt;/sdk-node @opentelemetry/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="nx"&gt;opentelemetry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;instrumentation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;
 &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;opentelemetry&lt;/span&gt;&lt;span class="sr"&gt;/exporter-trace-otlp-htt&lt;/span&gt;&lt;span class="err"&gt;p
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your package.json should look like this:&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="p"&gt;{&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otel-collector-sender&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&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.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node ./bin/www&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^1.0.4&lt;/span&gt;&lt;span class="dl"&gt;"&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/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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.28.0&lt;/span&gt;&lt;span class="dl"&gt;"&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/instrumentation-http&lt;/span&gt;&lt;span class="dl"&gt;"&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.28.0&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.28.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie-parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.4.4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~2.6.9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~4.16.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http-errors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.6.3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.11.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;morgan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.9.1&lt;/span&gt;&lt;span class="dl"&gt;"&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-instrumentation-express&lt;/span&gt;&lt;span class="dl"&gt;"&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.27.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launch the app to see that it works as expected. If you run “npm start” and go to localhost:3000 you should see this, which means our Express app is up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e2T3gOTr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/image7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e2T3gOTr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/image7.png" alt="Welcome to express" width="298" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a tracing.js file to enable tracing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* tracing.js */&lt;/span&gt;
&lt;span class="c1"&gt;// Require dependencies&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="nx"&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;diag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DiagConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DiagLogLevel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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/api&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="nx"&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;ExpressInstrumentation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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-instrumentation-express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// For troubleshooting, set the log level to DiagLogLevel.DEBUG&lt;/span&gt;
&lt;span class="nx"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DiagConsoleLogger&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;DiagLogLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INFO&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="nx"&gt;OTLPTraceExporter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
 &lt;span class="c1"&gt;// optional - url default value is http://localhost:55681/v1/traces&lt;/span&gt;
 &lt;span class="na"&gt;url&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://localhost:4318/v1/traces&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="c1"&gt;// optional - collection of custom headers to be sent with each request, empty by default&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="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="nx"&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ExpressInstrumentation&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="nx"&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;Now, this code has to be run before our app does, so that traces could be created.&lt;/p&gt;

&lt;p&gt;The way to do so is by modifying the start script in our package.json to contain -r:&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="p"&gt;{&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otel-collector-sender&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;version&lt;/span&gt;&lt;span class="dl"&gt;"&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.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node -r ./tracing.js ./bin/www&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opentelemetry/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^1.0.4&lt;/span&gt;&lt;span class="dl"&gt;"&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/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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.28.0&lt;/span&gt;&lt;span class="dl"&gt;"&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/instrumentation-http&lt;/span&gt;&lt;span class="dl"&gt;"&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.28.0&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.28.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie-parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.4.4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~2.6.9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~4.16.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http-errors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.6.3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.11.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;morgan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~1.9.1&lt;/span&gt;&lt;span class="dl"&gt;"&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-instrumentation-express&lt;/span&gt;&lt;span class="dl"&gt;"&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.27.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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the app and refresh your browser at localhost:3000.&lt;/p&gt;

&lt;p&gt;Head over to Jaeger, refresh your page, and select  &lt;em&gt;unknown_service:node&lt;/em&gt; from the panel on your left. Then hit Find Traces, and you should see a list of traces that have gone from the OTEL JS SDK to the collector to Jaeger.&lt;/p&gt;

&lt;p&gt;Would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VUKLaYpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/OTEL-Collector-spans-in-Jaeger-1024x323.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VUKLaYpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/OTEL-Collector-spans-in-Jaeger-1024x323.png" alt="list of traces that have gone from the OpenTelemetry JS SDK to the collector to Jaeger" width="880" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty nice, isn’t it?&lt;/p&gt;

&lt;p&gt;Now you may ask – “but you promised to add some attributes, where are they?” – I am glad you asked that.&lt;/p&gt;

&lt;p&gt;Let’s see the HTTP GET trace, for example, pick any span, and check out the process area:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yDRhJmZA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/OTEL-Collector-spans-to-Jaeger-trace-drill-down-1024x378.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yDRhJmZA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/OTEL-Collector-spans-to-Jaeger-trace-drill-down-1024x378.png" alt="OTEL Collector spans to Jaeger trace drill down" width="880" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the bottom, we have a test.key and test-value just like we defined in the collector.&lt;/p&gt;

&lt;p&gt;You could use this to add any data you want, not just test-value. And of course, you could use any other type of processor as mentioned above.&lt;/p&gt;

&lt;p&gt;But now, you might ask, what if I want to use a vendor instead of Jaeger? Before I show you how to add a vendor into the loop, let’s talk about usage with vendors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage with observability vendors
&lt;/h3&gt;

&lt;p&gt;Since the OpenTelemetry collector works with the standard OpenTelemetry specification, it is vendor agnostic. You could configure the collector to send data to any vendor that supports OpenTelemetry data by switching or adding another exporter to your collector.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Configuring Your Collector to Send Data to an Observability Vendor&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Modify your collector-gateway.yaml file to look like this:&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="nx"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4318&lt;/span&gt;
     &lt;span class="nx"&gt;grpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
       &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4317&lt;/span&gt;
&lt;span class="nx"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;
 &lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
       &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
       &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;insert&lt;/span&gt;
&lt;span class="nx"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;loglevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;
 &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;14250&lt;/span&gt;
   &lt;span class="nx"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
 &lt;span class="nx"&gt;otlphttp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//otelcol-fast.aspecto.io&lt;/span&gt;
   &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ASPECTO&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;TOKEN&lt;/span&gt;

&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;pprof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1888&lt;/span&gt;
 &lt;span class="nx"&gt;zpages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;55679&lt;/span&gt;
&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
 &lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pprof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zpages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;health_check&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="nx"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;traces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="nx"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;otlp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
     &lt;span class="nx"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;batch&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="nx"&gt;exporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jaeger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;otlphttp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At  &lt;a href="https://www.aspecto.io/"&gt;Aspecto&lt;/a&gt;, you can sign up for free and use our generous  &lt;a href="https://www.aspecto.io/pricing/"&gt;free-forever plan&lt;/a&gt;  (no limited features).&lt;/p&gt;

&lt;p&gt;Just grab your token and paste it where it says YOUR-ASPECTO-TOKEN (&lt;a href="https://app.aspecto.io/app/integration/token"&gt;https://app.aspecto.io/app/integration/token&lt;/a&gt;  (Settings &amp;gt; Integrations &amp;gt; Tokens).&lt;/p&gt;

&lt;p&gt;Now re-run using docker-compose up and refresh your localhost:3000 browser page.&lt;/p&gt;

&lt;p&gt;Then, in the Aspecto app, the traces would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U4SPGa4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/Aspecto-Traces-from-Collector-1024x329.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U4SPGa4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/Aspecto-Traces-from-Collector-1024x329.png" alt="Aspecto Traces Search view traces from OpenTelemetry Collector" width="880" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drill down into the trace below, it would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c_5hqd33--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/Aspecto-Traces-from-Collector-Trace-View-1024x418.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c_5hqd33--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2022/05/Aspecto-Traces-from-Collector-Trace-View-1024x418.png" alt="Aspecto Traces from Collector Trace View" width="880" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below you can see the  &lt;em&gt;test.key: “test-value”&lt;/em&gt; we added.&lt;/p&gt;

&lt;p&gt;It sent it both to Aspecto and Jaeger, and we have a high level of visibility this way 🙂&lt;/p&gt;

&lt;p&gt;If you want to see payloads and not just standard otel data, you can use Aspecto Node SDK written on top of OTEL. It adds this and other capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;OpenTelemetry Collector Extensions&lt;/strong&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This article wouldn’t be complete without talking about the extensions you’ve seen earlier.&lt;/p&gt;

&lt;p&gt;OpenTelemetry collector extensions provide additional capabilities that are not provided in the standard collector. In addition, these abilities are not part of the logs, metrics, and traces pipeline.&lt;/p&gt;

&lt;p&gt;Let’s talk about the extensions you’ve seen above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  health_check – responsible for responding to health check calls on behalf of the collector.&lt;/li&gt;
&lt;li&gt;  PProf – fetches the collector’s performance data&lt;/li&gt;
&lt;li&gt;  zPages – serves as an http endpoint that provides live debugging data about instrumented components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information on the above, read  &lt;a href="https://github.com/open-telemetry/opentelemetry-collector/blob/main/extension/README.md"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That would sum up everything you need to know to get started with the OpenTelemetry collector.  &lt;/p&gt;

&lt;p&gt;If you want to learn more about the collector and OpenTelemetry in general, watch our OpenTelemetry Bootcamp. It’s a  &lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/"&gt;free and vendor-neutral video series&lt;/a&gt;  (6-episodes) that you can use as your OpenTelemetry playbook. It contains everything you need from the basics to production deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9o6Lsc4A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.aspecto.io/wp-content/uploads/2021/12/Banner-OTel-Bootcamp.svg" alt="OpenTelemetry Bootcamp" width="743" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Episode 1: OpenTelemetry Fundamentals&lt;/li&gt;
&lt;li&gt; Episode 2: Integrate Your Code (logs, metrics, and traces)&lt;/li&gt;
&lt;li&gt; Episode 3: Deploy to Production + Collector&lt;/li&gt;
&lt;li&gt; Episode 4: Sampling and Dealing with High Volumes&lt;/li&gt;
&lt;li&gt; Episode 5: Custom Instrumentation&lt;/li&gt;
&lt;li&gt; Episode 6: Testing with OpenTelemetry&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Get Started with OpenTelemetry Python: A Practical Guide</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Wed, 19 Jan 2022 16:51:44 +0000</pubDate>
      <link>https://dev.to/aspecto/get-started-with-opentelemetry-python-a-practical-guide-3d3i</link>
      <guid>https://dev.to/aspecto/get-started-with-opentelemetry-python-a-practical-guide-3d3i</guid>
      <description>&lt;p&gt;This is a practical guide that gives you just what you need to get started with OpenTelemetry in Python without any prior knowledge in OpenTelemetry.&lt;/p&gt;

&lt;h1&gt;
  
  
  Intro to OpenTelemetry
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is a CNCF project (same folks responsible for Kubernetes), which, among other things, allows the collection of traces, logs, and metrics (also known as the three pillars of observability). &lt;/p&gt;

&lt;p&gt;It enables us to instrument our distributed services, meaning, to collect data from the events that happen in our systems which ultimately help us understand our software’s performance and behavior.&lt;/p&gt;

&lt;p&gt;OpenTelemetry has been widely covered in various posts – You can learn more about OpenTelemetry and distributed tracing here.&lt;/p&gt;

&lt;p&gt;For this guide (and for using OpenTelemetry), &lt;strong&gt;here are the relevant terms you must be familiar with:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Span&lt;/strong&gt;: The basic building block (I like to call it the “atom”) of OpenTelemetry.&lt;br&gt;
A span is an action that occurs in our system, like a POST/GET request or a db.insert operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trace&lt;/strong&gt;: A trace is a tree of spans representing the progression of a single request as it is handled by the different services of your app.&lt;/p&gt;

&lt;p&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%2Fz3katx4dzq8grtk2ttck.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%2Fz3katx4dzq8grtk2ttck.png" alt="distributed traces in Aspecto"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exporter&lt;/strong&gt;: Once we have created a span we need to send it to some backend. It may be in memory, Jaeger, or even as console output. The exporter handles sending the data to our backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual / Automatic instrumentation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual: manually creating a span inside the application code&lt;/li&gt;
&lt;li&gt;Automatic: using instrumentation libraries (like pymongo for mongodb), to automatically create spans for us and send them to the backend through the exporter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to learn more terms, you can see the official documentation related to this here: &lt;a href="https://opentelemetry.io/docs/concepts/data-sources/" rel="noopener noreferrer"&gt;https://opentelemetry.io/docs/concepts/data-sources/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to learn about the advantages of tracing and their comparison to logs, &lt;a href="https://www.aspecto.io/blog/logging-vs-tracing-why-logs-arent-enough-to-debug-your-microservices/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;check out this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aspecto.io/blog/logging-vs-tracing-why-logs-arent-enough-to-debug-your-microservices/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&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%2Fhzz4fzat68ajwk4fykvf.png" alt="distributed tracing vs logging"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Hello World: OpenTelemetry Python
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Create spans and see them in the console output:
&lt;/h3&gt;

&lt;p&gt;Let’s begin by writing some simple code that creates manual spans and logs them to console output.&lt;/p&gt;

&lt;p&gt;1.Start a new python project (python 3.6+ is supported by OpenTelemetry)&lt;/p&gt;

&lt;p&gt;2.Make the following installs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install opentelemetry-api
pip install opentelemetry-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Create a tracing.py file with 2 manual spans created – rootSpan and childSpan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# tracing.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
   BatchSpanProcessor,
   ConsoleSpanExporter,
)

provider = TracerProvider()
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("rootSpan"):
   with tracer.start_as_current_span("childSpan"):
           print("Hello world!")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launch the tracing.py file and this is the output you should see – 2 manual spans:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "name": "childSpan",
    "context": {
        "trace_id": "0x6a37f0f0678f07485a01ba001b1119b0",
        "span_id": "0x4c162caa4e6d10c4",
        "trace_state": "[]"
    },
    "kind": "SpanKind.INTERNAL",
    "parent_id": "0xdaf18d32c6af7c38",
    "start_time": "2022-01-03T14:10:46.601440Z",
    "end_time": "2022-01-03T14:10:46.601490Z",
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {},
    "events": [],
    "links": [],
    "resource": {
        "telemetry.sdk.language": "python",
        "telemetry.sdk.name": "opentelemetry",
        "telemetry.sdk.version": "1.8.0",
        "service.name": "unknown_service"
    }
}
{
    "name": "rootSpan",
    "context": {
        "trace_id": "0x6a37f0f0678f07485a01ba001b1119b0",
        "span_id": "0xdaf18d32c6af7c38",
        "trace_state": "[]"
    },
    "kind": "SpanKind.INTERNAL",
    "parent_id": null,
    "start_time": "2022-01-03T14:10:46.601349Z",
    "end_time": "2022-01-03T14:10:46.601515Z",
    "status": {
        "status_code": "UNSET"
    },
    "attributes": {},
    "events": [],
    "links": [],
    "resource": {
        "telemetry.sdk.language": "python",
        "telemetry.sdk.name": "opentelemetry",
        "telemetry.sdk.version": "1.8.0",
        "service.name": "unknown_service"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started with OpenTelemetry Python and Jaeger – Advancing
&lt;/h2&gt;

&lt;p&gt;Even though our newly created spans are really beautiful in the console, you are (rightfully) not satisfied just by having them. You most likely want to get some visualization of how they play out together.&lt;/p&gt;

&lt;p&gt;That’s probably what got you interested in distributed tracing in the first place.&lt;/p&gt;

&lt;p&gt;You’ll be happy to know about the open-source &lt;a href="https://www.jaegertracing.io/" rel="noopener noreferrer"&gt;Jaeger&lt;/a&gt;, which is a storage backend for telemetry data that also contains a basic UI for visualizing spans and traces.&lt;/p&gt;

&lt;p&gt;You’ll even be happier to know that exporting spans to Jaeger is almost as easy as it was to send to our console output.&lt;/p&gt;

&lt;p&gt;To send the spans to Jaeger, we’d use the OpenTelemetry Jaeger exporter instead of the console span exporter we used before.&lt;/p&gt;

&lt;p&gt;Here’s how it’s done:&lt;/p&gt;

&lt;p&gt;1.Start a new python project (or keep the same one, as you wish)&lt;/p&gt;

&lt;p&gt;2.Run installs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install opentelemetry-exporter-jaeger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Run Jaeger locally&lt;/p&gt;

&lt;p&gt;4.Create a jaeger_tracing.py file with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# jaeger_tracing.py
from opentelemetry import trace
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

trace.set_tracer_provider(
   TracerProvider(
       resource=Resource.create({SERVICE_NAME: "my-hello-service"})
   )
)

jaeger_exporter = JaegerExporter(
   agent_host_name="localhost",
   agent_port=6831,
)

trace.get_tracer_provider().add_span_processor(
   BatchSpanProcessor(jaeger_exporter)
)

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("rootSpan"):
   with tracer.start_as_current_span("childSpan"):
           print("Hello world!")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5.Run the jaeger_tracing.py file &lt;/p&gt;

&lt;p&gt;6.Use a browser to go to &lt;a href="http://localhost:16686/" rel="noopener noreferrer"&gt;http://localhost:16686/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;7.You can now see the Jaeger UI. Select my-hello-service and click on Find traces. You should see your trace with &lt;code&gt;rootSpan&lt;/code&gt; and &lt;code&gt;childSpan&lt;/code&gt; here on the right:&lt;/p&gt;

&lt;p&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%2Fifos3tw5ki0squcfgd7d.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%2Fifos3tw5ki0squcfgd7d.png" alt="rootSpan and childSpan in Jaeger Tracing UI&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;8.After clicking on our &lt;code&gt;rootSpan&lt;/code&gt; from the list you can see more details about it which you can further investigate on your own:&lt;/p&gt;

&lt;p&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%2Fgmy6shqam98zhgtpn2ms.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%2Fgmy6shqam98zhgtpn2ms.png" alt="Jaeger tracing"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  An auto instrumentation example
&lt;/h3&gt;

&lt;p&gt;In real life, you would most likely use auto instrumentation more than you would use manual ones. I chose to start with the manual ones as it’s simpler, to begin with, and understand.&lt;/p&gt;

&lt;p&gt;Let’s say we have a small script that writes data to a MongoDB database using the PyMongo library.&lt;/p&gt;

&lt;p&gt;For us to create and visualize these spans in Jaeger, we would use an automatic instrumentation library forPyMongo. &lt;/p&gt;

&lt;p&gt;In this case, it’s called opentelemetry-pymongo-instrumentation. &lt;/p&gt;

&lt;p&gt;First, let’s start mongo locally using docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d -p 27017:27017 mongo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have updated our script from before to connect to a DB I created called pytest, and saved a post document in a collection called posts. &lt;/p&gt;

&lt;p&gt;After that, it tries to find the exact same document using mongo &lt;code&gt;find_one&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the updated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
   BatchSpanProcessor,
   ConsoleSpanExporter,
)
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from pymongo import MongoClient
from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
import datetime

# Setup tracing
provider = TracerProvider(
       resource=Resource.create({SERVICE_NAME: "my-mongo-service"})
)
jaeger_exporter = JaegerExporter(
   agent_host_name="localhost",
   agent_port=6831,
)
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(BatchSpanProcessor(jaeger_exporter))
PymongoInstrumentor().instrument(tracer_provider=provider)

client = MongoClient('mongodb://localhost:27017')
db = client.pytest

posts = db.posts

# insert post
post = {"author": "Tom",
       "text": "My blog post",
       "date": datetime.datetime.utcnow()}
print('inserting post')
post_id = posts.insert_one(post).inserted_id
print('Inserted post with ID:', post_id)

# find our newly created post
found_post = posts.find_one({"_id":post_id})
print('post is', found_post)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we head back to our Jaeger UI and query for ‘&lt;em&gt;my-mongo-service&lt;/em&gt;’ service, we will see 2 spans have been created. One for inserting the post, and another for finding it. &lt;/p&gt;

&lt;p&gt;All are created automatically by the pymongo instrumentation library.&lt;/p&gt;

&lt;p&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%2Ftig2o40t5ey071yavkom.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%2Ftig2o40t5ey071yavkom.png" alt="Jaeger tracing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why are they not in the same trace?
&lt;/h3&gt;

&lt;p&gt;Well, simply because there was no piece of code that was meant to create any span for our script runner.&lt;/p&gt;

&lt;p&gt;In real life, you would most likely use some web app framework like Django and instrument it, so that a span would be created for a call to your endpoint, becoming the root span and containing these 2 spans under it. But that use-case is out of the scope of this tutorial.&lt;/p&gt;

&lt;p&gt;This is what it would look like if we selected the &lt;code&gt;find.posts&lt;/code&gt; span in Jaeger:&lt;/p&gt;

&lt;p&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%2Fxn4t601ciqv4i98fqv1n.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%2Fxn4t601ciqv4i98fqv1n.png" alt="selected the find.posts span in Jaeger"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Advanced Visualization with Aspecto
&lt;/h2&gt;

&lt;p&gt;By now you should have a basic understanding of what a span is and how OpenTelemetry can be used to add distributed tracing for code written in Python.&lt;/p&gt;

&lt;p&gt;But you probably reached distributed tracing because you wanted to visualize your distributed services and understand them better, which is exactly what &lt;a href="https://www.aspecto.io/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;Aspecto&lt;/a&gt; does.&lt;/p&gt;

&lt;p&gt;Give our &lt;a href="https://app.aspecto.io/play/search?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;Live Playground&lt;/a&gt; a try to get a better idea – it’s free and no sign-up is required.&lt;/p&gt;

&lt;p&gt;At the time of writing this, Aspecto has a &lt;a href="https://www.aspecto.io/pricing/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;free forever plan&lt;/a&gt; that you could start using today.&lt;/p&gt;

&lt;p&gt;Here’s how to do it:&lt;/p&gt;

&lt;p&gt;1.First, create a new free account at &lt;a href="https://www.aspecto.io/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;www.aspecto.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2.Then, let’s install the following packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install opentelemetry-instrumentation
pip install opentelemetry-distro
pip install opentelemetry-exporter-otlp-proto-grpc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Modify your python file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# main.py
from pymongo import MongoClient
import datetime

client = MongoClient('mongodb://localhost:27017')
db = client.pytest

posts = db.posts

# insert post
post = {"author": "Tom",
       "text": "My blog post",
       "date": datetime.datetime.utcnow()}
print('inserting post')
post_id = posts.insert_one(post).inserted_id
print('Inserted post with ID:', post_id)

# find our newly created post
found_post = posts.find_one({"_id":post_id})
print('post is',found_post)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to Aspecto settings and copy your API keys.&lt;/p&gt;

&lt;p&gt;Run like this so that spans are sent to Aspecto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OTEL_SERVICE_NAME=your-service-name 
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://otelcol.aspecto.io:4317 
OTEL_EXPORTER_OTLP_HEADERS=Authorization=your-aspecto-api-key-here opentelemetry-instrument python main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&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%2Fih4wzz06ey35jhoueu66.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%2Fih4wzz06ey35jhoueu66.png" alt="Aspecto distributed tracing Python"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You got yourself a clean list of traces with easy-to-use filters.&lt;/p&gt;

&lt;p&gt;Now let’s dive into one of the traces by selecting it:&lt;/p&gt;

&lt;p&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%2F7tx42dhql7w1gye3960n.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%2F7tx42dhql7w1gye3960n.png" alt="Aspecto distributed services in Python"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See how you got a nice visualization that your service (a python file in this case) has made a call to mongo with the query ‘find.posts’?&lt;/p&gt;

&lt;p&gt;Just imagine how it would look in your production, giving you complete visibility on all your microservices as you troubleshoot issues 🤯&lt;/p&gt;

&lt;p&gt;And that would be it! If you have any questions, feel free to &lt;a href="https://twitter.com/thetomzach" rel="noopener noreferrer"&gt;reach out at any time&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;P.S. If you want to learn more about OpenTelemetry, you can check out this free, 6 episodes, &lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;OpenTelemetry Bootcamp&lt;/a&gt; (vendor-neutral). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&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%2Fzv2dlchpgyub7k14gb2b.png" alt="Learn OpenTelemetry with the OpenTelemetry Bootcamp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s basically your &lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=getting-started-with-opentelemetry-python"&gt;OpenTelemetry playbook&lt;/a&gt; where you will learn everything, from the very basics to scaling and deploying to production:&lt;/p&gt;

&lt;p&gt;Episode 1: OpenTelemetry Fundamentals&lt;br&gt;
Episode 2: Integrate Your Code (logs, metrics and traces)&lt;br&gt;
Episode 3: Deploy to Production + Collector&lt;br&gt;
Episode 4: Sampling and Dealing with High Volumes&lt;br&gt;
Episode 5: Custom Instrumentation&lt;br&gt;
Episode 6: Testing with OpenTelemetry&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>microservices</category>
      <category>opentelemetry</category>
    </item>
    <item>
      <title>How To Use OpenTelemetry With AWS Lambda</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Thu, 11 Nov 2021 16:54:44 +0000</pubDate>
      <link>https://dev.to/aspecto/how-to-use-opentelemetry-with-aws-lambda-87l</link>
      <guid>https://dev.to/aspecto/how-to-use-opentelemetry-with-aws-lambda-87l</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt; is the AWS solution for serverless functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is an open-source meant to create traces and send them to a backend and gain visibility.&lt;/p&gt;

&lt;p&gt;The observability-aware developer which has serverless lambdas as part of his stack will surely tackle the need to connect OpenTelemetry with lambda.&lt;/p&gt;

&lt;p&gt;If you are such a developer, this guide is for you.&lt;/p&gt;

&lt;p&gt;Today, I’ll show you exactly how to deploy a tracing-enabled lambda with OpenTelemetry.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is part of the &lt;a href="https://www.aspecto.io/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=how-to-use-opentelemetry-with-aws"&gt;Aspecto&lt;/a&gt; Hello World series, where we tackle distributed services-related topics for you. Our team searches the web for common issues, then we solve them ourselves and bring you complete how-to guides. Aspecto is an OpenTelemetry-based distributed tracing platform for developers and teams of distributed applications.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting Up
&lt;/h1&gt;

&lt;p&gt;Create a new directory for your project, and add the following package.json (or this packages to your existing project):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "lambda-otel-post",
 "version": "1.0.0",
 "description": "",
 "main": "handler.js",
 "dependencies": {
   "@opentelemetry/api": "1.0.2",
   "@opentelemetry/instrumentation": "0.25.0",
   "@opentelemetry/auto-instrumentations-node": "0.25.0",
   "@opentelemetry/instrumentation-aws-lambda": "0.25.0",
   "@opentelemetry/instrumentation-http": "0.25.0",
   "@opentelemetry/sdk-trace-base": "0.25.0",
   "@opentelemetry/sdk-trace-node": "0.25.0",
   "axios": "^0.24.0"
 },
 "devDependencies": {},
 "scripts": {
   "test": "echo \"Error: no test specified\" &amp;amp;&amp;amp; exit 1"
 },
 "author": "",
 "license": "ISC"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run installs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the handler.js
&lt;/h2&gt;

&lt;p&gt;This code is a simple lambda entry point that contains a call to an external API and returns a message.&lt;/p&gt;

&lt;p&gt;Later on, we will want to make sure that a span has been created for this HTTP call, and also for the actual lambda invocation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use strict';

const axios = require("axios");

module.exports.hello = async (event) =&amp;gt; {
 const todoItem = await axios('https://jsonplaceholder.typicode.com/todos/1');

 return {
   statusCode: 200,
   body: JSON.stringify(
     {
       message: 'Some Message Here',
       input: event,
     },
     null,
     2
   ),
 };

 // Use this code if you don't use the http event with the LAMBDA-PROXY integration
 // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Add the lambda wrapper file that enables tracing with OpenTelemetry
&lt;/h1&gt;

&lt;p&gt;Let’s add the following lambda-wrapper.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { SimpleSpanProcessor, ConsoleSpanExporter } = require("@opentelemetry/sdk-trace-base");
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { AwsLambdaInstrumentation } = require('@opentelemetry/instrumentation-aws-lambda');
const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()))
provider.register();

registerInstrumentations({
 instrumentations: [
   getNodeAutoInstrumentations(),
   new AwsLambdaInstrumentation({
     disableAwsContextPropagation: true
   })
 ],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice I am using &lt;code&gt;ConsoleSpanExporter&lt;/code&gt;, which writes all the telemetry data to the console. &lt;/p&gt;

&lt;p&gt;In production, you would probably want to have this sent to some other tool like &lt;a href="https://www.jaegertracing.io/" rel="noopener noreferrer"&gt;Jaeger&lt;/a&gt; or an &lt;a href="https://www.aspecto.io/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=how-to-use-opentelemetry-with-aws"&gt;observability vendor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For this blog post, however, this exporter will do.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note on &lt;code&gt;disableAwsContextPropagation&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Another thing you’re probably wondering is why I added &lt;code&gt;disableAwsContextPropagation:true&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The reason for this is the lambda instrumentation is trying to use the &lt;a href="https://aws.amazon.com/blogs/developer/category/developer-tools/aws-x-ray/" rel="noopener noreferrer"&gt;X-Ray&lt;/a&gt; context headers by default (even when we’re not using X-Ray), causing us to have a non-sampled context and a &lt;code&gt;NonRecordingSpan&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;To fix this, we use the &lt;code&gt;disableAwsContextPropagation&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;More information about this can be found &lt;a href="https://github.com/open-telemetry/opentelemetry-js-contrib/pull/546" rel="noopener noreferrer"&gt;here&lt;/a&gt; and in the instrumentation &lt;a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-aws-lambda" rel="noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy the lambda
&lt;/h1&gt;

&lt;p&gt;There are various ways of deploying lambda to S3 and this is not the scope of the tutorial.&lt;/p&gt;

&lt;p&gt;I chose to use a serverless framework, but you can also use AWS CLI / other forms to do this.&lt;/p&gt;

&lt;p&gt;If you use serverless, this is the serverless.yml file.&lt;/p&gt;

&lt;p&gt;Do not forget to set the correct region &amp;amp; function name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service: lambda-otel-post

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: '2'

provider:
 name: aws
 runtime: nodejs12.x
 lambdaHashingVersion: 20201221
 environment:
   NODE_OPTIONS: --require lambda-wrapper



 region: eu-west-2

functions:
 tom-otel-lambda-post:
   handler: handler.hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add environment variable
&lt;/h2&gt;

&lt;p&gt;For the tracing code to run, we need to make sure Node requires it before any other file has been required.&lt;/p&gt;

&lt;p&gt;That’s why we need to add this value for the NODE_OPTIONS environment variable: “–require lambda-wrapper”.&lt;/p&gt;

&lt;p&gt;If you use serverless with the file above, it is done for you automatically.&lt;/p&gt;

&lt;p&gt;If not, head to the configuration section of the deployed lambda and set it:&lt;/p&gt;

&lt;p&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%2Fdcvzuzz0gdhg5o3uakwz.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%2Fdcvzuzz0gdhg5o3uakwz.png" alt="AWS configuration section, environment variable "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason for this necessity is that the wrapper file must be included before any other file for the OpenTelemetry instrumentations to work properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the lambda function
&lt;/h2&gt;

&lt;p&gt;Now when you run your lambda(I used the built-in AWS console’s test utility), you should expect to see 2 spans being created – one for the lambda invocation, and the other for the outgoing HTTP call.&lt;/p&gt;

&lt;p&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%2Fxa135ifguf6w0ogqbgar.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%2Fxa135ifguf6w0ogqbgar.png" alt="The AWS console UI for invoking the lambda function"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Indeed, that’s what we get:&lt;/p&gt;

&lt;h2&gt;
  
  
  This is the outgoing HTTP span
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  traceId: '4f373b61315c23fa47605a72b94ab59e',
  parentId: '7ce4ab2283755eda',
  name: 'HTTPS GET',
  id: '54c07955525dad7f',
  kind: 2,
  timestamp: 1635332193754154,
  duration: 82864,
  attributes: {
    'http.url': 'https://jsonplaceholder.typicode.com/todos/1',
    'http.method': 'GET',
    'http.target': '/todos/1',
    'net.peer.name': 'jsonplaceholder.typicode.com',
    'net.peer.ip': '104.21.4.48',
    'net.peer.port': 443,
    'http.host': 'jsonplaceholder.typicode.com:443',
    'http.response_content_length_uncompressed': 83,
    'http.status_code': 200,
    'http.status_text': 'OK',
    'http.flavor': '1.1',
    'net.transport': 'ip_tcp'
  },
  status: { code: 1 },
  events: []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the lambda invocation span:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  traceId: '4f373b61315c23fa47605a72b94ab59e',
  parentId: undefined,
  name: 'lambda-otel-post-dev-tom-otel-lambda-post',
  id: '7ce4ab2283755eda',
  kind: 1,
  timestamp: 1635332193747990,
  duration: 93019,
  attributes: {
    'faas.execution': 'ed075caa-4d54-44f8-96b4-b96085acbf9a',
    'faas.id': 'arn:aws:lambda:eu-west-2:MY-AWS-ID:function:lambda-otel-post-dev-tom-otel-lambda-post',
    'cloud.account.id': 'MY-AWS-ID'
  },
  status: { code: 0 },
  events: []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would be it for today folks, you can now export those spans to wherever you like.&lt;/p&gt;

&lt;p&gt;P.S. If you don’t have an easy way of visualizing these traces just yet, feel free to check out &lt;a href="https://www.aspecto.io/?utm_source=post&amp;amp;utm_medium=dev.to&amp;amp;utm_campaign=how-to-use-opentelemetry-with-aws"&gt;Aspecto&lt;/a&gt; (it’s free). This is what a single trace would look like:&lt;/p&gt;

&lt;p&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%2Fe174usakq3tm1sqpc3s3.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%2Fe174usakq3tm1sqpc3s3.png" alt="Aspecto distributed tracing platform UI trace overview"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Improve Your Integration Tests Using OpenTelemetry</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Thu, 28 Oct 2021 15:12:32 +0000</pubDate>
      <link>https://dev.to/aspecto/how-to-use-opentelemetry-to-improve-your-integration-tests-5h37</link>
      <guid>https://dev.to/aspecto/how-to-use-opentelemetry-to-improve-your-integration-tests-5h37</guid>
      <description>&lt;p&gt;This article is part of the &lt;a href="https://www.aspecto.io/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-use-opentelemetry-to-improve-your-integration-tests"&gt;Aspecto&lt;/a&gt; &lt;strong&gt;Hello World series&lt;/strong&gt;, where we tackle microservices-related topics for you. Our team searches the web for common issues, then we solve them ourselves and bring you complete how-to guides. Aspecto is an OpenTelemetry-based distributed tracing platform for developers and teams of distributed applications.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: this tutorial assumes you are familiar with OpenTelemetry, traces, and spans. If you want to learn more about OpenTelemetry, check out &lt;strong&gt;&lt;a href="https://www.aspecto.io/opentelemetry-bootcamp/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-use-opentelemetry-to-improve-your-integration-tests"&gt;The OpenTelemetry Bootcamp&lt;/a&gt;&lt;/strong&gt;. This is a free, vendor-neutral, six-episode video series that brings you everything you need to know to get started with OpenTelemetry, from zero to hero.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;The evolution of &lt;a href="https://opentelemetry.io/"&gt;OpenTelemetry (OTEL)&lt;/a&gt; in recent years has made it a lot easier for developers that are interested in better understanding their microservices – to instrument their services and gain that desired view.&lt;/p&gt;

&lt;p&gt;But so far, the usage has been mainly for debugging production issues.&lt;/p&gt;

&lt;p&gt;What if I told you there’s a way of utilizing OpenTelemetry’s power to prevent production issues, by using it in your integration test environment?&lt;/p&gt;

&lt;p&gt;Sounds interesting? read on as I show you how it can be done easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it would look like to use OpenTelemetry in an integration test
&lt;/h2&gt;

&lt;p&gt;The end goal is to instrument our service under test while the test is running and make assertions on the created spans.&lt;/p&gt;

&lt;p&gt;We call this: &lt;strong&gt;Trace-Based Testing&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Now you might be starting to think practically on such an implementation and say – “Oh, so I need to integrate the OpenTelemetry SDK now in my test run? Where do I store the spans? How do I get them while the test is already running so that I can assert?”&lt;/p&gt;

&lt;p&gt;Well, those questions are legit indeed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Luckily, you don’t need to implement all that on your own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This use case is exactly what has led to the creation of &lt;a href="https://github.com/aspecto-io/malabi"&gt;Malabi&lt;/a&gt;, an open-source that wraps the OpenTelemetry SDK and does all this setup for you so that you can simply add it to your project and start asserting.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. You can read more about the concept of Trace-Based testing and Malabi &lt;a href="https://www.aspecto.io/blog/trace-based-testing-with-opentelemetry-meet-open-source-malabi/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-use-opentelemetry-to-improve-your-integration-tests"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--paM7h7pc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4p7o51qxxgouned5yt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--paM7h7pc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4p7o51qxxgouned5yt7.png" alt="Malabi open source logo" width="810" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does Malabi help?
&lt;/h2&gt;

&lt;p&gt;(Practical guide below, this is the theoretical part)&lt;/p&gt;

&lt;p&gt;The way it works is simple. &lt;/p&gt;

&lt;p&gt;You add Malabi to your project by using npm or yarn and add 3 lines of code at the top of the main file of the service.&lt;/p&gt;

&lt;p&gt;Then, Malabi uses OpenTelemetry SDK for you and creates spans as your service is run (in the context of an integration test – for example in CI).&lt;/p&gt;

&lt;p&gt;Malabi then &lt;strong&gt;stores these spans in memory and exposes an endpoint&lt;/strong&gt; that lets you access these spans, and gives you utility functions to extract the data that you need for your assertions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical part – how to take your existing NodeJs microservice and utilize OpenTelemetry to make assertions in an integration test
&lt;/h2&gt;

&lt;p&gt;The following code is the ExpressJS code of the microservice that we want to test.&lt;/p&gt;

&lt;p&gt;It is a simple service that uses SQLite as an in-memory database to store &amp;amp; retrieve data about users. It also stores some of the fetched data in a redis cache for faster retrieval. &lt;/p&gt;

&lt;p&gt;Here is the code in the index.ts file:&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1 – The microservice code
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;* Note that you can find the complete code in the Malabi &lt;a href="https://github.com/aspecto-io/malabi/tree/master/examples"&gt;examples&lt;/a&gt; folder:&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;index.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as malabi from 'malabi';
malabi.instrument();
malabi.serveMalabiFromHttpApp(18393);

import axios from 'axios';
import express from 'express';
import body from "body-parser";
import User from "./db";
import { getRedis } from "./redis";
import Redis from "ioredis";
let redis: Redis.Redis;

getRedis().then((redisConn) =&amp;gt; {
   redis = redisConn;
   app.listen(PORT, () =&amp;gt; console.log(`service-under-test started at port ${PORT}`));

})
const PORT = process.env.PORT || 8080;

const app = express();
app.use(body.json())
app.get('/',(req,res)=&amp;gt;{
   res.sendStatus(200);
})
app.get('/todo', async (req, res) =&amp;gt; {
   try {
       const todoItem = await axios('https://jsonplaceholder.typicode.com/todos/1');
       res.json({
           title: todoItem.data.title,
       });
   } catch (e) {
       res.sendStatus(500);
       console.error(e, e);
   }
});

app.get('/users', async (req, res) =&amp;gt; {
   try {
       const users = await User.findAll({});
       res.json(users);
   } catch (e) {
       res.sendStatus(500);
       console.error(e, e);
   }
});

app.get('/users/:firstName', async (req, res) =&amp;gt; {
   try {
       const firstName = req.param('firstName');
       if (!firstName) {
           res.status(400).json({ message: 'Missing firstName in url' });
           return;
       }

       let users = [];
       users = await redis.lrange(firstName, 0, -1);
       if (users.length === 0) {
           users = await User.findAll({ where: { firstName } });
           if (users.length !== 0) {
               await redis.lpush(firstName, users)
           }
       }

       res.json(users);
   } catch (e) {
       res.sendStatus(500);
       console.error(e, e);
   }
});

app.post('/users', async (req, res) =&amp;gt; {
   try {
       const { firstName, lastName } = req.body;
       const user = await User.create({ firstName, lastName });
       res.json(user);
   } catch (e) {
       res.sendStatus(500);
   }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above file, you see all the endpoints of the microservice. Mostly self-explanatory – fetching, storing data in SQLite DB / Redis as cache.&lt;/p&gt;

&lt;p&gt;But notice the top three lines where the Malabi magic happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import * as malabi from 'malabi';
malabi.instrument();
malabi.serveMalabiFromHttpApp(18393);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, we require Malabi (after running npm install –save-dev malabi of course).&lt;/p&gt;

&lt;p&gt;Then, Malabi instruments our service – meaning it will create spans (in memory) as it runs.&lt;/p&gt;

&lt;p&gt;At that point, we tell it to serve the created spans from port 18393.&lt;/p&gt;

&lt;p&gt;In part 2, you will see how we use Malabi util functions to query this endpoint and use Jest to make assertions on them. But first, let’s continue to understand our service.&lt;/p&gt;

&lt;p&gt;This db.ts file that handles SQLite with Sequelize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Sequelize, DataTypes } from 'sequelize';

const sequelize = new Sequelize({
   dialect: 'sqlite',
   storage: ':memory:'
});

const User = sequelize.define('User', {
   firstName: {
       type: DataTypes.STRING,
       allowNull: false
   },
   lastName: {
       type: DataTypes.STRING
   }
});

User.sync({ force: true }).then(() =&amp;gt; {
   User.create({ firstName: "Rick", lastName: 'Sanchez' });
})

export default User;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The redis.ts file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { RedisMemoryServer } from 'redis-memory-server';
import Redis from "ioredis";
const redisServer = new RedisMemoryServer();

export async function getRedis() {
   const host = await redisServer.getHost();
   const port = await redisServer.getPort();
   const redis = new Redis(port, host);
   return redis;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Part 2 – The Test Code
&lt;/h3&gt;

&lt;h4&gt;
  
  
  service-under-test.spec.ts file:
&lt;/h4&gt;

&lt;p&gt;This file will be run using Jest.&lt;/p&gt;

&lt;p&gt;Notice we have the port of the service itself (to call the actual running service, which you would run in the CI environment or locally).&lt;/p&gt;

&lt;p&gt;We also have the Malabi utility functions – &lt;code&gt;fetchRemoteTelemetry&lt;/code&gt;, &lt;code&gt;clearRemoteTelemetry&lt;/code&gt; that like their name suggests – fetch the spans from the endpoint for assertions and clear the in-memory cache (which is useful to clean up between tests to maintain a clean slate each time).&lt;/p&gt;

&lt;p&gt;Take a look at the code, more info follows below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const SERVICE_UNDER_TEST_PORT = process.env.PORT || 8080;
import axios from 'axios';
import { fetchRemoteTelemetry, clearRemoteTelemetry } from 'malabi';
const getTelemetryRepository = async () =&amp;gt; await fetchRemoteTelemetry({ portOrBaseUrl: 18393 });

describe('testing service-under-test remotely', () =&amp;gt; {
   beforeEach(async () =&amp;gt; {
       // We must reset all collected spans between tests to make sure spans aren't leaking between tests.
       await clearRemoteTelemetry({ portOrBaseUrl: 18393 });
   });

   it('successful /todo request', async () =&amp;gt; {
       // call to the service under test - internally it will call another API to fetch the todo items.
       const res = await axios(`http://localhost:${SERVICE_UNDER_TEST_PORT}/todo`);

       // get spans created from the previous call
       const telemetryRepo = await getTelemetryRepository();

       // Validate internal HTTP call
       const todoInteralHTTPCall = telemetryRepo.spans.outgoing().first;
       expect(todoInteralHTTPCall.httpFullUrl).toBe('https://jsonplaceholder.typicode.com/todos/1')
       expect(todoInteralHTTPCall.statusCode).toBe(200);
   });

   it('successful /users request', async () =&amp;gt; {
       // call the service under test
       const res = await axios.get(`http://localhost:${SERVICE_UNDER_TEST_PORT}/users`);

       // get spans created from the previous call
       const telemetryRepo = await getTelemetryRepository();

       // Validating that /users had ran a single select statement and responded with an array.
       const sequelizeActivities = telemetryRepo.spans.sequelize();
       expect(sequelizeActivities.length).toBe(1);
       expect(sequelizeActivities.first.dbOperation).toBe("SELECT");
       expect(Array.isArray(JSON.parse(sequelizeActivities.first.dbResponse))).toBe(true);
   });

   it('successful /users/Rick request', async () =&amp;gt; {
       // call the service under test
       const res = await axios.get(`http://localhost:${SERVICE_UNDER_TEST_PORT}/users/Rick`);

       // get spans created from the previous call
       const telemetryRepo = await getTelemetryRepository();

       const sequelizeActivities = telemetryRepo.spans.sequelize();
       expect(sequelizeActivities.length).toBe(1);
       expect(sequelizeActivities.first.dbOperation).toBe("SELECT");

       const dbResponse = JSON.parse(sequelizeActivities.first.dbResponse);
       expect(Array.isArray(dbResponse)).toBe(true);
       expect(dbResponse.length).toBe(1);
   });

   it('Non existing user - /users/Rick111 request', async () =&amp;gt; {
       // call the service under test
       const res = await axios.get(`http://localhost:${SERVICE_UNDER_TEST_PORT}/users/Rick111`);

       // get spans created from the previous call
       const telemetryRepo = await getTelemetryRepository();

       const sequelizeActivities =  telemetryRepo.spans.sequelize();
       expect(sequelizeActivities.length).toBe(1);
       expect(sequelizeActivities.first.dbOperation).toBe("SELECT");

       const dbResponse = JSON.parse(sequelizeActivities.first.dbResponse);
       expect(Array.isArray(dbResponse)).toBe(true);
       expect(dbResponse.length).toBe(0);

       expect(telemetryRepo.spans.httpGet().first.statusCode).toBe(200);
   });

   it('successful POST /users request', async () =&amp;gt; {
       // call the service under test
       const res = await axios.post(`http://localhost:${SERVICE_UNDER_TEST_PORT}/users`,{
           firstName:'Morty',
           lastName:'Smith',
       });

       expect(res.status).toBe(200);

       // get spans created from the previous call
       const telemetryRepo = await getTelemetryRepository();

       // Validating that /users created a new record in DB
       const sequelizeActivities =  telemetryRepo.spans.sequelize();
       expect(sequelizeActivities.length).toBe(1);
       expect(sequelizeActivities.first.dbOperation).toBe("INSERT");
   });


   /* The expected flow is:
       1) Insert into db the new user (due to first API call; POST /users).
       ------------------------------------------------------------------
       2) Try to fetch the user from Redis (due to the second API call; GET /users/Jerry).
       3) The user shouldn't be present in Redis so fetch from DB.
       4) Push the user object from DB to Redis.
   */
   it('successful create and fetch user', async () =&amp;gt; {
       // Creating a new user
       const createUserResponse = await axios.post(`http://localhost:${SERVICE_UNDER_TEST_PORT}/users`,{
           firstName:'Jerry',
           lastName:'Smith',
       });
       expect(createUserResponse.status).toBe(200);

       // Fetching the user we just created
       const fetchUserResponse = await axios.get(`http://localhost:${SERVICE_UNDER_TEST_PORT}/users/Jerry`);
       expect(fetchUserResponse.status).toBe(200);

       // get spans created from the previous calls
       const telemetryRepo = await getTelemetryRepository();
       const sequelizeActivities = telemetryRepo.spans.sequelize();
       const redisActivities =  telemetryRepo.spans.redis();

       // 1) Insert into db the new user (due to first API call; POST /users).
       expect(sequelizeActivities.first.dbOperation).toBe('INSERT');
       // 2) Try to fetch the user from Redis (due to a second API call; GET /users/Jerry).
       expect(redisActivities.first.dbStatement).toBe("lrange Jerry 0 -1");
       expect(redisActivities.first.dbResponse).toBe("[]");
       // 3) The user shouldn't be present in Redis so fetch from DB.
       expect(sequelizeActivities.second.dbOperation).toBe("SELECT");
       //4) Push the user object from DB to Redis.
       expect(redisActivities.second.dbStatement.startsWith('lpush Jerry')).toBeTruthy();
   });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have fetched the spans, we can use Jest’s expect function to make assertions, as we would in any other test regardless of trace-based testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examined example 1 – test named “successful /users request”:
&lt;/h3&gt;

&lt;p&gt;Let’s examine the above code – for example, the test named “successful /users request”.&lt;/p&gt;

&lt;p&gt;First, we call the service to fetch all users.&lt;/p&gt;

&lt;p&gt;Then, we use the &lt;code&gt;fetchRemoteTelemetry&lt;/code&gt; wrapped by the &lt;code&gt;getTelemetryRepository&lt;/code&gt; function to get the spans from Malabi.&lt;/p&gt;

&lt;p&gt;After that, we use the sequelize accessor to filter only sequelize spans.&lt;/p&gt;

&lt;p&gt;Once we have the sequelize spans at hand, we can assert to have only 1 as we only fetch the DB once.&lt;/p&gt;

&lt;p&gt;We also know it’s a SELECT operation, so we assert that it’s a SELECT operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Examined example 2 – test named “successful create and fetch user”:
&lt;/h3&gt;

&lt;p&gt;Let’s now examine a slightly more complicated test.&lt;/p&gt;

&lt;p&gt;In the indicated test, we create a new user using POST /users. Then, we try to query for that user using GET /users/:firstName.&lt;/p&gt;

&lt;p&gt;As expected, we assert for 200 as you would in any other test.&lt;/p&gt;

&lt;p&gt;Now here again we use Malabi utilities to fetch relevant spans, and store them in variables – one for redis spans and another for sequelize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const telemetryRepo = await getTelemetryRepository();
const sequelizeActivities = telemetryRepo.spans.sequelize();
const redisActivities =  telemetryRepo.spans.redis();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first assertion – we want to make sure that the initial POST operation had caused a DB INSERT operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect(sequelizeActivities.first.dbOperation).toBe('INSERT');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the user was just created, we expect it to not exist in redis, so we assert the redis query to be as we want it and expect the response from redis to be an empty array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect(redisActivities.first.dbStatement).toBe("lrange Jerry 0 -1");
expect(redisActivities.first.dbResponse).toBe("[]");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the user was not present in redis, we expect to have fetched the DB – so assert that the second DB operation select&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect(sequelizeActivities.second.dbOperation).toBe("SELECT");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we expect redis to have received push command to make sure in real life(not in test runs since we clean up everything), subsequent runs would not have to fetch the DB (but take from redis):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expect(redisActivities.second.dbStatement.startsWith('lpush Jerry')).toBeTruthy();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would be it! I hope you can see how simple it can be to use OpenTelemetry &amp;amp; Malabi to write powerful integration tests, in a much easier way than before.&lt;/p&gt;

&lt;p&gt;P.S. Malabi is a relatively new library implementing a new approach, and its authors (myself included) would love to hear your thoughts on it and hear any improvements/suggestions you have. So feel free to open a &lt;a href="https://github.com/aspecto-io/malabi/discussions"&gt;discussion&lt;/a&gt; in GitHub or contact me via &lt;a href="https://twitter.com/thetomzach"&gt;Twitter&lt;/a&gt; DMs.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>microservices</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>How To Write Integration Tests Easily Using Trace-Based Testing</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Wed, 29 Sep 2021 14:19:29 +0000</pubDate>
      <link>https://dev.to/aspecto/how-to-write-integration-tests-easily-using-trace-based-testing-3lm2</link>
      <guid>https://dev.to/aspecto/how-to-write-integration-tests-easily-using-trace-based-testing-3lm2</guid>
      <description>&lt;p&gt;This article is part of the &lt;strong&gt;&lt;a href="https://www.aspecto.io/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=write-integration-tests-trace-based-testing"&gt;Aspecto&lt;/a&gt; Hello World series&lt;/strong&gt;, where we tackle microservices-related topics for you. Our team searches the web for common issues, then we solve them ourselves and bring you complete how-to guides. Aspecto is an OpenTelemetry-based distributed tracing platform for developers and teams of distributed applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Integration tests are never fun to write.&lt;/p&gt;

&lt;p&gt;Setting up an environment, finding a way to reproduce the needed state of it, populating it with relevant data, etc – It’s all work no play.&lt;/p&gt;

&lt;p&gt;In this blog post, I will show you how to write an integration test and talk about why you want to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The motivation for writing an integration test
&lt;/h2&gt;

&lt;p&gt;Let’s begin by talking about why we even want to write one.&lt;/p&gt;

&lt;p&gt;So you finished writing all of the code of your task and everything is working.&lt;/p&gt;

&lt;p&gt;How do you make sure it stays this way and does not break when another developer changes just one tiny thing that happens to break your feature? (Not on purpose of course, or so we hope).&lt;/p&gt;

&lt;p&gt;The solution: Instead of moving onto the next feature right away, you can write an integration test to make sure that your team gets notified if something breaks (by running it in a CI/CD pipeline that alerts you). &lt;/p&gt;

&lt;p&gt;The role of the integration test is to verify that not only does each function you wrote works as a standalone (that’s unit tests), but also to verify that all of the functions play out well together.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Spoiler alert: in this guide, I will also show you how to make sure that a database received the relevant params.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What we will build:&lt;/p&gt;

&lt;p&gt;I like to keep everything as simple as possible, giving you only what you need to get on with your day.&lt;/p&gt;

&lt;p&gt;Therefore, I will build a simple to-do app: a nodejs express service that authenticates the user, with endpoints for creating &amp;amp; viewing his tasks.&lt;/p&gt;

&lt;p&gt;The integration test will verify the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Login&lt;/li&gt;
&lt;li&gt;Create a new TODO item&lt;/li&gt;
&lt;li&gt;Verify the todo item was saved with the relevant email &amp;amp; todo text was saved to the database&lt;/li&gt;
&lt;li&gt;Fetch all todo items&lt;/li&gt;
&lt;li&gt;Verify that the relevant email was used to query the database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tools I will be using:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;NodeJS + Express&lt;/li&gt;
&lt;li&gt;Jest – a framework for authoring and executing nodejs tests&lt;/li&gt;
&lt;li&gt;Malabi – a Trace-Based Testing library that lets me make assertions on real data passed to moving parts like mongodb / elasticsearch and more (I will further elaborate on this below).&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Part 1 – Setting up the tested microservice
&lt;/h1&gt;

&lt;p&gt;Note: If you already have a service you wish to test, feel free to skip to part 2 where I show how to write the actual integration test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 – Set up the project
&lt;/h2&gt;

&lt;p&gt;Let’s use express-generator to generate the initial code for our express app, with the pug view engine.&lt;/p&gt;

&lt;p&gt;Also, let’s install the packages we will be using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx express-generator -v pug
npm i --save jsonwebtoken mongoose passport passport-jwt axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will be storing the data in a MongoDB database, which you can run locally using docker like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker pull mongo
docker run -d -p 27017:27017 mongo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 – create the views
&lt;/h2&gt;

&lt;p&gt;We’ll add a very simple UI for our imaginary users to add TODO items.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;login.pug&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extends layout

block content
 body
   form(action='/auth/login?redirectToTodos=true', method='POST')
     p
       | username:
       input(type='text', name='username', value='tom@a.com')
       |          &amp;lt;br/&amp;gt;password:
       input(type='password', name='password', value='password')
     input(type='submit', value='Submit')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;app.pug&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extends layout

block content
   p Welcome to the app screen &amp;lt;b&amp;gt;#{user}&amp;lt;/b&amp;gt;
   form(action='/todos/', method='POST')
       p
           | new todo text:
           input(type='text', name='username', value='hi')
       input(type='submit', value='Submit')
   ul
     each val in todos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 – Adding routes
&lt;/h2&gt;

&lt;p&gt;In the routes folder, let’s delete the users’ file, we won’t be using it.&lt;/p&gt;

&lt;p&gt;Now place the following code in the index.js file.&lt;/p&gt;

&lt;p&gt;&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 plaintext"&gt;&lt;code&gt;const var express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const TOKEN_SECRET = 'SECRET';

router.get('/login', function(req, res, next) {
 res.render('login', { title: 'Express' });
});

router.post('/auth/login', function(req, res, next) {
 const email = req.body.username;
 const token = jwt.sign({ email }, TOKEN_SECRET, {
   expiresIn: 60 * 60,
 });
 res.cookie('auth', token, { httpOnly: true });
 req.query.redirectToTodos ? res.redirect('/todos') : res.json({ success: true });
});


module.exports = router;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have two endpoints – one for rendering the login view for our users, and the other one for performing the login with a username &amp;amp; password. We’re not verifying passwords here, as authentication is not the focus of the blog.&lt;/p&gt;

&lt;p&gt;The way it works in general: login, sign a JWT token with email, send to client with the response.&lt;/p&gt;

&lt;p&gt;I added a small modification here – redirectToTodos param, to avoid writing client-side code.&lt;/p&gt;

&lt;p&gt;In real life, I would not add it and have the client handle the JSON response. For the purpose of this post, it’s good enough.&lt;/p&gt;

&lt;p&gt;Let’s add another routes file called todos.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const router = express.Router();
const passport = require('passport');
const mongoose = require('mongoose');

async function connectToMongoose() {
 await mongoose.connect('mongodb://localhost:27017/test');
}

connectToMongoose().catch(err =&amp;gt; console.log(err));

const todoSchema = new mongoose.Schema({
 text: String,
 email: String
});

const Todo = mongoose.model('Todo', todoSchema);

router.get('/',
 (req, res, next) =&amp;gt; {
   passport.authenticate('jwt', { session: false },  async (err, user, info) =&amp;gt; {
     if (err) {
       console.log('error is', err);
       res.status(500).send('An error has occurred, we cannot greet you at the moment.');
     }
     else {
       console.log('user is', user);
       console.log('info is', info);
       const { email } = user;
       const todos = await Todo.find({ email });

       res.render('app',{ success: true, user: email, todos });
     }
   })(req, res, next);
 });

router.post('/',
 (req, res, next) =&amp;gt; {
   passport.authenticate('jwt', { session: false },  async (err, user, info) =&amp;gt; {
     if (err) {
       console.log('error is', err);
       res.status(500).send('An error has occurred, we cannot greet you at the moment.');
     }
     else {
       console.log('user is', user);
       console.log('info is', info);
       const todo = new Todo({ text: 'Hey that is my new todo', email: user.email });
       await todo.save();
       res.json({ success: true });
     }
   })(req, res, next);
 });


module.exports = router;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file I define the mongoose Todo model and add two endpoints: GET for retrieving all todos for the current user, and POST for creating one.&lt;/p&gt;

&lt;p&gt;I use the passport-jwt strategy to decode the user’s email (which is sent with the request as a cookie)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 – Adding app.js
&lt;/h2&gt;

&lt;p&gt;The app.js file contains different setups for authentication &amp;amp; routing. I won’t dive into this deeply. If you’re curious feel free to &lt;a href="https://www.aspecto.io/blog/microservices-authentication-strategies-theory-to-practice/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=write-integration-tests-trace-based-testing"&gt;check out my guide to microservices authentication strategies&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var todoRouter = require('./routes/todos');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy,
 ExtractJwt = require('passport-jwt').ExtractJwt;

app.use(passport.initialize());
app.use(passport.session());

app.use('/', indexRouter);
app.use('/todos', todoRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
 next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
 // set locals, only providing error in development
 res.locals.message = err.message;
 res.locals.error = req.app.get('env') === 'development' ? err : {};

 // render the error page
 res.status(err.status || 500);
 res.render('error');
});

const cookieExtractor = function(req) {
 let token = null;
 if (req &amp;amp;&amp;amp; req.cookies)
 {
   token = req.cookies['auth'];
 }
 return token;
};

const TOKEN_SECRET = 'SECRET';

const opts = {
 jwtFromRequest: ExtractJwt.fromExtractors([cookieExtractor]),
 secretOrKey: TOKEN_SECRET,
};

passport.use(
 'jwt',
 new JwtStrategy(opts, (jwt_payload, done) =&amp;gt; {
   try {
     console.log('jwt_payload', jwt_payload);
     done(null, jwt_payload);
   } catch (err) {
     done(err);
   }
 }),
);

module.exports = app;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 5 – Running the app
&lt;/h1&gt;

&lt;p&gt;Now you can simply run the app using and go to localhost:3000/login, login and submit the form to create a todo item and see that it is working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Part 2: Writing an integration test
&lt;/h1&gt;

&lt;p&gt;In this part, we will be using jest &amp;amp; Malabi to write our integration test.&lt;/p&gt;

&lt;p&gt;Jest is a library that lets you author and execute tests for nodejs. &lt;/p&gt;

&lt;p&gt;As for Malabi – an explanation follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aspecto-io/malabi?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=write-integration-tests-trace-based-testing"&gt;Malabi&lt;/a&gt; is an open-source Javascript framework based on OpenTelemetry that allows you to leverage trace data and improve assertion capabilities. This library introduces a new way of testing services: Trace-based testing (TBT).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;em&gt;Here’s a quick tutorial on Malabi and how it works:&lt;/em&gt;&lt;/em&gt;&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/UWtnqGA6E44"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace based testing
&lt;/h2&gt;

&lt;p&gt;So what is Trace Based Testing anyway? Good thing you ask.&lt;/p&gt;

&lt;p&gt;It is a new approach that utilizes &lt;a href="http://opentelemtry.io/"&gt;OpenTelemetry&lt;/a&gt; to our advantage in testing.&lt;/p&gt;

&lt;p&gt;Opentelemetry gives us SDKs that let us instrument our application, meaning to create data of what calls are being made to endpoints, databases and essentially is aimed at giving us visibility on our microservices.&lt;/p&gt;

&lt;p&gt;You can read more about Malabi and Trace-based testing in &lt;a href="https://www.aspecto.io/blog/trace-based-testing-with-opentelemetry-meet-open-source-malabi/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=write-integration-tests-trace-based-testing"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By instrumenting our tested microservice, we’re able to make assertions on what API calls were made, with what parameters. &lt;/p&gt;

&lt;p&gt;We can also assert how a database was queried – in our case, which exact parameter did Mongo receive when it was asked for all todos by the current user. We want to assert the current user and know we don’t have a bug causing us to log in with one user but query another user’s data. &lt;/p&gt;

&lt;p&gt;Malabi wraps the opentelemetry SDK, creates spans (actions performed in the service), and exposes an HTTP endpoint for you to query &amp;amp; assert on.&lt;/p&gt;

&lt;p&gt;Enough theory for now – Let’s begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 – perform installs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev jest malabi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following lines at the top of our app.js file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const malabi = require('malabi');
malabi.instrument();
malabi.serveMalabiFromHttpApp(18393);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Malabi to create spans for us (actions we can assert on, for example – a MongoDB query and an HTTP request are 2 examples of possible spans).&lt;/p&gt;

&lt;p&gt;It also tells malabi to expose an API endpoint at port 18393 that lets the test runner get these spans and then assert on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2- add a new folder &amp;amp; test file: tests/integ.spec.js
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const axios = require('axios').default;
const { fetchRemoteTelemetry, clearRemoteTelemetry } = require('malabi');
const getMalabiTelemetryRepository = async () =&amp;gt; await fetchRemoteTelemetry({ portOrBaseUrl: 18393 });

describe('testing service-under-test remotely', () =&amp;gt; {
 beforeEach(async () =&amp;gt; {
   // We must reset all collected spans between tests to make sure span aren't leaking between tests.
   await clearRemoteTelemetry({ portOrBaseUrl: 18393 });
 });

 it('successful /todo request', async () =&amp;gt; {
   // simulate login
   const loginRes = await axios.post(`http://localhost:3000/auth/login`, { username: 'tom@a.com', password: 'password' });
   const authCookie = loginRes.headers['set-cookie'];

   // Create a new todo item
   const newTodoRes = await axios.post(`http://localhost:3000/todos`, {}, { headers: {
       Cookie: authCookie
   } });
   // console.log('newTodoRes', newTodoRes);

   // call to the service under test - internally it will call another API to fetch the todo items.
   await axios(`http://localhost:3000/todos/`, { headers: {
       Cookie: authCookie
   } });

   // Get instrumented spans
   const repo = await getMalabiTelemetryRepository({ portOrBaseUrl: 13893 });

   // This is the span that holds data from the creation of the new todo item
   const postTodoSpan = repo.spans.mongo().first;
   const emailUsedForCreatingNewTodo = JSON.parse(postTodoSpan.dbStatement).document.email;
   console.log('emailUsedForCreatingNewTodo', emailUsedForCreatingNewTodo);
   // Assert the email was saved equals the email we logged in with
   expect(emailUsedForCreatingNewTodo).toEqual("tom@a.com")

   // This is the span that holds data from the fetching of todos from mongo for current user
   const getTodosSpan = repo.spans.mongo().second;
   const emailUsedInMongoQuery = JSON.parse(getTodosSpan.dbStatement).condition.email;

   // Assert the email was used for querying equals the email we logged in with
   expect(emailUsedInMongoQuery).toEqual("tom@a.com")
 });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note about the above file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getMalabiTelemetryRepository&lt;/code&gt; – fetches spans created in the current run, from the malabi endpoint.&lt;/p&gt;

&lt;p&gt;We can then use the received object to query for specific span types, like all Mongo spans, all http requests, etc.&lt;/p&gt;

&lt;p&gt;We also have clearRemoteTelemetry that cleans up the in-memory cache of spans between test runs. This helps maintain our test clean &amp;amp; not polluted between test runs.&lt;/p&gt;

&lt;p&gt;Notice we have made 2 assertions: one that the post request saved the todo item with the relevant email (the one we logged in with), and the second one verifies that we fetched only todos that belong to that user.&lt;/p&gt;

&lt;p&gt;You can now run the test using the “jest” command, and see that everything is working as expected.&lt;/p&gt;

&lt;p&gt;As you can see, this is a very simple and clean way we can make sure the connections between our different functions and moving parts are working as expected.&lt;/p&gt;

&lt;p&gt;Of course, in production, you would most likely set this up as part of a CI/CD workflow, but this is not the focus of this guide.&lt;/p&gt;

&lt;p&gt;That’s basically it, I hope this guide was useful for you, feel free to reach out to me &lt;a href="https://twitter.com/magnificoder"&gt;@magnificoder&lt;/a&gt; for any questions you may have!&lt;/p&gt;




&lt;p&gt;Feel free to check out some of my other articles, like this one: &lt;a href="https://www.aspecto.io/blog/how-to-deploy-jaeger-on-aws-a-comprehensive-step-by-step-guide/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=write-integration-tests-trace-based-testing"&gt;How to Deploy Jaeger on AWS: a Comprehensive Step-by-Step Guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>microservices</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Deploy Jaeger on AWS: a Comprehensive Step-by-Step Guide</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Thu, 26 Aug 2021 14:08:29 +0000</pubDate>
      <link>https://dev.to/aspecto/how-to-deploy-jaeger-on-aws-a-comprehensive-step-by-step-guide-23pe</link>
      <guid>https://dev.to/aspecto/how-to-deploy-jaeger-on-aws-a-comprehensive-step-by-step-guide-23pe</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is part of the &lt;strong&gt;&lt;a href="https://www.aspecto.io/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-deploy-jaeger-on-aws"&gt;Aspecto&lt;/a&gt; Hello World series&lt;/strong&gt;, where we tackle microservices-related topics for you. Our team searches the web for common issues, then we solve them ourselves and bring you complete how-to guides. Aspecto is an OpenTelemetry-based distributed tracing platform for developers and teams of distributed applications.&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In this tutorial, I will be showing you how to host jaeger on AWS ECS. We will do so step by step: set up an AWS Elasticsearch service domain and use it as a storage backend. For this purpose, we will use the jaeger all-in-one image inside our own ECS cluster &amp;amp; service.&lt;/p&gt;

&lt;p&gt;(Note: for a production use case you’d probably want to use the jaeger images separately and not the all-in-one. We’re doing this to simplify the blog post).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Jaeger
&lt;/h2&gt;

&lt;p&gt;If you’re here you probably already know this, but just in case: jaeger is an open-source distributed tracing system, originally developed by Uber.&lt;/p&gt;

&lt;p&gt;Essentially it stores traces &amp;amp; spans (in a storage backend) and hosts a UI that gives us visibility on these traces and spans (if you’re not familiar with the OpenTelemetry jargon, you can get more info here: &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;https://opentelemetry.io/&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  A small note about security
&lt;/h3&gt;

&lt;p&gt;Before we dive in – it is important to know that the blog post assumes you are running your ES &amp;amp; jaeger in private subnets inside a secured VPC. For additional security measures, go to &lt;a href="https://www.jaegertracing.io/docs/1.25/security/" rel="noopener noreferrer"&gt;https://www.jaegertracing.io/docs/1.25/security/&lt;/a&gt; and check out the official recommendations from Jaeger.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting up AWS Elasticsearch instance
&lt;/h1&gt;

&lt;p&gt;Since the jaeger collector is persistent – it requires a storage backend. You could use memory as a storage backend but it is not suitable for production use cases.&lt;/p&gt;

&lt;p&gt;So we will begin by creating a new elasticsearch domain to serve as a storage backend for our Jaeger. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to AWS ES console.&lt;/li&gt;
&lt;li&gt;Click on “Create a new domain”&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select custom &amp;amp; latest version of ES and hit Next.&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%2F6zu9m7nqtsj8l8flo8j1.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%2F6zu9m7nqtsj8l8flo8j1.png" alt="Choosing the deployment type and version of ES&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let’s set the following settings (but you can of course change them as needed):&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Name: as you wish&lt;/li&gt;
&lt;li&gt;No auto-tuning (you may want this in production)&lt;/li&gt;
&lt;li&gt;1 AZ, 1 Node (note that in production you would probably want at least 2 AZ &amp;amp; nodes for redundancy)&lt;/li&gt;
&lt;li&gt;Instance type: T2.small&lt;/li&gt;
&lt;li&gt;No master node (you may want this in production)
&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%2Fmrp912plfvo95iy5ou09.png" alt="ECS configure domain"&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%2Fj1c51emk29fvry4jrqri.png" alt="ECS data nodes"&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%2F98lv7xu8zitmengprema.png" alt="ECS data node storage"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After everything is set and done – hit Next.&lt;/p&gt;

&lt;p&gt;5.Configuring access &amp;amp; security:&lt;/p&gt;

&lt;p&gt;Since most likely you’re going to send sensitive data to your Jaeger, you want to make sure it is stored securely. Therefore, it is recommended to only allow access within your VPC. AWS recommends a dedicated subnet for each elasticsearch domain. For this tutorial’s purposes, I am choosing an existing private subnet.&lt;/p&gt;

&lt;p&gt;As for security groups, I created a security group that allows open access inside the VPC. For this blog post – I’m ok with it since the elasticsearch is only accessible from private subnets. In production, you may want to be stricter and enable fine-grained access control / different &amp;amp; IP, port settings.&lt;/p&gt;

&lt;p&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%2Fly3zaekdvf9fxh5ichp2.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%2Fly3zaekdvf9fxh5ichp2.png" alt="inbound rules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For domain access policy – to simplify the installation and since we’re inside the VPC – I chose to allow open access to the domain. But here also, for production, you may want to be stricter and allow access from a specific IP address or IAM ARN (like the jaeger IP address for example).&lt;/p&gt;

&lt;p&gt;As for encryption – I’m leaving the defaults, but feel free to modify it to fit your needs.&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%2F2b40yjhbai3o0wj4bp0m.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%2F2b40yjhbai3o0wj4bp0m.png" alt="ECS access policy and encryption "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6.At this point, we’re done. Let’s hit next as needed and let AWS create our ES domain.&lt;/p&gt;
&lt;h1&gt;
  
  
  Deploying Jaeger
&lt;/h1&gt;

&lt;p&gt;In order to deploy Jaeger, we will be using AWS ECS. Jaeger provides us with an all-in-one docker image that contains everything that Jaeger needs to work.&lt;/p&gt;

&lt;p&gt;Let’s create an ECS service running that image.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating our new ECS cluster
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to ECS Cluster -&amp;gt; Create cluster&lt;/li&gt;
&lt;li&gt;Select EC2 Linux + Networking
&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%2F1a3vvd351g2v4z73vo5w.png" alt="Select cluster template"&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure the cluster:&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%2F0zepb7gm2u6srdfbidli.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%2F0zepb7gm2u6srdfbidli.png" alt="Configure cluster"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As for subnets – since I have an AWS VPN client enabled, I put the Jaeger on two private subnets, so that no one from outside the VPC can access its data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For the security group – let’s create a new one and add inbound rules for the following ports from within the VPC. Replace 10.100.0.0/16 with your own VPC address range. You can learn more about the Jaeger ports here: &lt;a href="https://www.jaegertracing.io/docs/1.25/getting-started/" rel="noopener noreferrer"&gt;https://www.jaegertracing.io/docs/1.25/getting-started/.&lt;/a&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%2Fo6448vxnlxovrm628vit.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%2Fo6448vxnlxovrm628vit.png" alt="Jaeger ports"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change anything else if you need it and hit create.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Creating a Task Definition
&lt;/h1&gt;

&lt;p&gt;For us to create an ECS Service we need to define a Task Definition. Head over to Task Definition and hit Create.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Since we chose EC2, we will choose EC2 as for type compatibility:&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%2Fj3d4aubpk0guemscng4v.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%2Fj3d4aubpk0guemscng4v.png" alt="Select EC2 type compatibility"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select “Configure from JSON”. Paste the following JSON in the relevant field:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "requiresCompatibilities": [
   "EC2"
 ],
 "inferenceAccelerators": [],
 "containerDefinitions": [    {
   "dnsSearchDomains": [],
   "environmentFiles": null,
   "logConfiguration": null,
   "entryPoint": [],
   "portMappings": [
     {
       "hostPort": 14269,
       "protocol": "tcp",
       "containerPort": 14269
     },
     {
       "hostPort": 14268,
       "protocol": "tcp",
       "containerPort": 14268
     },
     {
       "hostPort": 6832,
       "protocol": "udp",
       "containerPort": 6832
     },
     {
       "hostPort": 6831,
       "protocol": "udp",
       "containerPort": 6831
     },
     {
       "hostPort": 5775,
       "protocol": "udp",
       "containerPort": 5775
     },
     {
       "hostPort": 14250,
       "protocol": "tcp",
       "containerPort": 14250
     },
     {
       "hostPort": 16685,
       "protocol": "tcp",
       "containerPort": 16685
     },
     {
       "hostPort": 5778,
       "protocol": "tcp",
       "containerPort": 5778
     },
     {
       "hostPort": 16686,
       "protocol": "tcp",
       "containerPort": 16686
     },
     {
       "hostPort": 9411,
       "protocol": "tcp",
       "containerPort": 9411
     }
   ],
   "command": [
     "--collector.zipkin.host-port",
     "9411"
   ],
   "linuxParameters": null,
   "cpu": 1024,
   "environment": [
     {
       "name": "ES_SERVER_URLS",
       "value": "https://some-es-name.some-es-region.es.amazonaws.com"
     },
     {
       "name": "SPAN_STORAGE_TYPE",
       "value": "elasticsearch"
     }
   ],
   "resourceRequirements": null,
   "ulimits": null,
   "dnsServers": [],
   "mountPoints": [],
   "workingDirectory": null,
   "secrets": null,
   "dockerSecurityOptions": [],
   "memory": 1024,
   "memoryReservation": null,
   "volumesFrom": [],
   "stopTimeout": null,
   "image": "jaegertracing/all-in-one:1.25.0",
   "startTimeout": null,
   "firelensConfiguration": null,
   "dependsOn": null,
   "disableNetworking": null,
   "interactive": null,
   "healthCheck": null,
   "essential": true,
   "links": [],
   "hostname": null,
   "extraHosts": null,
   "pseudoTerminal": null,
   "user": null,
   "readonlyRootFilesystem": null,
   "dockerLabels": null,
   "systemControls": [],
   "privileged": null,
   "name": "tom-jaeger-new"
 }],
 "volumes": [],

 "networkMode": null,
 "memory": "2048",
 "cpu": "2048",
 "placementConstraints": [],
 "family": "tom-jaeger-new",
 "taskRoleArn": "arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/ecsTaskExecutionRole",
 "executionRoleArn": "arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/ecsTaskExecutionRole",
 "tags": []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Make sure to make the relevant modifications like your own account id, and copy the correct elasticsearch URL from the AWS ES console, and put it as an environment variable called &lt;code&gt;ES_SERVER_URLS&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Creating the ECS Service
&lt;/h1&gt;

&lt;p&gt;Now we’re ready to create the ECS service that runs Jaeger.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go back to your Jaeger cluster -&amp;gt; services -&amp;gt; create.&lt;/li&gt;
&lt;li&gt;This is the configuration you want (be sure to select the newly created task definition):&lt;/li&gt;
&lt;/ol&gt;

&lt;p&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%2Fjoexyvy6mefhylsvt7wy.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%2Fjoexyvy6mefhylsvt7wy.png" alt="ECS configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;3.Unlike what the screenshot suggests, instead of minimum 100, maximum 200 -we use 0-100 so that we only have 1 instance running and no port issues. Again, we do this to simplify the tutorial.&lt;/p&gt;

&lt;p&gt;4.From here on you can hit next until creating the service. For this tutorial I chose not to create a load balancer. If you feel you need one feel free to create it.&lt;/p&gt;
&lt;h1&gt;
  
  
  Accessing Jaeger UI
&lt;/h1&gt;

&lt;p&gt;Now Jaeger should be up and running. Let’s go to the ECS cluster -&amp;gt; ECS instances -&amp;gt; click on the instance id inside the ECS instances column. That should lead you to the corresponding EC2 instance.&lt;/p&gt;

&lt;p&gt;Clicking on its ID should give you info about the instance. What you are looking for is the instance IP.&lt;/p&gt;

&lt;p&gt;I’m assuming that you are inside the VPC. If you’re not, you may have to find a different way of obtaining network access to your Jaeger like placing it in a public subnet &amp;amp; allowing access to your IP (it is not recommended from a security standpoint).&lt;/p&gt;

&lt;p&gt;Copy the private IP address of the instance, and head over to it in the browser with port 16686. For example: &lt;code&gt;10.100.30.224:16686&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You now have access to the Jaeger UI:&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%2Fj782p7crqmx5jw7ctypw.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%2Fj782p7crqmx5jw7ctypw.png" alt="Jaeger UI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Sending traces to Jaeger
&lt;/h1&gt;

&lt;p&gt;At this point you have the jaeger UI running. Now we need to start sending traces to it.&lt;/p&gt;

&lt;p&gt;If you take a look at Kibana, you can already see that Jaeger created its own &lt;code&gt;jaeger-span-DATE-FORMAT&lt;/code&gt;. Currently, it only contains internal Jaeger spans, but let’s send our own.&lt;/p&gt;

&lt;p&gt;Note: for the simplicity of this tutorial we did not implement any index rollover, but you may want to do so to optimize resources allocated to indices. You can read more about this here: &lt;a href="https://www.jaegertracing.io/docs/1.25/deployment/#elasticsearch-rollover" rel="noopener noreferrer"&gt;https://www.jaegertracing.io/docs/1.25/deployment/#elasticsearch-rollover&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Sending traces to Jaeger with the OpenTelemetry SDK (In NodeJS)
&lt;/h2&gt;

&lt;p&gt;Step 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx express-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Perform npm install&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Install OpenTelemetry libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save @opentelemetry/instrumentation-http 
@opentelemetry/instrumentation-express @opentelemetry/api 
@opentelemetry/node @opentelemetry/exporter-jaeger 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4: Create a tracing.js file (replace the relevant IP to your EC2 IP)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use strict';

const opentelemetry = require('@opentelemetry/api');

// Not functionally required but gives some insight what happens behind the scenes
const { diag, DiagConsoleLogger, DiagLogLevel } = opentelemetry;
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);

const { registerInstrumentations } = require('@opentelemetry/instrumentation');
const { NodeTracerProvider } = require('@opentelemetry/node');
const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes: ResourceAttributesSC } = require('@opentelemetry/semantic-conventions');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');

const Exporter = JaegerExporter;

const { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');
const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');

module.exports = () =&amp;gt; {
 const serviceName = 'serviceName';

 const provider = new NodeTracerProvider({
   resource: new Resource({
     [ResourceAttributesSC.SERVICE_NAME]: serviceName,
   }),
 });
 registerInstrumentations({
   tracerProvider: provider,
   instrumentations: [
     // Express instrumentation expects HTTP layer to be instrumented
     HttpInstrumentation,
     ExpressInstrumentation,
   ],
 });

 const exporter = new Exporter({
   host: '10.100.40.132',
   port: 6832,
 });

 provider.addSpanProcessor(new SimpleSpanProcessor(exporter));

 // Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings
 provider.register();

 return opentelemetry.trace.getTracer('express-example');
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 5: At the top of the app.js file, add this line to enable tracing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require('./tracing')();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 6: Run npm start &amp;amp; go to &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt; in the browser&lt;/p&gt;

&lt;p&gt;Step 7: Back in the Jaeger UI – you can see this trace now:&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%2Fvnbb9is9uc673g1iwdvx.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%2Fvnbb9is9uc673g1iwdvx.png" alt="Jaeger UI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus (faster route) – Sending traces to Jaeger with the Aspecto SDK:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.aspecto.io/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-deploy-jaeger-on-aws"&gt;Aspecto&lt;/a&gt; provides a free and easy-to-use &lt;a href="https://docs.aspecto.io/v1/getting-started/install/install-the-sdk?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-deploy-jaeger-on-aws"&gt;SDK&lt;/a&gt; that can be configured to export traces to Jaeger (and to Aspecto, that enables additional abilities that Jaeger does not have) with only one line of code.&lt;/p&gt;

&lt;p&gt;Step 1: Create a new express app using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx express-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Perform npm installs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
npm install @aspecto/opentelemetry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Register for free at &lt;a href="http://www.aspecto.io" rel="noopener noreferrer"&gt;www.aspecto.io&lt;/a&gt;, and obtain your API key.&lt;/p&gt;

&lt;p&gt;Step 4: At the top of your app.js file, add this (before any import):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require('@aspecto/opentelemetry')({
  local:true,
  aspectoAuth: '*your-aspecto-api-key-goes-here*',
  customZipkinEndpoint: 'http://10.100.40.132:9411/api/v2/spans',
  otCollectorEndpoint: 'http://10.100.40.132:9411/v1/trace',
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not forget to change the IP to your own EC2 IP address.&lt;/p&gt;

&lt;p&gt;Step 5: Let’s modify the route in index.js to /hello and open the browser at localhost:3000/hello.&lt;/p&gt;

&lt;p&gt;Step 6: And voila! our Jaeger is showing the span we just sent:&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%2Fd12peu8x7dnioyjtdi3a.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%2Fd12peu8x7dnioyjtdi3a.png" alt="Jaeger UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see how this looks in Aspecto UI (click on the Aspecto link in your terminal):&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%2Ff3wddvi2e6ooov7is8jb.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%2Ff3wddvi2e6ooov7is8jb.png" alt="Aspecto UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you choose to use it, you don’t even need to maintain Jaeger at all but simply use &lt;a href="https://www.aspecto.io/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-deploy-jaeger-on-aws"&gt;Aspecto&lt;/a&gt;. Feel free to learn more here.&lt;/p&gt;

&lt;p&gt;That’s it, I hope that was helpful for you and wish you years of happy tracing! 😁 Reach out to me &lt;a href="https://twitter.com/magnificoder" rel="noopener noreferrer"&gt;@magnificoder&lt;/a&gt; for any questions you may have.&lt;/p&gt;




&lt;p&gt;Feel free to check out some of my other articles, like this one: &lt;a href="https://www.aspecto.io/blog/microservices-authentication-strategies-theory-to-practice/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=how-to-deploy-jaeger-on-aws"&gt;Microservices Authentication Strategies: Theory to Practice.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>webdev</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Why I started my own bootstrapped software business</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Sat, 31 Jul 2021 15:24:39 +0000</pubDate>
      <link>https://dev.to/tomweiss/why-i-started-my-own-bootstrapped-software-business-mkl</link>
      <guid>https://dev.to/tomweiss/why-i-started-my-own-bootstrapped-software-business-mkl</guid>
      <description>&lt;p&gt;&lt;strong&gt;It all started off at the age of 7:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In hindsight, it all started very early. At the age of 7, I decided I need to find ways to make money. I went with my sister at summer time to sell lemonades at the beach. The business model was great: right at my own home I found 2 investors who happen to be our parents, and have funded the whole adventure. That meant that every dollar that we earned stay as our very own profit.&lt;/p&gt;

&lt;p&gt;Some time later, I decided to extend my services, that included cleaning services, haircuts, deliveries, writing songs: all for a few dollars. Here’s an image of a “marketing brochure” I created at the time describing my services, hoping to count the dollars apparently. Not the best marketing image selection was it? ;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BQjhfxDa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7iu8yd7lajsyfpsinui3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BQjhfxDa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7iu8yd7lajsyfpsinui3.png" alt="My first marketing brochure"&gt;&lt;/a&gt;&lt;br&gt;My first “Marketing brochure”, in Hebrew
  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Helping my dad with his business&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It was at 2009 when my dad decided to quit his job at a hospital as the manager of the technical department, to pursue his own business of selling medical equipment.&lt;/p&gt;

&lt;p&gt;At the time I was only beginning my career as a software engineer, and had a full time job.&lt;/p&gt;

&lt;p&gt;Him being excited doing his own thing, and me being able to see it first hand was the first time I realized that one day I want to do the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;He also needed my help.&lt;/strong&gt; At weekends &amp;amp; evenings when I got back from work I used to work with him: building stock management software for his physical products, went to AdWords courses in google, learned about paid advertising, SEO and everything in between. We even flew abroad to conferences together and negotiated deals with equipment manufacturers.&lt;/p&gt;

&lt;p&gt;It was a great experience, and even though not long after my dad decided having a business was not for him, &lt;strong&gt;the hunger for my own one stayed with me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First international trip: enter the digital nomad dream&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fx0TBZI0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6unq55ws8ko37ns54xhv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fx0TBZI0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6unq55ws8ko37ns54xhv.jpeg" alt="playa-del-carmen-outskirts"&gt;&lt;/a&gt;&lt;br&gt;An image I took at the outskirts of Playa Del Carmen
  &lt;/p&gt;

&lt;p&gt;It was late 2015 when I left my first job and went on my first trip that did not include a ticket back home. Amongst other places, I went to Playa Del Carmen, Mexico, where I stayed for over a month.&lt;/p&gt;

&lt;p&gt;There, at a beach hostel I saw something I never thought I’d ever see: a person working while being on vacation.&lt;/p&gt;

&lt;p&gt;I was shocked, and remember being “angry” at him, saying — why are you working now? You’re here to have fun!&lt;/p&gt;

&lt;p&gt;He said: Yeah, at my job they don’t care where I’m at as long as work is being done. I’d just rather be traveling than staying at home all the time.&lt;/p&gt;

&lt;p&gt;Little did I know this had a name: a Digital Nomad, and little did he know that from that day I swore to do the same.&lt;/p&gt;

&lt;p&gt;Since I fell in love with the culture &amp;amp; beauty of Latin America, I made a rule that until I get my dream come true I have to be there at least for a month every year. Spoiler alert: That did happen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Working at small startups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It was 2016 when I landed at my first tiny startup job. I remember asking recruiters to not send me any company bigger than 15 employees, since I knew that one day I’d start my own and thought it would be wise to learn how it is first hand first.&lt;/p&gt;

&lt;p&gt;And boy did I learn the hard way: 10 months later the first startup ran out of money, so I had to find another one.&lt;/p&gt;

&lt;p&gt;So I did, and again it was one of about the same size, ~15 employees.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f02kjs66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9thuwmvx31jpat6n71u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f02kjs66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9thuwmvx31jpat6n71u.jpg" alt="startup-office"&gt;&lt;/a&gt;&lt;br&gt;Photo by Proxyclick Visitor Management System on Unsplash
  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2019: Forming my own business, and making the nomad dream come true&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After almost 2 years working at the second startup, I started feeling the itch of traveling.&lt;/p&gt;

&lt;p&gt;I had asked my boss to try and work remotely, but he said he does not believe in this type of working. I just knew it was time to resign, even though I loved everything about that job.&lt;/p&gt;

&lt;p&gt;So I did resign, and have then created my own business. I packed my laptop and booked a one way ticket to Latin America. No plan, &lt;strong&gt;I just knew that once you begin you find your way with things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One week before my flight, I met someone very interesting. He runs a site for greetings that is ranked very high in google. He monetizes this site using advertising. And said he knows he could probably make more money but doesn’t want to invest any time in this site.&lt;/p&gt;

&lt;p&gt;I said, “hey: I’m a developer and am free. Let’s do something and split the money.”&lt;/p&gt;

&lt;p&gt;Long story short: a day later I’m building a site that lets users take a picture &amp;amp; a greeting text, edit &amp;amp; send to their loved ones for birthdays, holidays, etc. I had done so in the airplane and after and couldn’t stop until it’s ready due to the excitement of having something that generates a passive income.&lt;/p&gt;

&lt;p&gt;About a month later, it was ready, and &lt;strong&gt;is generating a few passive dollars every day to this day.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seeing my business partner doing this, I just knew bootstrapping was possible. Since then I was determined to get to a point where I have a steady income from my own websites, as fast as possible.&lt;/p&gt;

&lt;p&gt;That meant I had to start my own projects, and combined with inspiration from the indie hackers community it has given me the ideas that led me to 2 indie projects I’m currently working on: one marketplace, one Saas, which when the time comes(very soon) I will share more about.&lt;/p&gt;

&lt;p&gt;As for the DN life — I had stayed a total of 6 months being a digital nomad, building my own things and trying to see if I can find remote work opportunities at websites.&lt;/p&gt;

&lt;p&gt;Then came COVID, which made it easy to find remote work, but less to travel. But it’s ok since I’m back home focusing on growing my business.&lt;/p&gt;

&lt;p&gt;Since the beginning of COVID and to this day, I am working part time as a freelance software engineer, while I pursue my indie dreams. Feel free to &lt;strong&gt;follow me on &lt;a href="https://twitter.com/magnificoder"&gt;Twitter&lt;/a&gt;&lt;/strong&gt; where I document the continuation of my journey.&lt;/p&gt;

</description>
      <category>entrepreneurship</category>
      <category>career</category>
      <category>freelancing</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Microservices Authentication Strategies: Theory to Practice</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Thu, 15 Jul 2021 16:12:11 +0000</pubDate>
      <link>https://dev.to/aspecto/microservices-authentication-strategies-theory-to-practice-86i</link>
      <guid>https://dev.to/aspecto/microservices-authentication-strategies-theory-to-practice-86i</guid>
      <description>&lt;p&gt;In this article, we will walk through common ways of implementing authentication microservices. &lt;/p&gt;

&lt;p&gt;We will have 2 parts: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;1.The theoretical part&lt;/em&gt; talking about OpenID Connect, OAuth 2.0, JWT, etc.&lt;/p&gt;

&lt;p&gt;Here I try to save you time wandering through the web and giving you all the basics you need to understand in order to start coding.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;2.The practical part, where we will implement two Node.js microservices&lt;/em&gt;, one responsible for user authentication via google login, another responsible for greeting the user that has a token created by the previous service. Plus, we add a react JS app to interact with those services.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Theory
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is authentication?
&lt;/h2&gt;

&lt;p&gt;Authentication is the answer users give us when we ask them “Who are you?”. For us to believe users, they need to go through a process providing some proof.&lt;/p&gt;

&lt;p&gt;For example – by providing a username &amp;amp; password or by using a social login provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is authorization?
&lt;/h2&gt;

&lt;p&gt;Authorization is usually relevant when we already know who the user is, thus the user is authenticated (unless we allow anonymous access, but we won’t get into that use case here).&lt;/p&gt;

&lt;p&gt;Our users want to perform certain actions in our system, and the process of checking if they are allowed to do it or not is called authorization.&lt;/p&gt;

&lt;p&gt;(Note: The reason we’re talking about this authorization in an authentication article is that these terms are often confused, and we need to understand it to understand concepts like OAuth 2.0 &amp;amp; OpenID Connect)&lt;/p&gt;

&lt;p&gt;A good real-world analogy for both of the above would be while checking in a hotel room. Authentication is your passport, and authorization is if I’m allowed to enter a certain room (because I booked it).&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth 2.0
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0 is an authorization protocol. To understand it best, let’s remember the days when it did not exist. In the image below you can see a Facebook screen asking for our Gmail password to search for our contacts on Facebook and add them as friends.&lt;/p&gt;

&lt;p&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%2Fhwgwoqg0vuju4yahf646.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%2Fhwgwoqg0vuju4yahf646.png" alt="Facebook from the days before OAuth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Facebook from the days before OAuth, source: &lt;a href="https://oauth.net/videos/" rel="noopener noreferrer"&gt;https://oauth.net/videos/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Think of what this means: Facebook developers would have access to your Gmail password, and essentially all your Gmail data including emails.&lt;/p&gt;

&lt;p&gt;OAuth enables an app like Gmail to grant access solely to specific resources from that app, in this case, your contacts. It does so by creating an access token that can talk to an API and retrieve this data. &lt;/p&gt;

&lt;p&gt;OAuth provides us with 2 tokens: a refresh toke and an access token.&lt;/p&gt;

&lt;p&gt;The access token is short-lived and enables you to access the restricted API.&lt;/p&gt;

&lt;p&gt;The refresh token’s role is to enable us to obtain new access tokens, without requiring the user to log in each time our access token expires, which results in a better user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenID Connect (OIDC)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://openid.net/connect/" rel="noopener noreferrer"&gt;OpenID&lt;/a&gt; is an authentication protocol built on top of OAuth 2.0 and its main addition is the ID token. The ID token is intended for use with client-side applications, whereas the access token provided by OAuth 2.0 is meant to be used with the resource server (the API).&lt;/p&gt;

&lt;p&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%2F4em48u24tw3zkkm5eqj3.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%2F4em48u24tw3zkkm5eqj3.png" alt="OAuth token audiences"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;OAuth token audiences, source: &lt;a href="https://oauth.net/videos/" rel="noopener noreferrer"&gt;https://oauth.net/videos/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  JWT
&lt;/h2&gt;

&lt;p&gt;JWT – &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;Json Web Token&lt;/a&gt; is a standard method for representing claims securely between two parties. The information in the token is digitally signed to avoid tampering. &lt;/p&gt;

&lt;p&gt;While OAuth doesn’t enforce token type, a lot of implementations use JWT tokens to store the refresh &amp;amp; access tokens. OpenID Connect on the other hand – defines that the token must be in the&lt;br&gt;
JWT format.&lt;/p&gt;
&lt;h2&gt;
  
  
  NodeJS Microservices Authentication Strategies
&lt;/h2&gt;

&lt;p&gt;Now that we have a basic understanding of the relevant terms (and before we dive into practice) we can start exploring possibilities to implement authentication in our microservices:&lt;/p&gt;

&lt;p&gt;1.&lt;em&gt;The obvious way&lt;/em&gt;: use a database to store user data, write your logic for creating users, registration, store passwords, etc. You can then create a form in your client-side where the user logs in, and once logged in you can store user information wherever you see fit (cookies, app state, etc).&lt;/p&gt;

&lt;p&gt;2.&lt;em&gt;OpenID Connect&lt;/em&gt;: you can use services like Google &amp;amp; Facebook that would enable your users to log in using their corresponding accounts. Then, you create a corresponding user in your database. Here you don’t need to implement any user creation UI or store passwords.&lt;/p&gt;

&lt;p&gt;When your user logs in you can store the JWT token in a cookie, and your microservice could know who the user is according to that token. It could also allow or forbid certain actions based on that, but that’s out of the scope of this post.&lt;/p&gt;

&lt;p&gt;You could of course combine this with option one, having some of the users created via identity providers like Google/Facebook and others in your own system.&lt;/p&gt;

&lt;p&gt;3.&lt;em&gt;Use an identity as a service tool like &lt;a href="https://auth0.com/" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; / &lt;a href="https://www.okta.com/" rel="noopener noreferrer"&gt;Okta&lt;/a&gt;&lt;/em&gt;, which essentially helps with both use cases above and can save you time implementing everything on your own. I won’t dive into this one, but you can check their websites for more info.&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;em&gt;For the practical part of this guide I have selected option 2, because I feel it gives the most benefit in understanding authentication in Node.js and you will most likely use it anyway, whether directly or under the hood.&lt;/em&gt;
&lt;/h4&gt;
&lt;h1&gt;
  
  
  The Practice
&lt;/h1&gt;

&lt;p&gt;Let’s begin implementing our service with OpenID connect enabled.&lt;/p&gt;

&lt;p&gt;Here’s what we’re going to create:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;account-service – a rest API that handles user creation. We will be using passport with passport-google-oauth strategy, which is based on OpenID Connect.&lt;/li&gt;
&lt;li&gt;greeting-service – a simple rest api that greets the user.&lt;/li&gt;
&lt;li&gt;A React app that lets the user login with google by consuming account-service and greets the user consuming the greeting-service.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Step 1 – Setting up Google project for login
&lt;/h2&gt;

&lt;p&gt;1.Go to &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;https://console.cloud.google.com/&lt;/a&gt; and register if you haven’t done so yet&lt;/p&gt;

&lt;p&gt;2.Create a new project&lt;/p&gt;

&lt;p&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%2Fth0gbwnfpxa9h1mlr69n.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%2Fth0gbwnfpxa9h1mlr69n.png" alt="Google OAuth project creation"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Google OAuth project creation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;3.Select the newly created project, and click on Create Credentials&lt;/p&gt;

&lt;p&gt;4.Select OAuth Client ID&lt;/p&gt;

&lt;p&gt;5.Select Configure Consent Screen&lt;/p&gt;

&lt;p&gt;6.Select Internal &amp;amp; Hit Create&lt;/p&gt;

&lt;p&gt;7.Fill in the name and emails (you can leave the rest blank for now, we will get to it later)&lt;/p&gt;

&lt;p&gt;8.In the scopes screen, click add scopes, and then select &lt;em&gt;userinfo.email&lt;/em&gt;&lt;/p&gt;

&lt;p&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%2F3tee2xf1usjxb1jh5p2i.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%2F3tee2xf1usjxb1jh5p2i.png" alt="Updating selected Google scopes&amp;lt;br&amp;gt;
"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Updating selected Google scopes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;9.Hit Continue. Now we have our consent screen. Let’s go back to credentials &amp;amp; hit create credentials and choose OAuth Client ID&lt;/p&gt;

&lt;p&gt;10.Choose your name, and for authorized redirect URI – add “&lt;a href="http://localhost:5000/auth/google/callback%E2%80%9D" rel="noopener noreferrer"&gt;http://localhost:5000/auth/google/callback”&lt;/a&gt; and hit create&lt;/p&gt;

&lt;p&gt;11.You should now receive a pop-up with client id &amp;amp; client secret. Keep them, we will be using them soon&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2 – Creating account-service
&lt;/h2&gt;

&lt;p&gt;Note: throughout this tutorial, for the sake of simplicity we’re using the default code generated with express-generator. So no good-looking typescript-like things to see here.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Create the project with express-generator &amp;amp; perform the installs needed
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx express-generator --no-view account-service
cd account-service
npm install
npm install --save jsonwebtoken passport passport-google-oauth cors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Express generator created some default routes.
&lt;/h3&gt;

&lt;p&gt;We won’t be using the users’ one, but use the index.js. So I’m deleting references to it. The initial project looks like this.&lt;/p&gt;

&lt;p&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%2F2qps2kk7spwqkyy76cjn.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%2F2qps2kk7spwqkyy76cjn.png" alt="Express generator"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. In bin/www let’s change the port from 3000 to 5000
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var port = normalizePort(process.env.PORT || '5000');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Add passport
&lt;/h3&gt;

&lt;p&gt;Passport is authentication middleware for Nodejs. It’s very simple to use and supports all the options we need so I chose to use it. Passport uses strategies to handle certain types of login. passport-google-login is a strategy for logging in with Google and is based on OpenID Connect.&lt;/p&gt;

&lt;p&gt;In our app.js file – let’s add the following code so that it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const cors = require('cors');
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;

const indexRouter = require('./routes/index');

const app = express();
// This is here for our client side to be able to talk to our server side. you may want to be less permissive in production and define specific domains.
app.use(cors());

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


app.use(passport.initialize());
app.use(passport.session());

app.use('/', indexRouter);

passport.serializeUser(function(user, cb) {
 cb(null, user);
});

passport.deserializeUser(function(obj, cb) {
 cb(null, obj);
});

passport.use(new GoogleStrategy({
   clientID: 'your-google-client-id',
   clientSecret: 'your-google-client-secret',
   callbackURL: "http://localhost:5000/auth/google/callback"
 },
 function(accessToken, refreshToken, profile, done) {
   // here you can create a user in the database if you want to
   return done(null, profile);
 }
));

module.exports = app;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice we have a few interesting things here. &lt;/p&gt;

&lt;p&gt;One – we added cors for the client-side (localhost:3000) to be able to make requests from our server-side. Having security in mind – you probably want to only allow specific domains in production.&lt;/p&gt;

&lt;p&gt;Serialize &amp;amp; deserialize user – these are functions responsible for serializing &amp;amp; deserializing the user to and from the session.&lt;/p&gt;

&lt;p&gt;GoogleStrategy – this is how we tell Passport we would be using google authentication.&lt;/p&gt;

&lt;p&gt;Remember the client id &amp;amp; secret you saved earlier? Now is a good time to insert them. &lt;/p&gt;

&lt;h3&gt;
  
  
  5. Adding authentication routes
&lt;/h3&gt;

&lt;p&gt;Now let’s go to the routes/index.js file and add the relevant routes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const router = express.Router();
const passport = require('passport');
const jwt = require('jsonwebtoken');

router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express' });
});

const TOKEN_SECRET = 'SECRET';

router.get('/auth/google',
 passport.authenticate('google', { scope : ['profile', 'email'] }));

router.get('/auth/google/callback',
 passport.authenticate('google', { failureRedirect: '/error' }),
 function(req, res) {
   const token = jwt.sign({ id: req.user.sub, name: req.user.name }, TOKEN_SECRET, {
     expiresIn: 60 * 60,
   });
   res.cookie('auth', token, { httpOnly: true });
   res.redirect('http://localhost:3000/');
});

module.exports = router;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TOKEN_SECRET – we will be using this to sign our JWT token.&lt;/p&gt;

&lt;p&gt;/auth/google – the actual google login route. &lt;/p&gt;

&lt;p&gt;Users are redirected to Google. Once they are done, they are redirected back to our server at /auth/google/callback. There we can create our JWT token. &lt;/p&gt;

&lt;p&gt;Once created, we add it on the request as an &lt;code&gt;httpOnly&lt;/code&gt; cookie, so that it is not accessible to javascript code (which is good in terms of security). You’ll soon see how this works on the client end.&lt;/p&gt;

&lt;p&gt;When ready, we redirect back to the client-side.&lt;/p&gt;

&lt;p&gt;Side note: we store the name inside the JWT for demonstration purposes, but you probably don’t need it, and may not be a best practice in terms of security.&lt;/p&gt;

&lt;p&gt;Now that we’re done with our account service, let’s go on to the client-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Create a client-side react application
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-react-app auth-strategies-client
cd auth-strategies-client/
yarn add axios
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have a default react app. Let’s modify the app js file to contain a link to google authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import logo from './logo.svg';
import './App.css';

function App() {
 return (
   &amp;lt;div className="App"&amp;gt;
     &amp;lt;header className="App-header"&amp;gt;
       &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
       &amp;lt;a href="http://localhost:5000/auth/google"&amp;gt;Sign in with Google&amp;lt;/a&amp;gt;
     &amp;lt;/header&amp;gt;
   &amp;lt;/div&amp;gt;
 );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after running it with yarn start, it looks 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%2Fmhjaor4jixc446k51rgq.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%2Fmhjaor4jixc446k51rgq.png" alt="React sign in with Google"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on “sign in with Google”. After doing so you should be redirected to Google for authentication.&lt;/p&gt;

&lt;p&gt;(Make sure you run npm start on accounts-service for it to run in port 5000).&lt;/p&gt;

&lt;p&gt;Let’s take a look at what happened after our call to the accounts-service/auth/google/callback:&lt;/p&gt;

&lt;p&gt;1.accounts-service made a POST request to google, which returned an access token &amp;amp; id token.&lt;/p&gt;

&lt;p&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%2Fz74ro7qbw6x881nvp35q.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%2Fz74ro7qbw6x881nvp35q.png" alt="Aspecto live flow viewer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&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%2F4b1l1ycl8keqn02vgfgb.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%2F4b1l1ycl8keqn02vgfgb.png" alt="Zoom in to the response"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Zoom in to the response&lt;/em&gt;&lt;/p&gt;

&lt;p&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%2F5h123jfct98rq8ueyaj4.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%2F5h123jfct98rq8ueyaj4.png" alt="Zoom in to the services"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Zoom in to the services&lt;/em&gt;&lt;/p&gt;
&lt;h5&gt;
  
  
  [P.S. These images are generated using Aspecto’s &lt;a href="https://docs.aspecto.io/v1/live-flows-overview?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=authentication-strategies-theory-to-practice"&gt;live flow viewer&lt;/a&gt;. If you’d like to visualize your services the way I did, you should &lt;a href="https://www.aspecto.io/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=authentication-strategies-theory-to-practice"&gt;try Aspecto&lt;/a&gt; (for free). It takes 2 minutes to start sending traffic].
&lt;/h5&gt;

&lt;p&gt;2.It used that token to make another GET request to google to get the user’s personal info.&lt;/p&gt;

&lt;p&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%2Fs7fy4ady989h9sxq8wjh.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%2Fs7fy4ady989h9sxq8wjh.png" alt="make another GET request to google"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&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%2F0t5nkvioyzp33mmx699s.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%2F0t5nkvioyzp33mmx699s.png" alt="make another GET request to google, Zoom in to the response"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Zoom in to the response&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;3.Our microservice has redirected us back to the client-side, with set-cookie in the response for our auth cookie creation.&lt;/p&gt;

&lt;p&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%2Fnodyjzjwv8ev2dk9df0b.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%2Fnodyjzjwv8ev2dk9df0b.png" alt="Our microservice has redirected us back to the client-side"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore – after that, you will be redirected back to the exact same screen, with one difference: If you open your browser’s DevTools, you should see an auth cookie that is http only:&lt;/p&gt;

&lt;p&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%2Fge75cjepwo16f9sosrce.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%2Fge75cjepwo16f9sosrce.png" alt="Browser’s DevTools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’re ready to make use of this token. That leads us to our greeting-service. Keep the client-side handy as we will modify it soon.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4 – Setting up the greetings-service
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Let’s create our service
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx express-generator --no-view greetings-service
cd greetings-service
npm install
npm install --save passport passport-jwt cors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Remove user.js file and routes in app.js, this is our fresh start:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');

const indexRouter = require('./routes/index');

const app = express();

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);

module.exports = app;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Modify port to 5001 in bin/www
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var port = normalizePort(process.env.PORT || '5001');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Set up passport to read our JWT token by modifying app.js:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const cors = require('cors');

const indexRouter = require('./routes/index');

const app = express();
app.use(cors({ credentials: true, origin: 'http://localhost:3000' }));


app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

const passport = require('passport');
const JwtStrategy = require('passport-jwt').Strategy,
 ExtractJwt = require('passport-jwt').ExtractJwt;

app.use(passport.initialize());
app.use(passport.session());

app.use('/', indexRouter);

const cookieExtractor = function(req) {
 let token = null;
 if (req &amp;amp;&amp;amp; req.cookies)
 {
   token = req.cookies['auth'];
 }
 return token;
};

const TOKEN_SECRET = 'SECRET';

const opts = {
 jwtFromRequest: ExtractJwt.fromExtractors([cookieExtractor]),
 secretOrKey: TOKEN_SECRET,
};

passport.use(
 'jwt',
 new JwtStrategy(opts, (jwt_payload, done) =&amp;gt; {
   try {
     console.log('jwt_payload', jwt_payload);
     done(null, jwt_payload);
   } catch (err) {
     done(err);
   }
 }),
);

module.exports = app;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here we need to pay attention to a few interesting things.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cookieExtractor&lt;/code&gt; is responsible for reading the token from the &lt;code&gt;httpOnly&lt;/code&gt; cookie we created earlier and will be passed along with the request (more on that later).&lt;/p&gt;

&lt;p&gt;Notice we must use the same TOKEN_SECRET we used to create the token in order to read it or we will get an invalid signature error when reading.&lt;/p&gt;

&lt;p&gt;The extractor is then passed to the &lt;code&gt;JwtStrategy&lt;/code&gt;, which is responsible for providing us with the &lt;code&gt;jwt_payload&lt;/code&gt;. We could be fetching more info about the user from the database if we were to add a database, but for the sake of simplicity, I decided not to.&lt;/p&gt;

&lt;p&gt;Now we’ll add our greeting route in index.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const router = express.Router();
const passport = require('passport');

router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express' });
});

router.get('/greetme', (req, res, next) =&amp;gt; {
 passport.authenticate('jwt', { session: false }, (err, user, info) =&amp;gt; {
   if (err) {
     console.log('error is', err);
     res.status(500).send('An error has occurred, we cannot greet you at the moment.');
   }
   else {
     res.send({ success: true, fullName: `${user.name.givenName} ${user.name.familyName}` })
   }
 })(req, res, next);
});


module.exports = router;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens here is that Passport extracts the info from the JWT for us, and all we do is return it to the client.&lt;/p&gt;

&lt;p&gt;Start the greetings-service in port 5001:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 – Modify the client side to send the httpOnly cookie
&lt;/h2&gt;

&lt;p&gt;Since we want the client JWT token to not be accessible to any malicious javascript code, we have stored it in an httpOnly cookie. &lt;/p&gt;

&lt;p&gt;(Side note: in real life, you may want to also make it secure so that it’s only accessible via HTTPS).&lt;/p&gt;

&lt;p&gt;So, we want to perform our greeting request to the greetings-service. For that, we need to send the contents of the cookie to the server. Let’s do that then.&lt;/p&gt;

&lt;p&gt;Back in our client-side react application, we modify App.js by adding a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from 'react';
import axios from "axios";
import logo from './logo.svg';
import './App.css';

function App() {
 const [name, setName] = useState('');
 return (
   &amp;lt;div className="App"&amp;gt;
     &amp;lt;header className="App-header"&amp;gt;
       &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
       &amp;lt;a href="http://localhost:5000/auth/google"&amp;gt;Sign in with Google&amp;lt;/a&amp;gt;
       &amp;lt;br /&amp;gt;
       &amp;lt;button onClick={async () =&amp;gt; {
         const result = await axios.get('http://localhost:5001/greetme', {
           withCredentials: true
         });
         setName(result.data.fullName);
       }}&amp;gt;Greet me please&amp;lt;/button&amp;gt;
       {name &amp;amp;&amp;amp; &amp;lt;span&amp;gt;{`Hi, ${name}`}&amp;lt;/span&amp;gt;}
     &amp;lt;/header&amp;gt;
   &amp;lt;/div&amp;gt;
 );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now once we get a response with the current user’s full name, we would see “Hi, full name”.&lt;/p&gt;

&lt;p&gt;Notice we added a basic axios with &lt;code&gt;withCredentials&lt;/code&gt;: true – that is what makes the cookies pass alongside our request, for the server to extract. &lt;/p&gt;

&lt;p&gt;And this emphasizes what happened here behind the scenes:&lt;/p&gt;

&lt;p&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%2Fw3fssa7zab5oh37598c7.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%2Fw3fssa7zab5oh37598c7.png" alt="Aspecto live flow viewer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple GET request that returns a JSON with the user’s full name as it came from google and stored in the JWT token.&lt;/p&gt;

&lt;p&gt;Here is what we get after clicking the button:&lt;/p&gt;

&lt;p&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%2F4s4s27idexpzgr5w47sj.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%2F4s4s27idexpzgr5w47sj.png" alt="React Tom Zach"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! &lt;/p&gt;

&lt;p&gt;We have successfully created the account service for registration &amp;amp; JWT creation, and the greetings service that knows how to read the JWT token and provide data about the user.&lt;/p&gt;

&lt;p&gt;I hope this helped you get a better understanding of authentication in general, and specifically while implementing it in nodejs.&lt;/p&gt;

&lt;p&gt;Check out some of my other articles, like this one: &lt;a href="https://www.aspecto.io/blog/lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=authentication-strategies-theory-to-practice"&gt;Lerna Hello World: How to Create a Monorepo for Multiple Node Packages&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Lerna Hello World: How to Create a Monorepo for Multiple Node Packages</title>
      <dc:creator>Tom Weiss</dc:creator>
      <pubDate>Wed, 12 May 2021 14:05:26 +0000</pubDate>
      <link>https://dev.to/aspecto/lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages-3188</link>
      <guid>https://dev.to/aspecto/lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages-3188</guid>
      <description>&lt;p&gt;In this post, I will walk you through how to use Lerna to manage, and publish, two packages under the same monorepo. Publishing will be done to my private GitHub repository under the GitHub packages registry.&lt;/p&gt;

&lt;p&gt;I decided to keep it as simple as possible, Lerna-only. No yarn workspaces to be found here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro &amp;amp; Motivation For Using Lerna
&lt;/h2&gt;

&lt;p&gt;Using a monolith, you have a single code base.&lt;/p&gt;

&lt;p&gt;It is usually quite easy to share code between the different parts of the monolith, just import from the relevant file.&lt;/p&gt;

&lt;p&gt;When it comes to microservices, however, by definition – you would have more than one microservice.&lt;/p&gt;

&lt;p&gt;Most likely, you would have shared logic between the microservices, whether it is for everyday authentication purposes, data access, etc.&lt;/p&gt;

&lt;p&gt;Then, one might (rightfully) suggest – let’s use a package. Where do you store that package? Yet another repo. &lt;/p&gt;

&lt;p&gt;So far so good, but what happens when you have 35 shared packages between 18 different microservices? &lt;/p&gt;

&lt;p&gt;You’d agree that it can be quite a hassle to manage all of these repos.&lt;/p&gt;

&lt;p&gt;That is the part where &lt;a href="https://lerna.js.org/" rel="noopener noreferrer"&gt;Lerna&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;A tool that enables us to manage (and publish) as many npm packages as we want in a single repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Github Repository Creation
&lt;/h2&gt;

&lt;p&gt;Create a new private github repository (I called mine learna but call it as you see fit).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Install Lerna &amp;amp; Setup the Project Locally
&lt;/h2&gt;

&lt;p&gt;In order to set up Lerna in our project, we first need to install it globally, create a git repository locally and run lerna init:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --global lerna
git init learna &amp;amp;&amp;amp; cd learna
lerna init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; there are two modes for initializing the Lerna repo independent and fixed. We’re going to use the default one for simplicity reasons. Essentially what it means is all version numbers are tied together and managed in top-level lerna.json. &lt;/p&gt;

&lt;p&gt;Read more about it here: &lt;a href="https://github.com/lerna/lerna#how-it-works" rel="noopener noreferrer"&gt;https://github.com/lerna/lerna#how-it-works&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s link this to our GitHub repository (replace names accordingly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote add origin git@github.com:aspectom/learna.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Create Lerna managed packages
&lt;/h2&gt;

&lt;p&gt;Create two packages, hello-world and aloha-world (with the default options):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna create hello-world
lerna create aloha-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lerna create&lt;/code&gt; is Lerna’s way to help us create packages managed by a Lerna initialized repo.&lt;/p&gt;

&lt;p&gt;Inside both of the packages, modify the corresponding js files to have them greet as we want them to:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;aloha-world.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use strict';

module.exports = alohaWorld;

function alohaWorld() {
 console.log('Aloha World');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;hello-world.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'use strict';

module.exports = helloWorld;

function helloWorld() {
 console.log('Hello World');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have to make a modification in our package.json to contain the GitHub username of our account / organization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "@aspectom/aloha-world",
 "version": "0.0.0",
 "description": "&amp;gt; TODO: description",
 "author": "Tom Z &amp;lt;tom@aspecto.io&amp;gt;",
 "homepage": "",
 "license": "ISC",
 "main": "lib/aloha-world.js",
 "directories": {
   "lib": "lib",
   "test": "__tests__"
 },
 "files": [
   "lib"
 ],
 "repository": {
   "type": "git",
   "url": "git@github.com:aspectom/learna.git"
 },
 "scripts": {
   "test": "echo \"Error: run tests from root\" &amp;amp;&amp;amp; exit 1"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this for both aloha-world and hello-world, and make sure to replace my GitHub username with your own.&lt;/p&gt;

&lt;p&gt;PS: While we’re making managing multiple repos easier, here’s how you can make running multiple microservices locally feels like a walk in the park. It’s a simple, easy-to-use hack we, at &lt;a href="https://www.aspecto.io/" rel="noopener noreferrer"&gt;Aspecto&lt;/a&gt;, came up with to make this process less messy – It’s called the &lt;a href="https://www.aspecto.io/blog/easy-way-to-route-traffic-between-microservices-during-development/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages"&gt;local router&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.aspecto.io/blog/easy-way-to-route-traffic-between-microservices-during-development/?utm_source=dev.to&amp;amp;utm_medium=post&amp;amp;utm_campaign=lerna-hello-world-how-to-create-a-monorepo-for-multiple-node-packages"&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%2Fx3ubszuzm7isd8fbn0bg.png" alt="How to Route Traffic Between Microservices During Development"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point you should have a directory structure that looks 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%2Fu5rwkn400yeckkg7zp2q.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%2Fu5rwkn400yeckkg7zp2q.png" alt="directory structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the root of the repository, add an empty LICENSE.md.&lt;/p&gt;

&lt;p&gt;This will be necessary later to avoid this error when publishing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna WARN ENOLICENSE Packages aloha-world and hello-world are missing a license.
lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.
lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s make our initial commit to GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .  
git commit -m 'Initial commit'
git push -u origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Generating a GitHub Personal Access Token
&lt;/h2&gt;

&lt;p&gt;First, create a GitHub personal access token to publish and read packages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://github.com/settings/profile" rel="noopener noreferrer"&gt;https://github.com/settings/profile&lt;/a&gt;, &lt;/li&gt;
&lt;li&gt;Click on developer settings&lt;/li&gt;
&lt;li&gt;Click on personal access token&lt;/li&gt;
&lt;li&gt;Select write &amp;amp; read packages, which should also mark the repo automatically&lt;/li&gt;
&lt;li&gt;Add a note so that you remember what it’s about and click on generate the token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&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%2F5tp52zh2s79dkci58ntg.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%2F5tp52zh2s79dkci58ntg.png" alt="Generating a GitHub Personal Access Token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, go to your .npmrc file and add the following lines (can be local .npmrc in each repo or global ~/.npmrc, but beware – better to not commit this file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//npm.pkg.github.com/:_authToken=TOKEN
@aspectom:registry=https://npm.pkg.github.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Do not forget&lt;/em&gt;&lt;/strong&gt; to replace TOKEN with the token you have just created, and aspectom with your own GitHub account.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Publishing The Packages to GPR
&lt;/h2&gt;

&lt;p&gt;Now let’s publish these packages to the GitHub package registry so that we can use them in a different project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna publish --registry=https://npm.pkg.github.com/ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you had the following error, you probably omitted the registry part from lerna publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;? Are you sure you want to publish these packages? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
Enter passphrase for key '/Users/tom/.ssh/aspecto_id_rsa': 
lerna info publish Publishing packages to npm...
lerna info Verifying npm credentials
lerna http fetch GET 401 https://registry.npmjs.org/-/npm/v1/user 1370ms
401 Unauthorized - GET https://registry.npmjs.org/-/npm/v1/user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since it tries to go to npm registry instead of GitHub packages.&lt;/p&gt;

&lt;p&gt;And if you had this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna info publish Publishing packages to npm...
lerna notice Skipping all user and access validation due to third-party registry
lerna notice Make sure you're authenticated properly ¯\_(ツ)_/¯
lerna http fetch PUT 404 https://npm.pkg.github.com/hello-world 694ms
lerna ERR! E404 404 Not Found - PUT https://npm.pkg.github.com/hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably forgot to use @YOUR_GITHUB/package-name in one of your package.json files under the “packages” folder.&lt;/p&gt;

&lt;p&gt;In my case – it was the hello-world package.&lt;/p&gt;

&lt;p&gt;After resolving issues (if any) you should receive a success message, and looking at the repository you can see you have 2 packages:&lt;/p&gt;

&lt;p&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%2Fbar114sjqaseyjkz72xb.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%2Fbar114sjqaseyjkz72xb.png" alt="Two packages"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any time you want to publish, you have to make a change and commit it otherwise lerna will say that there’s no change.&lt;/p&gt;

&lt;p&gt;You can make the change or force Lerna to publish by adding &lt;code&gt;--force-publish&lt;/code&gt; to the &lt;code&gt;lerna publish&lt;/code&gt; command, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lerna publish --registry=https://npm.pkg.github.com/ --force-publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Using The Packages in a Different Project
&lt;/h2&gt;

&lt;p&gt;First, create a project to consume the aloha-world and hello-world packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir use-lerna-repo
cd use-lerna-repo/
yarn init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming you’ve used global .npmrc, no further steps needed to consume the packages with yarn or npm install.&lt;/p&gt;

&lt;p&gt;If you used local npmrc in your lerna repo, copy it to the use-lerna-repo root folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add @aspectom/aloha-world
yarn add @aspectom/hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create an index.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const helloWorld = require('@aspectom/hello-world');
const alohaWorld = require('@aspectom/aloha-world');

helloWorld();
alohaWorld();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Package.json for this project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "name": "use-lerna-repo",
 "version": "1.0.0",
 "main": "index.js",
 "license": "MIT",
 "scripts": {
   "start": "node index.js"
 },
 "dependencies": {
   "@aspectom/aloha-world": "^0.0.4",
   "@aspectom/hello-world": "^0.0.4"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run node index.js and you should get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node index.js
Hello World
Aloha World
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voila! We have just finished creating, publishing, and consuming our lerna-managed packages in the one monorepo.&lt;/p&gt;

&lt;p&gt;Good luck, we at Aspecto wish you years of happy packaging and a lot of &lt;a href="https://github.com/aspecto-io" rel="noopener noreferrer"&gt;downloads&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
