<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Distributed Systems｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/distributed-systems/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>Distributed Systems｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/distributed-systems/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Tue, 15 Nov 2022 09:01:13 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/distributed-systems/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>善用分布式追蹤系統，幫你找出程式的效能瓶頸與問題影響範圍（Jaeger with OpenTelemetry Node.js）</title>
      <link>https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/</link>
      <pubDate>Tue, 15 Nov 2022 09:01:13 +0800</pubDate>
      <guid>https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>大型專案免不了要多個團隊合作，而每個團隊熟悉的程式語言可能不同，所部署的服務也可能分佈在多台 Server 上面。在這樣複雜的架構下，如果系統的某個服務出問題，又或是遇到效能瓶頸；即便有 log 紀錄，但在上下依賴不清楚的狀態下，工程師需要花不少時間來釐清問題原因。</description>
      <media:content url="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-og_hu_10728b3352759c18.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>大型專案免不了要多個團隊合作，而每個團隊熟悉的程式語言可能不同，所部署的服務也可能分佈在多台 Server 上面。</p>
<p>在這樣複雜的架構下，如果系統的某個服務出問題，又或是遇到效能瓶頸；即便有 log 紀錄，但在上下依賴不清楚的狀態下，工程師需要花不少時間來釐清問題原因。</p>
<p><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-01.webp" width="1400" height="730" alt="善用分布式追蹤系統，幫你找出程式的效能瓶頸與問題影響範圍（Jaeger with OpenTelemetry Node.js）（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱
一、為什麼需要分布式追蹤系統？
二、如何使用分布式追蹤系統？
三、手把手帶你建立 Node.js 專案 ＆ Jaeger 環境
四、啟動專案，並在 Jaeger UI 上查看自己發出的 Traces
</code></pre><h2 id="一為什麼需要分布式追蹤系統">一、為什麼需要分布式追蹤系統？<a class="h-anchor" href="#%e4%b8%80%e7%82%ba%e4%bb%80%e9%ba%bc%e9%9c%80%e8%a6%81%e5%88%86%e5%b8%83%e5%bc%8f%e8%bf%bd%e8%b9%a4%e7%b3%bb%e7%b5%b1" aria-label="這個段落的連結"></a></h2>
<p>下面舉 2 個簡單的例子：</p>
<p>如果系統每個月會「定時產出月報」給客戶，但這個功能突然壞了，在排查問題時可能會經歷如下痛苦：</p>
<ol>
<li>是「排程」的部分沒執行，還是「產出月報」的功能有問題？</li>
<li>產出月報的功能會呼叫「多個服務」，每個服務分別在不同的 Server 上。</li>
<li>服務間有不同的執行順序與依賴關係，需要按照每個節點排查。</li>
</ol>
<p>假使系統顯示 Dashboard（儀表板）的時間過長被客戶抱怨，工程師在優化時需要以下資訊才能判斷問題：</p>
<ol>
<li>Dashboard 渲染時會呼叫很多 API，而耗時長的 API 背後通常有複雜的商業邏輯（ex：串連多個服務後才得到的結果）。</li>
<li>即便知道哪個 API 是效能瓶頸，但因為串連的服務太多，需要逐一下 log 才能判斷出是哪個環節出了問題。</li>
</ol>
<p>相信有不少人遇過這些困擾，所以今天筆者想要跟大家分享，如何用「Jaeger + OpenTelemetry + Node.js」搭建分布式追蹤系統，來幫我們：</p>
<ol>
<li>快速找出問題來源。</li>
<li>判斷問題的影響範圍。</li>
<li>透過圖像梳理服務的依賴關係，以及依賴的合理性。</li>
<li>分析效能問題。</li>
</ol>
<blockquote>
<p><strong>小提醒</strong>：</p>
<p>每間公司的組織架構不同，第一時間接收錯誤訊息的未必是開發工程師，也可能是負責分析數據的團隊；如果系統的 log 訊息描述不清楚時，他們很難表達到底是哪裡出了問題，但如果使用分布式追蹤系統，他們就能很明確的指出問題，與工程師進行有效溝通。</p>
</blockquote>
<hr>
<h2 id="二如何使用分布式追蹤系統">二、如何使用分布式追蹤系統？<a class="h-anchor" href="#%e4%ba%8c%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8%e5%88%86%e5%b8%83%e5%bc%8f%e8%bf%bd%e8%b9%a4%e7%b3%bb%e7%b5%b1" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>下一節會帶讀者在 local 環境建立一個微型專案，這邊先簡述使用情境。</p>
</blockquote>
<p>我們的 Service A 提供下面 4 支 API：</p>
<ul>
<li><strong>GET /api/reportA</strong>：A 報告故意拋出錯誤。</li>
<li><strong>GET /api/reportB</strong>：B 報告內部會呼叫多個服務。</li>
<li><strong>GET /api/reportC</strong>：C 報告跑很久，為效能瓶頸。</li>
<li><strong>GET /api/allReports</strong>：產出 A、B、C、Other 報告。 我們的 Service B 僅提供下面 1 支 API：</li>
<li><strong>GET /api/reportOther</strong>：Other 報告會呼叫另一台 Server 的 API。</li>
</ul>
<p>如果執行「GET /api/allReports」這隻 API，它的運行架構是：</p>
<pre tabindex="0"><code>genAllReports(Service A)
    ├── reportA
    ├── reportB
    │       ├── reportB-1
    │       └── reportB-2
    ├── reportC
    └── reportOther(Service B)
</code></pre><p>因為在開頭有先說「GET /api/reportA」會拋出錯誤、「GET /api/reportC」是效能瓶頸；但如果在一無所知的狀態下直接呼叫「GET /api/allReports」這隻 API，那發生錯誤時，只能按照傳統的作法來慢慢排查 log。</p>
<p>初步了解專案架構後，下面我們先上幾張 Jaeger 的分析圖：</p>
<p><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-02.webp" width="1400" height="833" alt="二、如何使用分布式追蹤系統？（圖 2）" loading="lazy" decoding="async"></p>
<p>右上角的圖表中，紅色圓點是錯誤的標記，越大的圓點代表內含的 Span（邏輯運行單元）越多。</p>
<p>左邊則是搜尋條件：</p>
<ul>
<li><strong>Service</strong>：要追蹤的服務。</li>
<li><strong>Operations</strong>：曾經觸發過的操作。</li>
<li><strong>Tags</strong>：我們可以為 Span 設計 Tag 方便過濾。</li>
<li><strong>Lookback</strong>：以時間過濾。</li>
<li><strong>Min Duration/Min Duration</strong>：用最小/最大執行時間作為過濾條件。</li>
<li><strong>Limit Results</strong>：筆數顯示。</li>
</ul>
<figure class="figure"><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-03.webp" width="1090" height="793" alt="如果執行過程中有錯誤，會有警示的 label；若使用到不同 Service 也會用不同顏色的 label 區分。" loading="lazy" decoding="async">
  <figcaption>如果執行過程中有錯誤，會有警示的 label；若使用到不同 Service 也會用不同顏色的 label 區分。</figcaption>
</figure>

<p><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-04.webp" width="1400" height="807" alt="二、如何使用分布式追蹤系統？（圖 4）" loading="lazy" decoding="async"></p>
<p>我們可以點擊觀看執行的時間軸（Trace Timeline），上圖是「GET /api/allReports」這隻 API 的 Trace，我們可以很清楚每個 Span 之間的執行順序、依賴關係、執行時間、執行狀況。</p>
<ul>
<li><strong>Duration</strong>：總運行時間耗費 2s。</li>
<li><strong>Services</strong>：關聯的服務為 2（service-a、service-b）。</li>
<li><strong>Depth</strong>：最深的 Span 有 6 層（hadle_report_other 那個）。</li>
<li><strong>Total Spans</strong>：總共有 11 個 Span。</li>
</ul>
<figure class="figure"><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-05.webp" width="1400" height="792" alt="也可以切換到圖形（Trace Graph）介面來觀察。" loading="lazy" decoding="async">
  <figcaption>也可以切換到圖形（Trace Graph）介面來觀察。</figcaption>
</figure>

<p>相信圖片比起文字更有說服力，在圖表與搜尋條件的輔助下，我們能更快找到問題點。</p>
<hr>
<h2 id="三手把手帶你建立-nodejs-專案jaeger-環境">三、手把手帶你建立 Node.js 專案＆Jaeger 環境<a class="h-anchor" href="#%e4%b8%89%e6%89%8b%e6%8a%8a%e6%89%8b%e5%b8%b6%e4%bd%a0%e5%bb%ba%e7%ab%8b-nodejs-%e5%b0%88%e6%a1%88jaeger-%e7%92%b0%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<p>這邊筆者就不細說程式邏輯，主要的目的是讓讀者在 local 端建立分布式追蹤系統，如果懶得按照步驟執行，可以直接到<a href="https://github.com/dean9703111/jaeger-demo">筆者的 Github</a> Clone 一份下來。</p>
<blockquote>
<p>筆者 local 的 Node.js 版本為 v18.12.0</p>
</blockquote>
<p>SETP 1：建立專案、安裝必要套件。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm init -f
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/api
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/resources
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/semantic-conventions
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/sdk-trace-node
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/sdk-trace-base
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/exporter-jaeger
</span></span><span style="display:flex;"><span>npm install --save @opentelemetry/instrumentation-http
</span></span><span style="display:flex;"><span>npm install --save axios
</span></span><span style="display:flex;"><span>npm install --save express
</span></span></code></pre></div><p>SETP 2：新增「docker-compose.yml」貼上如下程式。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;2.0&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">jaeger</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">container_name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">practice-jaeger</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">jaegertracing/all-in-one:1.38</span><span style="color:#6e7681"> </span><span style="color:#8b949e;font-style:italic"># 當時用 1.39 或 latest 有出現一些 bug，故選用此版本</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">COLLECTOR_ZIPKIN_HOST_PORT=:9411</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;5775:5775/udp&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;6831:6831/udp&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;6832:6832/udp&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;5778:5778&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;16686:16686&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;14250:14250&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;14268:14268&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;14269:14269&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;9411:9411&#34;</span><span style="color:#6e7681">
</span></span></span></code></pre></div><p>SETP 3：輸入以下指令把 jaeger 的 Docker 拉下來並執行。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker-compose up -d
</span></span></code></pre></div><p>SETP 4：新增「tracing.js」貼上如下程式，如果你想要對「Opentelemetry」有更深入的了解，可以參考<a href="https://opentelemetry.io/docs/instrumentation/js/getting-started/nodejs/">官網</a>；筆者範例主要是增加「exporter-jaeger、instrumentation-http」這兩個設定。</p>
<blockquote>
<p>有些人分不清 Opentelemetry 跟 Jaeger 的角色定位；簡單來說，Opentelemetry 幫忙產生/收集觀察用的數據，而 Jaeger 則是一種分布式追蹤工具。</p>
</blockquote>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;use strict&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> opentelemetry <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/api&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { registerInstrumentations } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/instrumentation&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { NodeTracerProvider } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/sdk-trace-node&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { Resource } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/resources&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { SemanticResourceAttributes } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/semantic-conventions&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { BatchSpanProcessor } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/sdk-trace-base&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { JaegerExporter } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/exporter-jaeger&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { HttpInstrumentation } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/instrumentation-http&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>module.exports <span style="color:#ff7b72;font-weight:bold">=</span> (serviceName) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> provider <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> NodeTracerProvider({
</span></span><span style="display:flex;"><span>        resource<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72">new</span> Resource({
</span></span><span style="display:flex;"><span>            [SemanticResourceAttributes.SERVICE_NAME]<span style="color:#ff7b72;font-weight:bold">:</span> serviceName,
</span></span><span style="display:flex;"><span>        }),
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> exporter;
</span></span><span style="display:flex;"><span>    exporter <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">new</span> JaegerExporter({ endpoint<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;http://127.0.0.1:14268/api/traces&#34;</span> });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    provider.addSpanProcessor(<span style="color:#ff7b72">new</span> BatchSpanProcessor(exporter));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings
</span></span></span><span style="display:flex;"><span>    provider.register();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    registerInstrumentations({
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">// // when boostraping with lerna for testing purposes
</span></span></span><span style="display:flex;"><span>        instrumentations<span style="color:#ff7b72;font-weight:bold">:</span> [
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">new</span> HttpInstrumentation(),
</span></span><span style="display:flex;"><span>        ],
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> opentelemetry.trace.getTracer(<span style="color:#a5d6ff">&#39;my-service-tracer&#39;</span>);
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>SETP 5：新增「service-a.js」貼上如下程式，我們在這裡建立 Service A 的 4 支 API。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> api <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;@opentelemetry/api&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> tracer <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;./tracing&#39;</span>)(<span style="color:#a5d6ff">&#39;service-a&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> express <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;express&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> PORT <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;8080&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> app <span style="color:#ff7b72;font-weight:bold">=</span> express();
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> axios <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;axios&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> waitFor <span style="color:#ff7b72;font-weight:bold">=</span> delay =&gt; <span style="color:#ff7b72">new</span> Promise(resolve =&gt; setTimeout(resolve, delay));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> reportA() {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> span <span style="color:#ff7b72;font-weight:bold">=</span> tracer.startSpan(<span style="color:#a5d6ff">&#39;report_a&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">await</span> waitFor(<span style="color:#a5d6ff">500</span>);
</span></span><span style="display:flex;"><span>  span.setAttribute(<span style="color:#a5d6ff">&#39;report&#39;</span>, <span style="color:#a5d6ff">&#39;A&#39;</span>)
</span></span><span style="display:flex;"><span>  span.recordException(<span style="color:#ff7b72">new</span> Error(<span style="color:#a5d6ff">&#39;Report A not found&#39;</span>))
</span></span><span style="display:flex;"><span>  span.setStatus({ code<span style="color:#ff7b72;font-weight:bold">:</span> api.SpanStatusCode.ERROR, message<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;Something wrong!&#34;</span> })
</span></span><span style="display:flex;"><span>  span.addEvent(<span style="color:#a5d6ff">&#39;log&#39;</span>, {
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;log.severity&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;error&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;log.message&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;Report A not found&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;endReport.id&#39;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#39;123&#39;</span>,
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  span.end()
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">throw</span> <span style="color:#a5d6ff">&#34;Report A not found&#34;</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> reportB() {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> <span style="color:#ff7b72">await</span> tracer.startActiveSpan(<span style="color:#a5d6ff">&#39;report_b&#39;</span>, <span style="color:#ff7b72">async</span> (span) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> waitFor(<span style="color:#a5d6ff">100</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> child1 <span style="color:#ff7b72;font-weight:bold">=</span> tracer.startSpan(<span style="color:#a5d6ff">&#39;report_b_1&#39;</span>)
</span></span><span style="display:flex;"><span>    child1.end()
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> child2 <span style="color:#ff7b72;font-weight:bold">=</span> tracer.startSpan(<span style="color:#a5d6ff">&#39;report_b_2&#39;</span>)
</span></span><span style="display:flex;"><span>    child2.end()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    span.setAttribute(<span style="color:#a5d6ff">&#39;report&#39;</span>, <span style="color:#a5d6ff">&#39;B&#39;</span>)
</span></span><span style="display:flex;"><span>    span.end()
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> { <span style="color:#a5d6ff">&#34;b&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;context&#34;</span> }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> reportC() {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">let</span> span <span style="color:#ff7b72;font-weight:bold">=</span> tracer.startSpan(<span style="color:#a5d6ff">&#39;report_c&#39;</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">await</span> waitFor(<span style="color:#a5d6ff">2000</span>);
</span></span><span style="display:flex;"><span>  span.setAttribute(<span style="color:#a5d6ff">&#39;report&#39;</span>, <span style="color:#a5d6ff">&#39;C&#39;</span>)
</span></span><span style="display:flex;"><span>  span.end()
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">return</span> { <span style="color:#a5d6ff">&#34;c&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;context&#34;</span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> reportOther() {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> span <span style="color:#ff7b72;font-weight:bold">=</span> tracer.startSpan(<span style="color:#a5d6ff">&#39;report_other_request&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> result;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">await</span> api.context.<span style="color:#ff7b72">with</span>(api.trace.setSpan(api.context.active(), span),<span style="color:#ff7b72">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#ff7b72">const</span> { data } <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> axios.get(<span style="color:#a5d6ff">`http://localhost:8081/api/reportOther`</span>);
</span></span><span style="display:flex;"><span>      result <span style="color:#ff7b72;font-weight:bold">=</span> data;
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    span.end()
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> result
</span></span><span style="display:flex;"><span>  } <span style="color:#ff7b72">catch</span> (e) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">throw</span> e
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>app.get(<span style="color:#a5d6ff">&#34;/api/allReports&#34;</span>, <span style="color:#ff7b72">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  tracer.startActiveSpan(<span style="color:#a5d6ff">&#39;gen_all_reports&#39;</span>, <span style="color:#ff7b72">async</span> (span) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">const</span> promises <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>      reportA(),
</span></span><span style="display:flex;"><span>      reportB(),
</span></span><span style="display:flex;"><span>      reportC(),
</span></span><span style="display:flex;"><span>      reportOther()
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> Promise.allSettled(promises);
</span></span><span style="display:flex;"><span>    span.end()
</span></span><span style="display:flex;"><span>    res.send(result);
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>app.get(<span style="color:#a5d6ff">&#34;/api/reportA&#34;</span>, <span style="color:#ff7b72">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> reportA()
</span></span><span style="display:flex;"><span>    res.send(result);
</span></span><span style="display:flex;"><span>  } <span style="color:#ff7b72">catch</span> (e) {
</span></span><span style="display:flex;"><span>    res.send(e);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>app.get(<span style="color:#a5d6ff">&#34;/api/reportB&#34;</span>, <span style="color:#ff7b72">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> reportB()
</span></span><span style="display:flex;"><span>    res.send(result);
</span></span><span style="display:flex;"><span>  } <span style="color:#ff7b72">catch</span> (e) {
</span></span><span style="display:flex;"><span>    res.send(e);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>app.get(<span style="color:#a5d6ff">&#34;/api/reportC&#34;</span>, <span style="color:#ff7b72">async</span> (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> result <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> reportC()
</span></span><span style="display:flex;"><span>    res.send(result);
</span></span><span style="display:flex;"><span>  } <span style="color:#ff7b72">catch</span> (e) {
</span></span><span style="display:flex;"><span>    res.send(e);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app.listen(parseInt(PORT, <span style="color:#a5d6ff">10</span>), () =&gt; {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">`Service A listening for requests on http://localhost:</span><span style="color:#a5d6ff">${</span>PORT<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>SETP 6：新增「service-b.js」貼上如下程式，我們在這裡建立的 API 是要給 Service A 呼叫的。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> tracer <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#39;./tracing&#39;</span>)(<span style="color:#a5d6ff">&#39;service-b&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> express <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;express&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> PORT <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;8081&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> app <span style="color:#ff7b72;font-weight:bold">=</span> express();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app.get(<span style="color:#a5d6ff">&#34;/api/reportOther&#34;</span>, (req, res) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">let</span> span <span style="color:#ff7b72;font-weight:bold">=</span> tracer.startSpan(<span style="color:#a5d6ff">&#39;handle_report_other&#39;</span>);
</span></span><span style="display:flex;"><span>    span.end()
</span></span><span style="display:flex;"><span>    res.send({ <span style="color:#a5d6ff">&#34;other&#34;</span><span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#a5d6ff">&#34;context&#34;</span> });
</span></span><span style="display:flex;"><span>  } <span style="color:#ff7b72">catch</span> (e) {
</span></span><span style="display:flex;"><span>    res.send(e);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app.listen(parseInt(PORT, <span style="color:#a5d6ff">10</span>), () =&gt; {
</span></span><span style="display:flex;"><span>  console.log(<span style="color:#a5d6ff">`Service B listening for requests on http://localhost:</span><span style="color:#a5d6ff">${</span>PORT<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">`</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><hr>
<h2 id="四啟動專案並在-jaeger-ui-上查看自己發出的-traces">四、啟動專案，並在 Jaeger UI 上查看自己發出的 Traces<a class="h-anchor" href="#%e5%9b%9b%e5%95%9f%e5%8b%95%e5%b0%88%e6%a1%88%e4%b8%a6%e5%9c%a8-jaeger-ui-%e4%b8%8a%e6%9f%a5%e7%9c%8b%e8%87%aa%e5%b7%b1%e7%99%bc%e5%87%ba%e7%9a%84-traces" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>建議使用有分頁功能的終端機（如 iTerm2），這樣比較好觀察與操作。</p>
</blockquote>
<p>在專案根目錄下輸入 <code>node service-a.js</code> 啟動 Service A 服務，並在另一個分頁輸入 <code>node service-b.js</code> 啟動 Service B 服務。</p>
<p>然後在終端機第三個分頁複製貼上以下指令，呼叫 API。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl http://localhost:8080/api/reportA
</span></span><span style="display:flex;"><span>curl http://localhost:8080/api/reportB
</span></span><span style="display:flex;"><span>curl http://localhost:8080/api/reportC
</span></span><span style="display:flex;"><span>curl http://localhost:8081/api/reportOther
</span></span><span style="display:flex;"><span>curl http://localhost:8080/api/allReports
</span></span></code></pre></div><p><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-06.webp" width="1400" height="772" alt="四、啟動專案，並在 Jaeger UI 上查看自己發出的 Traces（圖 6）" loading="lazy" decoding="async"></p>
<p>如果都按照步驟執行，在瀏覽器的網址輸入「 <a href="http://localhost:16686/search">http://localhost:16686/search</a> 」就可以開始追蹤你所設定的 Traces 嚕！</p>
<p><img src="https://deanlin.net/blog/202211/distributed-tracing-jaeger-opentelemetry/images/distributed-tracing-jaeger-opentelemetry-07.webp" width="1400" height="824" alt="四、啟動專案，並在 Jaeger UI 上查看自己發出的 Traces（圖 7）" loading="lazy" decoding="async"></p>
<h3 id="一些小建議">一些小建議<a class="h-anchor" href="#%e4%b8%80%e4%ba%9b%e5%b0%8f%e5%bb%ba%e8%ad%b0" aria-label="這個段落的連結"></a></h3>
<ul>
<li>雖然分布式追蹤系統能幫我們許多忙，但如果所有的服務都靠它追蹤，那產生的 Record 數量會非常龐大，所以建議「追蹤重要的服務」為主。</li>
<li>這邊為了方便 Demo，筆者使用 http 來做 instrumentation 的舉例，但實務上 RPC(Remote Procedure Call) 的使用率可能更高。</li>
</ul>
<p>筆者也是剛學習分布式追蹤系統的新人，如果描述有誤，或是有其他建議，再麻煩大神們不吝賜教，感謝～</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
