カスタムメソッドをインスツルメントして、ビジネスロジックを詳細に可視化する
Datadog の調査レポート: サーバーレスの状態 レポート: サーバーレスの状態

カスタムメソッドをインスツルメントして、ビジネスロジックを詳細に可視化する

8 分で読了

ビジネスロジックを詳細に可視化するために、Datadog APM では、ニーズと実装に基づいてトレースを構成するスパンをカスタマイズできます。これにより、コードベース内のあらゆるメソッド、さらにはメソッド内の特定のコンポーネントをトレースすることができます。これを使用すれば、アプリケーションの重要な領域を最適な粒度で最適化、監視できます。

Datadog は、ウェブサービス、データベース、キャッシュなど、すぐに使用できる多くのフレームワークをインスツルメントするため、独自のビジネスロジックをインスツルメントして、求められる正確な可視性を獲得できます。メソッドのスパンを作成することにより、APM フレームグラフとモニターを使用してタイミングを最適化し、エラーを追跡できます。

コードのインスツルメンテーション

例に従いコードをインスツルメントします

以下の例では、BackupLedger.write メソッド全体をトレースして、実行時間とステータスを測定します。BackupLedger.write は、トランザクション台帳の現在の状態をメモリに保存してから、支払いデータベースを呼び出して新しい顧客請求を送信するアクションです。これは、支払いサービスの charge エンドポイントがヒットしたときに発生します。

http.request POST /charge/ スパンは、直接の子スパンがないと多くの時間がかかります。これは、このリクエストがその動作に対するより優れた情報を得るために、さらなるインスツルメンテーションを必要とする手がかりです。使用しているプログラミング言語に応じて、関数を異なる方法で装飾する必要があります。

Java の場合、Datadog APM により、メソッドデコレータを使用するか、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

デコレータを使用してメソッドをインスツルメントする

この例では、BackupLedger.write メソッドにスパンを追加し、トランザクション台帳に新しい行を追加します。投稿されたすべてのトランザクションを単一のユニットとして追跡するために、1 つのスパンが追加されます。

import datadog.trace.api.Trace

public class BackupLedger {

  // @Trace アノテーションを使用してカスタムメソッドをトレースします
  @Trace
  public void write(List<Transaction> transactions) {
    for (Transaction transaction : transactions) {
      ledger.put(transaction.getId(), transaction);
    }

    // [...]
  }
}

特定のコードブロックをインスツルメントする

この例では、上記で作成した BackupLedger.write スパンに子スパンを追加します。このメソッドは、台帳内のすべてのトランザクションの子スパンと、特定のトランザクション ID を持つカスタムタグを追加します。

import datadog.trace.api.Trace;
import io.opentracing.Scope;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;

public class BackupLedger {

  // `@Trace` アノテーションを使用してカスタムメソッドをトレースします
  @Trace
  public void write(List<Transaction> transactions) {
    for (Transaction transaction : transactions) {
      // `GlobalTracer` を使用してインラインコードのブロックをトレースします
      Tracer tracer = GlobalTracer.get();
      try (Scope scope = tracer.buildSpan("BackupLedger.persist").startActive(true)) {
        // スパンにカスタムメタデータを追加します
        scope.span().setTag("transaction.id", transaction.getId());
        ledger.put(transaction.getId(), transaction);
      }
    }

    // [...]
  }
}

Python の場合、Datadog APM により、メソッドデコレータを使用するか、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

デコレータを使用してメソッドをインスツルメントする

この例では、BackupLedger.write メソッドにスパンを追加し、トランザクション台帳に新しい行を追加します。投稿されたすべてのトランザクションを単一のユニットとして追跡するために、1 つのスパンが追加されます。

from ddtrace import tracer

class BackupLedger:

    # `tracer.wrap` デコレータを使用してカスタムメソッドをトレースします
    @tracer.wrap()
    def write(self, transactions):
        for transaction in transactions:
            self.ledger[transaction.id] = transaction

        # [...]

特定のコードブロックをインスツルメントする

この例では、上記で作成した BackupLedger.write スパンに子スパンを追加します。このメソッドは、台帳内のすべてのトランザクションの子スパンと、特定のトランザクション ID を持つカスタムタグを追加します。

from ddtrace import tracer

class BackupLedger:

    # `tracer.wrap` デコレータを使用してカスタムメソッドをトレースします
    @tracer.wrap()
    def write(self, transactions):
        for transaction in transactions:
            # `tracer.trace` コンテキストマネージャーを使用してインラインコードのブロックをトレースします
            with tracer.trace('BackupLedger.persist') as span:
                # "persist_transaction" スパンにカスタムメタデータを追加します
                span.set_tag('transaction.id', transaction.id)
                self.ledger[transaction.id] = transaction

        # [...]

Ruby の場合、Datadog APM により、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

この例では、BackupLedger.write メソッドの呼び出し用に新しいスパンを作成し、特定のトランザクション ID を持つカスタムタグで台帳に投稿されたすべてのトランザクションに子スパンを作成します。

require 'ddtrace'

class BackupLedger

  def write(transactions)
    # グローバルな `Datadog.tracer.trace` を使用してインラインコードのブロックをトレースします
    Datadog.tracer.trace('BackupLedger.write') do |method_span|
      transactions.each do |transaction|
        Datadog.tracer.trace('BackupLedger.persist') do |span|
          # "persist_transaction" スパンにカスタムメタデータを追加します
          span.set_tag('transaction.id', transaction.id)
          ledger[transaction.id] = transaction
        end
      end
    end

    # [...]
  end
end

Go の場合、Datadog APM により、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

この例では、台帳に投稿されたすべてのトランザクションに対して新しいスパンを作成し、特定のトランザクション ID を持つカスタムタグをスパンに追加します。

package ledger

import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

// [...]

func (bl *BackupLedger) write(ctx context.Context, transactions []*Transaction) (err error) {
  // `write` 関数をトレースし、存在する場合はエラーをキャプチャします
  span, ctx := tracer.StartSpanFromContext(ctx, "BackupLedger.write")
  defer func() {
    span.Finish(tracer.WithError(err))
  }()

  for _, t := range transactions {
    if err := bl.persistTransaction(ctx, t); err != nil {
      return err
    }
  }
  return nil
}

// persistTransaction は、トレースしたい内部関数です。 
// 以前と同じアプローチを使用できます。
// これは、渡した `ctx` に親/子関係を作成するための、すぐに使用できるスパン参照が含まれるためです。
func (bl *BackupLedger) persistTransaction(ctx context.Context, transaction *Transaction) error {
  id := transaction.ID
  span, _ := tracer.StartSpanFromContext(ctx, "BackupLedger.persist", tracer.Tag("transaction_id", id))
  defer span.Finish()

  if t, ok := bl.transactions[id]; ok {
    return errors.New("duplicate entry")
  }
  bl.transactions[id] = transaction
  return nil
}

Node.js の場合、Datadog APM により、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

この例では、BackupLedger.write メソッドの呼び出し用に新しいスパンを作成し、特定のトランザクション ID を持つカスタムタグで台帳に投稿されたすべてのトランザクションに子スパンを作成します。

const tracer = require('dd-trace')

function write (transactions) {
  // `tracer.trace` コンテキストマネージャーを使用してインラインコードのブロックをトレースします
  tracer.trace('BackupLedger.write', () => {
    for (const transaction of transactions) {
      // "persist_transaction" スパンにカスタムメタデータを追加します
      span.setTag('transaction.id', transaction.id)
      this.ledger[transaction.id] = transaction
    }
  })

  // [...]
}

.NET の場合、Datadog APM により、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

この例では、台帳に投稿されたすべてのトランザクションに対して新しいスパンを作成し、特定のトランザクション ID を持つカスタムタグをスパンに追加します。

using Datadog.Trace;

public void Write(List<Transaction> transactions)
{
    //グローバルトレーサーを使用してインラインコードのブロックをトレースします
    using (var scope = Tracer.Instance.StartActive("BackupLedger.write"))
    {
        foreach (var transaction in transactions)
        {
            using (var scope = Tracer.Instance.StartActive("BackupLedger.persist"))
            {
                // スパンにカスタムメタデータを追加します
                scope.Span.SetTag("transaction.id", transaction.Id);
                this.ledger[transaction.Id] = transaction;
            }
        }
    }

    // [...]
}

PHP の場合、Datadog APM により、メソッドラッパーを使用するか、特定のコードブロックをインスツルメントすることにより、コードをインスツルメントしてカスタムスパンを生成できます。

ラッパーを使用してメソッドをインスツルメントする

この例では、BackupLedger.write メソッドにスパンを追加し、トランザクション台帳に新しい行を追加します。dd_trace() 関数を使用して、投稿されたすべてのトランザクションを単一のユニットとして追跡するために、1 つのスパンが追加されます。

<?php
  class BackupLedger {

    public function write(array $transactions) {
      foreach ($transactions as $transaction) {
        $this->transactions[$transaction->getId()] = $transaction;
      }

      # [...]
    }
  }

  // dd_trace() を使用してカスタムメソッドをトレースします
  dd_trace('BackupLedger', 'write', function () {
    $tracer = \DDTrace\GlobalTracer::get();
    $scope = $tracer->startActiveSpan('BackupLedger.write');
    dd_trace_forward_call();
    $scope->close();
    return $result;
  });
?>

特定のコードブロックをインスツルメントする

この例では、上記で作成した BackupLedger.write スパンに子スパンを追加します。このメソッドは、台帳内のすべてのトランザクションの子スパンと、特定のトランザクション ID を持つカスタムタグを追加します。

<?php
  class BackupLedger {

    public function write(array $transactions) {
      foreach ($transactions as $transaction) {
        // グローバルトレーサーを使用してインラインコードのブロックをトレースします
        $scope = \DDTrace\GlobalTracer::get()->startActiveSpan('BackupLedger.persist');

        // スパンにカスタムメタデータを追加します
        $scope->getSpan()->setTag('transaction.id', $transaction->getId());
        $this->transactions[$transaction->getId()] = $transaction;

        // スパンを閉じます
        $scope->close();
      }

      # [...]
    }
  }

  // dd_trace() を使用してカスタムメソッドをトレースします
  dd_trace('BackupLedger', 'write', function () {
    $tracer = \DDTrace\GlobalTracer::get();
    $scope = $tracer->startActiveSpan('BackupLedger.write');
    dd_trace_forward_call();
    $scope->close();
    return $result;
  });
?>

Datadog UI を活用して新しいカスタムスパンを表示する

ビジネスロジックをインスツルメントしたら、Datadog APM UI で結果を確認します。

  1. Service List に移動し、カスタムスパンを追加したサービスを特定してから、サービス詳細画面に移動します。サービス詳細画面で、追加した特定のリソースをクリックし、時間フィルターを The past 15 minutes に変更して、スパンサマリーテーブルまでスクロールします。

これで、追加した新しいスパンを見つけることができるはずです

スパンサマリーテーブルでは、トレースを構成するスパンに関する集約情報を確認できます。ここで、異常な回数繰り返されるスパンを特定して、ループやデータベースアクセスの非効率性を見つけることができます(n+1 問題など)。

  1. トレースの一覧画面までスクロールダウンし、トレースのいずれかをクリックします。

これで、カスタムスパンがコードベースに正常に追加され、フレームグラフと App Analytics で利用できるようになりました。これは、Datadog のツールを最大限に活用するための最初のステップです。次にカスタムタグをスパンに追加すれば、さらに強力にすることができます。

その他の参考資料