ファンクションの分散トレース

OCI Functionsを使用したデバッグ時にトレースを有効にし、ファンクション・トレースを表示する方法をご紹介します。

関数が呼び出されたが、予期したとおりに実行または実行されない場合は、問題を詳細に調査する必要があります。分散トレース機能は、システムのさまざまなコンポーネントを通過する関数の実行を観察します。スタンドアロン関数をトレースおよび計測して、実行およびパフォーマンスの問題をデバッグできます。ファンクション・トレースを使用して、次のような複数の機能およびサービスで構成される完全なサーバーレス・アプリケーションに関する問題をデバッグすることもできます。

  • 別の関数を呼び出す関数
  • オブジェクト・ストレージ・サービスなどの他のサービスを呼び出す関数
  • APIゲートウェイ・サービスにデプロイされたAPIゲートウェイのバックエンドとして機能するファンクション
  • イベント・サービス、通知サービスまたはコネクタ・ハブによってイベントに応答してトリガーされるファンクション

OCI Functionsトレース機能は、Oracle Cloud Infrastructure Application Performance Monitoringサービスによって提供されます。Application Performance Monitoring (APM)の機能を使用すると、作成およびデプロイするファンクションの障害およびレイテンシの問題を特定してトラブルシューティングできます。

Application Performance Monitoringサービスで、次の手順を実行します。

  • APMドメインには、Application Performance Monitoringによってモニターされるシステムが含まれます。APMドメインは、データを格納、集計、表示およびビジュアル化するトレースおよびスパン・データのコレクタのインスタンスです。
  • トレースは、指定された期間に分散システムのすべてのコンポーネントを通過するリクエストの完全なフローです。これは、同じ単一全体のリクエスト・フローに関連するすべてのスパン・ツリー全体で構成されます。
  • スパンは、名前、開始時間および期間がトレース内の操作または論理的な作業単位です。スパンは、リクエスト・フロー全体内の作業単位の期間に関連付けられた時間セグメントです。

Application Performance Monitoringのトレース・エクスプローラを使用すると、リクエスト・フロー全体をビジュアル化し、診断のトレースおよびスパン詳細を確認できます。エラーのあるスロー・トレースおよびトレースを表示および監視できます。トレースの問題を分離して識別するために、ページ・ロード、AJAXコール、サービス・リクエストなどの特定のスパンにドリルダウンできます。Application Performance Monitoringサービスの詳細は、「Application Performance Monitoring」を参照してください。

関数のトレースを有効にするには、次を実行する必要があります。

  1. ポリシーが存在しない場合は、APMドメインにアクセスする権限をOCI Functionsサービスに付与するポリシーを設定します(OCI FunctionsサービスおよびOCI Functionsユーザーにトレース・リソースへのアクセス権を付与するポリシー・ステートメントを参照)。
  2. APMドメインを設定します。
  3. Functionsアプリケーションのトレースを有効にし、作成したAPMドメインを選択します。
  4. 1つ以上の関数のトレースを有効にします。

ファンクションのトレースを有効にすると、OCIファンクションによってデフォルトのファンクション呼出しスパンが自動的に生成されます。デフォルト・スパンでは、リクエストの処理およびコール元へのレスポンスの返送にかかった全体的な時間など、ファンクションの実行コンテキストに関する情報が取得されます。デフォルトのファンクション呼出しスパンに加えて、カスタム・スパンを定義するコードをファンクションに追加できます。カスタム・スパンを使用して、デバッグに役立つ、より機能固有の情報を取得します。たとえば、特定の作業単位の開始と終了を取得するカスタム・スパンを定義できます。たとえば、作業単位には、Vaultからのデータベース・パスワードの取得、データベース接続の開始、データベースからのレコードの取得などがあります。

OCI Functionsコンテキストには、有用なトレース情報を提供する4つの変数が追加されています。次の変数があります。

  • FN_APP_NAME:ファンクション・アプリケーション名。
  • FN_FN_NAME:関数名。
  • OCI_TRACE_COLLECTOR_URL: データ・キーを含むAPMドメインURL。
  • OCI_TRACING_ENABLED:トレースは有効になっていますか。
    • 環境変数から取得すると、0または1が返されます。
    • 関数コンテキストから取得すると、使用する言語に適したtrueまたはfalseが返されます。

トレースを有効にするために必要なIAMポリシー

トレースを有効にする前に、所属するグループに、既存のAPMドメインにアクセスする権限またはAPMドメインを作成する権限が必要です。また、OCI FunctionsにはAPMドメインにアクセスする権限が必要です。OCI FunctionsサービスおよびOCI Functionsユーザーにトレース・リソースへのアクセス権を付与するポリシー・ステートメントを参照してください。

コンソールを使用したトレースの有効化およびファンクション・トレースの表示

Oracle Cloud Infrastructure Application Performance Monitoring (APM)サービスのトレースの有効化およびファンクション・トレースの表示には、いくつかのステップが必要です。まず、関数を含むアプリケーションのトレースを有効にします。次に、1つ以上の関数に対してトレースを有効にします。その後、APMトレース・エクスプローラでファンクション・トレースを表示できます。

コンソールを使用したトレースの有効化

トレースを有効にするには、次のステップを実行します。

  1. ファンクション開発者としてコンソールにサインインします。
  2. ナビゲーション・メニューを開き、「開発者サービス」をクリックします。「ファンクション」で、「アプリケーション」をクリックします。
  3. Functionsアプリケーションを含むリージョンおよびコンパートメントを選択します。

    「アプリケーション」ページには、選択したコンパートメント内のすべてのアプリケーションが表示されます。

  4. トレースを有効にするFunctionsアプリケーションを選択します。
  5. アプリケーションのトレースを有効にするには:
    1. 「リソース」で、「トレース」をクリックします。
    2. 「トレース使用可能」オプションを選択し、次を指定します。
      • コンパートメント:トレースを作成するコンパートメント。デフォルトでは、現在のコンパートメントです。
      • APMドメイン:トレースを作成するAPMドメイン(Application Performance Monitoringサービスで定義)。既存のAPMドメインを使用するには、リストから既存のAPMドメインを選択します。または、新しいAPMドメインを作成するには、「APMドメイン」をクリックします。APMドメインの詳細は、Application Performance Monitoringスタート・ガイドを参照してください。
        Note

        The APM Domain needs to have both public and private data keys for function tracing to work.キーが存在しない場合は、コンソール・インタフェースを使用して作成できます。
    3. 「トレースの有効化」をクリックして、アプリケーションのトレースを有効にします。

    Functionsアプリケーションのトレースを有効にすると、アプリケーション内の1つ以上の関数に対してトレースを有効にできるようになりました。

  6. アプリケーション内の特定の関数のトレースを有効にするには:
    1. 「リソース」で、「ファンクション」をクリックします。
    2. トレースを有効にする1つ以上の関数の横にある「トレースの有効化」オプションを選択します。

      「トレースの有効化」オプションは、アプリケーションのトレースを以前に有効にした場合にのみ表示されます。次に注意してください:

      • 「トレースの有効化」オプションが表示されない場合は、アプリケーションのトレースを有効にする必要があります。アプリケーションのトレースをまだ有効にしていない場合は、前のステップを参照してください。
      • 以前にアプリケーションのトレースを有効にしたが、後で無効にした場合は、「アプリケーション・トレースの有効化」リンクが表示されます。「アプリケーション・トレースの有効化」リンクをクリックして、アプリケーションのトレースを再度有効にします(前のステップを参照)。アプリケーションのトレースを再度有効にすると、特定の関数に対してトレースを有効にできます。

アプリケーションおよび1つ以上のファンクションのトレースを有効にすると、ファンクション・トレースを表示できます。

コンソールを使用したファンクション・トレースの表示

トレースが有効になっている関数のトレースを表示するには:

  1. ファンクション開発者としてコンソールにサインインします。
  2. ナビゲーション・メニューを開き、「開発者サービス」をクリックします。「ファンクション」で、「アプリケーション」をクリックします。
  3. ファンクション・トレースを表示するファンクションを使用するファンクション・アプリケーションを含むリージョンおよびコンパートメントを選択します。

    「アプリケーション」ページには、選択したコンパートメント内のすべてのアプリケーションが表示されます。

  4. トレースを表示するファンクションを含むアプリケーションを選択します。
  5. 関数のトレースを表示するには:
    1. アプリケーションでトレースが有効になっているすべての関数のトレースを表示するには:
      1. 「リソース」で、「トレース」をクリックします。
      2. トレースの名前をクリックします。
        ノート

        トレース名は、アプリケーションのトレースをすでに有効にしている場合にのみ表示されます。
    2. トレースが有効になっている特定の関数のトレースを表示するには:
      1. 「リソース」で、「ファンクション」をクリックします。
      2. ファンクションの横にある「アクション」メニュー(アクション・メニュー)をクリックして、「トレースの表示」をクリックします。
        ノート

        「トレースの表示」オプションは、ファンクションのトレースをすでに有効にしている場合にのみ表示されます。

    選択した関数のトレースがAPMトレース・エクスプローラに表示されます。デフォルトでは、デフォルトの関数呼び出しスパンおよび関数に定義されたカスタムスパンに対してトレースが表示されます。

  6. APMトレース・エクスプローラで:
    1. トレースをクリックすると、そのトレースのスパンが表示されます。
    2. スパンをクリックすると、そのスパンについて取得された詳細が表示されます。

    APMトレース・エクスプローラの使用の詳細は、トレース・エクスプローラの使用を参照してください。

関数のチェーンのトレース

デフォルトでは、関数トレースは関数呼び出し全体のトレースを提供します。ただし、多くの場合、最新のクラウド・アプリケーションでは、関数の呼出しを連鎖する必要があります。OCIファンクション・トレースでは、別のファンクションによって起動されたファンクションの実行をトレースできます。この機能は、APMトレース・エクスプローラの単一のスパン・ツリーでコールのチェーン内の各関数の実行を調べることができることを意味します。

ファンクションのチェーンをトレースするには、ファンクション・コードからのファンクション呼出しリクエストで、X-B3ヘッダーX-B3-TraceIdX-B3-SpanIdX-B3-ParentSpanIdおよびX-B3-Sampledを伝播する必要があります。

ファンクションの実行後、ファンクションのトレース・データが収集され、APMトレース・エクスプローラで使用できます。APMトレース・エクスプローラの使用の詳細は、トレース・エクスプローラの使用を参照してください。

Pythonを使用した関数の連鎖のトレース

次に、一連の関数をトレースする方法の例を示します。この例では、2つのサンプル関数を作成する必要があります。これらのステップに従って、機能を設定します。

  1. トレースPython関数を作成します: fn init --runtime python <your-function-name-1>
  2. 「Hello World!Python関数: fn init --runtime python <your-function-name-2>
  3. 両方のファンクションをデプロイします: fn -v deploy --app <app-name>
  4. 2番目のファンクションOCIDを取得し、エンドポイントを起動します: fn inspect function your-app-name your-function-name-2
  5. JSONファイルを作成して、必要な情報を最初の関数に渡します。たとえば、test.jsonファイルは次のようになります。
    
    {
        "function_ocid": "ocid1.fnfunc.oc1.iad.aaaaaaaaxxxxxxxxxxx",
        "function_endpoint": "https://xxxxxxxxx.us-ashburn-1.functions.oci.oraclecloud.com",
        "function_body": "",
        "__comment": "Alternatively, you can set function_body to { \"name\": \"Oracle\" }"
    }                    
                    
  6. 最初のファンクションが呼び出されると、test.jsonを使用して2番目のファンクション情報を渡すことができます: fn invoke <app-name> <your-function-name-1> < test.json

これで、最初の関数を必要なコード更新で更新できます。

パッケージの構成

requirements.txtファイルを更新して、次のパッケージを含めます。


fdk
oci
            

ファイルを保存します。

X-B3ヘッダーを伝播するようにファンクション・コードを更新します

Python関数は、handler関数をコールし、invokeコマンドからJSON情報を渡します。handler関数は、簡略化するためにいくつかの小さいブロックに分割されます。完全なソース・ファイルは、このセクションの下部にあります。

JSONデータのロード

最初の部分では、JSONデータはファンクション呼出しからロードされます。


import io
import json
import logging
import oci
from fdk import response

def handler(ctx, data: io.BytesIO=None):
    app_name = ctx.AppName()
    func_name = ctx.FnName()
    logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler")

    try:
        body = json.loads(data.getvalue())
        function_endpoint = body.get("function_endpoint")
        function_ocid = body.get("function_ocid")
        function_body = body.get("function_body")
    except (Exception) as ex:
        print('ERROR: Missing key in payload', ex, flush=True)
        raise
            

呼出しクライアントの作成およびヘッダー情報の収集

OCI Python SDKおよびFunctionsリソース・プリンシパルを使用して、Functions呼出しクライアントを作成します。次に、tracing_contextを取得し、必要な情報を抽出してHTTPヘッダーを作成します。


    signer = oci.auth.signers.get_resource_principals_signer()
    client = oci.functions.FunctionsInvokeClient(config={}, signer=signer, service_endpoint=function_endpoint)
    
    #
    # Zipkin X-B3- header propagation
    #
    tracing_context = ctx.TracingContext()
    trace_id = tracing_context.trace_id()
    span_id = tracing_context.span_id()
    parent_span_id = tracing_context.parent_span_id()
    is_sampled = tracing_context.is_sampled()
            

X-B3ヘッダーの伝播

OCI Python SDKでは、カスタム・ヘッダーを設定できます。この手法を使用して、X-B3ヘッダーを2番目のファンクション呼出しに渡します。ヘッダー情報は、trace_idspan_idparent_span_idおよびis_sampledに渡されます。最後に、2番目のファンクションはclientを使用して起動され、レスポンスはこのファンクションのレスポンスに渡されます。


    # if tracing is enabled, is_sampled will be true in the tracing context
    if is_sampled:
        # To propagate headers in the OCI SDK in the request to the next function,
        # add the X-B3- headers in the request. This header will be included in ALL
        # subsequent calls made.
        if trace_id is not None:
            client.base_client.session.headers['X-B3-TraceId'] = trace_id
            logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | trace_id: " + trace_id)
        if span_id is not None:
            client.base_client.session.headers['X-B3-SpanId'] = span_id
            logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | span_id: " + span_id)
        if parent_span_id is not None:
            client.base_client.session.headers['X-B3-ParentSpanId'] = parent_span_id
            logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | parent_span_id: " + parent_span_id)
        client.base_client.session.headers['X-B3-Sampled'] = str(int(is_sampled))
        logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | is_sampled: " + str(int(is_sampled)))
    else:
        # function.trace is DISABLED
        logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | function tracing is DISABLED")

    resp = client.invoke_function(function_id=function_ocid, invoke_function_body=function_body)
    logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | Response: " + resp.data.text)

    return response.Response(
        ctx, 
        response_data=resp.data.text,
        headers={"Content-Type": "application/json"}
    )
            
レビュー完了機能ソース・コード

サンプルPython関数の完全なソース・コードを次に示します。


#
# oci-invoke-function-python version 2.0.
#
# Copyright (c) 2021 Oracle, Inc.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
#

import io
import json
import logging
import oci
from fdk import response

def handler(ctx, data: io.BytesIO=None):
    app_name = ctx.AppName()
    func_name = ctx.FnName()
    logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler")

    try:
        body = json.loads(data.getvalue())
        function_endpoint = body.get("function_endpoint")
        function_ocid = body.get("function_ocid")
        function_body = body.get("function_body")
    except (Exception) as ex:
        print('ERROR: Missing key in payload', ex, flush=True)
        raise
    
    signer = oci.auth.signers.get_resource_principals_signer()
    client = oci.functions.FunctionsInvokeClient(config={}, signer=signer, service_endpoint=function_endpoint)
    
    #
    # Zipkin X-B3- header propagation
    #
    tracing_context = ctx.TracingContext()
    trace_id = tracing_context.trace_id()
    span_id = tracing_context.span_id()
    parent_span_id = tracing_context.parent_span_id()
    is_sampled = tracing_context.is_sampled()

    # if tracing is enabled, is_sampled will be true in the tracing context
    if is_sampled:
        # To propagate headers in the OCI SDK in the request to the next function,
        # add the X-B3- headers in the request. This header will be included in ALL
        # subsequent calls made.
        if trace_id is not None:
            client.base_client.session.headers['X-B3-TraceId'] = trace_id
            logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | trace_id: " + trace_id)
        if span_id is not None:
            client.base_client.session.headers['X-B3-SpanId'] = span_id
            logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | span_id: " + span_id)
        if parent_span_id is not None:
            client.base_client.session.headers['X-B3-ParentSpanId'] = parent_span_id
            logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | parent_span_id: " + parent_span_id)
        client.base_client.session.headers['X-B3-Sampled'] = str(int(is_sampled))
        logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | is_sampled: " + str(int(is_sampled)))
    else:
        # function.trace is DISABLED
        logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | function tracing is DISABLED")

    resp = client.invoke_function(function_id=function_ocid, invoke_function_body=function_body)
    logging.getLogger().info("Inside app: " + app_name + " | function: " + func_name + " | method: handler | Response: " + resp.data.text)

    return response.Response(
        ctx, 
        response_data=resp.data.text,
        headers={"Content-Type": "application/json"}
    )
            

ファンクションへのカスタム・スパンの追加

関数トレースを有効にすると、デフォルトの関数呼び出しスパンが関数呼び出し全体のトレースを提供します。デフォルトのスパンは適切な情報を提供できますが、コードを調査するときは、より深く掘り下げることができます。カスタム・スパンがコードに直接追加され、メソッドまたはコード・ブロックのスパンを定義できます。結果のデータにより、実行時のファンクションの全体像がよりよくわかります。

カスタム・スパンを使用するには、Oracle Cloud Infrastructure Application Performance Monitoring (APM)サービスを使用してアプリケーションおよびファンクションのトレースを有効にする必要があります。トレースを設定するには、次の手順を実行する必要があります。

  1. ポリシーが存在しない場合は、APMドメインにアクセスする権限をOCI Functionsサービスに付与するポリシーを設定します(OCI FunctionsサービスおよびOCI Functionsユーザーにトレース・リソースへのアクセス権を付与するポリシー・ステートメントを参照)。
  2. APMドメインを設定します。
  3. Functionsアプリケーションのトレースを有効にし、作成したAPMドメインを選択します。
  4. 1つ以上の関数のトレースを有効にします。

これらのステップはすでにカバーされています。ただし、カスタム・スパンにはさらにいくつかのことが必要です。

  • Zipkinなどの分散トレース・クライアント・ライブラリを選択します。
  • 関数の依存関係にクライアントライブラリを追加します。
  • ファンクション・コードで、OCI_TRACING_ENABLEDファンクションのコンテキスト変数を使用して、トレースが有効になっているかどうかを確認します。
  • ファンクション・コードで、OCI_TRACE_COLLECTOR_URLファンクション・コンテキスト変数を使用して、カスタム・スパンをAPMドメインに送信します。
  • 関数コードにインストゥルメンテーションを追加します。
ノート

カスタム・スパンを使用するには、次のFn Project FDKの最小バージョンが必要です:

  • Java FDK: 1.0.129
  • Python FDK: 0.1.22
  • ノードFDK: 0.0.20
Java関数へのカスタム・スパンの追加

Zipkinを使用してJava関数にカスタム・スパンを追加する方法の例を次に示します。この例を試してみる場合は、Java "Hello World!"関数を作成して、カスタム・スパン・コードを追加できます。サンプル・ファンクションを作成するには:

  • Java関数fn init --runtime java apm-fn-javaを作成します。
  • わかりやすくするために、src/testディレクトリを削除します。

Mavenの構成

Maven pom.xmlファイルの<dependencies>セクションに次の依存関係を追加します。


<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-sender-urlconnection</artifactId>
    <version>2.16.3</version>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
    <version>2.16.3</version>
</dependency>
<dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave</artifactId>
    <version>5.13.3</version>
</dependency>
    <dependency>
    <groupId>io.zipkin.brave</groupId>
    <artifactId>brave-core</artifactId>
    <version>4.13.6</version>
</dependency>
            

ファイルを保存します。

HandleRequestメソッド

メソッドに関する監視は、handleRequestソース・コードに従います。


package com.example.fn;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.*;
import brave.sampler.Sampler;
import com.fnproject.fn.api.tracing.TracingContext;
import com.github.kristofa.brave.IdConversion;
import zipkin2.reporter.Sender;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;

public class HelloFunction {
    Sender sender;
    AsyncZipkinSpanHandler zipkinSpanHandler;
    Tracing tracing;
    Tracer tracer;
    String apmUrl;
    TraceContext traceContext;

    public String handleRequest(String input, TracingContext tracingContext) {
        try {
            intializeZipkin(tracingContext);
            // Start a new trace or a span within an existing trace representing an operation
            Span span = tracer.newChild(traceContext).name("MainHandle").start();
            System.out.println("Inside Java Hello World function");
            try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
                method1();
                method2();
                method3();
            } catch (RuntimeException | Error e) {
                span.error(e); // Unless you handle exceptions, you might not know the operation failed!
                throw e;
            } finally {
                span.finish(); // note the scope is independent of the span. Always finish a span.
                tracing.close();
                zipkinSpanHandler.flush();
            }
        } catch (Exception e) {
            return e.getMessage();
        }
        return "Hello, AppName " + tracingContext.getAppName() + " :: fnName " + tracingContext.getFunctionName();
    }
            
  • TracingContext tracingConextオブジェクトは、APMサービスへの接続に必要なすべてのAPM関連情報を渡します。
  • tracingContextを更新し、カスタム・スパンを設定するために使用されるtracerオブジェクトを作成するintializeZipkinメソッドがコールされます。
  • 親カスタム・スパンに対してspanが作成されます。次に、親スパンのスコープで3つのメソッドがコールされます。
  • finallyブロックでは、すべてのトレース・オブジェクトがクローズされています。

initializeZipkinメソッド

intializeZipkinメソッドに関する監視は、ソース・コードに従います。

     
    public void intializeZipkin(TracingContext tracingContext) throws Exception {
        System.out.println("Initializing the variables");
        apmUrl = tracingContext.getTraceCollectorURL();
        sender = URLConnectionSender.create(apmUrl);
        zipkinSpanHandler = AsyncZipkinSpanHandler.create(sender);
        tracing = Tracing.newBuilder()
                .localServiceName(tracingContext.getServiceName())
                .sampler(Sampler.NEVER_SAMPLE)
                .addSpanHandler(zipkinSpanHandler)
                .build();
        tracer = tracing.tracer();
        tracing.setNoop(!tracingContext.isTracingEnabled());
        traceContext = TraceContext.newBuilder()
                .traceId(IdConversion.convertToLong(tracingContext.getTraceId()))
                .spanId(IdConversion.convertToLong(tracingContext.getSpanId()))
                .sampled(tracingContext.isSampled()).build();
    }   
            
  • traceContextは、カスタム・スパンの作成に使用されるすべてのオブジェクトを作成するために渡されます。
  • apmURLは、getTraceCollectorURL()メソッドから取得されます。URLはAPMドメインのエンドポイントで、カスタム・スパンを構築するtracerオブジェクトの作成に使用されます。
  • ビルダーは、zipkinSpanHandlerとサービス名を使用してtracerオブジェクトを作成します。このtracerオブジェクトは、カスタム・スパンを作成するために使用されます。

カスタム・スパンの作成

tracerオブジェクトを初期化すると、カスタム・スパンを作成できます。


public void method1() {
    System.out.println("Inside Method1 function");
    TraceContext traceContext = tracing.currentTraceContext().get();
    Span span = tracer.newChild(traceContext).name("Method1").start();
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        span.finish();
    }
}
            
  • method1メソッドは、Method1という名前のカスタム・スパンを作成します。
レビュー完了機能ソース・コード

サンプルJavaトレース関数の完全なソース・コードを次に示します。


package com.example.fn;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.*;
import brave.sampler.Sampler;
import com.fnproject.fn.api.tracing.TracingContext;
import com.github.kristofa.brave.IdConversion;
import zipkin2.reporter.Sender;
import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;

public class HelloFunction {
    Sender sender;
    AsyncZipkinSpanHandler zipkinSpanHandler;
    Tracing tracing;
    Tracer tracer;
    String apmUrl;
    TraceContext traceContext;
    public void intializeZipkin(TracingContext tracingContext) throws Exception {
        System.out.println("Initializing the variables");
        apmUrl = tracingContext.getTraceCollectorURL();
        sender = URLConnectionSender.create(apmUrl);
        zipkinSpanHandler = AsyncZipkinSpanHandler.create(sender);
        tracing = Tracing.newBuilder()
                .localServiceName(tracingContext.getServiceName())
                .sampler(Sampler.NEVER_SAMPLE)
                .addSpanHandler(zipkinSpanHandler)
                .build();
        tracer = tracing.tracer();
        tracing.setNoop(!tracingContext.isTracingEnabled());
        traceContext = TraceContext.newBuilder()
                .traceId(IdConversion.convertToLong(tracingContext.getTraceId()))
                .spanId(IdConversion.convertToLong(tracingContext.getSpanId()))
                .sampled(tracingContext.isSampled()).build();
    }

    public String handleRequest(String input, TracingContext tracingContext) {
        try {
            intializeZipkin(tracingContext);
            // Start a new trace or a span within an existing trace representing an operation
            Span span = tracer.newChild(traceContext).name("MainHandle").start();
            System.out.println("Inside Java Hello World function");
            try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
                method1();
                method2();
                method3();
            } catch (RuntimeException | Error e) {
                span.error(e); // Unless you handle exceptions, you might not know the operation failed!
                throw e;
            } finally {
                span.finish(); // note the scope is independent of the span. Always finish a span.
                tracing.close();
                zipkinSpanHandler.flush();
            }
        } catch (Exception e) {
            return e.getMessage();
        }
        return "Hello, AppName " + tracingContext.getAppName() + " :: fnName " + tracingContext.getFunctionName();
    }

    public void method1() {
        System.out.println("Inside Method1 function");
        TraceContext traceContext = tracing.currentTraceContext().get();
        Span span = tracer.newChild(traceContext).name("Method1").start();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            span.finish();
        }
    }

    public void method2() {
        System.out.println("Inside Method2 function");
        TraceContext traceContext = tracing.currentTraceContext().get();
        Span span = tracer.newChild(traceContext).name("Method2").start();
        try {
            Thread.sleep(400);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            span.finish();
        }
    }

    public void method3() {
        System.out.println("Inside Method3 function");
        TraceContext traceContext = tracing.currentTraceContext().get();
        Span span = tracer.newChild(traceContext).name("Method3").start();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            span.finish();
        }
    }
}
            
Python関数へのカスタム・スパンの追加

Zipkinを使用してPython関数にカスタム・スパンを追加する方法の例を次に示します。この例を試してみる場合は、Pythonの"Hello World!"関数を作成し、カスタム・スパン・コードを追加できます。サンプル・ファンクションを作成するには:

  • Python関数を作成します: fn init --runtime python apm-fn-python

パッケージの構成

requirements.txtファイルを更新して、次のパッケージを含めます。


fdk
requests
py_zipkin
            

ファイルを保存します。

ハンドラ・クラスおよび親カスタム・スパンの作成

Python関数は、handler関数をコールし、関数コンテキストを渡してカスタム・スパンを作成します。

def handler(ctx, data: io.BytesIO = None):
    tracing_context = ctx.TracingContext()
    with zipkin_span(
        service_name=tracing_context.service_name(),
        span_name="Customer Code",
        transport_handler=(
            lambda encoded_span: transport_handler(
                encoded_span, tracing_context
            )
        ),
        zipkin_attrs=tracing_context.zipkin_attrs(),
        encoding=Encoding.V2_JSON,
        binary_annotations=tracing_context.annotations()
    ):
        name = "World"
        try:
            body = json.loads(data.getvalue())
            name = body.get("name")
        except (Exception, ValueError) as ex:
            logging.getLogger().info('error parsing json payload: ' + str(ex))

        logging.getLogger().info("Inside Python Hello World function")
        time.sleep(0.005)
        example(ctx)
        return response.Response(
            ctx, response_data=json.dumps(
                {"message": "Hello {0}".format(name)}),
            headers={"Content-Type": "application/json"}
        )         
  • tracing_contextはファンクション・コンテキストから渡され、カスタム・スパンを作成および構成するために必要なすべての情報が含まれます。
    ノート

    トレースが有効になっていない場合、トレース・コンテキストは空のオブジェクトです。空のトレース・コンテキストでは、is_sampledフラグはNoneに設定され、py_zipkinはスパンを発行しません。
  • with zipkin_span文を使用してスパンを作成します。
    • tracing_contextの情報は、service_nameの取得、transport_handlerのコールおよびzipking_attrsの設定に使用されます。
    • カスタム・スパン名は、span_nameを設定することによってのみ指定されます。
    • Zipkinに必要なトレース属性は、トレース・コンテキストtracing_context.zipkin_attrs()から取得されます。
  • カスタム・スパン設定では、メイン・ブロックでボイラープレート「Hello World!」コードが実行されます。唯一の例外として、example関数のコールがあります。

transport_handlerファンクション

transport_handler関数は、スパンの実行に関するメッセージとともにAPMドメインと通信します。

# transport handler, needed by py_zipkin
def transport_handler(encoded_span, tracing_context):
    return requests.post(
        tracing_context.trace_collector_url(),
        data=encoded_span,
        headers={"Content-Type": "application/json"},
    )            
  • trace_collector_urlは、ファンクション・コンテキストから戻されます。このURLは、カスタム・スパンのAPMドメインへの通信エンドポイントを提供します。

サンプル関数でのカスタム・スパンの作成

この例は、カスタム・スパンの作成を示しています。

def example(ctx):
    with zipkin_span(
        service_name=ctx.TracingContext().service_name(),
        span_name="Get ADB Password from OCI Vault",
        binary_annotations=ctx.TracingContext().annotations()
    ) as example_span_context:
        try:
            logging.getLogger().debug("Get ADB Password from OCI Vault")
            time.sleep(0.005)
            # throwing an exception to show how to add error messages to spans
            raise Exception('Request failed')
        except (Exception, ValueError) as error:
            example_span_context.update_binary_annotations(
                {"Error": True, "errorMessage": str(error)}
            )
        else:
            FakeResponse = namedtuple("FakeResponse", "status, message")
            fakeResponse = FakeResponse(200, "OK")
            # how to update the span dimensions/annotations
            example_span_context.update_binary_annotations(
                {
                    "responseCode": fakeResponse.status,
                    "responseMessage": fakeResponse.message
                }
            )            
  • with zipkin_span文は、カスタム・スパンを識別して名前を指定するために使用します。
  • example_span_contextブロックは例外を発生させ、エラー・メッセージを返します。
レビュー完了機能ソース・コード

サンプルPythonトレース関数の完全なソース・コードを次に示します。


import io
import json
import logging

from fdk import response

import requests
import time
from py_zipkin import Encoding
from py_zipkin.zipkin import zipkin_span
from collections import namedtuple


# transport handler, needed by py_zipkin
def transport_handler(encoded_span, tracing_context):
    return requests.post(
        tracing_context.trace_collector_url(),
        data=encoded_span,
        headers={"Content-Type": "application/json"},
    )


def handler(ctx, data: io.BytesIO = None):
    tracing_context = ctx.TracingContext()
    with zipkin_span(
        service_name=tracing_context.service_name(),
        span_name="Customer Code",
        transport_handler=(
            lambda encoded_span: transport_handler(
                encoded_span, tracing_context
            )
        ),
        zipkin_attrs=tracing_context.zipkin_attrs(),
        encoding=Encoding.V2_JSON,
        binary_annotations=tracing_context.annotations()
    ):
        name = "World"
        try:
            body = json.loads(data.getvalue())
            name = body.get("name")
        except (Exception, ValueError) as ex:
            logging.getLogger().info('error parsing json payload: ' + str(ex))

        logging.getLogger().info("Inside Python Hello World function")
        time.sleep(0.005)
        example(ctx)
        return response.Response(
            ctx, response_data=json.dumps(
                {"message": "Hello {0}".format(name)}),
            headers={"Content-Type": "application/json"}
        )


def example(ctx):
    with zipkin_span(
        service_name=ctx.TracingContext().service_name(),
        span_name="Get ADB Password from OCI Vault",
        binary_annotations=ctx.TracingContext().annotations()
    ) as example_span_context:
        try:
            logging.getLogger().debug("Get ADB Password from OCI Vault")
            time.sleep(0.005)
            # throwing an exception to show how to add error messages to spans
            raise Exception('Request failed')
        except (Exception, ValueError) as error:
            example_span_context.update_binary_annotations(
                {"Error": True, "errorMessage": str(error)}
            )
        else:
            FakeResponse = namedtuple("FakeResponse", "status, message")
            fakeResponse = FakeResponse(200, "OK")
            # how to update the span dimensions/annotations
            example_span_context.update_binary_annotations(
                {
                    "responseCode": fakeResponse.status,
                    "responseMessage": fakeResponse.message
                }
            )
            
ノード関数へのカスタム・スパンの追加

Zipkinを使用してNode.js関数にカスタム・スパンを追加する方法の例を次に示します。この例を試してみる場合は、Node "Hello World!"関数を作成し、カスタム・スパン・コードを追加できます。サンプル・ファンクションを作成するには:

  • ノード・ファンクションの作成: fn init --runtime node apm-fn-node

ノード依存性の構成

package.jsonファイルを更新して、次のパッケージを含めます。


{
    "name": "apm-tracing-node-fdk-simple-trace-final",
    "version": "1.0.0",
    "description": "Example APM tracing function",
    "main": "func.js",
    "author": "",
    "license": "Apache-2.0",
    "dependencies": {
        "@fnproject/fdk": ">=0.0.13",
        "node-fetch": "^2.6.1",
        "zipkin": "^0.22.0",
        "zipkin-transport-http": "^0.22.0"
    }
}
            

ファイルを保存します。

更新処理方法

fdk.handleメソッドに関する主な観測は、ソース・コードに従います。


// ZipkinJS core components.
const { 
    ExplicitContext, 
    Annotation, 
    Tracer, 
    TraceId, 
    BatchRecorder, 
    jsonEncoder, 
    sampler, 
    option
} = require('zipkin');

// An HTTP transport for dispatching Zipkin traces.
const {HttpLogger} = require('zipkin-transport-http');

fdk.handle(async function(input, ctx){
    tracer = createOCITracer(ctx);
    
    var result;
    // Start a new 'scoped' server handling span.
    await tracer.scoped(async function () {
        // Fetch some resource
        result = await tracer.local('fetchResource', () => {
            return fetchResource();
        });
        // Perform some processing
        result = await tracer.local('processResource', () => {
            return someComputation(result);
        });
        // Update some resource
        result = await tracer.local('updateResource', () => {
            return updateResource(result);
        });
        await flush();
    }); 
    
    return result;

})            
            
  • tracerが作成され、親カスタム・スパンの作成に使用されます。次に、fetchResourceprocessResourceおよびupdateResource関数に対して子スパンが作成されます。

createOCITracer関数の確認

ファンクションに関する主な観測は、ソース・コードに従います。


/**
 * Creates a basic Zipkin Tracer using values from context of the function
 * invocation.
 *
 * @param {*} ctx The function invocation context.
 * @returns       A configured Tracer for automatically tracing calls.
 */
function createOCITracer (ctx) {
  // An OCI APM configured Tracer
  //
  const tracingCxt = ctx.tracingContext
  const tracer = new Tracer({
    ctxImpl: new ExplicitContext(),
    recorder: new BatchRecorder({
      logger: new HttpLogger({
        // The configured OCI APM endpoint is available in the function
        // invocation context.
        endpoint: tracingCxt.traceCollectorUrl,
        jsonEncoder: jsonEncoder.JSON_V2
      })
    }),
    // APM Dimensions that should be included in all traces can be configured
    // directly on Tracer.
    defaultTags: createOCITags(ctx),
    // A custom sampling strategy can be defined.
    sampler: createOCISampler(ctx),
    localServiceName: tracingCxt.serviceName,
    supportsJoin: true,
    traceId128Bit: true
  })

  // The initial function invocation trace identifiers can be added directly.
  // If this is not defined a default TraceId is created.
  const traceId = createOCITraceId(tracer, ctx)
  tracer.setId(traceId)
  return tracer
}
            
  • ファンクション・コンテキスト(ctx)は、APMドメインへの接続に必要な情報を提供するこのファンクションに渡されます。関数コールに従うと、トレースIDおよびフィールドがどのように構築されているかを確認できます。
レビュー完了機能ソース・コード

サンプル・ノード・トレース機能の完全なソース・コードを次に示します。



const fdk = require('@fnproject/fdk')

// ZipkinJS core components.
const {
  ExplicitContext,
  Tracer,
  TraceId,
  BatchRecorder,
  jsonEncoder,
  sampler,
  option
} = require('zipkin')

// An HTTP transport for dispatching Zipkin traces.
const { HttpLogger } = require('zipkin-transport-http')

fdk.handle(async function (input, ctx) {
  var tracer = createOCITracer(ctx)

  var result
  // Start a new 'scoped' server handling span.
  await tracer.scoped(async function () {
    // Fetch some resource
    result = await tracer.local('fetchResource', () => {
      return fetchResource()
    })
    // Perform some processing
    result = await tracer.local('processResource', () => {
      return someComputation(result)
    })
    // Update some resource
    result = await tracer.local('updateResource', () => {
      return updateResource(result)
    })
    await flush()
  })

  return result
})

// ----------------------------------------------------------------------------
// App Simulation Functions
//

/**
 * Simulate fetching some required resource. This could be another OCI service
 * or an external call.
 *
 * @returns A Promise with the success or failure of the operation.
 */
function fetchResource () {
  return simulate(1000, { fetchResource: 'OK' })
}

/**
 * Simulate some work. This could be another OCI service.
 *
 * @returns A Promise with the success or failure of the operation.
 */
async function someComputation (toReturn) {
  var i
  for (i = 0; i < 5; i++) {
    await simulate(1000)
  }
  toReturn.processResource = 'OK'
  return toReturn
}

/**
 * Simulate updating some resource. This could be another OCI service or an
 * external call.
 *
 * @returns A Promise with the success or failure of the operation.
 */
async function updateResource (toReturn) {
  await simulate(500)
  toReturn.updateResource = 'OK'
  return toReturn
}

/**
 * A helper function to simulate an operation that takes a specified amount of time.
 *
 * @param {*} ms The simulated time for the activity in milliseconds.
 * @returns      A promise that resolves when the simulated activity finishes.
 */
function simulate (ms, result) {
  return new Promise(resolve => setTimeout(resolve, ms, result))
}

/**
 * Functions service may freeze or terminate the container on completion.
 * This function gives extra time to allow the runtime to flush any pending traces.
 * See: https://github.com/openzipkin/zipkin-js/issues/507
 *
 * @returns A Promise to await on.
 */
function flush () {
  return new Promise(resolve => setTimeout(resolve, 1000))
}

// ----------------------------------------------------------------------------
// OpenZipkin ZipkinJS Utility Functions
//

/**
 * Creates a basic Zipkin Tracer using values from context of the function
 * invocation.
 *
 * @param {*} ctx The function invocation context.
 * @returns       A configured Tracer for automatically tracing calls.
 */
function createOCITracer (ctx) {
  // An OCI APM configured Tracer
  //
  const tracingCxt = ctx.tracingContext
  const tracer = new Tracer({
    ctxImpl: new ExplicitContext(),
    recorder: new BatchRecorder({
      logger: new HttpLogger({
        // The configured OCI APM endpoint is available in the function
        // invocation context.
        endpoint: tracingCxt.traceCollectorUrl,
        jsonEncoder: jsonEncoder.JSON_V2
      })
    }),
    // APM Dimensions that should be included in all traces can be configured
    // directly on Tracer.
    defaultTags: createOCITags(ctx),
    // A custom sampling strategy can be defined.
    sampler: createOCISampler(ctx),
    localServiceName: tracingCxt.serviceName,
    supportsJoin: true,
    traceId128Bit: true
  })

  // The initial function invocation trace identifiers can be added directly.
  // If this is not defined a default TraceId is created.
  const traceId = createOCITraceId(tracer, ctx)
  tracer.setId(traceId)
  return tracer
}

/**
 * A ZipkinJS 'TraceId' can be created directly from the function invocation
 * context.
 *
 * @param {*} ctx The function invocation context.
 * @returns       A ZipkinJS 'TraceId' created from the invocation context.
 */
function createOCITraceId (tracer, ctx) {
  const tracingCxt = ctx.tracingContext
  if (tracingCxt.traceId && tracingCxt.spanId) {
    return new TraceId({
      traceId: tracingCxt.traceId,
      spanId: tracingCxt.spanId,
      sampled: new option.Some(tracingCxt.sampled),
      debug: new option.Some(tracingCxt.debug),
      shared: false
    })
  } else {
    return tracer.createRootId(
      new option.Some(tracingCxt.sampled),
      new option.Some(tracingCxt.debug)
    )
  }
}

/**
 * A ZipkinJS 'TraceId' can be crated directly from the function invocation
 * context.
 *
 * This configurations will automatically add the function meta-data as APM
 * dimensions to each trace. Function environment variable and other dimensions
 * could also be added.
 *
 * @param {*} ctx The function invocation context.
 * @returns       A map of key-value pairs, that will be added as APM
 *                dimensions to the traces.
 */
function createOCITags (ctx) {
  return {
    appID: ctx.appID,
    appName: ctx.appName,
    fnID: ctx.fnID,
    fnName: ctx.fnName
  }
}

/**
 * A ZipkinJS 'Sampler' can be created directly from the function invocation
 * context.
 *
 * This configuration will only create a trace if the function is configured
 * for tracing.
 *
 * @param {*} ctx The function invocation context.
 * @returns       A ZipkinJS 'TraceId' created from the invocation context.
 */
function createOCISampler (ctx) {
  return new sampler.Sampler((traceId) => ctx.tracingContext.isEnabled)
}