コンテンツにスキップ

APM を活用した完全なクラス関数呼び出しのトレース方法

通常、アプリケーションにAPMを導入すると、Tomcat、Redis、MySQLなど、アプリケーションに関連するコンポーネントやサービス間の呼び出しトレースを確認できます。これは、APMが標準的なコンポーネントに対してインストルメンテーション処理を施し、実際の使用におけるコンポーネント呼び出しがアプリケーションに与える影響をより良く観測できるようにしているためです。

一方、実際の本番環境では、非標準コード、つまりビジネスコードが業務に与える影響はより深く、開発者のコード理解度や記述能力にも差異があります。ビジネスコードにおいて、完全なクラス関数呼び出しをトレースし、重要な問題の根本を特定することは、依然として極めて重要です。

幸いなことに、APMの開発者もコンピュータサイエンスの専門教育を受けており、現場の課題を理解した上で、APMにさらなる期待を寄せています。DataDogやOpenTelemetryも関連機能を提供しており、その内容を見ていきましょう。

JAVAを例に、DDTrace(DataDog)とOpenTelemetryについてそれぞれ調査します。

DDTrace を使用した関数呼び出しのトレース方法

サンプルコードを用意します。

    @Autowired
    private TestService testService;

    @GetMapping("/user")
    @ResponseBody
    public String getUser(){
        logger.info("do getUser");
        return testService.users();
    }

Service インターフェース

public interface TestService {

    String users();
}

Service 実装クラス

package com.zy.observable.server.service;

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class TestServiceImpl implements TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
    public String getUsername(){
        return "lr";
    }

    public String users(){
        Map<Integer,Student> users =new HashMap<>();
        users.put(1,new Student("tom",18));
        users.put(2,new Student("joy",20));
        users.put(3,new Student("lucy",30));
        users.forEach((k,v)->print(k,v));
        return getUsername();
    }

    public void print(Integer level,Student student){
        logger.info("level:{},username:{}",level,student.getUsername());
    }
}

Controller 層が testService.users() を呼び出すとき、デフォルトでは users はトレースの一部になりません。以下のコマンドで実行します。

java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-jar springboot-server.jar

または、Idea ツールでデバッグを実行しても構いません。

http://localhost:8090/user にリクエストを送信すると、Guance プラットフォームでは以下のように表示されます。

Img

トレース状況を確認すると、2つのスパンが存在することがわかります。

users メソッドもトレースに含めるには、DDTrace は以下のパラメータを提供しており、ビジネスコード情報を発見できます。

  • パラメータ方式: -Ddd.trace.methods
  • 環境変数方式: DD_TRACE_METHODS

以下のコマンドを使用します。

java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-Ddd.trace.methods="com.zy.observable.server.service.TestService[users]" \
-jar springboot-server.jar

Guance プラットフォームで以下のように表示されます。

Img

クラス内のすべての関数呼び出しをトレースしたい場合は、* を使用します。

java -javaagent:D:/ddtrace/dd-java-agent-1.25.2-guance.jar \
-Ddd.service=springboot-server \
-Ddd.env=1.0 \
-Ddd.agent.port=9529 \
-Ddd.trace.methods="com.zy.observable.server.service.TestService[*]" \
-jar springboot-server.jar

Guance プラットフォームで以下のように表示されます。

Img

TestService にはインターフェースメソッドが1つしかありませんが、ワイルドカード * を使用すると、複数のスパン情報が表示されます。

注意深い方は、すでに問題に気づいているかもしれません。TestServiceImpl には getUsernameusersprint の3つの関数があり、usersgetUsernameprint をそれぞれ呼び出しています。なぜトレース上には print があるのに getUsername がないのでしょうか?

  • なぜトレース上に print があるのか

その理由は、TestServiceImplTestService インターフェースを実装しており、* がすべてを意味するため、ddtrace は実際には TestService の実装クラス TestServiceImpl に対して拡張処理を行っているからです。つまり、-Ddd.trace.methods="com.zy.observable.server.service.TestServiceImpl[*]" と同等です。

  • なぜトレース上に getUsername がないのか

主な理由は、DDTrace がいくつかの重要なメソッドを遮蔽しており、以下のタイプのメソッド関数は拡張処理の対象外となるためです。

  • コンストラクタ
  • getters
  • setters
  • synthetic
  • toString
  • equals
  • hashcode
  • finalizer メソッド呼び出し

注目すべき点として、上記のトレースでは print 関数に3つのスパンが表示されています。これは、stus が Map コレクションであり、3回ループ処理され、print が3回呼び出されたためです。これは状況によっては深刻な問題です。もし stus コレクションの要素数が1000だった場合、print はその数のスパンを生成することになります。これはトレースの確認においても、コストの見積もりにおいても、非常に高い代償を伴います。前者はスパンが多すぎるためブラウザのメモリ使用量が増加し、UI の応答が遅くなる可能性があります。後者はストレージとクエリのコストを増加させます。

OpenTelemetry を使用した関数呼び出しのトレース方法

前述のコードを引き続き使用し、パラメータを追加せずに以下のコマンドでアプリケーションを実行します。

-javaagent:/home/liurui/agent/opentelemetry-javaagent-1.26.1-guance.jar
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://localhost:4317
-Dotel.resource.attributes=service.name=springboot-server

Guance プラットフォームで以下のように表示されます。

Img

OpenTelemetry トレースのスパンは、DDTrace とほぼ同じであることがわかります。

コードを変更できない場合でも、OpenTelemetry は以下の設定で Java エージェントを使用して特定のメソッドのスパンをキャプチャする方法を提供しています。

  • パラメータ方式: -Dotel.instrumentation.methods.include
  • 環境変数方式: OTEL_INSTRUMENTATION_METHODS_INCLUDE

注意点として、otel はワイルドカードによる設定をサポートしていません。

以下のコマンドを実行します。

-javaagent:/home/liurui/agent/opentelemetry-javaagent-1.26.1-guance.jar
-Dotel.traces.exporter=otlp
-Dotel.exporter.otlp.endpoint=http://localhost:4317
-Dotel.resource.attributes=service.name=springboot-server
-Dotel.instrumentation.methods.include="com.zy.observable.server.service.TestService[users]"

Guance プラットフォームで以下のように表示されます。

Img

同様に、users 関数にもスパン情報が追加されました。

SDK 方式

以上では、DDTrace と OpenTelemetry が非侵入的な方法でビジネス上の特定の関数メソッドをトレースする方法について説明しました。両者とも SDK 方式も提供しており、ある程度のコードへの侵入性はありますが、より柔軟な表現が可能です。

  • DDTrace はアノテーション @Trace を使用してビジネススパンを設定します。実際の使用方法と関連する依存関係については、リンク を参照してください。
  • OpenTelemetry はアノテーション @WithSpan を使用してビジネススパンを設定します。リンク を参照してください。

SDK 方式については、ここでは詳しく分析しません。

参考ドキュメント

DDTrace Agent ダウンロードページ

OpenTelemetry Agent ダウンロードページ

OpenTelemetry methods

SpringBoot-Server demo

フィードバック

このページは役に立ちましたか?