Zenn
Open11

Orchestrion検証

ピン留めされたアイテム
sugar-catsugar-cat
sugar-catsugar-cat

rootのspanがnet/httpのコンポーネントでwrapされているので仕方がない感

ピン留めされたアイテム
sugar-catsugar-cat

コンパイル結果は-workフラグで制御できそう
https://docs.datadoghq.com/ja/tracing/troubleshooting/go_compile_time/#preserving-the-work-tree
orchestrion go build -work ./...
一時ファイルとして/varに格納される

original

package test

import (
	"net/http"

	"github.com/go-chi/chi/v5"
)

func main() {
	r := chi.NewRouter()

	r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, Chi!"))
	})

	http.ListenAndServe(":8080", r)
}
  • orchestrion
//line /Users/xxx/main.go:1:1
package test

import (
        "net/http"

        "github.com/go-chi/chi/v5"
//line <generated>:1
        __orchestrion_chitrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-chi/chi.v5"
)

//line /Users/xxx/main.go:9
func main() {
        r :=
//line <generated>:1
                func() *chi.Mux {
                        mux :=
//line /Users/xxx/main.go:10
                                chi.NewRouter()
//line <generated>:1
                        mux.Use(__orchestrion_chitrace.Middleware())
                        return mux
                }()

//line /Users/xxx/main.go:12
        r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("Hello, Chi!"))
        })

        http.ListenAndServe(":8080", r)
}
sugar-catsugar-cat

https://github.com/DataDog/orchestrion

https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/dd_libraries/go/?tab=compiletimeinstrumentation#prevent-instrumentation-of-some-code

前提

sugar-catsugar-cat

検証

Orchestrionで複数サービスを立ててspanの状態を確認する

services:
  service-b:
    build: ./serviceB
    container_name: service-b
    ports:
      - "8081:8081"
    networks:
      - app_network
    environment:
      - DD_AGENT_HOST=datadog-agent
      - DD_TRACE_AGENT_PORT=8126
    depends_on:
      - datadog-agent

  service-b2:
    build: ./serviceB2
    container_name: service-b2
    ports:
      - "8082:8082"
    networks:
      - app_network
    environment:
      - DD_AGENT_HOST=datadog-agent
      - DD_TRACE_AGENT_PORT=8126
      - DD_SERVICE_NAME=service-b2
    depends_on:
      - datadog-agent

  datadog-agent:
    image: gcr.io/datadoghq/agent:latest
    container_name: datadog-agent
    environment:
      - DD_API_KEY=xxx
      - DD_SITE=ap1.datadoghq.com
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /proc:/host/proc:ro
      - /sys/fs/cgroup:/host/sys/fs/cgroup:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    networks:
      - app_network


networks:
  app_network:
    driver: bridge
  • 検証用コードA

    package main
    
    import (
    	"io"
    	"log"
    	"net/http"
    	"time"
    
    	"github.com/go-chi/chi/v5"
    )
    
    // orchestrionベース
    const (
    	serviceName = "serviceB2"
    	addr        = ":8082"
    	serviceBURL = "http://service-b:8081/ping"
    )
    
    func req(serviceURL string) http.HandlerFunc {
    	return func(w http.ResponseWriter, r *http.Request) {
    		nestedFunc()
    		client := &http.Client{Timeout: 5 * time.Second}
    
    		req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, serviceURL, nil)
    		if err != nil {
    			http.Error(w, "Failed to create request", http.StatusInternalServerError)
    			return
    		}
    
    		resp, err := client.Do(req)
    		if err != nil {
    			http.Error(w, "Failed to reach service", http.StatusBadGateway)
    			return
    		}
    		defer resp.Body.Close()
    
    		body, err := io.ReadAll(resp.Body)
    		if err != nil {
    			http.Error(w, "Failed to read response", http.StatusInternalServerError)
    			return
    		}
    
    		w.Write(body)
    	}
    }
    
    func main() {
    	r := chi.NewRouter()
    	r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
    		w.Write([]byte("pong"))
    	})
    	r.Get("/ping-b", req(serviceBURL))
    
    	log.Printf("Starting serviceB on port %v", addr)
    	err := http.ListenAndServe(addr, r)
    	if err != nil {
    		log.Fatalf("Server failed: %v", err)
    	}
    }
    
    func nestedFunc() {
    	log.Println("nestedFunc")
    	//なんか重い処理
    	time.Sleep(1 * time.Second)
    }
    
    
  • 検証用コードB

    package main
    
    import (
    	"log"
    	"net/http"
    	"time"
    
    	"github.com/go-chi/chi/v5"
    )
    
    // orchestrionベース
    const (
    	serviceName = "serviceB"
    	addr        = ":8081"
    )
    
    func main() {
    	r := chi.NewRouter()
    	r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
    		nestedFunc()
    		w.Write([]byte("pong"))
    	})
    
    	log.Printf("Starting serviceB on port %v", addr)
    	err := http.ListenAndServe(addr, r)
    	if err != nil {
    		log.Fatalf("Server failed: %v", err)
    	}
    }
    
    func nestedFunc() {
    	log.Println("nestedFunc")
    	//なんか重い処理
    	time.Sleep(1 * time.Second)
    }
    
    

Spanの見え方

nestされたfunction

  • nestedFuncに対して//dd:span なし

nestedFuncのfuncは

  • nestedFuncに対して//dd:span directiveあり

spanが記録される

Trace ContextのPropagationも適切に行われており別サービスでもspanが繋がっている

filteringについて(health check等はignore)

コンパイル時計装の場合、routerライブラリにignore対象を組み込むのは難しいので、datadog.yaml or 環境変数に含める

https://docs.datadoghq.com/tracing/guide/ignoring_apm_resources/?tab=datadogyaml

今回はDD_APM_IGNORE_RESOURCESに定義

DD_APM_IGNORE_RESOURCES="(GET|POST|PUT|DELETE) /ping"

※ignoreするとingest対象から外れるので、trace metricsにも記録されなくなるので注意

https://docs.datadoghq.com/tracing/trace_pipeline/ingestion_controls/

sugar-catsugar-cat

エラー時の挙動

標準

  • ServiceB側のroot spanでerror

    • code

      	r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
      		err := errors.New("error")
      		if err != nil {
      			http.Error(w, "Failed to create request", http.StatusInternalServerError)
      			return
      		}
      	})
      
    • ServiceBの子span(nestedFunc)でerror

      • nestedFuncに対して//dd:span なし
        • 何も記録されない
  • nestedFuncに対して//dd:span あり

    • 子spanにはerro messageが記録される

デフォルトの挙動だと親spanにはエラーは紐づいていない

router以上のspanに紐づけるためには明示的なtag付が必要

	r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
		err := errorFunc()
		if err != nil {
			span, _ := tracer.SpanFromContext(r.Context())
			span.SetTag("error.msg", err.Error())
			http.Error(w, "Failed to create request", http.StatusInternalServerError)
			return
		}
		// w.Write([]byte("pong"))
	})

baseserviceとrouterのサービスが統合できない?(手動でrouterをつけても重複して計装されている)

r.Use(chitrace.Middleware(chitrace.WithServiceName("service-b")))

DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED=true でサービスオーバライドを削除

→できない

https://github.com/DataDog/dd-trace-go/blob/b143b60384364f6411395d99029ee7679473b2f4/contrib/go-chi/chi.v5/orchestrion.yml

↑見る限りmiddlawareは自動的に計装しないようになっているから重複する

sugar-catsugar-cat

panic

通常のままだと何も拾ってくれないのでミドルウェアでrecover

func Recoverer(next http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rvr := recover(); rvr != nil {
				if rvr == http.ErrAbortHandler {
					// we don't recover http.ErrAbortHandler so the response
					// to the client is aborted, this should not be logged
					panic(rvr)
				}
				span, _ := tracer.SpanFromContext(r.Context())
				span.SetTag("error.msg", rvr)
				if r.Header.Get("Connection") != "Upgrade" {
					w.WriteHeader(http.StatusInternalServerError)
				}
			}
		}()

		next.ServeHTTP(w, r)
	}

	return http.HandlerFunc(fn)
}

routerまでは伝播できる


service overrideしているところをどうにか一つにまとめたい。
chi.routerのservice nameを書き換える方法がなさそう
通ってない?
https://github.com/DataDog/dd-trace-go/blob/b143b60384364f6411395d99029ee7679473b2f4/ddtrace/tracer/option.go#L477

コンパイル時にservice名が決定されるらしいのでchi.routerに合わせてみたが、別spanとして認識されるので伝播されるわけではない

ログインするとコメントできます