OpenTelemetryを使用したカスタム計測 custom-instrumentation

環境でOpenTelemetryが有効になっている場合、AEM as a Cloud Serviceはすべてのリクエストをトレースし、JVM、HTTP、AEMの指標を自動的に書き出します。 このテレメトリはAEMを表しています。 これは、ページのレンダリングに900 ミリ秒かかったこと、およびどのコンポーネントがレンダリングされたかを示しています。 リポジトリクエリのリスト項目や見つかったページ、作成者がフィールドを省略したために高額な検索にフォールバックしたページなど、独自のコードが何であったかを表示することはできません。

OpenTelemetry APIを使用して、その知識を自分で追加することができます。 コードが作成するスパンと属性は、自動テレメトリ(同じサービス名、同じ処理、同じAPM プロバイダー)を使用して移動します。 コードにエンドポイント、資格情報、エクスポーターを設定しないでください。

AEM as a Cloud ServiceでOpenTelemetryを初めて使用する場合は、チュートリアル「OpenTelemetryを使用したオブザーバビリティ ​」から始めて、その仕組みについて説明します。 次に、AEM アプリケーションのインサイトを得るためにOpenTelemetryを設定しお使いの環境で有効にします。

このページの例では、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

プロジェクトの親pom.xmlのdependencyManagement セクションに、OpenTelemetry APIとインストルメンテーション注釈を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を使用する各バンドルには独自のコピーを含める必要があります。 バンドルのバンド設定に次の手順を追加します。 AEM プロジェクト アーキタイプ ​では、これはcore/pom.xmlのbnd-maven-plugin設定です。 Import-Package命令が既に持っているエントリはすべて保持します。

Import-Package: !io.opentelemetry.*,javax.annotation;version=0.0.0,*
-includepackage: io.opentelemetry.*
命令
なぜ必要なのか
!io.opentelemetry.*がImport-Packageの先頭に表示されます
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

コードベース用に1つの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 /*など)という名前が付けられます。 特定のページのトレースを検索するには、例3に示すように、リクエストスパンのurl.path属性を検索するか、独自の属性を追加します。

例 examples

例1:高価な操作をトレースする trace-operation

達成したいこと trace-operation-goal

WKND画像リストコンポーネント (wknd/components/image-list)には、ページのリストが表示され、それぞれに最初の画像が表示されます。 その画像を見つけるために、そのSling モデル ImageListImplは、リスト内の各項目に対して1つのQueryBuilder クエリを実行します。 AEMの独自のスパンには、既にそれらのクエリが表示されています。5つの記事を一覧表示するWKND Magazine ページでは、画像リストのHTL スパンの下に5つの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

5つの記事を一覧表示するWKND Magazine ページで、トレースに画像リスト内に5つのImageList.findPageComponentResources スパンが含まれるようになりました。各リスト項目に1つずつ表示されます。 各ノードは、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

WKNDがこのモデルを読み込むページのcustomheaderlibs.html スクリプトのスパンには、値featured_image、first_image_on_page、またはnoneを持つwknd.social_image.sourceが含まれます。 wknd.social_image.sourceがnoneのトレースを検索して、ソーシャルイメージなしで公開されているページを見つけます。

WKND サイトでは、すべてのページがfirst_image_on_pageまたはnoneと報告しています。おすすめ画像を設定するページはないので、例2からコンテンツツリーウォークに対して各ページが支払いを行います。 最も訪問されたページに特集画像を設定すると、その作業が削除されます。

例4:エラーを記録する record-errors

達成したいこと record-errors-goal

WKNDのContentAIAvailabilityImpl モデルは、コンテンツ AI 検索がOSGi設定を読み取って設定されているかどうかを確認します。 ルックアップが失敗した場合、モデルは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 イベントをスパンに追加します。
  • 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のコンテンツ 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とスパン 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 スパンのID
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. プレフィックスは使用しないでください。
  • 個人データまたはシークレット​をスパン名、属性、イベント、またはログメッセージに記録しないでください。 これには、名前、メールアドレス、IP アドレス、トークン、クエリ文字列が含まれます。
  • 反復処理ではなく、操作のスパンを追加します。 例えば、例2は、コンテンツツリーウォークに1つのスパンを作成し、各リソースにスパンを作成するのではなく、属性として訪問したリソースの数を記録します。 AEMでは、既に1つのページに対して多数のスパンが作成されており(WKND Magazine ページでは約260)、各スパンはAPM プロバイダーが取り込むボリュームに追加されます。
  • トレーサーを1回作成し、​して再利用します。
  • すべてのスパン​をfinally ブロックで終了し、開いたスレッドのScopeごとに閉じます。
  • 自分で開始したスレッド()にコンテキストを渡します。例えばContext.current().wrap(runnable)を使用すると、別のスレッドで行われた作業が同じトレースに結合されます。
  • リクエスト以外の作業に新しいトレースを期待します。 Sling ジョブ、スケジュールされたタスク、またはイベントリスナーで実行されるコードには、リクエストスパンがありません。 そこで作成したスパンは、実行ごとに1つずつ、独自のトレースを開始します。

インストルメンテーションのテスト test

  • 単体テスト: エージェントがなければ、APIは何も実行しません。そのため、既存の単体テストは変更されません。
  • ビルド: mvn clean installをローカルで実行します。 Cloud Managerも実行するAEM アナライザーは、バンドルのインポートが解決できることを確認します。
  • バンドルの内容: バンドルに完全なAPIが含まれていることを確認します(手順2を参照)。 例えば、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