Orchestrion検証

未解決問題

コンパイル結果は-workフラグで制御できそう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)
}

コールグラフを辿るのでnet/httpのルートスパンが生成されてしまうのは仕方がない
UST指定したときにErrorを伝搬できる方法があると嬉しいが、、、

前提
-
dd tagの仕込み方
Agent等に設定したものがUST tagsとして自動で使われる
-
custom span
//dd:span
directiveで制御 -
error
アノテーションされてる関数には自動でエラーの場合はspanに記録される
-
自動計装をしない
//orchestrion:ignore
directiveで制御 -
orchestrionがサポートしていないライブラリのトレーシングライブラリと併用可能(なお、対応後に重複するので注意が必要

検証
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 環境変数に含める
今回はDD_APM_IGNORE_RESOURCESに定義
DD_APM_IGNORE_RESOURCES="(GET|POST|PUT|DELETE) /ping"
※ignoreするとingest対象から外れるので、trace metricsにも記録されなくなるので注意

エラー時の挙動
標準
-
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に対して
-
-
nestedFuncに対して
//dd:span
あり- 子spanにはerro messageが記録される
- 子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
でサービスオーバライドを削除
→できない
↑見る限りmiddlawareは自動的に計装しないようになっているから重複する

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を書き換える方法がなさそう
通ってない?
コンパイル時にservice名が決定されるらしいのでchi.routerに合わせてみたが、別spanとして認識されるので伝播されるわけではない

管理について
コンパイル後の計装結果の管理方法について(差分確認)
→-wオプション今なさそう

DD_TRACE_GENERATE_ROOT_SPANというoptionがphpにはありそうだが、goにはない