blog.anqou.net
rss
author
tags

OCaml で OpenTelemetry を Eio で使う

ocaml-opentelemetry を使うと OCaml で OpenTelemetry によってトレース・メトリクス・ログを出力することができます。この記事ではこのうち、トレースとメトリクスを出力してみます。非同期処理のフレームワークとして Eio を使用します。なお ocaml-opentelemetry はバージョンによって API がかなり異なるため注意が必要です。ここでは執筆時点で最新の v0.91 を対象にします。おそらく v0.90 でも動作しますが v0.13[1] では動作しません。

#セットアップ

ocaml-opentelemetry を Eio と一緒に使用する場合、まず以下のようにセットアップします:

let setup_otel env f =
  Opentelemetry.Globals.service_name := "your-system";
  Ambient_context.set_current_storage Ambient_context_eio.storage;
  Opentelemetry_client_cohttp_eio.with_setup env (fun () ->
      Opentelemetry_trace.setup ();
      Opentelemetry.Meter.(add_to_main_exporter default);
      f ())

let () =
  Eio_main.run (fun env ->
      setup_otel env (main env)) (* main は別で定義されている想定 *)
  |> exit

dune ファイルは次のようになります:

(executable
 (name main)
 (libraries
  opentelemetry.trace
  opentelemetry-client-cohttp-eio
  ambient-context-eio))

Opentelemetry.Meter.add_to_main_exporter の呼び出しはメトリクスを使う場合に必要になります。トレースのみの使用なら不要です。逆にメトリクスを使う場合はこれを書いておかないと後述する Instrument などが動作しません[2]

#メトリクス

ocaml-opentelemetry の README には Opentelemetry.Meter.emit1 を使用する例が書かれていますが、これを使うとメトリクスが一度しか出力されず持続しません(一敗)。ad hoc な確認ならこれでよいですが、本格的に使用するためには Instrument を介して使う必要があります。まず以下のようにメトリックを定義します:

let your_first_metric =
  Opentelemetry.Instrument.Int_gauge.create
    ~name:"your_first_metric"
    ~description:"Your first metric description"
    ()

続いてコード中でこのメトリックを使います:

Opentelemetry.Instrument.Int_gauge.record your_first_metric 1;

どのような Instrument があるか、どのように使うかなどはリファレンスを参照してください。カスタムな定義もできるようです。

#トレース

続いてトレースの使用方法です。README には Opentelemetry.Tracer.with_ を使う方法が記載されていますが、面倒くさくて使う気になりません[3]ocaml-trace で配布されている ppx_trace を使うとより簡単にトレースを採取できます。これは PPX なので dune ファイルの preprocess に追加した後で以下のように書きます:

let%trace f () = do_something ()

ただし本家の実装ではネストしたトレースがうまく扱われません。私のフォークを使うとネストも正しく扱えます。waq-external-repo に含まれているので良ければ使ってみてください[4]

#動作確認

ocaml-opentelemetry に限った話ではないですが、OpenTelemetry を正しく取り扱えているか確認するには Docker で otel/opentelemetry-collector イメージを立ち上げて確認するのが簡単です。立ち上がっている先のエンドポイントは OTEL_EXPORTER_OTLP_ENDPOINT 環境変数で指定します。以下のような compose.yml を書き docker compose up で立ち上げます:

services:
  web2:
    image: your-system
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318

  otel-collector:
    image: otel/opentelemetry-collector
    volumes:
      - otel-collector-config.yaml:/etc/otelcol/config.yaml

opentelemetry-collector の設定ファイルは以下のように書いておくとログで何を受け取ったかが表示されるので便利です:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]
    metrics:
      receivers: [otlp]
      exporters: [debug]
    logs:
      receivers: [otlp]
      exporters: [debug]

なお OpenTelemetry の機能を無効にしたい場合は環境変数で OTEL_SDK_DISABLED=true と設定しておきます。

注釈

  1. ocaml-opentelemetry は謎のバージョニングを採用しており、今年の 3 月 7 日に出た v0.13 の次のバージョンが 4 月 4 日に出た v0.90 になっています。

  2. この仕様についてドキュメントに記載がないように見えています。コード中のコメントには記載があります。

  3. 個人の感想です。

  4. 本家に貢献しなきゃなぁと思っていたことをこの記事を書いて思い出しました。