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

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

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

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

OCIファンクションのトレース機能は、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ファンクション・サービス権限を付与するポリシーを設定します(OCIファンクション・サービスおよびOCIファンクション・ユーザーにリソースのトレースへのアクセス権を付与するポリシー・ステートメントを参照)。
  2. APMドメインを設定します。
  3. Functionsアプリケーションのトレースを有効にし、作成したAPMドメインを選択します。
  4. 1つ以上の関数のトレースを有効にします。

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

4つの変数がOCIファンクション・コンテキストに追加され、有効なトレース情報が提供されます。次の変数があります。

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

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

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

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

トレースを有効にし、Oracle Cloud Infrastructure Application Performance Monitoring (APM)サービスの機能トレースを表示するには、いくつかのステップが必要です。最初に、ファンクションを含むアプリケーションのトレースを有効にします。次に、1つ以上の関数のトレースを有効にします。APM Trace Explorerでファンクション・トレースを表示できます。

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

トレースを有効にするには、次のステップに従います。

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

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

  4. トレースを有効にするファンクション・アプリケーションを選択します。
  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 Trace Explorerに表示されます。デフォルトでは、デフォルトの関数呼び出しスパンと、その関数に定義されているカスタムスパンのトレースが表示されます。

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

    APM Trace Explorerの使用方法の詳細は、Trace Explorerの使用を参照してください。

ファンクションのチェーンのトレース

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

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

ファンクションの実行後、ファンクションからトレース・データが収集され、APM Trace Explorerで使用できるようになります。APM Trace Explorerの使用方法の詳細は、Trace Explorerの使用を参照してください。

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. 次のファンクション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を使用して別のファンクション情報を渡すことができます: 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
            

Invoke Clientの作成およびヘッダー情報の収集

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ヘッダーを別の関数呼出しに渡します。ヘッダー情報は、trace_idspan_idparent_span_idおよびis_sampledに渡されます。最後に、clientを使用して2番目の関数が呼び出され、レスポンスがこの関数のレスポンスに渡されます。


    # 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"}
    )
            

関数へのカスタム範囲の追加

関数トレースを有効にすると、デフォルトの関数呼び出しspanは関数呼び出し全体のトレースを提供します。デフォルトのスパンでは適切な情報が提供されますが、コードを調査する際にはさらに深くする必要があります。カスタム・スパンはコードに直接追加され、メソッドまたはコード・ブロックのスパンを定義できます。結果として得られるデータは、実行時の関数をより適切に表します。

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

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

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

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

カスタム・スパンを使用するには、次の最小バージョンの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関連情報を渡します。
  • intializeZipkinメソッドが呼び出され、tracingContextが更新されて、カスタム・スパンの設定に使用されるtracerオブジェクトが作成されます。
  • 親カスタム・スパンに対して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
}
            
  • APMドメインへの接続に必要な情報を提供するファンクション・コンテキスト(ctx)がこのファンクションに渡されます。ファンクション・コールをたどると、トレース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)
}