DataDog APM を Go の HTTP アプリケーションにフレキシブルに適用する

ソフトウェアエンジニアの北原です。

Nature ではモニタリングサービスとして DataDog を使用しています。また、DataDog APM を利用し、HTTPアプリケーションとしてのメトリクスの収集を行っています。

DataDog APM として、どのようなことができるかは、クラスメソッドさんの記事も参考になります。

今回は、Nature の API サーバに DataDog APM を適用するにあたり、オフィシャルのライブラリである github.com/DataDog/dd-trace-go パッケージにプルリクエストを出しマージ、リリースされたのでその説明をします。

github.com

github.com

まず dd-trace-go では、net/http, Gin, Gorilla Mux, Gorm などメジャーなパッケージに対し簡単に導入できるよう準備がされています。*1

例えば Gin では以下のように DataDog APM 用のミドルウェアが提供されており、簡単に導入することができます。 *2

package main

import (
    "github.com/gin-gonic/gin"
    gintrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gin-gonic/gin"
)

func main() {
    // Create a gin.Engine
    r := gin.New()

    // Use the tracer middleware with your desired service name.
    r.Use(gintrace.Middleware("my-web-app"))

    // Set up some endpoints.
    r.GET("/hello", func(c *gin.Context) {
        c.String(200, "hello world!")
    })

    // And start gathering request traces.
    r.Run(":8080")
}

しかし dd-trace-go で用意されていないパッケージへの対応がしづらいこともあり、内部でおおよその HTTP フレームワークに対して使用されていた httputil.TraceAndServe という関数をパブリックにしてもらうことにしました。

github.com

これにより、マイナーなパッケージ向けの DataDog APMミドルウェアを書くことができます。 基本としてはこのようなコードになります。

import(
    httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
    "gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
)

func MyDDTrace(serviceName string, analyticsRate float64) func(http.Handler) http.Handler {

    // build additional spanOpts
    spanOpts := []ddtrace.StartSpanOption{}
    if !math.IsNaN(analyticsRate) {
        spanOpts = append(spanOpts, tracer.Tag(ext.EventSampleRate, analyticsRate))
    }
    spanOpts = append(spanOpts, tracer.Measured()

    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
            // resolve resource name like "POST /1/signals/:id/send"
            httptrace.TraceAndServe(next, w, req, &httptrace.ServeConfig{
                Service:  serviceName,
                Resource: resource,
                SpanOpts: opts,
            })
        })
    }
}

Nature では、なるべく早いタイミングで APM の計測開始はしつつ、router ライブラリの使い方の都合上 resource 名 *3 の解決を行うミドルウェアを別途定義し、こちらは計測開始のタイミングとは別で行うなどをしています。

余談として、dd-trace-go の v1.33.0 までは実はこの TraceAndServe が定義されているファイルは internal パッケージとして定義されながらも、他の internal パッケージに依存していない状態でした。 なので、一旦の初期導入の調査として、このコードをそのまま API レポジトリに持っていき正しくメトリクスが取得できるかなどを確認しました。*4 しかし、v1.34.0 でその他の internal パッケージへの依存がされるようになり、またこの初期導入の形を続けるわけにもいかないので今回のプルリクエストを送ることとなりました。

また、初期導入の調査のタイミングで http.host の項目が反映されないという問題も見つかりました。こちらは、SREの黒田さんが DataDog チームとやりとりをしてくれて解決しました。

Natureではデバイス事業、電気事業を共に進め、またそれらを融合させ新しい体験を作りたいと思っており、エンジニアを積極的に採用しています。

herp.careers

また、カジュアル面談も実施していますので、Nature のことに少しでも興味ある方はぜひお申し込みください。

herp.careers

*1:https://docs.datadoghq.com/tracing/setup_overview/compatibility_requirements/go/

*2:https://github.com/DataDog/dd-trace-go/blob/v1/contrib/gin-gonic/gin/example_test.go

*3:POST /1/signals/:id/send などパスをある程度抽象化した文字列

*4:CIでこのファイルが変わっていないかなどのテストをするようにしていました。