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

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

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

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

すべてのNodeJSプロジェクトは「npm初期化」、私たちのプロジェクトでも同じことをします。 ただし、その前に、別のディレクトリを作成します。

先に進み、プロジェクトのディレクトリを作成し、そのディレクトリに変更します。

mkdir "InstrumentationProject"
cd "InstrumentationProject"

ディレクトリができたので、nodeJSプロジェクトを初期化します。

npm init esm -y

これにより、ES-6モジュールを使用してプロジェクトが開始され、作成されます package.json。 次に、依存関係を取得しましょう。

npm i @opentelemetry/sdk-trace-base @opentelemetry/api @opentelemetry/resources
@opentelemetry/exporter-collector
@opentelemetry/semantic-conventions

この時点で、すべての依存関係がインストールされます。 「」という名前のファイルを作成しますtracer.js」。 フォルダ構造は次のようになります。

Node.jsフォルダー構造

あなたが欲しい tracer.js アプリケーションで他の何よりも先に実行して、アプリケーションが要求の取得を開始する前に、トレースに必要なすべてのコンポーネントが構成されるようにします。

これを実現するには、編集します index.js 前に追加します module.exports。

require('./tracer')

これで、トレーサープロバイダーの初期化を開始する準備が整いました。

トレーサープロバイダーの初期化

このセクションでは、編集します tracer.js。 必要なインポートは次のとおりです。

import { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { CollectorTraceExporter } from "@opentelemetry/exporter-collector";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";

のインスタンスを取得しましょう BasicTracerProvider:

const provider = new BasicTracerProvider();

リソース属性の設定

リソース属性は、テレメトリデータを生成しているリソースを説明するために使用されます。

例:

  • サービスの名前
  • 展開ゾーン
  • クラウドプロバイダー
  • ホストのタイプ(VM、コンテナー、Kubernetes)

いくつかのリソース属性を定義しましょう。

const resource = new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'authentication-service',
    [SemanticResourceAttributes.SERVICE_NAMESPACE]: 'US-WEST-1',
    'host.type': 'VM'
})

で事前定義されたキーを使用してリソース属性を記述できます。 SemanticResourceAttributes または、カスタム属性キーを使用できます。

先に進み、トレーサーを使用して新しく定義されたリソース属性を構成します。

provider.resource = resource

トレース/スパンエクスポータの設定

エクスポータは、収集されたテレメトリデータをリモートバックエンド、ローカルで実行されているコレクタ、またはファイルに送信するコンポーネントです。 後で説明するように、収集したデータをコンソールにエクスポートすることもできます。

この例で使用するエクスポーターを初期化してみましょう。

const consoleSpanExporter = new ConsoleSpanExporter
const collectorTraceExporter = new CollectorTraceExporter

consoleSpanExporter:これは、コンソールでスパンを確認するために使用されます。

CollectorTraceExporter:これは、OpenTelemetry形式でトレースをエクスポートするために使用されます。

スパンプロセッサの追加とトレーサーの登録

provider.addSpanProcessor(new SimpleSpanProcessor(consoleSpanExporter))
provider.addSpanProcessor(new SimpleSpanProcessor(collectorTraceExporter))
provider.register()

我々は、使用しています SimpleSpanProcessor。 あなたも使用することができます BatchSpanProcessor、 これは、システムリソースを効率的に使用するために、スパンをバッチで送信します。 バッチサイズをカスタマイズできます。

最後に、Opentelemetry APIがこのトレーサーを使用できるように、トレーサーを登録します。

アプリケーションのインストルメント化

このセクションでは、アプリケーションのインストルメント化のさまざまな側面について説明します。 インストルメントされたアプリケーションのコードは最後にあります。

スパンは単一の作業単位であり、トレースは多くの場合、複数のスパンで構成されます。 操作に関するより多くの情報でスパンを充実させるために、スパン属性を活用します。

新しいスパンの作成

ここで、「親」はスパンに付けたい名前です。

const parentSpan = opentelemetry.trace.getTracer('default').startSpan('parent');

スパンへの属性の追加

スパンには任意の数の属性を追加できます。

parentSpan.setAttribute("microservice","server")
parentSpan.setAttribute("prodEnv","true")
childFunction(parentSpan) //Passing the context the function
parentSpan.end() //DO NOT forget to end the span

で親スパンを使用して子スパンを作成する 子関数:

function childFunction(parentSpan) {

const ctx = opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan);
const childSpan = opentelemetry.trace.getTracer('default').startSpan('child', undefined, ctx);
.
.
.
.
childSpan.end()
}

スパンのステータスの設定

デフォルトでは、ステータスはUNSETです。 エラーが発生した場合は、エラーステータスを次のように設定できます。

childSpan.setStatus({
            code: SpanStatusCode.ERROR,
            message: 'Authentication failed.'
          })

インストルメント化されたアプリケーションコード

index.js

require = require("esm")(module/* , options */)
require('./tracer')
module.exports = require("./main.js")

tracer.js

import { BasicTracerProvider, ConsoleSpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { CollectorTraceExporter } from "@opentelemetry/exporter-collector";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";

const provider = new BasicTracerProvider();


const resource = new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'authentication-service',
    [SemanticResourceAttributes.SERVICE_NAMESPACE]: 'US-WEST-1',
    'host.type': 'VM'
})


const consoleSpanExporter = new ConsoleSpanExporter
const collectorTraceExporter = new CollectorTraceExporter

provider.resource = resource

provider.addSpanProcessor(new SimpleSpanProcessor(consoleSpanExporter))
provider.addSpanProcessor(new SimpleSpanProcessor(collectorTraceExporter))
provider.register()

main.js

import Auth from './auth'
import opentelemetry from "@opentelemetry/api"
console.log("Hello")

const server = async (username, password) => {
    
    const parentSpan = opentelemetry.trace.getTracer('default').startSpan('parent');
    
    parentSpan.addEvent("Parent Span")
    parentSpan.setAttribute("microservice","server")
    parentSpan.setAttribute("prodEnv","true")
    parentSpan.addEvent("Received request")
    console.log("Got request")
    await Auth(parentSpan, username,password)

    console.log("SERVER END")
    parentSpan.end()
};

server("user1","password");
server("wrongUser","wrongPassword");

export {}

auth.js

import opentelemetry, { SpanStatusCode } from "@opentelemetry/api"

export default async function(parentSpan, username,password) {
    const ctx = opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan);
    const childSpan = opentelemetry.trace.getTracer('default').startSpan('child', undefined, ctx);
    childSpan.setAttribute("user",username)
    childSpan.setAttribute("microservice","DB-Service")
    
    //Simulate a network delay
    await sleep(Math.floor(
        Math.random() * (500 - 200) + 200
      ))
    try{
        if (username==="user1" && password==="password") {
                
            //Success span
            console.log("Authenticated.")
            childSpan.addEvent("Authentication Failed", { authentication: "Successful" }  )

        } else {
            
            throw("Authentication Failed Exception")
            //Error Span

        }
    }
    catch(error) {
        console.log("Failed to Authenticate.")
        childSpan.recordException(error)
        childSpan.setStatus({
            code: SpanStatusCode.ERROR,
            message: 'Authentication failed.'
          })
    }
    finally {
        childSpan.end()
    }
}


function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

アプリケーションの実行

プロジェクトディレクトリから:

ノードindex.js

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

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

トレースの詳細ビュー

親スパン:

トレースの詳細ビュー

子スパン:

子スパン

結論

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