😺

DatadogでRUMとAPM Traceを関連づける方法(Golang)

2023/12/25に公開

背景

私が担当するサービスではDatadogを使って、分散トレーシングを行なっています。
これによってバックエンドシステム間のトレースを見ることができていたのですが、RUMとの関連付けができておらず、多少不便なところがありました。
これを解消するためにGolang製のBFFサーバーで設定を変更しようとしたのですが、あまり具体的なコード例が出てこなかったので、ここで紹介しようと思います。

目的

  • Golang製のサーバーでRUMとAPM Traceをいい感じに関連づけるコード例を紹介する。

実装内容

まず、DatadogのドキュメントHow are RUM resources linked to traces?
には以下のように書いてあります。

Datadog uses the distributed tracing protocol and sets up the following
HTTP headers:

  • x-datadog-trace-id
    Generated from the Real User Monitoring SDK. Allows Datadog to link the trace with the RUM resource.
  • x-datadog-parent-id
    Generated from the Real User Monitoring SDK. Allows Datadog to generate the first span from the trace.
  • x-datadog-origin: rum
    To make sure the generated traces from Real User Monitoring don’t affect your APM Index Spans counts.
  • x-datadog-sampling-priority: 1
    To make sure that the Agent keeps the trace.

x-datadog-trace-idとx-datadog-parent-idを使ってAPM TraceとRUNの関連づけが行えることがわかりました。

RUMとAPM Traceを紐づけるには、RUMによって生成されたSpanとバックエンドシステムによって生成されたSpanを一つのTraceとして認識させる必要があり、これを実現するために上記の2つのIDが使われている認識です。

また、ddtrace/tracerパッケージのOverviewには以下の記載があり、分かりやすい解説がされています。

引用部分(長いのでトグルで)

To create spans, use the functions StartSpan and StartSpanFromContext. Both accept StartSpanOptions that can be used to configure the span. A span that is started with no parent will begin a new trace. See the function documentation for details on specific usage. Each trace has a hard limit of 100,000 spans, after which the trace will be dropped and give a diagnostic log message. In practice users should not approach this limit as traces of this size are not useful and impossible to visualize.

See the contrib package ( https://pkg.go.dev/gopkg.in/DataDog/dd-trace-go.v1/contrib ) for integrating datadog with various libraries, frameworks and clients.

All spans created by the tracer contain a context hereby referred to as the span context. Note that this is different from Go's context. The span context is used to package essential information from a span, which is needed when creating child spans that inherit from it. Thus, a child span is created from a span's span context. The span context can originate from within the same process, but also a different process or even a different machine in the case of distributed tracing.

To make use of distributed tracing, a span's context may be injected via a carrier into a transport (HTTP, RPC, etc.) to be extracted on the other end and used to create spans that are direct descendants of it. A couple of carrier interfaces which should cover most of the use-case scenarios are readily provided, such as HTTPCarrier and TextMapCarrier. Users are free to create their own, which will work with our propagation algorithm as long as they implement the TextMapReader and TextMapWriter interfaces. An example alternate implementation is the MDCarrier in our gRPC integration.

As an example, injecting a span's context into an HTTP request would look like this. (See the net/http contrib package for more examples https://pkg.go.dev/gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http):

コード例

そして、本題です。
上記の引用部分の少し下に

Then, on the server side, to continue the trace one would do:
sctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(req.Header))
// ...
span := tracer.StartSpan("child.span", tracer.ChildOf(sctx))

と記載があり、これがほぼ答えですが、私たちは概ねこのような実装をしました。

httpCarier := tracer.HTTPHeadersCarrier(req.Header)
spanContext, err := tracer.Extract(httpCarier)
if err != nil {
  spanContext = nil
}

spanOpts = append(spanOpts,
		tracer.ChildOf(spanContext),
		// 他の設定をここで追加する
		// tracer.Tag(...),
		// tracer.ServiceName(...),
		// https://pkg.go.dev/gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer#StartSpanOption に定義されているメソッドを使う
		)
_, ctx = tracer.StartSpanFromContext(ctx, "sample.sample", spanOpts...)

最後に

DatadogでRUMとAPM Traceの関連づけは紹介したコード例でできると思います。
調べてみて、「おぉ、こんなに簡単にできるのか!」と感動したので、まだやってない方がいたら是非導入してみてはいかがでしょうか?

参考資料

Discussion