AWS Distro for OpenTelemetryのCollectorとGo SDK on Traces Instrumentation

## AWS Distro for OpenTelemetry
OpenTelemetryは、オープンソースのプロジェクトで、
アプリケーション監視用の分散トレースとメトリックを収集するために、
ライブラリ・API・エージェントが提供されています。
AWS Distro for OpenTelemetryはOpenTelemetryをAWSで使うためのディストリビューションです。

OpenTelemetry自体については、[freeCodeCamp.orgの動画](https://www.youtube.com/watch?v=r8UvWSX3KA8)が参考になるかと思います。

## AWS Distro for OpenTelemetry Collector
OpenTelemetry Collectorについては[こちら](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/design.md)が参考になります。

こちらのdocker版サンプルを実施します。

https://github.com/aws-observability/aws-otel-collector/blob/main/docs/developers/docker-demo.md

以下のソースコードをcheckoutし、exampleフォルダへ移動します。
```
% git clone https://github.com/aws-observability/aws-otel-collector.git
% cd aws-otel-collector/examples/docker/
```
以下コマンドで実行します。
```
% docker-compose up
```
すると、X-Rayでトレースが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2329077/b01766cd-fce8-cce6-b104-b05cccf07ee3.png)


## OpenTelemetry Go SDK on Traces Instrumentation
以下を参考に実施します。

https://aws-otel.github.io/docs/getting-started/go-sdk/trace-manual-instr

### Global Tracerの設定
以下のサンプルアプリを使います。

https://github.com/aws-observability/aws-otel-go/tree/main/sampleapp

#### OpenTelemetry Protocol (OTLP) Exporterの作成
OpenTelemetry Goではバックエンドにトレースを送信する場合には、Exporterが必要になるようです。
ここではADOT Collectorへ送信するため、以下のようにExporterを作成します。
```go:sampleapp/main.go
// Create and start new OTLP trace exporter
traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint("0.0.0.0:4317"), otlptracegrpc.WithDialOption(grpc.WithBlock()))
if err != nil {
     log.Fatalf("%s: %v", "failed to create new OTLP trace exporter", err)
}
```

#### Tracer Providerの作成
Traceの生成用に、Tracer Providerを作成します。
上記で作成したExporterと、ID generatorを設定します。

```go:sampleapp/main.go
idg := xray.NewIDGenerator()

tp := trace.NewTracerProvider(
    trace.WithSampler(trace.AlwaysSample()),
    trace.WithBatcher(traceExporter)
    trace.WithIDGenerator(idg),
)
```

#### アプリケーションの実装
エンドポイントが２つ用意されています。
`/aws-sdk-call`と
```go:sampleapp/main.go
	r.HandleFunc("/aws-sdk-call", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		w.Header().Set("Content-Type", "application/json")
		ctx := r.Context()

		xrayTraceID := getXrayTraceID(trace.SpanFromContext(ctx))
		json := simplejson.New()
		json.Set("traceId", xrayTraceID)
		payload, _ := json.MarshalJSON()

		_, _ = w.Write(payload)

	}))
```

`/outgoing-http-call`
こちらはhttpリクエストのタイミングと、その後のカスタム計装の２箇所でspanが作成されています。
```go:sampleapp/main.go
	r.HandleFunc("/outgoing-http-call", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

		w.Header().Set("Content-Type", "application/json")

		client := http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
		ctx := r.Context()

		xrayTraceID, _ := func(ctx context.Context) (string, error) {

			req, _ := http.NewRequestWithContext(ctx, "GET", "https://aws.amazon.com", nil)

			res, err := client.Do(req)
			if err != nil {
				handleErr(err, "HTTP call to aws.amazon.com failed")
			}

			_, _ = ioutil.ReadAll(res.Body)
			_ = res.Body.Close()

			return getXrayTraceID(trace.SpanFromContext(ctx)), err

		}(ctx)

		ctx, span := tracer.Start(
			ctx,
			"CollectorExporter-Example",
			trace.WithAttributes(commonLabels...))
		defer span.End()

		json := simplejson.New()
		json.Set("traceId", xrayTraceID)
		payload, _ := json.MarshalJSON()

		_, _ = w.Write(payload)

	}))
```
以下のコマンドで実行します。
```
% go build -o sampleapp
% ./sampleapp
```

### Aws-OTel-Collectorインスタンスの実行
先ほどのdocker版サンプルの[シングルインスタンス版](https://github.com/aws-observability/aws-otel-collector/blob/main/docs/developers/docker-demo.md#run-a-single-aws-otel-collector-instance-in-docker)を使用します。
`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`を環境変数に設定した上で、以下を実行します。
```
% cd aws-otel-collector/examples/docker/
% docker run --rm -p 4317:4317 -p 55680:55680 -p 8889:8888 \
  -e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" \
  -e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" \
  -e AWS_REGION=us-west-2 \
  -v "${PWD}/examples/docker/config-test.yaml":/otel-local-config.yaml \
  --name awscollector public.ecr.aws/aws-observability/aws-otel-collector:latest \
  --config otel-local-config.yaml;
```

この状態で、`localhost:8080/aws-sdk-call`にアクセスすると、X-Rayで以下トレースが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2329077/a37e2f0f-9970-06cd-084f-1fcc4f488a41.png)

また、`localhost:8080/outgoing-http-call`にアクセスすると同じく以下が確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2329077/05ca6115-a9fe-c832-e89b-5d96529d559c.png)
`CollectorExporter-Example`の方のメタデータは以下になっていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2329077/5e4fb70f-2d2d-e4b0-ee08-44f56406bda7.png)