使用OpenTelemetry的自訂檢測 custom-instrumentation

為您的環境啟用OpenTelemetry時,AEM as a Cloud Service會追蹤每個要求並自動匯出JVM、HTTP和AEM量度。 該遙測描述了AEM。 它會顯示頁面花費900毫秒來呈現,以及呈現的元件。 它無法顯示您自己的程式碼正在做什麼,例如存放庫查詢是用於哪個清單專案,以及它是否找到任何內容,或是哪些頁面會因作者遺漏欄位而回覆為昂貴的搜尋。

您可以使用OpenTelemetry API自行新增該知識。 程式碼建立的範圍和屬性會透過自動遙測傳輸:相同的服務名稱、相同的處理、相同的APM提供者。 您不需要在程式碼中設定端點、認證或匯出工具。

如果您剛開始使用AEM as a Cloud Service上的OpenTelemetry,請從教學課程透過OpenTelemetry的可觀察性開始,瞭解其運作方式。 然後依照設定OpenTelemetry以深入分析AEM應用程式為您的環境啟用它。

此頁面上的範例檢測WKND參考網站。 每個範例都說明您想要達成的目的、要新增的程式碼,以及您可以在APM提供者中預期的結果。 完整的檢測程式碼(包括宣告於跨區上的單元測試)位於WKND存放庫的tutorial/otel-custom-instrumentation分支上。

NOTE
自訂檢測需要為您的環境啟用OpenTelemetry。 請參閱設定整合。

自訂檢測如何運作 how-it-works

  • Adobe在您的AEM環境中執行的OpenTelemetry Java代理程式包含OpenTelemetry SDK和匯出工具。 您的程式碼僅依賴OpenTelemetry API。
  • 當您的程式碼呼叫GlobalOpenTelemetry時,代理程式會將其連線到自己的實作。 您的範圍會成為執行範圍之要求追蹤的一部分,而您的遙測會隨環境遙測的其餘部分匯出。
  • 當代理程式不存在時(本機AEM SDK、單元測試或環境未啟用OpenTelemetry),API不會執行任何動作,成本極低。 您可以將相同的程式碼部署到每個環境。
新增內容
如何
已傳送給您的APM提供者
範圍
Tracer或@WithSpan註解
是
範圍上的屬性、事件和錯誤狀態
Span API
是
與追蹤關聯的記錄專案
您現有的SLF4J記錄
是,在WARN層級以上啟用記錄時
NOTE
自訂量度(例如您的程式碼建立的計數器和色階分佈圖)預計將在未來版本推出。

設定您的專案 set-up-project

步驟1:新增OpenTelemetry API add-api

將OpenTelemetry API和檢測註解新增至專案上層pom.xml的dependencyManagement區段(範圍為provided):

<properties>
    <opentelemetry.version>1.61.0</opentelemetry.version>
    <opentelemetry-instrumentation.version>2.27.0</opentelemetry-instrumentation.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <version>${opentelemetry.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.instrumentation</groupId>
            <artifactId>opentelemetry-instrumentation-annotations</artifactId>
            <version>${opentelemetry-instrumentation.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

然後將兩個相依性(不含版本)新增至使用它們的每個套件組合的pom.xml,例如core/pom.xml:

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-instrumentation-annotations</artifactId>
</dependency>

使用opentelemetry-api 1.61.0和opentelemetry-instrumentation-annotations 2.27.0或更早版本。 它們與AEM as a Cloud Service執行的OpenTelemetry Java代理程式相符,而且較新版本的代理程式會繼續支援它們。 在代理程式支援的版本之後新增的API功能沒有作用。

步驟2:將API內嵌於您的套件組合中 embed-api

AEM as a Cloud Service不會匯出OpenTelemetry API套件,因此使用該API的每個套件組合都必須包含自己的復本。 將下列指示新增至套件組合的bnd設定。 使用AEM專案原型,這就是core/pom.xml中的bnd-maven-plugin設定。 保留您的Import-Package指示已有的任何專案。

Import-Package: !io.opentelemetry.*,javax.annotation;version=0.0.0,*
-includepackage: io.opentelemetry.*
指示
為何需要
在Import-Package開頭的!io.opentelemetry.*
此API會參考AEM未提供的選用OpenTelemetry套件,且只有在存在時才載入。 若沒有此排除專案,您的套件組合會匯入這些套件、無法啟動,且Cloud Manager建置失敗。 請參閱疑難排解。
-includepackage: io.opentelemetry.*
將​complete API複製到您的套件組合中。 代理程式只有在存在整個API時,才會將您套件的API復本連線到其本身的實施。 如果您只內嵌程式碼參考的封裝(例如,使用-conditionalpackage),則會捨棄您的範圍與屬性,而不會發生錯誤。

每個使用API的套件都內嵌其自己的復本,而代理程式會個別連線每個復本。

CAUTION
請勿將OpenTelemetry SDK、匯出工具或SDK自動設定模組新增至您的專案,也不要建立您自己的OpenTelemetrySdk執行個體或呼叫GlobalOpenTelemetry.set()。 以這種方式傳送的遙測會略過代理程式、移除所傳送的資料中描述的Cloud Service基礎結構詳細資料,以及您設定的端點。

步驟3:建立追蹤器 create-tracer

為您的程式碼基底建立一個Tracer,並在任何地方重複使用。 在WKND中,有一個小類別儲存它:

package com.adobe.aem.guides.wknd.core.telemetry;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;

public final class WkndTelemetry {

    /** Instrumentation scope: identifies telemetry produced by WKND code in your APM provider. */
    public static final String SCOPE = "com.adobe.aem.guides.wknd";

    public static final Tracer TRACER = GlobalOpenTelemetry.getTracer(SCOPE);

    private WkndTelemetry() {
    }
}

範圍名稱會出現在您透過此追蹤器建立的每個範圍上,作為檢測範圍(或程式庫名稱)。 使用可識別程式碼的名稱,例如專案的基本Java套件。

您的範圍顯示位置 where-spans-appear

在AEM as a Cloud Service上,頁面請求已經從AEM本身產生範圍。 程式碼建立的範圍會新增到其中。 例如,這是WKND Magazine頁面追蹤中「影像清單」元件的路徑:

GET /*                                                     the request (AEM)
└─ SlingMainServlet.service                                Sling request processing (AEM)
   └─ AEM component: cq:Page                               (AEM)
      └─ AEM component: wknd/components/page               the page component (AEM)
         └─ HTL: libs.core.wcm.components.page.v3.page.page__002e__html
            └─ ...                                         containers and layout grids (AEM)
               └─ AEM component: wknd/components/image-list
                  └─ HTL: apps.wknd.components.image__002d__list.image__002d__list__002e__html
                     └─ ...                                spans that the Image List's Sling Model creates

在元件轉譯時,其HTL指令碼的範圍就是目前的範圍。 指令碼載入且包含data-sly-use的Sling模型在該範圍內執行,因此跨度以致模型建立的範圍會成為其子系。

每個要求範圍都命名為GET /* (或POST /*,以此類推)。 若要尋找特定頁面的追蹤,請搜尋請求範圍的url.path屬性,或新增您自己的屬性,如範例3所示。

範例 examples

範例1:追蹤昂貴的作業 trace-operation

您想要達成的目標 trace-operation-goal

WKND影像清單元件(wknd/components/image-list)會顯示頁面清單,每個頁面都有其第一個影像。 若要尋找該影像,其Sling模型ImageListImpl會針對清單中的每個專案執行一個QueryBuilder查詢。 AEM自己的範圍已顯示這些查詢:在列出五篇文章的WKND Magazine頁面上,追蹤在影像清單的HTL範圍下包含五個JCR query範圍。 它們不會顯示每個查詢針對的清單專案、是否找到影像或處理結果所花費的時間。 原始程式碼使用System.currentTimeMillis()將查詢計時,並將結果寫入DEBUG記錄專案,這在生產中無法使用。 您希望每個查詢在追蹤中以已命名作業的形式出現,並包含該內容。

程式碼 trace-operation-code

在ImageListImpl.findPageComponentResources()中,建立查詢周圍的範圍:

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;

// ... build the QueryBuilder parameters, as before

final Span span = WkndTelemetry.TRACER.spanBuilder("ImageList.findPageComponentResources")
        .setAttribute("wknd.query.path", page.getContentResource().getPath())
        .setAttribute("wknd.query.resource_type", slingResourceType)
        .setAttribute("wknd.query.limit", limit)
        .startSpan();

try (Scope ignored = span.makeCurrent()) {
    // ... run the query and collect the resources, as before

    span.setAttribute("wknd.query.result_count", componentResources.size());
} catch (RuntimeException e) {
    span.recordException(e);
    span.setStatus(StatusCode.ERROR, "Image List query failed");
    throw e;
} finally {
    span.end();
}

return componentResources;
  • makeCurrent()將您的範圍設定為區塊內執行之所有專案的父系,包括AEM為存放庫查詢建立的範圍。
  • 一律呼叫finally區塊中的end()。 不會匯出未結束的範圍。
  • 為範圍指定描述操作的固定名稱,並將不同的值(例如路徑)放入屬性中。

結果 trace-operation-outcome

在列出五篇文章的WKND Magazine頁面上,追蹤現在在影像清單中包含五個ImageList.findPageComponentResources範圍,每個清單專案一個。 每一個都是AEM為其查詢建立的JCR query範圍的父系:

AEM component: wknd/components/image-list
└─ HTL: apps.wknd.components.image__002d__list.image__002d__list__002e__html
   ├─ ImageList.findPageComponentResources        your span
   │  └─ JCR query                                AEM
   ├─ ImageList.findPageComponentResources
   │  └─ JCR query
   └─ ...                                         three more

您的每個範圍都有檢測範圍com.adobe.aem.guides.wknd和下列屬性:

屬性
範例值
wknd.query.path
/content/wknd/us/en/magazine/arctic-surfing/jcr:content
wknd.query.resource_type
wknd/components/image
wknd.query.limit
1
wknd.query.result_count
1

您現在可以針對每個清單專案,檢視查詢所搜尋的頁面、是否找到影像(wknd.query.result_count是0,但並未找到),以及整個查詢所花費的時間(包括處理結果)。 在WKND Magazine頁面上,每個查詢大約需要6.5毫秒,其中存放庫查詢本身大約需要3.5毫秒:一半的時間會用於處理結果,而AEM自己的範圍不會顯示這些結果。 由於查詢計數會隨著清單的長度而增加,因此您也可以檢視查閱佔用的元件轉譯時間量。

範例2:使用註解追蹤方法 with-span

您想要達成的目標 with-span-goal

WKND的SocialImageImpl模型會在每個頁面上執行,以填入og:image中繼標籤。 如果作者尚未設定精選影像,模型會逐步瀏覽頁面的內容樹狀結構以尋找第一個影像,並造訪最多500個資源。 您想要瞭解該步驟執行的頻率,以及它瀏覽的樹狀結構比例,而不需要重新建構方法。

程式碼 with-span-code

使用@WithSpan附註方法:

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.instrumentation.annotations.WithSpan;

@WithSpan("SocialImage.resolveFromFirstImageOnPage")
protected boolean resolveFromFirstImageOnPage() {
    Resource contentResource = currentPage.getContentResource();
    if (contentResource == null) {
        return false;
    }

    int[] visitedCount = new int[]{0};
    Resource firstImage = findFirstImage(contentResource, 0, visitedCount);

    // Span.current() is the span that @WithSpan started for this method.
    Span.current().setAttribute("wknd.social_image.nodes_visited", visitedCount[0]);
    Span.current().setAttribute("wknd.social_image.walk_truncated", visitedCount[0] > MAX_NODES_TO_VISIT);

    // ... use firstImage, as before
}
  • 代理程式會在每次方法執行時啟動範圍,並在方法傳回或擲回例外狀況時結束範圍。
  • @WithSpan使用公用、受保護及私用方法。
  • 若要將方法引數記錄為屬性,請以@SpanAttribute標註引數,例如void applyTemplate(@SpanAttribute("wknd.page.template") String template)。
  • 註解只有在代理程式執行時才會生效。 在本機AEM SDK和單元測試中,方法會以不變方式執行。

結果 with-span-outcome

SocialImage.resolveFromFirstImageOnPage範圍只出現在沒有精選影像的頁面的追蹤中:

屬性
範例值
wknd.social_image.nodes_visited
21
wknd.social_image.walk_truncated
false
code.namespace
com.adobe.aem.guides.wknd.core.models.impl.SocialImageImpl (由代理程式新增)
code.function
resolveFromFirstImageOnPage (由代理程式新增)

搜尋此範圍名稱可列出需要精選影像的頁面。 如果wknd.social_image.walk_truncated是true,則徒步停止在500個資源,且頁面可能完全沒有社交影像。

由@WithSpan建立的範圍包含代理程式的檢測範圍(io.opentelemetry.opentelemetry-instrumentation-annotations-1.16),而不是您的範圍。

範例3:將業務內容新增至目前範圍 current-span

您想要達成的目標 current-span-goal

您想要透過程式碼知道頁面社交影像來自的事實來篩選追蹤,而不需要建立另一個範圍。

程式碼 current-span-code

在SocialImageImpl中,將屬性新增至已啟用的範圍:

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;

private static final AttributeKey<String> IMAGE_SOURCE = AttributeKey.stringKey("wknd.social_image.source");

@PostConstruct
protected void init() {
    if (currentPage == null) {
        return;
    }

    final String source;
    if (resolveFromFeaturedImage()) {
        source = "featured_image";
    } else if (resolveFromFirstImageOnPage()) {
        source = "first_image_on_page";
    } else {
        source = "none";
    }

    Span.current().setAttribute(IMAGE_SOURCE, source);
}
  • Span.current()會傳回最內層的使用中範圍,無論AEM或您建立的程式碼為何。 對於從HTL載入的Sling模型,這是載入模型的HTL指令碼範圍。
  • 如果沒有作用中的範圍,呼叫不會執行任何動作。
  • 定義AttributeKey常數可讓屬性名稱在程式碼中保持一致。

結果 current-span-outcome

頁面的customheaderlibs.html指令碼範圍(WKND在此指令碼中載入此模型)攜帶wknd.social_image.source,其值為featured_image、first_image_on_page或none。 搜尋wknd.social_image.source為none的追蹤以尋找沒有社交影像的已發佈頁面。

在WKND網站上,每個頁面都會報告first_image_on_page或none:沒有任何頁面會設定精選影像,因此每個頁面都會支付從範例2開始的內容樹狀結構瀏覽的費用。 在造訪次數最多的頁面上設定精選影像,會移除正常運作的專案。

範例4:記錄錯誤 record-errors

您想要達成的目標 record-errors-goal

WKND的ContentAIAvailabilityImpl模型會透過讀取OSGi設定來檢查內容AI 搜尋是否已設定。 如果查詢失敗,模型會將Content AI視為未設定並記錄警告。 頁面仍會呈現,且請求成功,因此很容易遺漏失敗。 您想要失敗顯示在APM提供者的錯誤檢視中,附加到失敗的作業。

程式碼 record-errors-code

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;

private boolean resolveConfigured() {
    if (configurationAdmin == null) {
        return false;
    }
    final Span span = WkndTelemetry.TRACER.spanBuilder("ContentAIAvailability.resolveConfigured").startSpan();
    // Close the scope in finally, not with try-with-resources: try-with-resources closes it before
    // the catch block runs, and the warning logged there must carry this span's trace and span IDs.
    final Scope scope = span.makeCurrent();
    try {
        Configuration configuration = lookupConfiguration();
        // ... read the API key, as before
        span.setAttribute("wknd.content_ai.configured", isConfigured);
        return isConfigured;
    } catch (IOException e) {
        span.recordException(e);
        span.setStatus(StatusCode.ERROR, "Content AI configuration lookup failed");
        LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);
        return false;
    } finally {
        scope.close();
        span.end();
    }
}
  • recordException()將exception事件新增至span,並包含例外狀況的型別、訊息及棧疊追蹤。
  • setStatus(StatusCode.ERROR, ...)將範圍標籤為失敗。 在代表失敗作業的範圍上設定它。 請求本身不會標籤為失敗,因為元件已復原且頁面已轉譯。

結果 record-errors-outcome

追蹤包含狀態為​ 錯誤 ​且描述為Content AI configuration lookup failed的ContentAIAvailability.resolveConfigured範圍。 範圍有exception個事件:

事件屬性
範例值
exception.type
java.io.IOException
exception.message
Configuration store unavailable
exception.stacktrace
完整棧疊追蹤

範圍在您的APM提供者中計為錯誤,因此無論您的提供者顯示失敗作業,它都會顯示。 請求範圍會保持其狀態,因為請求傳回HTTP 200。

範例5:將記錄檔與追蹤建立關聯 correlate-logs

您想要達成的目標 correlate-logs-goal

當範例4中的Content AI查詢失敗時,您想要從記錄檔中的警告移至產生該警告的請求追蹤,然後再返回。

程式碼 correlate-logs-code

您不需要任何OpenTelemetry程式碼。 照常使用SLF4J進行記錄,並在OpenTelemetry設定中啟用記錄(logs: true):

LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);

代理程式會將目前範圍的追蹤和範圍ID新增至每個記錄專案。 因為ContentAIAvailabilityImpl會記錄其從範例4跨越的時程,而且是最新的,所以警告會連結到該範圍。

結果 correlate-logs-outcome

您的APM提供者會收到如以下的記錄專案,並可顯示其追蹤:

欄位
範例值
嚴重程度
WARN
內文
Unable to determine whether Content AI is configured (PID com.adobe.cq.wcm.core.components.internal.services.contentai.ContentAIClientImpl)
追蹤ID和範圍ID
ContentAIAvailability.resolveConfigured範圍的識別碼
exception.type, exception.message, exception.stacktrace
已記錄的例外狀況
檢測範圍
記錄器名稱,com.adobe.aem.guides.wknd.core.models.impl.contentai.ContentAIAvailabilityImpl
NOTE
只傳送WARN層級及以上的記錄專案。 INFO和DEBUG專案不是。 請勿依賴這些資訊進行生產診斷:請將該資訊記錄為Span屬性。

最佳實務 best-practices

  • 名稱在作業之後延伸,而非資料。 使用名稱(例如ImageList.findPageComponentResources),並將不同的值(例如路徑和ID)放入屬性中。
  • 為您自己的屬性名稱​加上專案的名稱空間,例如wknd.。 這可避免與OpenTelemetry語意慣例和AEM使用的名稱衝突。 請勿使用保留給AEM的aem.首碼。
  • 請勿在Span名稱、屬性、事件或記錄訊息中記錄個人資料或秘密。 這包括名稱、電子郵件地址、IP位址、權杖,以及可包含這些專案的查詢字串。
  • 新增作業範圍,而非反複專案。 例如,範例2會為內容樹狀結構步驟建立一個範圍,並將瀏覽的資源數目記錄為屬性,而不是為每個資源建立一個範圍。 AEM已經為單一頁面建立了許多跨頁(WKND Magazine頁面約為260個),每個跨頁都會增加您的APM提供者所擷取的磁碟區。
  • 建立追蹤器一次,​並重複使用。
  • 在finally區塊中結束每個span,並在開啟它的執行緒上每隔Scope關閉一次。
  • 將內容傳遞給您自己啟動的執行緒, (例如Context.current().wrap(runnable)),以便在另一個執行緒上完成的工作會加入相同的追蹤。
  • 要求以外的工作需要新的追蹤。 在Sling工作、排程工作或事件接聽程式中執行的程式碼沒有請求範圍。 您建立的範圍會開始自己的追蹤,每個回合各一個。

測試您的檢測工具 test

  • 單元測試:​若沒有代理程式,API將不會執行任何動作,因此您現有的單元測試會保持傳遞不變。
  • 組建:​在本機執行mvn clean install。 Cloud Manager也會執行的AEM分析器會檢查您的套件組合匯入是否可解析。
  • 套件組合內容:​如步驟2所述,檢查您的套件組合是否包含完整的API。 例如,unzip -l core/target/<your-core-bundle>.jar | grep "io/opentelemetry/api/baggage/"必須列出類別,而組合的META-INF/MANIFEST.MF不可匯入任何io.opentelemetry封裝。
  • 您的APM提供者:​部署至已啟用OpenTelemetry的開發環境、要求執行程式碼的頁面,以及搜尋您的APM提供者,以尋找您的跨區名稱或您在步驟3中選擇的檢測範圍。

疑難排解 troubleshooting

症狀
原因和解決方案
Cloud Manager組建失敗,錯誤為[api-regions-exportsimports] ... is importing package(s) [io.opentelemetry.api.incubator, io.opentelemetry.api.incubator.internal, io.opentelemetry.sdk.autoconfigure] in start level 20 but no bundle is exporting these for that start level.
您的套件組合會匯入選用的OpenTelemetry套件。 在Import-Package的開頭新增!io.opentelemetry.*。 請參閱步驟2。
您的組合已安裝但未啟用,並具有missing requirement ... osgi.wiring.package=io.opentelemetry.api.incubator。
與上一列相同的原因和解決方案。
@WithSpan範圍會出現,但您的其他範圍以及您使用Span.current()設定的屬性不會出現。
您的套件僅包含API的一部分,因此代理程式不會連線您的副本。 新增-includepackage: io.opentelemetry.*。 請參閱步驟2。
您的範圍都不會顯示。
檢查是否已為環境啟用OpenTelemetry,以及部署程式碼後是否已執行部署。 檢查程式碼路徑是否已執行,以及每個範圍是否已結束。
您的範圍會顯示為個別的追蹤,而非在請求內。
範圍是在另一個執行緒上建立的,沒有要求的內容。 將內容傳遞給執行緒,例如Context.current().wrap(runnable)。
您的記錄專案不會顯示。
只會傳送WARN層級以上的專案,而且只有在您的設定中啟用記錄時才傳送。
recommendation-more-help
experience-manager-cloud-service-help-main-toc