🐝

OpenTelemetryの計装をデバッグする

2023/10/25に公開

はじめに

この記事ではOpenTelemetryでの計装内容をローカルで確認するために、次の方法を紹介します。

  1. stdoutに出力する
  2. OpenTelemetry Collector からファイルに出力する
  3. ローカルでOpenTelemetryのサーバーを建てる

1. stdoutに出力する

一番簡単なのはstdoutに出力することです。
言語毎にnullの表現方法など微妙に違う部分がありますが、「計装できているか」「入れたいAttributeが入っているか」という確認には十分です。

多くの言語ではstdoutに出力する方法は公式で用意されています。
例えばgoではstdouttraceを使います。

import(
  "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
  ...
)

func setOtel() {
  // stdouttraceを使用
  exporter := stdouttrace.New(stdouttrace.WithPrettyPrint())

  tp := trace.NewTracerProvider(
    trace.WithBatcher(exporter),
  )
  otel.SetTracerProvider(tp)
}

このように、既存のExporterをstdouttraceに入れ替えるだけでstdoutに出力できるようになります。
(ここではかなり簡略した実装でOpenTelemetryを使っています。細かい実装については別の記事を参照してください)

stdouttraceを使って動かすと、stdoutに下のようなJSONが出力されます。

{
  "Name": "gorm.Query",
  "SpanContext": {
    "TraceID": "8b50a436cba088a3c4e635c5b5cc733f",
    "SpanID": "704b0c869eeeef22",
  },
  "Parent": {
    "TraceID": "8b50a436cba088a3c4e635c5b5cc733f",
    "SpanID": "9c7cdeaff0b82ec0",
  },
  "Attributes": [
    {
      "Key": "db.system",
      "Value": {
        "Type": "STRING",
        "Value": "mysql"
      }
    },
    {
      "Key": "db.statement",
      "Value": {
        "Type": "STRING",
        "Value": "SELECT * FROM `payment_methods` WHERE id = 1"
      }
    },
  ],
  ...
}

これをみて、gorm.Queryという名前から「gorm部分も計装できているな」と確認することができるというわけです。

2. OpenTelemetry Collector からファイルに出力する

stdoutで物足りない場合は、OpenTelemetry Collectorを使う方法もあります。
OpenTelemetry CollectorにはFile Exporterというexporterが用意されています。
https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/fileexporter

File Exporter を使うには、OpenTelemetry Collectorに次の設定を追加します。

exporters:
  file:
    path: /data/trace
    format: json

service:
  pipelines:
    traces:
      ...
      exporters: [file]

これで、OpenTelemetryが外部に出力している内容が/data/traceに書き込まれます。
(このyamlでは必要な部分しか書いていません。yamlの設定については別の記事を参照してください)

ファイルには下のようなJSONが書き込まれます。

{
  "resourceSpans": [
    {
      "resource": {...},
      "scopeSpans": [
        {
          "scope": {
            "name": "gorm.io/plugin/opentelemetry"
          },
          "spans": [
            {
              "traceId": "8b50a436cba088a3c4e635c5b5cc733f",
              "spanId": "704b0c869eeeef22",
              "parentSpanId": "9c7cdeaff0b82ec0",
              "name": "gorm.Query",
              "attributes": [
                {
                  "key": "db.system",
                  "value": {
                    "stringValue": "mysql"
                  }
                },
                {
                  "key": "db.statement",
                  "value": {
                    "stringValue": "SELECT * FROM `users` WHERE id = 1 ORDER BY `users`.`id` LIMIT 1"
                  }
                },
                ...
              ],
            },
          ]
        },
      ],
      "schemaUrl": "https://opentelemetry.io/schemas/1.21.0"
    }
  ]
}

OpenTelemetryの出力はネストが深いですが、ネストの最深部にstdoutと同じくgorm.Queryの名前が確認できます。この部分が計装した内容です。
基本的な内容はstdoutと同じ内容ですが、省略が無くprotoと一対一で対応するので詳しく調査する場合に有益です。

ちなみに、ymlでformatをprotoに設定するとProtocol Buffersで出力することができますが、

each encoded object is preceded by 4 bytes (an unsigned 32 bit integer) which represent the number of bytes contained in the encoded object

READMEにあるように「先頭4バイトから長さを取って、その長さでファイルを分割して、その後にprotocにかけて・・・」とやらないと中身が見れないのでjsonの方が読みやすいです。

一応、一行だけなら下のようにすれば中身が見れます。

tail -c +5 /data/trace | head -c $(head -c 4 /data/trace | xxd -p | awk '{print "0x"$1}' |xargs printf "%d") | protoc --decode_raw

3. ローカルでOpenTelemetryのサーバーを建てる

ローカルにサーバーを建ててしまうという方法もあります。
Dockerの準備などがあるものの、グラフなどで可視化されるので直感的に確認できるのが利点です。

例えば、以下のOSSが使えます。

終わりに

以上、OpenTelemetryの計装内容を確認する方法でした。

Vaxila

Discussion