OpenTelemetry を使用してアプリケーションのメトリックを任意の監視バックエンドに送信する際には、OpenTelemetry コレクターという層を 1 つ挟んで、その OpenTelemetry コレクターが監視バックエンドへ送る構成が一般的です。ですが、ちょっと動作確認をしたいだけなのに、OpenTelemetry コレクターを導入するのはやや大仰です。OpenTelemetry では、さまざまな監視バックエンドに対応したエクスポーターが提供されています。そのようなエクスポーターを使うことで、アプリケーションから直接メトリックを送信できます。
今回は、その中から Mackerel に対応したエクスポーターである mackerel-exporter を使用して、Node.js のアプリケーションのメトリックを Mackerel に送信する方法をご紹介します。
Node.js でメトリックを送信するコードを書く
Node.js で簡単なメトリックを送信するコードを書いて、Mackerel 上でメトリックが表示されることを確認してみましょう。はじめに必要なパッケージをインストールします。
npm install @opentelemetry/api @opentelemetry/sdk-metrics @opentelemetry/resources @opentelemetry/semantic-conventions mackerel-exporter
以下のコードでは、1000ms ごとに dice
というメトリックを送信しています。このメトリックのラベルとして、dice
というキーに 1 から 6 までのランダムな値を設定しています。
import { DiagConsoleLogger, DiagLogLevel, diag } from "@opentelemetry/api"; import { PeriodicExportingMetricReader, MeterProvider, } from "@opentelemetry/sdk-metrics"; import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"; import { Resource } from "@opentelemetry/resources"; import { MackerelExporter } from "mackerel-exporter"; // グローバルなロガーを有効にする diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); // MeterProvider は Meter を生成する const meterProvider = new MeterProvider({ // resource は必須 resource: new Resource({ // サービス名を指定。サービス名は、どのサービスからメトリックが送信されたかを識別するために使用される [SemanticResourceAttributes.SERVICE_NAME]: "example-metric-service", }), }); // エクスポーターの設定 const exporter = new MackerelExporter({ // API キーは必須 // 環境変数に API キーを設定しておく apiKey: process.env.MACKEREL_API_KEY, }); // MeterProvider に MetricReader の設定を追加 // このように MeterProvider はただ 1 つだけ存在して // 設定を保持する役割を担う meterProvider.addMetricReader( new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 1000, }), ); // MeterProvider から Meter を取得 const meter = meterProvider.getMeter("example-mackerel-exporter"); // Meter から Instrument を取得 // Instrument がメトリックの送信を行う const diceRollCounter = meter.createCounter("dice", { description: "Dice roll counter", }); // 1000ms ごとにメトリックを送信 setInterval(() => { const dice = Math.floor(Math.random() * 6) + 1; // .add() で渡した数だけ increment してメトリックを送信 // 第 2 引数にはラベルを設定できる diceRollCounter.add(1, { dice: dice, }); console.log(`Rolling a ${dice}`); }, 1000);
OpenTelemetry における Metrics API は、以下の 3 つのコンポーネントから構成されています。
- MeterProvider
- Meter
- Instrument
https://opentelemetry.io/docs/specs/otel/metrics/api/
MeterProvider が Meter を生成し、Meter が Instrument を生成する、という関係となっています。それぞれ詳細を見ていきましょう。
MeterProvider
MeterProvider は Metrics API のエントリーポイントで、Meter へのアクセスを提供します。MeterProvider はステートフルなオブジェクトであり、設定を保持する役割を担います。
const meterProvider = new MeterProvider({ resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "example-metric-service", }), }); meterProvider.addMetricReader( new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 1000, }), );
ここでは、addMetricReader
メソッドで MetricReader の設定を追加しています。MetricReader はどのようにメトリックを収集するかを定義するためのインターフェースです。MetricReader
にエクスポーターを設定することで、メトリックがエクスポートされる方法を設定できます。
エクスポーターのインターフェースはあらかじめ OpenTelemetry によって定義されていますので、自由に設定を変更できます。今回は Mackerel にメトリックを送信するために MackerelExporter を使用していますが、以下のようなエクスポーターに簡単に取り替えることができます。
- ConsoleMetricExporter:メトリックをコンソールに出力する。
- PrometheusExporter:メトリックを Prometheus に送信する。
- OTLPExporter:メトリックを OpenTelemetry Protocol(OTLP) で送信する。
Meter
Meter は MeterProvider により生成され、Instrument を生成するためのインターフェースを提供します。meterProvider.getMeter()
の引数として渡す名前は、Instrumentation Scope を表します。Instrumentation Scope とは、計測対象となるアプリケーションの論理的な単位です。
const meter = meterProvider.getMeter("example-mackerel-exporter");
Meter は Instrument を生成する役割を担っており、以下の種類の Instrument を生成できます。
- Counter
- Asynchronous Counter
- Histogram
- Asynchronous Gauge
- UpDownCounter
- Asynchronous UpDownCounter
ここでは meter.createCounter()
メソッドを使って Counter を生成しています。Counter は以下のような非負のインクリメントを計測するために使用します。
- リクエストが完了した数。
- 5xx HTTP エラーの数。
const diceRollCounter = meter.createCounter("dice", { description: "Dice roll counter", });
Instrument
Instrument は Meter により生成され、メトリックを送信するためのインターフェースを提供します。diceRollCounter.add()
メソッドを呼び出すたびに、メトリックのカウント数をインクリメントし、メトリックの送信を行います。
const dice = Math.floor(Math.random() * 6) + 1; diceRollCounter.add(1, { dice: dice, });
diceRollCounter.add()
の第 1 引数はインクリメントする数、第 2 引数はラベルを設定します。ラベルはメトリックの分類に使用されます。ここではサイコロの目をラベルと指定しているので、例えば以下のようなクエリを実行することで 1 の目が出た割合を集計できます。
scala(dice{dice="1"}) / sum(dice)
コードを実行する
以上のコードを index.js
として保存し、実行してみましょう。
node index.js
正常に Mackerel にメトリックを送信できている場合、以下のようなログが出力されます。
Rolling a 6 Objects sent items to be sent [ { resource: Resource { _attributes: [Object], asyncAttributesPending: false, _syncAttributes: [Object], _asyncAttributesPromise: undefined }, scopeMetrics: [ [Object] ] } ]
API キーが誤っている、または書き込み権限がない場合には、認証に失敗して PERMISSION_DENIED
というエラーが出力されます。
{"stack":"Error: PeriodicExportingMetricReader: metrics export failed (error Error: 7 PERMISSION_DENIED: authenticate error)\n ...
Mackerel でメトリックを確認する
API キーを取得したオーガニゼーションの Mackerel のウェブコンソールから「ダッシュボード」>「カスタムダッシュボードを追加」を選択してください。
https://mackerel.io/my/dashboards
カスタムダッシュボードを追加する画面が表示されたら、グラフウィジェットを選択してグリッド上にドラッグ&ドロップしてください。
グラフウィジェットを追加するダイアログが表示されたら、「グラフのタイプ」で「クエリグラフ」を選択します(「クエリグラフ」はベータ版利用者のみに表示されます)。PromQL を記述するエディタが表示されますので、以下のクエリを入力してください。
dice
すると、以下のようにダイスの目ごとにグラフが表示されます。凡例を見やすくするために、「凡例設定」>「値で」は dice
を選択しています。凡例でラベルのキーをしていると、対応する価が凡例として表示されます。
まとめ
今回の内容をまとめます。
- mackerel-exporter をエクスポーターとして使用することで、Node.js アプリケーションから直接 Mackerel にメトリックを送信できる。
- OpenTelemetry の Metrics API では MeterProvider が Meter を生成し、Meter が Instrument を生成する。
- インターフェースがあらかじめ定義されているので、エクスポーターは自由に取り替えることができる。そのため、アプリケーションの実装は変えずに簡単にメトリックの送信先を変更できる。
「ラベル付きメトリック機能」ベータ版テストの参加者募集中
現在、「ラベル付きメトリック機能」ベータ版テストの参加者を募集しています。下記のフォームよりベータ版テストへの参加お申し込みをいただけます。