在 Golang 服務中整合 tracing
的那件小事

David Chou @ Golang Taipei / Crescendo Lab

CC-BY-SA-3.0-TW

@ Crescendo Lab

@ Golang Taipei Co-organizer 🙋‍♂️


Software engineer, DevOps, and Gopher 👨‍💻

david74.chou @ gmail
david74.chou @ facebook
david74chou @ telegram

想在 服務之間 整合好 tracing 該怎麼做?

以 Python 為例

$ ddtrace-run python app.py

以 datadog 為例

以 Golang / Gin 為例

r := gin.New()
r.Use(gintrace.Middleware("web-app"))

以 Golang / HTTP Request 為例

if s, ok := tracer.SpanFromContext(ctx); ok {
	_ = tracer.Inject(s.Context(), 
    		tracer.HTTPHeadersCarrier(req.Header),
        )
}

想在 服務內部 整合好 tracing 該怎麼做?

func doSomething() {
	span := tracer.StartSpan("do.something")
	defer span.Finish()

	// do something cool
    
}

一個一個加,很累

或者只加在 component 的邊界?

type SearchRepository interface {
    SearchMessages(ctx context.Context, ...) (*chat.Result, error)
    SearchMembersByName(ctx context.Context, ...) (*chat.Result, error)
    
    ... more ...
}

func NewSearchService(r SearchRepository) SearchService {
    return SearchService {
        searchRepo: r,
    }
}

今晚我想來點
Decorator Pattern

type SearchRepository interface {
    SearchMessages(ctx context.Context, ...) (*chat.Result, error)
    SearchMembersByName(ctx context.Context, ...) (*chat.Result, error)
    
    ... more ...
}

func NewSearchService(r SearchRepository) SearchService {
    return SearchService {
        searchRepo: autotrace.NewSearchRepositoryTracer(r),
    }
}

手寫 Decorator 也很累,能不能 Codegen?

gowrap

 

GoWrap is a command line tool for generating decorators for Go interfaces

//go:generate gowrap gen -g -p . -i SearchRepository 
//  -t file://template/ddog-trace.tmpl -o ./autotrace/search_repository.go
type SearchRepository interface {
    SearchMessages(ctx context.Context, ...) (*chat.Result, error)
    SearchMembersByName(ctx context.Context, ...) (*chat.Result, error)
    
    ... more ...
}

只要多一行!

// Code generated by gowrap. DO NOT EDIT.
// template: ../../../../../template/ddog-trace.tmpl

package autotrace

type SearchRepositoryTracer struct {
	SearchRepository
}

func NewSearchRepositoryTracer(base SearchRepository) SearchRepositoryTracer {
	return SearchRepositoryTracer{
		SearchRepository: base,
	}
}

// SearchMessages implements chat.SearchRepository
func (d SearchRepositoryTracer) SearchMessages(ctx context.Context, ...) 
	(rp1 *chat.ResultMessageSearch, e1 error) {
	
	span, ctx := tracer.StartSpanFromContext(ctx, "SearchRepository.SearchMessages")
	defer func() {
		span.Finish(d.withError(map[string]interface{}{
			"rp1": rp1,
			"e1":  e1}))
	}()
	return d.SearchRepository.SearchMessages(ctx, channelID, searchText)
}

... more ...

看看 template 長怎樣...

type {{$decorator}} struct {
  {{.Interface.Name}}
}

func New{{$decorator}}(base {{.Interface.Name}}) {{$decorator}} {
  return {{$decorator}}{
    {{.Interface.Name}}: base,
  }
}

{{range $method := .Interface.Methods}}
  {{if $method.AcceptsContext}}
    
    // {{$method.Name}} implements {{$.Interface.Type}}
    func (d {{$decorator}}) {{$method.Declaration}} {
      span, ctx := tracer.StartSpanFromContext(ctx, "{{$.Interface.Name}}.{{$method.Name}}")
      defer func() {
        span.Finish(d.withError({{$method.ResultsMap}}))
      }()
      
      {{$method.Pass (printf "d.%s." $.Interface.Name) }}
    }
    
  {{end}}
{{end}}

gowrap 還有一堆好用的 template:

 

timeout, retry, ratelimit, opentracing, etc

希望沒有講太久QQ
Question?

在 Golang 服務中整合 tracing 的那件小事

By Ting-Li Chou

在 Golang 服務中整合 tracing 的那件小事

  • 136