開發與維運

Sentry(v20.12.1) K8S 雲原生架構探索,JavaScript 性能監控之管理 Transactions

Automatic Instrumentation


要自動捕獲 transactions,必須首先在應用程序中啟用跟蹤。

@sentry/tracing 包提供了一個 BrowserTracing 集成,以添加 automatic instrumentation 來監視瀏覽器應用程序的性能。

What Automatic Instrumentation Provides

BrowserTracing 集成為每個頁面 load 和 navigation 事件創建一個新 transaction,併為在打開這些 transactions 時發生的每個 XMLHttpRequestfetch 請求創建一個 child span。進一步瞭解 traces, transactions, and spans。

Enable Automatic Instrumentation

要啟用此自動跟蹤,請在 SDK 配置選項中包含 BrowserTracing 集成。(請注意,使用 ESM 模塊時,主要的 @sentry/* import 必須先於 @sentry/tracing import。)

配置完成後,在 sentry.io 中查看 transactions 時,您將同時看到 pageloadnavigation

ESM

// If you're using one of our integration packages, like `@sentry/react` or `@sentry/angular`,
// substitute its name for `@sentry/browser` here
import * as Sentry from "@sentry/browser";
import { Integrations as TracingIntegrations } from "@sentry/tracing"; // Must import second
Sentry.init({
  dsn: "https://[email protected]/0",
  integrations: [
    new Integrations.BrowserTracing({
      tracingOrigins: ["localhost", "my-site-url.com", /^\//],
      // ... other options
    }),
  ],
  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

CDN

Sentry.init({
  dsn: "https://[email protected]/0",
  integrations: [
    new Sentry.Integrations.BrowserTracing({
      tracingOrigins: ["localhost", "my-site-url.com", /^\//],
      // ... other options
    }),
  ],
  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

Configuration Options

您可以將許多不同的選項傳遞給 BrowserTracing 集成(作為 {optionName: value} 形式的對象),但是它具有合理的默認值。有關所有可能的選項,請參見 TypeDocs。

tracingOrigins

tracingOrigins 的默認值是 ['localhost', /^\//]。JavaScript SDK 將 sentry-trace header 附加到其目標包含列表中的字符串或匹配列表中的正則表達式的所有傳出的 XHR/fetch 請求。如果您的前端向另一個域發出請求,則需要在其中添加它,以將 sentry-trace header 傳播到後端服務,這是將 transactions 鏈接在一起作為單個跟蹤的一部分所必需的。tracingOrigins 選項與整個請求 URL 匹配,而不僅僅是域。使用更嚴格的正則表達式來匹配 URL 的某些部分,可以確保請求不用不必要地附加 sentry-trace header。

例如:

  • 前端應用程序是從 example.com 提供的
  • 後端服務由 api.example.com 提供
  • 前端應用程序對後端進行 API 調用
  • 因此,該選項需要這樣配置:new Integrations.BrowserTracing({tracingOrigins: ['api.example.com']})
  • 現在,向 api.example.com 發出的 XHR/fetch 請求將獲得附加的 sentry-trace header

Sentry.init({
  dsn: "https://[email protected]/0",
  integrations: [
    new Integrations.BrowserTracing({
      tracingOrigins: ["localhost", "my-site-url.com"],
    }),
  ],
  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

您將需要配置您的 Web 服務器 CORS 以允許 sentry-trace header。該配置可能類似於 "Access-Control-Allow-Headers: sentry-trace",但是該配置取決於您的設置。如果您不允許使用 sentry-trace header,則該請求可能會被阻止。

beforeNavigate

對於 pageloadnavigation transactions,BrowserTracing 集成使用瀏覽器的 window.location API 生成 transaction 名稱。要自定義 pageloadnavigation transactions 的名稱,您可以向 BrowserTracing 集成提供 beforeNavigate 選項。該選項允許您修改 transaction 名稱以使其更通用,例如,名為 GET /users/12312012GET /users/11212012 的 transactions 都可以重命名為 GET /users/:userid,以便他們可以在一起。

import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
Sentry.init({
  dsn: "https://[email protected]/0",
  integrations: [
    new Integrations.BrowserTracing({
      beforeNavigate: context => {
        return {
          ...context,
          // You could use your UI's routing library to find the matching
          // route template here. We don't have one right now, so do some basic
          // parameter replacements.
          name: location.pathname
            .replace(/\d+/g, "<digits>")
            .replace(/[a-f0-9]{32}/g, "<hash>"),
        };
      },
    }),
  ],
  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

shouldCreateSpanForRequest

此函數可用於過濾掉不需要的 spans,例如 XHR 的運行狀況檢查或類似的檢查。默認情況下,shouldCreateSpanForRequest 已經過濾掉了除了 tracingOrigins 中定義的內容以外的所有內容。

import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
Sentry.init({
  dsn: "https://[email protected]/0",
  integrations: [
    new Integrations.BrowserTracing({
      shouldCreateSpanForRequest: url => {
        // Do not create spans for outgoing requests to a `/health/` endpoint
        return !url.match(/\/health\/?$/);
      },
    }),
  ],
  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

Manual Instrumentation

要手動捕獲 transactions,必須首先在應用程序中啟用跟蹤。

要手動 instrument 代碼的某些區域,可以創建 transactions 來捕獲它們。

這對於所有 JavaScript SDK(後端和前端)均有效,並且獨立於 ExpressHttpBrowserTracing 集成而工作。

const transaction = Sentry.startTransaction({ name: "test-transaction" });
const span = transaction.startChild({ op: "functionX" }); // This function returns a Span
// functionCallX
span.finish(); // Remember that only finished spans will be sent with the transaction
transaction.finish(); // Finishing the transaction will send it to Sentry

例如,如果要為頁面上的用戶交互創建 transaction,請執行以下操作:

// Let's say this function is invoked when a user clicks on the checkout button of your shop
shopCheckout() {
  // This will create a new Transaction for you
  const transaction = Sentry.startTransaction('shopCheckout');
  // set the transaction on the scope so it picks up any errors
  hub.configureScope(scope => scope.setSpan(transaction));
  // Assume this function makes an xhr/fetch call
  const result = validateShoppingCartOnServer();
  const span = transaction.startChild({
    data: {
      result
    },
    op: 'task',
    description: `processing shopping cart result`,
  });
  processAndValidateShoppingCart(result);
  span.finish();
  transaction.finish();
}

這個例子將發送一個 transaction shopCheckout 到 Sentry。交易將包含一個 task span,該 span 衡量 processAndValidateShoppingCart 花費了多長時間。最後,對 transaction.finish() 的調用將完成transaction 並將其發送給 Sentry。

在為異步操作創建 spans 時,您還可以利用 Promises。但是請記住,必須在調用 transaction.finish() 之前將其 span 包含在事務中。

例如:

function processItem(item, transaction) {
  const span = transaction.startChild({
    op: "http",
    description: `GET /items/:item-id`,
  });
  return new Promise((resolve, reject) => {
    http.get(`/items/${item.id}`, response => {
      response.on("data", () => {});
      response.on("end", () => {
        span.setTag("http.status_code", response.statusCode);
        span.setData("http.foobarsessionid", getFoobarSessionid(response));
        span.finish();
        resolve(response);
      });
    });
  });
}

Connect Backend and Frontend Transactions


要將後端和前端 transactions 連接到單個一致的跟蹤中,Sentry 使用 trace_id 值,該值在前端和後端之間傳播。根據情況,此 ID 可以在請求 header 或 HTML <meta> 標記中傳輸。以這種方式鏈接 transactions 使您可以在 Sentry UI 中在它們之間進行導航,因此您可以更好地瞭解系統的不同部分如何相互影響。您可以在我們的分佈式跟蹤文檔中瞭解有關此模型的更多信息。

Pageload

在前端和後端都啟用跟蹤並利用自動前端 instrumentation 功能時,可以將前端上自動生成的 pageload transaction 與後端上的為頁面服務提供請求的 transaction 相連接。因為在瀏覽器中運行的 JavaScript 代碼無法讀取當前頁面的響應 headers,所以 trace_id 必須在響應本身中傳輸,尤其是在從後端發送的 HTML <head> 中的 <meta> 標籤中。

<html>
  <head>
    <meta name="sentry-trace" content="{{ span.toTraceparent() }}" />
    <!-- ... -->
  </head>
</html>

name 屬性必須是字符串 "sentry-trace"content 屬性必須由後端的 Sentry SDK 使用 span.toTraceparent()(或等效項,取決於後端平臺)生成。這保證了將為每個請求生成一個新的唯一值。

span 引用是為 HTML 提供服務的 transaction,或其任何 child spans。它定義了 pageload transaction 的父級。

一旦數據被包含在 <meta> 標籤中,我們的 BrowserTracing 集成將自動獲取數據並將其鏈接到在 pageload 時生成的 transaction。(請注意,它不會鏈接到自動生成的 navigation transactions,即不需要重新加載整個頁面的 transaction。每個 transaction 都是後端不同請求 transaction 的結果,因此應具有唯一的 trace_id。)

Navigation and Other XHR Requests

加載頁面後,它發出的任何請求(以及後端產生的任何請求)都通過請求 header 鏈接。

就像上面討論的 <meta> 標籤一樣,標題的名稱是 sentry-trace,其值是通過調用 span.toTraceparent()(或等效的)來獲得的,其中 span 是相關 transaction 或其任何子項。

Sentry 的所有與跟蹤相關的集成(BrowserTracingHttpExpress)都會針對它們生成的所有 transactions 和 spans 自動生成或拾取並傳播此 header。在手動創建 transaction 或 span 的任何情況下,您都可以自己附加和讀取 header,這樣做很有意義。

Control Data Truncation

當前,每個標籤的最大字符數限制為200個字符。超過200個字符限制的標籤將被截斷,丟失潛在的重要信息。要保留此數據,您可以將數據拆分為多個標籤。

例如,一個200多個字符標記的請求:

https://empowerplant.io/api/0/projects/ep/setup_form/?user_id=314159265358979323846264338327&tracking_id=EasyAsABC123OrSimpleAsDoReMi&product_name=PlantToHumanTranslator&product_id=161803398874989484820458683436563811772030917980576

上面200個字符以上的請求將被截斷為:

https://empowerplant.io/api/0/projects/ep/setup_form/?user_id=314159265358979323846264338327&tracking_id=EasyAsABC123OrSimpleAsDoReMi&product_name=PlantToHumanTranslator&product_id=1618033988749894848

相反,使用 span.set_tagspan.set_data 會使用結構化元數據保留此查詢的詳細信息。這可以通過 baseUrlendpointparameters 完成:

const baseUrl = "https://empowerplant.io";
const endpoint = "/api/0/projects/ep/setup_form";
const parameters = {
  user_id: 314159265358979323846264338327,
  tracking_id: "EasyAsABC123OrSimpleAsDoReMi",
  product_name: PlantToHumanTranslator,
  product_id: 161803398874989484820458683436563811772030917980576,
};
const span = transaction.startChild({
  op: "request",
  description: "setup form",
});
span.setTag("baseUrl", baseUrl);
span.setTag("endpoint", endpoint);
span.setData("parameters", parameters);
// you may also find some parameters to be valuable as tags
span.setData("user_id", parameters.user_id);
http.get(`${base_url}/${endpoint}/`, (data = parameters));

Group Transactions

Sentry 捕獲 transactions 時,將為它們分配一個 transaction 名稱。該名稱通常由 Sentry SDK 根據您使用的框架集成自動生成。如果您無法利用自動 transaction 生成(或想要自定義 transaction 名稱的生成方式),則可以使用,在使用配置初始化 SDK 時註冊的全局事件處理器。

在 node.js 應用程序中執行此操作的示例:

import { addGlobalEventProcessor } from "@sentry/node";
addGlobalEventProcessor(event => {
  // if event is a transaction event
  if (event.type === "transaction") {
    event.transaction = sanitizeTransactionName(event.transaction);
  }
  return event;
});

對於使用 BrowserTracing 集成的瀏覽器 JavaScript 應用程序,beforeNavigate 選項可用於根據 URL 更好地將 navigation/pageload transactions 分組在一起。

import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";
Sentry.init({
  // ...
  integrations: [
    new Integrations.BrowserTracing({
      beforeNavigate: context => {
        return {
          ...context,
          // You could use your UI's routing library to find the matching
          // route template here. We don't have one right now, so do some basic
          // parameter replacements.
          name: location.pathname
            .replace(/\d+/g, "<digits>")
            .replace(/[a-f0-9]{32}/g, "<hash>"),
        };
      },
    }),
  ],
});

Retrieve an Active Transaction

如果要將 Spans 附加到已在進行中的 transaction 中,例如在對 transaction 進行分組時,可以使用 Sentry.getCurrentHub().getScope().getTransaction()。當 scope 中有正在運行的 transaction 時,此函數將返回一個 Transaction 對象,否則它將返回 undefined。如果您使用的是 BrowserTracing 集成,則默認情況下,我們會將 transaction 附加到 Scope,因此您可以執行以下操作:

function myJsFunction() {
  const transaction = Sentry.getCurrentHub()
    .getScope()
    .getTransaction();
  if (transaction) {
    let span = transaction.startChild({
      op: "encode",
      description: "parseAvatarImages",
    });
    // Do something
    span.finish();
  }
}

Leave a Reply

Your email address will not be published. Required fields are marked *