計装
計装は、アプリに自分でオブザーバビリティコードを追加する行為です。
アプリを計装している場合は、その言語のOpenTelemetry SDKを使用する必要があります。 次に、SDKを使用してOpenTelemetryを初期化し、APIを使用してコードを計装します。 これにより、アプリから、および計装も付属しているインストール済みライブラリからテレメトリーが発行されます。
ライブラリを計装している場合は、その言語のOpenTelemetry APIパッケージのみをインストールしてください。 ライブラリは単独ではテレメトリーを発行しません。 OpenTelemetry SDKを使用するアプリの一部である場合にのみテレメトリーを発行します。 ライブラリの計装の詳細については、ライブラリを参照してください。
OpenTelemetry APIとSDKの詳細については、仕様を参照してください。
このページでは、コードに 手動で トレース、メトリクス、ログを追加する方法を学びます。 ただし、1種類の計装のみを使用することに制限されているわけではありません。 自動計装を使用して開始し、必要に応じて手動計装でコードを充実させることができます。
また、コードが依存するライブラリについては、自分で計装コードを書く必要はありません。 OpenTelemetryが ネイティブに 組み込まれている場合があるか、計装ライブラリを利用できる場合があります。
サンプルアプリケーションの準備
このページでは、はじめにのサンプルアプリケーションの修正版を使用して、手動計装について学習します。
サンプルアプリケーションを使用する必要はありません。 独自のアプリケーションやライブラリを計装したい場合は、ここでの指示に従って、プロセスを独自のコードに適応させてください。
依存関係
新しいディレクトリに空のNPM package.json
ファイルを作成します。
npm init -y
次に、Expressの依存関係をインストールします。
npm install typescript \
ts-node \
@types/node \
express \
@types/express
npm install express
HTTPサーバーの作成と起動
ライブラリ とスタンドアロン アプリケーション の計装の違いを強調するために、 サイコロを振る処理を ライブラリファイル に分割し、それを アプリケーションファイル で依存関係としてインポートします。
dice.ts
(TypeScriptを使用していない場合はdice.js
)という名前の ライブラリファイル を作成し、次のコードを追加します。
/*dice.ts*/
function rollOnce(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function rollTheDice(rolls: number, min: number, max: number) {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
/*dice.js*/
function rollOnce(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function rollTheDice(rolls, min, max) {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
module.exports = { rollTheDice };
app.ts
(TypeScriptを使用していない場合はapp.js
)という名前の アプリケーションファイル を作成し、次のコードを追加します。
/*app.ts*/
import express, { Express } from 'express';
import { rollTheDice } from './dice';
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("Request parameter 'rolls' is missing or not a number.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
/*app.js*/
const express = require('express');
const { rollTheDice } = require('./dice.js');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("Request parameter 'rolls' is missing or not a number.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
動作することを確認するには、次のコマンドでアプリケーションケーションを実行し、Webブラウザでhttp://localhost:8080/rolldice?rolls=12を開きます。
$ npx ts-node app.ts
Listening for requests on http://localhost:8080
$ node app.js
Listening for requests on http://localhost:8080
手動計装のセットアップ
依存関係
OpenTelemetry APIパッケージをインストールします。
npm install @opentelemetry/api @opentelemetry/resources @opentelemetry/semantic-conventions
SDKの初期化
ライブラリを計装している場合は、このステップをスキップしてください。
Node.jsアプリケーションケーションを計装する場合は、Node.js用OpenTelemetry SDKをインストールします。
npm install @opentelemetry/sdk-node
アプリケーションケーション内の他のモジュールがロードされる前に、SDKを初期化する必要があります。 SDKの初期化に失敗した場合、または遅すぎる場合、APIからトレーサーまたはメーターを取得するライブラリにはno-op実装が提供されます。
/*instrumentation.ts*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';
import { resourceFromAttributes } from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'yourServiceName',
[ATTR_SERVICE_VERSION]: '1.0',
}),
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
});
sdk.start();
/*instrumentation.js*/
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-node');
const {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} = require('@opentelemetry/sdk-metrics');
const { resourceFromAttributes } = require('@opentelemetry/resources');
const {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'dice-server',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
});
sdk.start();
デバッグとローカル開発のために、次の例ではテレメトリーをコンソールにエクスポートします。 手動計装の設定が完了したら、アプリケーションのテレメトリーデータをエクスポートするために、1つ以上のテレメトリーバックエンドに適切なエクスポーターを構成する必要があります。
この例では、必須のSDKデフォルト属性service.name
(サービスの論理名を保持)と、オプション(ただし強く推奨される!)属性service.version
(サービスAPIまたは実装のバージョンを保持)も設定します。
リソース属性を設定する代替方法があります。詳細については、リソースを参照してください。
コードを確認するには、ライブラリを要求してアプリケーションを実行します。
npx ts-node --require ./instrumentation.ts app.ts
node --require ./instrumentation.js app.js
この基本的なセットアップは、まだアプリケーションに影響を与えません。 トレース、メトリクス、および/またはログのコードを追加する必要があります。
依存関係のテレメトリーデータを生成するために、計装ライブラリをNode.js用OpenTelemetry SDKに登録できます。 詳細については、ライブラリを参照してください。
トレース
トレーシングの初期化
ライブラリを計装している場合は、このステップをスキップしてください。
アプリケーションでトレーシングを有効にするには、Tracer
を作成できる初期化されたTracerProvider
が必要です。
TracerProvider
が作成されない場合、トレーシング用のOpenTelemetry APIはno-op実装を使用し、データの生成に失敗します。
次に説明するように、NodeとブラウザですべてのSDK初期化コードを含めるようにinstrumentation.ts
(またはinstrumentation.js
)ファイルを変更します。
Node.js
上記のSDKの初期化の指示に従った場合、すでにTracerProvider
がセットアップされています。
トレーサーの取得に進むことができます。
ブラウザ
ブラウザ向けのクライアント計装は実験的であり、主に未規定です。協力に興味をお持ちの場合は、Client Instrumentation SIGまでご連絡ください。
まず、適切なパッケージがあることを確認します。
npm install @opentelemetry/sdk-trace-web
次に、instrumentation.ts
(またはinstrumentation.js
)を更新して、すべてのSDK初期化コードを含めます。
import {
defaultResource,
resourceFromAttributes,
} from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import {
BatchSpanProcessor,
ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-base';
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'service-name-here',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const exporter = new ConsoleSpanExporter();
const processor = new BatchSpanProcessor(exporter);
const provider = new WebTracerProvider({
resource: resource,
spanProcessors: [processor],
});
provider.register();
const opentelemetry = require('@opentelemetry/api');
const {
defaultResource,
resourceFromAttributes,
} = require('@opentelemetry/resources');
const {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} = require('@opentelemetry/semantic-conventions');
const { WebTracerProvider } = require('@opentelemetry/sdk-trace-web');
const {
ConsoleSpanExporter,
BatchSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'service-name-here',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const exporter = new ConsoleSpanExporter();
const processor = new BatchSpanProcessor(exporter);
const provider = new WebTracerProvider({
resource: resource,
spanProcessors: [processor],
});
provider.register();
このファイルをWebアプリケーションケーションにバンドルして、Webアプリケーションケーションの残りの部分でトレーシングを使用できるようにする必要があります。
これはまだアプリケーションに影響を与えません。アプリケーションからテレメトリーを発行するには、スパンを作成する必要があります。
適切なスパンプロセッサーの選択
デフォルトでは、Node SDKはBatchSpanProcessor
を使用し、Web SDKの例でもこのスパンプロセッサーが選択されています。
BatchSpanProcessor
は、エクスポートされる前にスパンをバッチで処理します。
これは通常、アプリケーションケーションに使用する適切なプロセッサーです。
対照的に、SimpleSpanProcessor
はスパンが作成されると処理します。
つまり、5つのスパンを作成した場合、それぞれがコードで次のスパンが作成される前に処理およびエクスポートされます。
これは、バッチを失うリスクを冒したくないシナリオや、開発中にOpenTelemetryを試している場合に役立ちます。
ただし、特にスパンがネットワーク経由でエクスポートされている場合、潜在的に大きなオーバーヘッドが発生します。
スパンを作成する呼び出しが行われるたびに、アプリケーションの実行が続行される前に処理され、ネットワーク経由で送信されます。
ほとんどの場合、SimpleSpanProcessor
よりもBatchSpanProcessor
を使用してください。
トレーサーの取得
手動トレーシングコードを記述するアプリケーションケーション内のどこでも、getTracer
を呼び出してトレーサーを取得する必要があります。
例を挙げましょう。
import opentelemetry from '@opentelemetry/api';
//...
const tracer = opentelemetry.trace.getTracer(
'instrumentation-scope-name',
'instrumentation-scope-version',
);
// これで'tracer'を使用してトレーシングができます!
const opentelemetry = require('@opentelemetry/api');
//...
const tracer = opentelemetry.trace.getTracer(
'instrumentation-scope-name',
'instrumentation-scope-version',
);
// これで'tracer'を使用してトレーシングができます!
instrumentation-scope-name
とinstrumentation-scope-version
の値は、パッケージ、モジュール、またはクラス名など、計装スコープを一意に識別する必要があります。
名前は必須ですが、バージョンはオプションであるにもかかわらず推奨されます。
アプリケーションで必要なときにgetTracer
を呼び出すことが、tracer
インスタンスをアプリケーションの残りの部分にエクスポートするよりも一般的に推奨されます。
これは、他の必要な依存関係が関与している場合のより複雑なアプリケーションケーションロードの問題を回避するのに役立ちます。
サンプルアプリケーションの場合、適切な計装スコープでトレーサーを取得できる場所が2つあります。
まず、アプリケーションケーションファイル app.ts
(またはapp.js
)で下記を実装します。
/*app.ts*/
import { trace } from '@opentelemetry/api';
import express, { Express } from 'express';
import { rollTheDice } from './dice';
const tracer = trace.getTracer('dice-server', '0.1.0');
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("Request parameter 'rolls' is missing or not a number.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
/*app.js*/
const { trace } = require('@opentelemetry/api');
const express = require('express');
const { rollTheDice } = require('./dice.js');
const tracer = trace.getTracer('dice-server', '0.1.0');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("Request parameter 'rolls' is missing or not a number.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
そして2番目に、ライブラリファイル dice.ts
(またはdice.js
)で、以下を実装します。
/*dice.ts*/
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('dice-lib');
function rollOnce(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function rollTheDice(rolls: number, min: number, max: number) {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
/*dice.js*/
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('dice-lib');
function rollOnce(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function rollTheDice(rolls, min, max) {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
module.exports = { rollTheDice };
スパンの作成
OpenTelemetry JavaScript APIは、スパンを作成できる2つのメソッドを公開しています。
tracer.startSpan
:コンテキストに設定せずに新しいスパンを開始します。tracer.startActiveSpan
:新しいスパンを開始し、作成されたスパンを最初の引数として渡す特定のコールバック関数を呼び出します。新しいスパンはコンテキストに設定され、このコンテキストは関数呼び出しの期間中アクティブになります。
ほとんどの場合、スパンとそのコンテキストをアクティブに設定するため、後者(tracer.startActiveSpan
)を使用することをお勧めします。
以下のコードは、アクティブなスパンを作成する方法を示しています。
import { trace, Span } from '@opentelemetry/api';
/* ... */
export function rollTheDice(rolls: number, min: number, max: number) {
// スパンを作成します。スパンは閉じる必要があります。
return tracer.startActiveSpan('rollTheDice', (span: Span) => {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
// 必ずスパンを終了してください!
span.end();
return result;
});
}
function rollTheDice(rolls, min, max) {
// スパンを作成します。スパンは閉じる必要があります。
return tracer.startActiveSpan('rollTheDice', (span) => {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
// 必ずスパンを終了してください!
span.end();
return result;
});
}
ここまでのサンプルアプリケーションを使用して指示に従った場合、上記のコードをライブラリファイルdice.ts
(またはdice.js
)にコピーできます。
これで、アプリケーションから発行されるスパンを確認できるはずです。
次のようにアプリケーションを開始し、ブラウザまたはcurl
でhttp://localhost:8080/rolldice?rolls=12にアクセスしてリクエストを送信します。
ts-node --require ./instrumentation.ts app.ts
node --require ./instrumentation.js app.js
しばらくすると、ConsoleSpanExporter
によってコンソールにスパンが出力されるのが表示されるはずです。
次のようなものです。
{
"traceId": "6cc927a05e7f573e63f806a2e9bb7da8",
"parentId": undefined,
"name": "rollTheDice",
"id": "117d98e8add5dc80",
"kind": 0,
"timestamp": 1688386291908349,
"duration": 501,
"attributes": {},
"status": { "code": 0 },
"events": [],
"links": []
}
ネストされたスパンの作成
ネストされたスパンを使用すると、ネストされた性質の作業を追跡できます。
たとえば、以下のrollOnce()
関数はネストされた操作を表します。次のサンプルは、rollOnce()
を追跡する
ネストされたスパンを作成します。
function rollOnce(i: number, min: number, max: number) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span: Span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
span.end();
return result;
});
}
export function rollTheDice(rolls: number, min: number, max: number) {
// スパンを作成します。スパンは閉じる必要があります。
return tracer.startActiveSpan('rollTheDice', (parentSpan: Span) => {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(i, min, max));
}
// 必ずスパンを終了してください!
parentSpan.end();
return result;
});
}
function rollOnce(i, min, max) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
span.end();
return result;
});
}
function rollTheDice(rolls, min, max) {
// スパンを作成します。スパンは閉じる必要があります。
return tracer.startActiveSpan('rollTheDice', (parentSpan) => {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(i, min, max));
}
// 必ずスパンを終了してください!
parentSpan.end();
return result;
});
}
このコードは、各 ロール に対して、parentSpan
のIDを親IDとして持つ子スパンを作成します。
{
"traceId": "ff1d39e648a3dc53ba710e1bf1b86e06",
"parentId": "9214ff209e6a8267",
"name": "rollOnce:4",
"id": "7eccf70703e2bccd",
"kind": 0,
"timestamp": 1688387049511591,
"duration": 22,
"attributes": {},
"status": { "code": 0 },
"events": [],
"links": []
}
{
"traceId": "ff1d39e648a3dc53ba710e1bf1b86e06",
"parentId": undefined,
"name": "rollTheDice",
"id": "9214ff209e6a8267",
"kind": 0,
"timestamp": 1688387049510303,
"duration": 1314,
"attributes": {},
"status": { "code": 0 },
"events": [],
"links": []
}
独立したスパンの作成
前の例では、アクティブなスパンを作成する方法を示しました。 場合によっては、ネストされているのではなく、互いに兄弟である非アクティブなスパンを作成したいことがあります。
const doWork = () => {
const span1 = tracer.startSpan('work-1');
// 何かの作業
const span2 = tracer.startSpan('work-2');
// さらに何かの作業
const span3 = tracer.startSpan('work-3');
// さらにもっと作業
span1.end();
span2.end();
span3.end();
};
この例では、span1
、span2
、およびspan3
は兄弟スパンであり、どれも現在アクティブなスパンとは見なされません。
それらは互いの下にネストされるのではなく、同じ親を共有します。
この配置は、一緒にグループ化されているが、概念的に互いに独立している作業単位がある場合に役立ちます。
現在のスパンの取得
プログラム実行の特定の時点で、現在の/アクティブなスパンで何かを行うと便利な場合があります。
const activeSpan = opentelemetry.trace.getActiveSpan();
// アクティブなスパンで何かを行い、ユースケースに適している場合は、オプションで終了します。
コンテキストからスパンを取得
必ずしもアクティブなスパンではない、特定のコンテキストからスパンを取得することも便利な場合があります。
const ctx = getContextFromSomewhere();
const span = opentelemetry.trace.getSpan(ctx);
// 取得したスパンで何かを行い、ユースケースに適している場合は、オプションで終了します。
属性
属性を使用すると、Span
にキー/値ペアを添付して、追跡している現在の操作に関する詳細情報を伝えることができます。
function rollOnce(i: number, min: number, max: number) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span: Span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
// スパンに属性を追加
span.setAttribute('dicelib.rolled', result.toString());
span.end();
return result;
});
}
function rollOnce(i, min, max) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
// スパンに属性を追加
span.setAttribute('dicelib.rolled', result.toString());
span.end();
return result;
});
}
スパンの作成時に属性を追加することもできます。
tracer.startActiveSpan(
'app.new-span',
{ attributes: { attribute1: 'value1' } },
(span) => {
// 何かの作業...
span.end();
},
);
function rollTheDice(rolls: number, min: number, max: number) {
return tracer.startActiveSpan(
'rollTheDice',
{ attributes: { 'dicelib.rolls': rolls.toString() } },
(span: Span) => {
/* ... */
},
);
}
function rollTheDice(rolls, min, max) {
return tracer.startActiveSpan(
'rollTheDice',
{ attributes: { 'dicelib.rolls': rolls.toString() } },
(span) => {
/* ... */
},
);
}
セマンティック属性
HTTPやデータベース呼び出しなどの既知のプロトコルでの操作を表すスパンには、セマンティック規約があります。これらのスパンのセマンティック規約は、トレースセマンティック規約の仕様で定義されています。 このガイドのシンプルな例では、ソースコード属性を使用できます。
まず、依存関係としてセマンティック規約をアプリケーションケーションに追加します。
npm install --save @opentelemetry/semantic-conventions
アプリケーションケーションファイルの先頭に次を追加します。
import {
SEMATTRS_CODE_FUNCTION,
SEMATTRS_CODE_FILEPATH,
} from '@opentelemetry/semantic-conventions';
const {
SEMATTRS_CODE_FUNCTION,
SEMATTRS_CODE_FILEPATH,
} = require('@opentelemetry/semantic-conventions');
最後に、セマンティック属性を含めるようにファイルを更新できます。
const doWork = () => {
tracer.startActiveSpan('app.doWork', (span) => {
span.setAttribute(SEMATTRS_CODE_FUNCTION, 'doWork');
span.setAttribute(SEMATTRS_CODE_FILEPATH, __filename);
// 何かの作業を行う...
span.end();
});
};
スパンイベント
スパンイベントは、スパン上の人間が読める形式のメッセージで、単一のタイムスタンプで追跡できる期間のない離散イベントを表します。 プリミティブログのようなものと考えることができます。
span.addEvent('Doing something');
const result = doWork();
追加の属性を持つスパンイベントを作成することもできます。
span.addEvent('some log', {
'log.severity': 'error',
'log.message': 'Data not found',
'request.id': requestId,
});
スパンリンク
Span
は、因果関係がある他のスパンへのゼロ個以上のLink
を使用して作成できます。
一般的なシナリオは、1つ以上のトレースを現在のスパンと相関させることです。
const someFunction = (spanToLinkFrom) => {
const options = {
links: [
{
context: spanToLinkFrom.spanContext(),
},
],
};
tracer.startActiveSpan('app.someFunction', options, (span) => {
// 何かの作業を行う...
span.end();
});
};
スパンステータス
ステータスはスパンに設定でき、通常はスパンが正常に完了しなかったことを示すために使用されます。
つまりError
です。
デフォルトでは、すべてのスパンはUnset
であり、これはスパンがエラーなく完了したことを意味します。
Ok
ステータスは、デフォルトのUnset
(つまり「エラーなし」)のままにするのではなく、スパンを明示的に成功としてマークする必要がある場合に予約されています。
ステータスは、スパンが終了する前であればいつでも設定できます。
import opentelemetry, { SpanStatusCode } from '@opentelemetry/api';
// ...
tracer.startActiveSpan('app.doWork', (span) => {
for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) {
if (i > 10000) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: 'Error',
});
}
}
span.end();
});
const opentelemetry = require('@opentelemetry/api');
// ...
tracer.startActiveSpan('app.doWork', (span) => {
for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) {
if (i > 10000) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: 'Error',
});
}
}
span.end();
});
例外の記録
例外が発生したときに記録することは良いアイデアです。 スパンステータスの設定と組み合わせて行うことをお勧めします。
import opentelemetry, { SpanStatusCode } from '@opentelemetry/api';
// ...
try {
doWork();
} catch (ex) {
if (ex instanceof Error) {
span.recordException(ex);
}
span.setStatus({ code: SpanStatusCode.ERROR });
}
const opentelemetry = require('@opentelemetry/api');
// ...
try {
doWork();
} catch (ex) {
if (ex instanceof Error) {
span.recordException(ex);
}
span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR });
}
sdk-trace-base
の使用と手動でのスパンコンテキストの伝播
場合によっては、Node.js SDKもWeb SDKも使用できない場合があります。 初期化コード以外の最大の違いは、ネストされたスパンを作成できるように、現在のコンテキストでスパンを手動でアクティブに設定する必要があることです。
sdk-trace-base
でトレーシングを初期化
トレーシングの初期化は、Node.jsまたはWeb SDKで行う方法と似ています。
import opentelemetry from '@opentelemetry/api';
import {
CompositePropagator,
W3CTraceContextPropagator,
W3CBaggagePropagator,
} from '@opentelemetry/core';
import {
BasicTracerProvider,
BatchSpanProcessor,
ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-base';
opentelemetry.trace.setGlobalTracerProvider(
new BasicTracerProvider({
// エクスポーターにスパンを送信するようにスパンプロセッサーを構成
spanProcessors: [new BatchSpanProcessor(new ConsoleSpanExporter())],
}),
);
opentelemetry.propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
}),
);
// これはすべての計装コードでアクセスするものです
const tracer = opentelemetry.trace.getTracer('example-basic-tracer-node');
const opentelemetry = require('@opentelemetry/api');
const {
CompositePropagator,
W3CTraceContextPropagator,
W3CBaggagePropagator,
} = require('@opentelemetry/core');
const {
BasicTracerProvider,
ConsoleSpanExporter,
BatchSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
opentelemetry.trace.setGlobalTracerProvider(
new BasicTracerProvider({
// エクスポーターにスパンを送信するようにスパンプロセッサーを構成
spanProcessors: [new BatchSpanProcessor(new ConsoleSpanExporter())],
}),
);
opentelemetry.propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
}),
);
// これはすべての計装コードでアクセスするものです
const tracer = opentelemetry.trace.getTracer('example-basic-tracer-node');
このドキュメントの他の例と同様に、これによりアプリケーション全体で使用できるトレーサーがエクスポートされます。
sdk-trace-base
でネストされたスパンを作成
ネストされたスパンを作成するには、現在作成されているスパンを現在のコンテキストでアクティブなスパンとして設定する必要があります。
startActiveSpan
を使用しても、これを行ってくれないので、使用しないでください。
const mainWork = () => {
const parentSpan = tracer.startSpan('main');
for (let i = 0; i < 3; i += 1) {
doWork(parentSpan, i);
}
// 必ず親スパンを終了してください!
parentSpan.end();
};
const doWork = (parent, i) => {
// 子スパンを作成するには、現在の(親)スパンをコンテキストでアクティブなスパンとしてマークし、
// 結果のコンテキストを使用して子スパンを作成する必要があります。
const ctx = opentelemetry.trace.setSpan(
opentelemetry.context.active(),
parent,
);
const span = tracer.startSpan(`doWork:${i}`, undefined, ctx);
// ランダムな作業をシミュレートします。
for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) {
// 空
}
// 必ずこの子スパンを終了してください!そうしないと、
// 'doWork'を超えて作業を追跡し続けます!
span.end();
};
sdk-trace-base
を使用する場合、他のすべてのAPIは、Node.jsまたはWeb SDKと比較して
同じように動作します。
メトリクス
メトリクスは、個々の測定値を集計に結合し、システム負荷の関数として一定のデータを生成します。 集計には、低レベルの問題を診断するために必要な詳細が欠けていますが、傾向を特定し、アプリケーションケーションランタイムのテレメトリーを提供することでスパンを補完します。
メトリクスの初期化
ライブラリを計装している場合は、このステップをスキップしてください。
アプリケーションでメトリクスを有効にするには、Meter
を作成できる初期化されたMeterProvider
が必要です。
MeterProvider
が作成されない場合、メトリクス用のOpenTelemetry APIはno-op実装を使用し、データの生成に失敗します。次に説明するように、NodeとブラウザですべてのSDK初期化コードを含めるようにinstrumentation.ts
(またはinstrumentation.js
)ファイルを変更します。
Node.js
上記のSDKの初期化の指示に従った場合、すでにMeterProvider
がセットアップされています。
メーターの取得に進むことができます。
sdk-metrics
でメトリクスを初期化
場合によっては、Node.js用の完全なOpenTelemetry SDKを使用できないか、使用したくない場合があります。 ブラウザでOpenTelemetry JavaScriptを使用する場合も同様です。
その場合は、@opentelemetry/sdk-metrics
パッケージでメトリクスを初期化できます。
npm install @opentelemetry/sdk-metrics
トレーシング用にまだ作成していない場合は、すべてのSDK初期化コードを含む別のinstrumentation.ts
(またはinstrumentation.js
)ファイルを作成します。
import opentelemetry from '@opentelemetry/api';
import {
ConsoleMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
import {
defaultResource,
resourceFromAttributes,
} from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'dice-server',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const metricReader = new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
// デフォルトは60000ms(60秒)。デモンストレーション目的でのみ10秒に設定。
exportIntervalMillis: 10000,
});
const myServiceMeterProvider = new MeterProvider({
resource: resource,
readers: [metricReader],
});
// このMeterProviderを計装されるアプリケーションにグローバルに設定します。
opentelemetry.metrics.setGlobalMeterProvider(myServiceMeterProvider);
const opentelemetry = require('@opentelemetry/api');
const {
MeterProvider,
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} = require('@opentelemetry/sdk-metrics');
const {
defaultResource,
resourceFromAttributes,
} = require('@opentelemetry/resources');
const {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} = require('@opentelemetry/semantic-conventions');
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'service-name-here',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const metricReader = new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
// デフォルトは60000ms(60秒)。デモンストレーション目的でのみ10秒に設定。
exportIntervalMillis: 10000,
});
const myServiceMeterProvider = new MeterProvider({
resource: resource,
readers: [metricReader],
});
// このMeterProviderを計装されるアプリケーションにグローバルに設定します。
opentelemetry.metrics.setGlobalMeterProvider(myServiceMeterProvider);
アプリケーションを実行するときに、このファイルを--require
する必要があります。
ts-node --require ./instrumentation.ts app.ts
node --require ./instrumentation.js app.js
MeterProvider
が構成されたので、Meter
を取得できます。
メーターの取得
手動で計装されたコードがあるアプリケーションケーションのどこでも、getMeter
を呼び出してメーターを取得できます。
例を挙げましょう。
import opentelemetry from '@opentelemetry/api';
const myMeter = opentelemetry.metrics.getMeter(
'instrumentation-scope-name',
'instrumentation-scope-version',
);
// これで'meter'を使用してインストルメントを作成できます!
const opentelemetry = require('@opentelemetry/api');
const myMeter = opentelemetry.metrics.getMeter(
'instrumentation-scope-name',
'instrumentation-scope-version',
);
// これで'meter'を使用してインストルメントを作成できます!
instrumentation-scope-name
とinstrumentation-scope-version
の値は、パッケージ、モジュール、またはクラス名など、計装スコープを一意に識別する必要があります。
名前は必須ですが、バージョンはオプションであるにもかかわらず推奨されます。
アプリケーションで必要なときにgetMeter
を呼び出すことが、メーターインスタンスアプリケーションの残りの部分にエクスポートするよりも一般的に推奨されます。
これは、他の必要な依存関係が関与している場合のより複雑なアプリケーションケーションロードの問題を回避するのに役立ちます。
サンプルアプリケーションの場合、適切な計装スコープでトレーサーを取得できる場所が2つあります。
まず、アプリケーションケーションファイル app.ts
(またはapp.js
)を以下のように実装します。
/*app.ts*/
import { metrics, trace } from '@opentelemetry/api';
import express, { Express } from 'express';
import { rollTheDice } from './dice';
const tracer = trace.getTracer('dice-server', '0.1.0');
const meter = metrics.getMeter('dice-server', '0.1.0');
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("Request parameter 'rolls' is missing or not a number.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
/*app.js*/
const { trace, metrics } = require('@opentelemetry/api');
const express = require('express');
const { rollTheDice } = require('./dice.js');
const tracer = trace.getTracer('dice-server', '0.1.0');
const meter = metrics.getMeter('dice-server', '0.1.0');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("Request parameter 'rolls' is missing or not a number.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Listening for requests on http://localhost:${PORT}`);
});
そして2番目に、ライブラリファイル dice.ts
(またはdice.js
)で、以下のように実装します。
/*dice.ts*/
import { trace, metrics } from '@opentelemetry/api';
const tracer = trace.getTracer('dice-lib');
const meter = metrics.getMeter('dice-lib');
function rollOnce(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function rollTheDice(rolls: number, min: number, max: number) {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
/*dice.js*/
const { trace, metrics } = require('@opentelemetry/api');
const tracer = trace.getTracer('dice-lib');
const meter = metrics.getMeter('dice-lib');
function rollOnce(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function rollTheDice(rolls, min, max) {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
module.exports = { rollTheDice };
カウンターの使用
カウンターは、非負の増加する値を測定するために使用できます。
サンプルアプリケーションの場合、これを使用してサイコロが振られた回数をカウントできます。
/*dice.ts*/
const counter = meter.createCounter('dice-lib.rolls.counter');
function rollOnce(min: number, max: number) {
counter.add(1);
return Math.floor(Math.random() * (max - min + 1) + min);
}
/*dice.js*/
const counter = meter.createCounter('dice-lib.rolls.counter');
function rollOnce(min, max) {
counter.add(1);
return Math.floor(Math.random() * (max - min + 1) + min);
}
UpDownカウンターの使用
UpDownカウンターは増減できるため、上下する累積値を観察できます。
const counter = myMeter.createUpDownCounter('events.counter');
//...
counter.add(1);
//...
counter.add(-1);
ヒストグラムの使用
ヒストグラムは、時間経過に伴う値の分布を測定するために使用されます。
たとえば、Expressを使用してAPIルートの応答時間の分布を報告する方法は次のとおりです。
import express from 'express';
const app = express();
app.get('/', (_req, _res) => {
const histogram = myMeter.createHistogram('task.duration');
const startTime = new Date().getTime();
// API呼び出しで何かの作業を行う
const endTime = new Date().getTime();
const executionTime = endTime - startTime;
// タスク操作の期間を記録
histogram.record(executionTime);
});
const express = require('express');
const app = express();
app.get('/', (_req, _res) => {
const histogram = myMeter.createHistogram('task.duration');
const startTime = new Date().getTime();
// API呼び出しで何かの作業を行う
const endTime = new Date().getTime();
const executionTime = endTime - startTime;
// タスク操作の期間を記録
histogram.record(executionTime);
});
Observable(非同期)カウンターの使用
Observableカウンターは、加法的、非負、単調増加する値を測定するために使用できます。
const events = [];
const addEvent = (name) => {
events.push(name);
};
const counter = myMeter.createObservableCounter('events.counter');
counter.addCallback((result) => {
result.observe(events.length);
});
//... addEventへの呼び出し
Observable(非同期)UpDownカウンターの使用
Observable UpDownカウンターは増減できるため、加算的、非負、非単調増加の累積値を測定できます。
const events = [];
const addEvent = (name) => {
events.push(name);
};
const removeEvent = () => {
events.pop();
};
const counter = myMeter.createObservableUpDownCounter('events.counter');
counter.addCallback((result) => {
result.observe(events.length);
});
//... addEventとremoveEventへの呼び出し
Observable(非同期)ゲージの使用
Observableゲージは、非加算的な値を測定するために使用する必要があります。
let temperature = 32;
const gauge = myMeter.createObservableGauge('temperature.gauge');
gauge.addCallback((result) => {
result.observe(temperature);
});
//... temperature変数はセンサーによって変更されます
計装の説明
カウンター、ヒストグラムなどの計装を作成するときに、説明を付けることができます。
const httpServerResponseDuration = myMeter.createHistogram(
'http.server.duration',
{
description: 'HTTPサーバー応答時間の分布',
unit: 'milliseconds',
valueType: ValueType.INT,
},
);
JavaScriptでは、各構成タイプは次のことを意味します。
description
- 計装の人間が読める説明unit
- 値が表すことを意図している測定単位の説明。たとえば、期間を測定するためのmilliseconds
、バイト数をカウントするためのbytes
。valueType
- 測定で使用される数値の種類。
作成する各計装を説明することを一般的にお勧めします。
属性の追加
メトリクスが生成されるときに属性を追加できます。
const counter = myMeter.createCounter('my.counter');
counter.add(1, { 'some.optional.attribute': 'some value' });
メトリクスビューの構成
メトリクスビューは、開発者にメトリクスSDKによって公開されるメトリクスをカスタマイズする機能を提供します。
セレクター
ビューをインスタンス化するには、まずターゲット計装を選択する必要があります。 以下は、メトリクスの有効なセレクターです。
instrumentType
instrumentName
meterName
meterVersion
meterSchemaUrl
instrumentName
(string型)による選択はワイルドカードをサポートしているため、*
を使用してすべての計装を選択したり、http*
を使用して名前がhttp
で始まるすべての計装を選択したりできます。
例
すべてのメトリクスタイプで属性をフィルタリング場合。
const limitAttributesView = {
// 属性'environment'のみをエクスポート
attributeKeys: ['environment'],
// すべてのインストルメントにビューを適用
instrumentName: '*',
};
メーター名pubsub
を持つすべての計装をドロップする場合。
const dropView = {
aggregation: { type: AggrgationType.DROP },
meterName: 'pubsub',
};
http.server.duration
という名前のヒストグラムに明示的なバケットサイズを定義する場合。
const histogramView = {
aggregation: {
type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM,
options: { boundaries: [0, 1, 5, 10, 15, 20, 25, 30] },
},
instrumentName: 'http.server.duration',
instrumentType: InstrumentType.HISTOGRAM,
};
メータープロバイダーにアタッチ
ビューが構成されたら、対応するメータープロバイダーにアタッチします。
const meterProvider = new MeterProvider({
views: [limitAttributesView, dropView, histogramView],
});
ログ
ログAPIとSDKは現在開発中です。
次のステップ
また、テレメトリーデータをエクスポートするために、1つ以上のテレメトリーバックエンドに適切なエクスポーターを設定する必要があります。
フィードバック
このページは役に立ちましたか?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!