LogicMonitor は、2024 Gartner Voice of the Customer の Observability プラットフォーム部門で、Gartner Peer Insights™ の Customers' Choice に選出されました。

続きを読む

ベストプラクティス

分散トレース用のJavaアプリケーションマニュアルインスツルメンテーション

このガイドを使用して、OpenTelemetry Protocol(OTLP)仕様を使用してトレースを発行するJavaアプリケーションを作成します。 必要なのは、Javaを使用したアプリケーション開発の基本的な理解だけです。

分散トレース用のJavaアプリケーションマニュアルインスツルメンテーション

手動インストルメンテーションにより、分散システムの操作に関する詳細な情報が得られます。Java アプリケーションを手動でインストルメンテーションすることで、収集したデータをより細かく制御できるようになり、分散アーキテクチャ全体の可視性が向上します。

このブログシリーズでは、複数の言語にまたがるOpenTelemetry標準を使用した分散トレースのアプリケーションインストルメンテーション手順について説明します。 以前にカバーしました 分散トレース用のGolangアプリケーションインストルメンテーション分散トレース用のDotNetアプリケーションインストルメンテーション。 ここでは、Javaのインストルメンテーションについて説明します。

主要な取り組み

チェックマーク
Java アプリケーションでの手動インストルメンテーションにより、分散システムの可視性が向上します。
チェックマーク
これにより、開発者はアプリケーションのパフォーマンスをリアルタイムで監視できます。
チェックマーク
プロアクティブな計測により、効率的なトラブルシューティングとより迅速な問題解決が可能になります。
チェックマーク
分散トレースは、システムの信頼性を維持し、サービス全体のダウンタイムを最小限に抑えるのに役立ちます。

OpenTelemetry の概念を探る

OpenTelemetry は、分散システムからテレメトリ データ (具体的にはトレース、ログ、メトリック) をキャプチャ、処理、エクスポートするために設計されたライブラリ、API、エージェント、ツールのセットです。ベンダー中立でオープンソースであるため、企業は相互運用性と選択の自由を持ち、幅広いサービスとテクノロジーにわたって可観測性システムを実装できます。 

OpenTelemetry は、シグナル、API、コンテキストと伝播、リソースとセマンティック規則といういくつかの主要な概念に分類できます。

シグナル

OpenTelemetry のシグナルは、トレース、メトリック、およびログです。トレースは、サービス全体の操作におけるエンドツーエンドのレイテンシを表します。これらは、開始と終了のタイムスタンプとコンテキスト属性を持つ個々の作業単位の名前であるスパンで構成されています。 

メトリックとは、時間の経過に伴う定性的な測定値 (CPU 使用率、メモリ使用率、ディスク使用率) であり、アプリケーションの全体的なパフォーマンスを理解するのに役立ちます。一方、ログは、システムで発生したイベントの記録であり、エラーやその他のイベントに関する洞察を提供します。

API

OpenTelemetry は、言語に依存しない API を定義します。これにより、チームは API を実装するコードを作成し、データを収集して処理し、選択したバックエンドにエクスポートできます。この API を使用すると、カスタム ソフトウェアを使用する場合でも、すぐに使用できる監視ソリューションを使用する場合でも、誰でも同じデータを収集できるため、独自の方法でデータを処理し、ニーズに基づいて監視ソリューションをカスタマイズできます。

コンテキストと伝播

コンテキストは、コードとネットワーク間でデータ (スパン コンテキストなど) を共有するために使用される概念です。コンテキストの伝播により、リクエストがさまざまなサービスを通じてネットワーク間を移動するときに、分散トレースの接続が維持され、チームがインフラストラクチャ全体の全体像を把握できるようになります。

リソースと意味規則

リソースは、データを生成するエンティティに関する情報を提供するものです。ホスト名、デバイス環境、ホストの詳細などの情報が含まれます。 意味的慣習 標準化された属性と命名規則により、テレメトリ データの一貫性が高まり、データ出力のばらつきを気にすることなく、あらゆる環境でデータを一様に解釈できるようになります。

これらの概念を理解すると、テレメトリ出力を解読し、OpenTelemetry プロジェクトを開始するのに役立ちます。それでは、新しいプロジェクトを設定することから始めましょう。

カスタム計測と属性

Java アプリケーションのカスタム インストルメンテーションにより、開発者は自動インストルメンテーションが提供するものよりも詳細なテレメトリ データを取得できます。スパンを手動で定義し、属性を追加することで、チームは分散システム内の特定のアプリケーションの動作とビジネス ロジックについてより深い洞察を得ることができます。

スパンに属性を追加する

属性はスパンに添付されたキーと値のペアであり、操作に関するコンテキスト メタデータを提供します。これらの属性には、ユーザー ID、トランザクション タイプ、HTTP 要求の詳細、データベース クエリなどの詳細を含めることができます。関連する属性を追加することで、開発者は追跡可能性を強化し、アプリケーション固有の有益な情報に基づいてパフォーマンス データをフィルタリングおよび分析しやすくなります。

マルチスパン属性の作成

マルチスパン属性を使用すると、開発者は複数の操作にわたって主要なメタデータを伝播することで、スパン間の一貫性を維持できます。これは、サービス間でリクエストを追跡する場合に特に役立ち、相関 ID やセッションの詳細などの関連情報がトレース全体にわたってリンクされたままになります。

新しいプロジェクトを初期化する

まず、新しいJavaプロジェクトを作成し、以下に必要な依存関係を追加します。 オープンテレメトリ 手動計装。

達人

<project>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-bom</artifactId>
        <version>1.2.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-api</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-sdk</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-semconv</artifactId>
      <version>1.5.0-alpha</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.39.0</version>
    </dependency>
  </dependencies>
</project>

受け台

dependencies {
  implementation platform("io.opentelemetry:opentelemetry-bom:1.2.0")
  implementation('io.opentelemetry:opentelemetry-api')
  implementation('io.opentelemetry:opentelemetry-sdk')
  implementation('io.opentelemetry:opentelemetry-exporter-otlp')
  implementation('io.opentelemetry:opentelemetry-semconv:1.5.0-alpha')
  implementation('io.grpc:grpc-netty-shaded:1.39.0')
}

OpenTelemetry BOMを使用して、さまざまなコンポーネントのバージョンの同期を維持することをお勧めします。

他の最終的なアプリケーションで使用されるライブラリを開発している場合、コードはopentelemetry-apiにのみ依存します。

分散トレースを使用するとパフォーマンスのボトルネックを正確に特定できますが、手動インストルメンテーションを使用するとボトルネックを正確に解決できます。

リソース検出器を作成する

リソースは、を生成したオブジェクトを記述します テレメトリー 信号。 基本的に、これはサービスまたはアプリケーションの名前である必要があります。 OpenTelemetryは、サービス実行環境を説明するための標準を定義しています。 ホスト名、hostType(クラウド、コンテナー、サーバーレス)、名前空間、cloud-resource-idなど。これらの属性は以下で定義されます。 リソースセマンティックコンベンション またはsemconv。

ここでは、いくつかの環境属性を持つリソースを作成します。

属性説明必須
サービス名これは、サービスの論理名です。Yes
サービス.名前空間サービスをグループ化するために使用されます。たとえば、service.namespaceを使用して、QA、UAT、PRODなどの環境間でサービスを区別できます。
 
いいえ
ホスト名サービスが実行されているホストの名前。いいえ
//Create Resource
AttributesBuilder attrBuilders = Attributes.builder()
   .put(ResourceAttributes.SERVICE_NAME, SERVICE_NAME)
   .put(ResourceAttributes.SERVICE_NAMESPACE, "US-West-1")
   .put(ResourceAttributes.HOST_NAME, "prodsvc.us-west-1.example.com");
 
Resource serviceResource = Resource
   .create(attrBuilders.build());

Init SpanExporter

エクスポータは、アプリケーションからリモートバックエンドへのテレメトリ信号(トレース)のエクスポート、ファイルへのログ、stdoutへのストリームなどを担当するSDKのコンポーネントです。

分散トレースがシステム パフォーマンスに与える影響を考慮してください。適切なトレース サンプリングにより、詳細なトレースの必要性とシステム全体の効率性のバランスが取れ、パフォーマンスの低下やデータの過負荷を防ぐことができます。

この例では、localhost:55680で実行されているOTLPレシーバーバックエンドにトレースを送信するgRPCエクスポーターを作成しています。 おそらくOTELコレクター。 

//Create Span Exporter
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
   .setEndpoint("http://localhost:55680")
   .build();

TracerProviderを構築し、SDKを構成します

TracerProvider を使用すると、スパンを作成し、パフォーマンス メトリックを追跡するために使用される、Java パフォーマンス監視の主要コンポーネントである Tracer にアクセスできます。

//Create SdkTracerProvider
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
   .addSpanProcessor(BatchSpanProcessor.builder(spanExporter)
       .setScheduleDelay(100, TimeUnit.MILLISECONDS).build())
   .setResource(serviceResource)
   .build();
 
//This Instance can be used to get tracer if it is not configured as global
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
   .setTracerProvider(sdkTracerProvider)
   .buildAndRegisterGlobal();

アプリケーションの最初のステップとして、SDKを構成し、トレーサーを作成する必要があります。

適切な構成が整っていれば、開発者はアプリケーションのパフォーマンスをリアルタイムで監視できます。これにより、迅速な調整と最適化が可能になり、問題が発生したらすぐに対処したり、パフォーマンスを向上させたりすることができます。

トレーサーを作成する 

Tracer tracer= GlobalOpenTelemetry.getTracer("auth-Service-instrumentation");
//Tracer tracer= GlobalOpenTelemetry.getTracer("auth-Service-instrumentation","1.0.0");
 
//OR use the OpenTelemetry instance from previous step to get tracer
//openTelemetry.getTracer("auth-Service-instrumentation");

GlobalOpenTelemetryは、前の手順でOpenTelemeryインスタンスがグローバルとして登録されている場合にのみ使用できます。それ以外の場合は、SDKビルダーから返されたOpenTelemetryインスタンスを使用できます。

getTracerメソッドでは、パラメーターとしてインストルメンテーションライブラリ名が必要です。これはnullであってはなりません。

GlobalOpenTelemetry の使用は、複数のサービスにわたる複雑なプロセスを追跡するために不可欠です。これを有効にすると、複数ステップのワークフローの追跡が合理化され、全体的な運用効率が向上し、スムーズで最適化されたシステム パフォーマンスが保証されます。

スパンの作成と管理

OpenTelemetry 計測をセットアップした後の次のステップは、スパンを効率的に作成して管理することです。スパンを適切に定義、構造化、管理することで、システム内での操作の流れを理解し、問題のトラブルシューティングを行う際に役立ちます。

適切なスパンを作成するために役立つ要素は、スパン属性、子スパン、イベントなどです。

  • スパン属性: スパン属性は、スパンに意味を割り当てるのに役立ちます。スパン属性は、操作を区別し、下流の分析ツールに貴重なメタデータを提供します。属性を使用して、ビジネスの優先順位、環境の詳細、およびユーザー情報を表します。サービスの一貫性を確保するために、セマンティック規則を使用して環境間で属性を標準化することを忘れないでください。
  • 子の範囲: より複雑なワークフローには複数のステップと依存関係が必要であり、これらを単一のスパンで表現するのは困難です。子スパンを使用すると、単一の操作をサブ操作に分割できるため、遅延やエラーを見つけやすくなります。これらを使用して親子関係を作成し、データの構造化されたビューを提供して、トラブルシューティングを迅速化します。
  • イベントログ: イベントとログを使用すると、タイムスタンプ付きのデータ ポイントやデバイスの内部状態の変化を記録できます。イベントとログを埋め込むと、外部のログ ソリューションだけに頼らず、すべてのコンテキスト情報がトレース データ内の特定の操作に直接関連付けられるようになります。このデータはパフォーマンスと異常の即時コンテキストを提供するため、問題を診断する際に非常に役立ちます。 

テレメトリを最大限に活用するために考慮すべきベスト プラクティスもいくつかあります。その一部を以下に示します。

  • 問題のあるスパンを素早く特定しやすくするために、スパンにステータスとエラー処理 (StatusCode.OK、StatusCode.ERROR) を実装します。
  • すべてのリクエストに完全なインストルメンテーションが必要なわけではないので、パフォーマンスと観測性のバランスをとるためにサンプリング戦略を最適化します。
  • 共通の関係を共有しているが、必ずしも親子関係ではないスパンについては、スパンをリンクして、関連しているが独立してトリガーされるスパンを相関させることを検討してください。
  • OpenTelemetryのコンテキストとスコープの管理ユーティリティを使用して、スパンが正しく伝播しない可能性があるマルチスレッドワークフローでスパンにアクセスできるようにします。

これらの基礎を理解することで、組織は計測機器を最適化し、より有意義なテレメトリを生成できるようになります。それでは、スパンを効果的に作成および管理する方法の例をいくつか見てみましょう。

一般的な問題のトラブルシューティング

適切に構造化されたスパンであっても、OpenTelemetry 計測では課題が生じる場合があります。一般的なトラブルシューティング手法には次のものがあります。

  • 適切なスパンの伝播を確保する: 予想されるトレースにスパンが表示されない場合は、サービス境界を越えてコンテキスト伝播が正しく実装されていることを確認します。
  • エクスポータの構成を確認しています: バックエンドからトレースが見つからない場合、エクスポータ設定が正しく構成されていることを確認し、アプリケーションがテレメトリ エンドポイントにネットワーク アクセスできることを確認します。
  • トレースデータにおける高レイテンシの管理: トレースが遅延したり欠落したりする場合は、パフォーマンスとデータ量のバランスをとるためにサンプリング レートを調整することを検討してください。
  • 不完全なスパンの処理: 特にマルチスレッドまたは非同期ワークフローでは、不適切なスコープ管理によりスパンが失われる可能性があるため、スパンが適切に終了していることを確認します。

テレメトリデータ伝送の代替プロトコル

デフォルトでは、OpenTelemetry はテレメトリ データのエクスポートに gRPC を使用します。ただし、特に gRPC をサポートしていないレガシー システム、ファイアウォール、または監視ツールを使用する場合は、HTTP ベースの転送方法の方が適している場合があります。

スパンを作成し、スパン属性を定義する

スパンは、操作の単一の実行です。これは、スパン タグと呼ばれることもある一連の属性によって識別されます。アプリケーション所有者は、スパンに必要な情報を取得できる属性を自由に選択できます。スパンあたりのスパン属性の数に制限はありません。

この例では、サンプルアプリケーションのXNUMXスパン属性を定義しています。

Span parentSpan = tracer.spanBuilder("doLogin").startSpan();
parentSpan.setAttribute("priority", "business.priority");
parentSpan.setAttribute("prodEnv", true);

子スパンを作成する

setParentメソッドを使用して、スパンを手動で相互に関連付けることができます。

Span childSpan = tracer.spanBuilder("child")
   .setParent(Context.current().with(parentSpan))
   .startSpan();

OpenTelemetry APIは、現在のスレッドで親スパンを伝播する自動化された方法も提供します。

makeCurrentメソッドを使用して、現在のスレッドで親スパンを自動的に伝播します。

try (Scope scope = parentSpan.makeCurrent()) {
   Thread.sleep(200);
   boolean isValid=isValidAuth(username,password);
   //Do login
 
} catch (Throwable t) {
   parentSpan.setStatus(StatusCode.ERROR, "Change it to your error message");
} finally {
   parentSpan
       .end(); // closing the scope does not end the span, this has to be done manually
}
 
//Child Method
private boolean isValidAuth(String username,String password){
 
   Span childSpan = tracer.spanBuilder("isValidAuth").startSpan();
   // NOTE: setParent(...) is not required;
   // `Span.current()` is automatically added as the parent
   childSpan.setAttribute("Username", username)
       .setAttribute("id", 101);
   //Auth code goes here
   try {
       Thread.sleep(200);
       childSpan.setStatus(StatusCode.OK);
   } catch (InterruptedException e) {
       childSpan.setStatus(StatusCode.ERROR, "Change it to your error message");
   }finally {
       childSpan.end();
   }
   return true;
}

スパンへのイベント/ログの追加

スパンは、スパンの実行中に発生したいくつかの実行ログ/イベントで強化できます。 この情報は、常にそれぞれのスパンに関連付けられたコンテキストログを提供するのに役立ちます。

Attributes eventAttributes = Attributes.builder().put("Username", username)
   .put("id", 101).build();
childSpan.addEvent("User Logged In", eventAttributes);

それをまとめる

テストアプリケーション.java

package com.logicmonitor.example;
 
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.util.concurrent.TimeUnit;
 
public class TestApplication {
 
   private static final String SERVICE_NAME = "Authentication-Service";
   static {
       //Create Resource
       AttributesBuilder attrBuilders = Attributes.builder()
           .put(ResourceAttributes.SERVICE_NAME, SERVICE_NAME)
           .put(ResourceAttributes.SERVICE_NAMESPACE, "US-West-1")
           .put(ResourceAttributes.HOST_NAME, "prodsvc.us-west-1.example.com");
 
       Resource serviceResource = Resource
           .create(attrBuilders.build());
       //Create Span Exporter
       OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
           .setEndpoint("http://localhost:55680")
           .build();
 
       //Create SdkTracerProvider
       SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
           .addSpanProcessor(BatchSpanProcessor.builder(spanExporter)
               .setScheduleDelay(100, TimeUnit.MILLISECONDS).build())
           .setResource(serviceResource)
           .build();
 
       //This Instance can be used to get tracer if it is not configured as global
       OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
           .setTracerProvider(sdkTracerProvider)
           .buildAndRegisterGlobal();
   }
   public static void main(String[] args) throws InterruptedException {
       Auth auth = new Auth();
       auth.doLogin("testUserName", "testPassword");
       Thread.sleep(1000);
   }
}

認証.Java

package com.logicmonitor.example;
 
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
 
public class Auth {
 
   Tracer tracer = GlobalOpenTelemetry.getTracer("auth-Service-instrumentation");
 
   //Tracer tracer= GlobalOpenTelemetry.getTracer("auth-Service-instrumentation","1.0.0");
   public void doLogin(String username, String password) {
       Span parentSpan = tracer.spanBuilder("doLogin").startSpan();
       parentSpan.setAttribute("priority", "business.priority");
       parentSpan.setAttribute("prodEnv", true);
 
       try (Scope scope = parentSpan.makeCurrent()) {
           Thread.sleep(200);
           boolean isValid = isValidAuth(username, password);
           //Do login
 
       } catch (Throwable t) {
           parentSpan.setStatus(StatusCode.ERROR, "Change it to your error message");
       } finally {
           parentSpan
               .end(); // closing the scope does not end the span, this has to be done manually
       }
 
   }
 
   private boolean isValidAuth(String username, String password) {
 
       Span childSpan = tracer.spanBuilder("isValidAuth").startSpan();
       // NOTE: setParent(...) is not required;
       // `Span.current()` is automatically added as the parent
 
       //Auth code goes here
 
       try {
           Thread.sleep(200);
           childSpan.setStatus(StatusCode.OK);
           Attributes eventAttributes = Attributes.builder().put("Username", username)
               .put("id", 101).build();
           childSpan.addEvent("User Logged In", eventAttributes);
       } catch (InterruptedException e) {
           childSpan.setStatus(StatusCode.ERROR, "Change it to your error message");
       } finally {
           childSpan.end();
       }
       return true;
   }
}

アプリケーションを実行する

TestApplication.javaを実行します。

LogicMonitorプラットフォームで受信したトレース

トレースはLogicMonitorで実行されます

トレースの詳細ビュー

親スパン:

Logicmonitorプラットフォームのトレースセクション内の親スパン。

子スパン:

LogicMonitorのトレースの子スパン

まとめ

おめでとうございます。OpenTelemetryProtocol(OTLP)仕様を使用してトレースを発行するJavaアプリケーションを作成しました。 OTLP仕様を使用してビジネスアプリケーションのインストルメント化を開始するときは、このコードを参照として自由に使用してください。 LogicMonitor APM仕様は、ベンダーロックインなしで100%OTLPに準拠しています。 LogicMonitorプラットフォームでトラブルシューティングするための複数のサービスのトレースを受信して​​視覚化するには、無料の試用アカウントにサインアップしてください こちら。 複数の言語にわたるOpenTelemetry標準を使用した分散トレースのアプリケーションインストルメンテーション手順をカバーするブログをチェックしてください。

分散トレースは、システムの安定性を維持し、サービスの中断を最小限に抑える上で重要な役割を果たします。さまざまなコンポーネントにわたってトレースを監視することで、複雑な環境でも、より信頼性の高い操作と高い稼働率を確保できます。LogicMonitor の強力な監視プラットフォームを使用して、分散トレースの可能性を最大限に引き出しましょう。

著者
LogicMonitorチーム
免責事項: このブログで述べられている見解は著者の見解であり、LogicMonitor またはその関連会社の見解を必ずしも反映するものではありません。

私たちのブログを購読する

このような記事をあなたの受信箱に直接お届けします