OpenTelemetry Go を使ったトレースとログの紐付け on AWS CloudWatch
この文書は何か
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 Segment の cloudwatch_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 で設定できる属性の対比表はこちらにまとまっています。
ここを見ると、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
のような形式です。
トレースの場合は、上述のように 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 に紐付くログを以下のように表示することができます。
カラクリとしては、上部で設定したロググループに、以下のようなクエリを発行して取得したログを自動表示しているだけです。
fields @log, @timestamp, @message
| filter @message like "1-633c5463-7113739f2924791b2c00a397"
| sort @timestamp, @message desc
最後に
オブザーバビリティを実現するためには、テレメトリー(ログやトレースやメトリクス等)が相互に連携できることが重要になります。今回は1パターンであるトレースとログの紐付けを紹介しました。
AWS 公式では Java 版の AWS X-Ray SDK を使った CloudWatch Logs 統合のドキュメントは見当たりましたが、今回の opentelemetry-go での紐付けについては言及がなかったためニッチですが記事にしました。
OpenTelemetry を使って標準化の波に乗りましょう。
Discussion