🔭

OpenTelemetry Go を使ったトレースとログの紐付け on AWS CloudWatch

2022/10/05に公開

この文書は何か

opentelemetry-go を使って計装したサービスから、トレースやログを AWS のモニタリングソリューション(X-Ray, CloudWatch logsなど)に送信して可視化することができます。

ここでは、CloudWatch 上でトレースとログを紐付けて表示する方法について紹介させていただきます。OpenTelemetry(Otel) や X-Ray などについては、他の良記事に委ねることとして、本記事では基盤構築部分にフォーカスして執筆します。

想定読者

  • opentelemetry-go を使っている
  • AWS X-Ray や CloudWatch logs などのモニタリングソリューションを使っている
  • トレースとログを個別に可視化しているため、トラブルシューティング時に、
    トレース ID を CloudWatch logs でフィルターして関連するログを抽出し消耗している方

設定方法

実施することは以下 2 つです。
①:X-Ray Segmentcloudwatch_logs.log_group という Section にロググループを設定する
②:CloudWatch logs に送るログに トレース ID を付与する
※ 但し、OTel SDK の生成するトレース ID を X-Ray のトレース ID 体型にフォーマットする必要あり

① X-Ray Segment の設定方法

OpenTelemtry で計装している場合、一般的に OTLP を使用して gRPC や HTTP 経由で Otel Collector にトレースを送信します。Otel Collector のコンポーネントである AWS X-Ray Exporter は OTLP 形式のトレースデータAWS X-Ray 形式のトレースデータに変換します。
その際の X-Ray Segment と Otel で設定できる属性の対比表はこちらにまとまっています。
https://aws-otel.github.io/docs/getting-started/x-ray#otel-span-cw-logs-metadata-translation

ここを見ると、X-Ray Segment で cloudwatch_logs.log_group を設定したい場合は、
Otel 側の属性として aws.log.group.names を設定すれば良いと書いてあります。

aws.log.group.names の設定は traceProvider への resource 追加で以下のように実施できます。

  • 以下のような resource 追加の関数を定義する
func newResource() *resource.Resource {
	var LogGroupNames [1]string
	LogGroupNames[0] = "<ご自分のロググループ名を設定>"
	return resource.NewWithAttributes(
		semconv.SchemaURL,
		semconv.AWSLogGroupNamesKey.StringSlice(LogGroupNames[:]),
	)
}
  • traceProvider へ resource の追加を行う
tracerProvider = sdktrace.NewTracerProvider(
	sdktrace.WithSampler(sdktrace.AlwaysSample()),
	sdktrace.WithSpanProcessor(bsp),
	sdktrace.WithIDGenerator(idg),
	sdktrace.WithResource(newResource()), // <- 今回作った関数を追加
)

これにより、Otel で用意している AWS 用の resource にロググループが格納されます。

② CloudWatch logs に送る トレース ID のフォーマット

トレースとログを扱う場合、ログに trace_id や span_id のフィールドを設けるのが基本です。
Otel SDK で生成するトレース ID は 例: 1633c54637113739f2924791b2c00a397 のような形式です。
X-Ray で処理されるトレースIDは 例: 1-633c5463-7113739f2924791b2c00a397 のような形式です。
https://docs.aws.amazon.com/ja_jp/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader
トレースの場合は、上述のように Otel Collector が X-Ray 形式に変換してくれるため意識しないで良いのですが、ログの trace_id にはフォーマットされた ID を渡す必要があります。

今回は以下のような関数を定義して ID をフォーマットして CloudWatch logs に送信しました。

func IdOtel2Xray(OtelId string) string {
	xrayId := "1-" + OtelId[0:8] + "-" + OtelId[8:]
	return xrayId
}

_, span := tracer.Start(c.Request.Context(), msg)
traceId := IdOtel2Xray(span.SpanContext().TraceID().String())

結果

正常に設定がされていると、CloudWatch 上でトレースと、そのトレース ID に紐付くログを以下のように表示することができます。

カラクリとしては、上部で設定したロググループに、以下のようなクエリを発行して取得したログを自動表示しているだけです。

cloudwatch logs
fields @log, @timestamp, @message
| filter @message like "1-633c5463-7113739f2924791b2c00a397"
| sort @timestamp, @message desc

最後に

Observability を実現するためには、テレメトリー(ログやトレースやメトリクス等)が相互に連携できることが重要になります。今回は1パターンであるトレースとログの紐付けを紹介しました。
AWS 公式では Java 版の AWS X-Ray SDK を使った CloudWatch Logs 統合のドキュメントは見当たりましたが、今回の opentelemetry-go での紐付けについては言及がなかったためニッチですが記事にしました。
OpenTelemetry を使って標準化の波に乗りましょう。

Discussion