iT邦幫忙

2024 iThome 鐵人賽

DAY 19
1
DevOps

全端監控技術筆記---從Sentry到Opentelemetry系列 第 19

Day19-- 寫一個有關trace的sdk(下)--攔截發起的請求

  • 分享至 

  • xImage
  •  

前言

在前一篇中,能夠在NodeJS express的專案中使用宣告的方式來自動增加中間件、攔截請求,並且獲取traceparent header。

下一步,就是要處理「發起請求時,附上traceparent header」,也就是對請求的覆蓋。

而在NodeJS中,網路HTTP請求幾乎都是由httphttps這兩個庫來實現,所以我們就專注在對這兩個庫的請求覆蓋。

覆蓋 http/https 請求

  • 先保存原本的http request 方法:
const originalHttpRequest = http.request
  • 然後覆蓋 http.request 方法。覆蓋的邏輯我們就先console看看,然後返回上一步我們保存的 originalHttpRequest 函數執行
http.request = (options, callback) => {
    console.log('---intercept http');
    return originalHttpRequest(options, callback)
};
  • demo看看,把邏輯寫在demo.js中:
const http = require('http');

const demoStart = () => {
    const originalHttp = http.request;

    http.request = (option, callback) => {
        console.log('---intercept HTTP request');
        return originalHttp(option, callback);
    };
};
  • 根文件中的最上層引入並執行覆蓋函數
const { demoStart } = require('./utils/demo');
demoStart();

const express = require('express');
const cors = require('cors');

....
  • 然後在一支api中請求一個mock server,例如在之前demo中的python server
app.get('/demo', async (req, res) => {
    const data = await axios.get(`${PYTHON_SERVICE_URL}/api/demo-01`);
    res.send('demo success');
});
  • 運行後,呼叫該api,可以看到terminal的結果,console了HTTP請求

image

Propagation 和 Context

根據上述,我們了解到了覆蓋Http請求的方法。接下來是要在請求中增加traceparent header,然後再發出請求給其他service。但,要如何獲取到當前的traceId和spanId、把其當作parentSpanId來傳給其他service?

在上一篇「攔截請求」中,我們是先簡單在請求過來的時候,檢查請求的headers;但這個作法沒辦法讓其他feature(如現在的覆蓋請求)獲取到當時的上下文。此時,需要獲取到當前這個請求的trace 上下文,在Opentelemetry中就是 Context Propagation

Context Propagation

Context 就是包含每個請求或操作的 traceId 和 spanID 等請求鏈路上下文,在 OpenTelemetry 中,Context 負責攜帶這些追蹤資料,它的作用是確保在你的應用程式中,不論是同一個請求的不同部分還是跨服務,這些追蹤資料都能夠正確傳遞下去。

它是在應用程式內部保持 tracing data 的一種機制,例如在處理一個請求的時候,Context就會確保在這個應用程式內部都可以共享同一個 tracing 上下文。(不過目前的demo,我們先直接用應用內的全局變量來存儲 context。)

然後當需要對其他服務發送請求的時候,Context需要透過 HTTP headers(例如 traceparent)來把該上下文傳給下一個服務,這個機制就是 Propagation---在請求header中、對context的解析以及注入

簡單程式碼如下:

class Propagation {
    inject(context, headers) {
        const traceparent = `00-${context.traceId}-${context.spanId}-01`;
        headers['traceparent'] = traceparent;
    }

    extract(headers) {
        const traceparent = headers['traceparent'];
        if (traceparent) {
            const parts = traceparent.split('-');
            return {
                traceId: parts[1],
                spanId: parts[2],
            };
        }
        return null;
    }
}

整合起來

完整的對http攔截的demo的程式碼如下:

 _interceptHttp() {
        this.originalHttpsRequest = http.request;
        // 攔截發起的 HTTP 請求,附上 traceparent header
        http.request = (options, callback) => {
            // 記錄請求
            const currSpan = this._tracingHandler(options);
            // 注入trace context
            propagation.inject(
                { traceId: currSpan.traceId, spanId: currSpan.spanId },
                options.headers,
            );
            const req = this.originalHttpsRequest(options, (response) => {
                // 當請求完成時,結束 span
                currSpan.end();
                if (callback) callback(response);
            });

            req.on('error', (error) => {
                console.error(`Request error: ${error.message}`);
                currSpan.end(); // 當請求失敗時也結束 span
            });
            return req;
        };
    }

接著我們嘗試呼叫另一個 server 的 api,在該 api 的 controller 中打印 headers ,可以看到有包含 traceparent ,代表我們注入的 trace context 成功傳遞到另一個service中

image

小結

在今天和昨天的兩篇文章中,我們完成了在opentelemetry協議中的trace簡單demo,能夠攔截請求、獲取trace id、新增span id、然後再傳遞給下一個service。

完整程式碼可以在此 Github repository查看。

補充

在之後的研究中,才發現也可以直接使用 http.Server.prototype來攔截接收到的請求;而使用 express middleware 來攔截主要是可以更精確追蹤到請求經過哪些middleware、route等。

ref

ChangeLog

  • 20241003--補充內文和小結
  • 20240924--初稿

上一篇
Day18--寫一個有關trace的sdk(上)--攔截接收的請求
下一篇
Day20--簡單demo看看 Opentelemetry metric + Prometheus
系列文
全端監控技術筆記---從Sentry到Opentelemetry30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言