GoのアプリケーションにDatadogAPMを導入する。

業務でDatadogAPMを導入する機会があったので、詰まった箇所などを簡単にまとめようと思います。
# Datadogでやりたいこと
- カスタムエージェントホストの設定
- サービス名の指定
- 環境毎にTraceListのページを切り替える
- 1つのリクエストに、クエリ等のログを紐づける

# DatadogTracerのインストール
[Tracing Go Applications #Installation](https://docs.datadoghq.com/ja/tracing/setup/go/#installation)
このページに記載されている通りに、下記のコマンドでインストールします。

```
go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace
```
現時点では、Frameworkは`echo`、ORMは`gorm`を使用しているので、今回対象となるファイルは下記です。

```
$ cd $GOPATH/pkg/mod/gopkg.in/\!data\!dog/dd-trace-go.v1@v1.18.0
$ tree contrib/jinzhu
contrib/jinzhu
└── gorm
    ├── example_test.go
    ├── gorm.go
    ├── gorm_test.go
    └── option.go
$ tree contrib/labstack
contrib/labstack
└── echo
    ├── echotrace.go
    ├── echotrace_test.go
    ├── example_test.go
    └── option.go
```

# 公式のドキュメントを見てみる
参考にしたのは下記の4つ
[Tracing Go Applications #Configuration](https://docs.datadoghq.com/ja/tracing/setup/go/#configuration)
[Tracing Go ApplicationsChange Agent Hostname](https://docs.datadoghq.com/ja/tracing/setup/go/#change-agent-hostname)
[GoDoc dd-trace-go.v1: gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo](https://godoc.org/gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo)
[GoDoc dd-trace-go.v1: gopkg.in/DataDog/dd-trace-go.v1/contrib/jinzhu/gorm](https://godoc.org/gopkg.in/DataDog/dd-trace-go.v1/contrib/jinzhu/gorm)

# 実装
### カスタムエージェントホストの設定、サービス名の指定
```go
func startDatadogTrace() {
	// See: https://docs.datadoghq.com/ja/tracing/setup/go/#change-agent-hostname
	addr := net.JoinHostPort(
		os.Getenv("DD_AGENT_HOST"),
		os.Getenv("DD_TRACE_AGENT_PORT"),
	)

	// start the tracer with zero or more options
	tracer.Start(tracer.WithServiceName("my-app"), tracer.WithAgentAddr(addr))
}
```

これでTrace開始の実装完了です。
※呼び出し等で、`tracer.Stop()`を忘れずに実装してください。

### 環境毎にTraceListのページを切り替える
上記の実装だけだと環境毎にTraceListを分けられないので、少し手を加えます。

```go
func startDatadogTrace() {
	// See: https://docs.datadoghq.com/ja/tracing/setup/go/#change-agent-hostname
	addr := net.JoinHostPort(
		os.Getenv("DD_AGENT_HOST"),
		os.Getenv("DD_TRACE_AGENT_PORT"),
	)

	var opts []tracer.StartOption
	opts = append(opts, tracer.WithServiceName("my-app"), tracer.WithAgentAddr(addr))

	datadogEnv := os.Getenv("DATADOG_ENV")
	if datadogEnv != "" {
		opts = append(opts, tracer.WithGlobalTag(ext.Environment, datadogEnv))
	}

	// start the tracer with zero or more options
	tracer.Start(opts...)
}
```
環境変数である必要はありませんが、`tracer.Start`に`tracer.WithGlobalTag(ext.Environment, datadogEnv)`を渡す必要があります。
こうすることで、下記のスクショのように環境の切り替えが行えます。
<img width="547" alt="Screen Shot 2019-10-10 at 14.03.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112715/a011a234-a2be-fd2b-1537-35f8c3aabd49.png">

### 1つのリクエストに、クエリ等のログを紐づける
これに結構苦戦しました。
公式のドキュメントをみると、下記のように実装するように書いてあります。

- echo  
[Example (SpanFromContext)](https://godoc.org/gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo#example-package--SpanFromContext)


```go
// Create a new instance of echo
r := echo.New()

// Use the tracer middleware with your desired service name.
r.Use(Middleware(WithServiceName("image-encoder")))

// Set up some endpoints.
r.GET("/image/encode", func(c echo.Context) error {
    // create a child span to track an operation
    span, _ := tracer.StartSpanFromContext(c.Request().Context(), "image.encode")

    // encode an image ...

    // finish the child span
    span.Finish()

    return c.String(200, "ok!")
})
```
- gorm  
[Example Open](https://godoc.org/gopkg.in/DataDog/dd-trace-go.v1/contrib/jinzhu/gorm#example-Open)

```go
// Register augments the provided driver with tracing, enabling it to be loaded by gormtrace.Open.
sqltrace.Register("postgres", &pq.Driver{}, sqltrace.WithServiceName("my-service"))

// Open the registered driver, allowing all uses of the returned *gorm.DB to be traced.
db, err := gormtrace.Open("postgres", "postgres://pqgotest:password@localhost/pqgotest?sslmode=disable")
defer db.Close()
if err != nil {
    log.Fatal(err)
}

user := struct {
    gorm.Model
    Name string
}{}

// All calls through gorm.DB are now traced.
db.Where("name = ?", "jinzhu").First(&user)
```

これをほぼそのまま実装してみると、1リクエストにクエリが紐づいて表示されるのですが、それとは別にクエリのみのログも転送されてしまいます。
恥ずかしながら、これを解決するのに結構時間がかかってしまいました。(問い合わせるも、明確な回答は得られず。。。)

そこでDatadogTracerのgromの実装を見てみました。

```go
// Open opens a new (traced) database connection. The used dialect must be formerly registered
// using (gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql).Register.
func Open(dialect, source string, opts ...Option) (*gorm.DB, error) {
	sqldb, err := sqltraced.Open(dialect, source)
	if err != nil {
		return nil, err
	}
	db, err := gorm.Open(dialect, sqldb)
	if err != nil {
		return db, err
	}
	return WithCallbacks(db, opts...), err
}
```

うん、`sqltrace.Register`を事前に実行する必要があるようですね。
となるとやはり公式の実装が正しいと思えてしまいます。
ですが、大事なのは`gormtrace.WithCallbacks`関数でした。

```go
// WithCallbacks registers callbacks to the gorm.DB for tracing.
// It should be called once, after opening the db.
// The callbacks are triggered by Create, Update, Delete,
// Query and RowQuery operations.
func WithCallbacks(db *gorm.DB, opts ...Option) *gorm.DB {
	afterFunc := func(operationName string) func(*gorm.Scope) {
		return func(scope *gorm.Scope) {
			after(scope, operationName)
		}
	}

	cb := db.Callback()
	cb.Create().Before("gorm:before_create").Register("dd-trace-go:before_create", before)
	cb.Create().After("gorm:after_create").Register("dd-trace-go:after_create", afterFunc("gorm.create"))
	cb.Update().Before("gorm:before_update").Register("dd-trace-go:before_update", before)
	cb.Update().After("gorm:after_update").Register("dd-trace-go:after_update", afterFunc("gorm.update"))
	cb.Delete().Before("gorm:before_delete").Register("dd-trace-go:before_delete", before)
	cb.Delete().After("gorm:after_delete").Register("dd-trace-go:after_delete", afterFunc("gorm.delete"))
	cb.Query().Before("gorm:query").Register("dd-trace-go:before_query", before)
	cb.Query().After("gorm:after_query").Register("dd-trace-go:after_query", afterFunc("gorm.query"))
	cb.RowQuery().Before("gorm:row_query").Register("dd-trace-go:before_row_query", before)
	cb.RowQuery().After("gorm:row_query").Register("dd-trace-go:after_row_query", afterFunc("gorm.row_query"))

	cfg := new(config)
	defaults(cfg)
	for _, fn := range opts {
		fn(cfg)
	}
	return db.Set(gormConfigKey, cfg)
}
```

結論、これさえ実行させれば、`sqltrace.Register`も、`gormtrace.Open`も必要ありませんでした。
最終的なgorm周りの実装は下記の通りです。

```go
func connectDB() *gorm.DB {
	connection := os.Getenv("MYSQL_CONNECTION")

	db, err := gorm.Open("mysql", connection)
	if err != nil {
		panic(err.Error())
	}

	// Add callbacks for Datadog.
	db = gormtrace.WithCallbacks(db)
	return db
}
```

# まとめ
当たり前のことですが、公式のドキュメントだけでなく、ライブラリの実装も見て、自分のアプリケーションに適切な導入方法を見つけ出すことが大切ですね。