Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tracing.opentracing endpoint middleware options #1072

Merged
merged 9 commits into from May 7, 2021
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -35,7 +35,7 @@ require (
github.com/oklog/run v1.0.0 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
github.com/opentracing/basictracer-go v1.0.0 // indirect
github.com/opentracing/opentracing-go v1.1.0
github.com/opentracing/opentracing-go v1.2.0
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5
github.com/openzipkin/zipkin-go v0.2.2
github.com/pact-foundation/pact-go v1.0.4
Expand Down
5 changes: 2 additions & 3 deletions go.sum
Expand Up @@ -106,7 +106,6 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
Expand Down Expand Up @@ -254,8 +253,9 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go
github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
Expand Down Expand Up @@ -380,7 +380,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand Down
127 changes: 98 additions & 29 deletions tracing/opentracing/endpoint.go
Expand Up @@ -2,54 +2,123 @@ package opentracing

import (
"context"
"strconv"

"github.com/opentracing/opentracing-go"
otext "github.com/opentracing/opentracing-go/ext"
otlog "github.com/opentracing/opentracing-go/log"

"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/sd/lb"
)

// TraceServer returns a Middleware that wraps the `next` Endpoint in an
// TraceEndpoint returns a Middleware that wraps the `next` Endpoint in an
// OpenTracing Span called `operationName`.
//
// If `ctx` already has a Span, it is re-used and the operation name is
// overwritten. If `ctx` does not yet have a Span, one is created here.
func TraceServer(tracer opentracing.Tracer, operationName string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
serverSpan := opentracing.SpanFromContext(ctx)
if serverSpan == nil {
// All we can do is create a new root span.
serverSpan = tracer.StartSpan(operationName)
} else {
serverSpan.SetOperationName(operationName)
}
defer serverSpan.Finish()
otext.SpanKindRPCServer.Set(serverSpan)
ctx = opentracing.ContextWithSpan(ctx, serverSpan)
return next(ctx, request)
}
// If `ctx` already has a Span, child span is created from it.
// If `ctx` doesn't yet have a Span, the new one is created.
func TraceEndpoint(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware {
cfg := &EndpointOptions{
Tags: make(opentracing.Tags),
}

for _, opt := range opts {
opt(cfg)
}
}

// TraceClient returns a Middleware that wraps the `next` Endpoint in an
// OpenTracing Span called `operationName`.
func TraceClient(tracer opentracing.Tracer, operationName string) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
var clientSpan opentracing.Span
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
if cfg.GetOperationName != nil {
if newOperationName := cfg.GetOperationName(ctx, operationName); newOperationName != "" {
operationName = newOperationName
}
}

var span opentracing.Span
if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
clientSpan = tracer.StartSpan(
span = tracer.StartSpan(
operationName,
opentracing.ChildOf(parentSpan.Context()),
)
} else {
clientSpan = tracer.StartSpan(operationName)
span = tracer.StartSpan(operationName)
}
defer span.Finish()

applyTags(span, cfg.Tags)
if cfg.GetTags != nil {
extraTags := cfg.GetTags(ctx)
applyTags(span, extraTags)
}
defer clientSpan.Finish()
otext.SpanKindRPCClient.Set(clientSpan)
ctx = opentracing.ContextWithSpan(ctx, clientSpan)

ctx = opentracing.ContextWithSpan(ctx, span)

defer func() {
if err != nil {
if lbErr, ok := err.(lb.RetryError); ok {
// handle errors originating from lb.Retry
fields := make([]otlog.Field, 0, len(lbErr.RawErrors))
for idx, rawErr := range lbErr.RawErrors {
fields = append(fields, otlog.String(
"gokit.retry.error."+strconv.Itoa(idx+1), rawErr.Error(),
))
}

otext.LogError(span, lbErr, fields...)

return
}

// generic error
otext.LogError(span, err)

return
}

// test for business error
if res, ok := response.(endpoint.Failer); ok && res.Failed() != nil {
span.LogFields(
otlog.String("gokit.business.error", res.Failed().Error()),
)

if cfg.IgnoreBusinessError {
return
}

// treating business error as real error in span.
otext.LogError(span, res.Failed())

return
}
}()

return next(ctx, request)
}
}
}

// TraceServer returns a Middleware that wraps the `next` Endpoint in an
// OpenTracing Span called `operationName` with server span.kind tag..
func TraceServer(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware {
opts = append(opts, WithTags(map[string]interface{}{
otext.SpanKindRPCServer.Key: otext.SpanKindRPCServer.Value,
}))

return TraceEndpoint(tracer, operationName, opts...)
}

// TraceClient returns a Middleware that wraps the `next` Endpoint in an
// OpenTracing Span called `operationName` with client span.kind tag.
func TraceClient(tracer opentracing.Tracer, operationName string, opts ...EndpointOption) endpoint.Middleware {
opts = append(opts, WithTags(map[string]interface{}{
otext.SpanKindRPCClient.Key: otext.SpanKindRPCClient.Value,
}))

return TraceEndpoint(tracer, operationName, opts...)
}

func applyTags(span opentracing.Span, tags opentracing.Tags) {
for key, value := range tags {
span.SetTag(key, value)
}
}
74 changes: 74 additions & 0 deletions tracing/opentracing/endpoint_options.go
@@ -0,0 +1,74 @@
package opentracing

import (
"context"

"github.com/opentracing/opentracing-go"
)

// EndpointOptions holds the options for tracing an endpoint
type EndpointOptions struct {
// IgnoreBusinessError if set to true will not treat a business error
// identified through the endpoint.Failer interface as a span error.
IgnoreBusinessError bool

// GetOperationName is an optional function that can set the span operation name based on the existing one
// for the endpoint and information in the context.
//
// If the function is nil, or the returned name is empty, the existing name for the endpoint is used.
GetOperationName func(ctx context.Context, name string) string

// Tags holds the default tags which will be set on span
// creation by our Endpoint middleware.
Tags opentracing.Tags

// GetTags is an optional function that can extract tags
// from the context and add them to the span.
GetTags func(ctx context.Context) opentracing.Tags
}

// EndpointOption allows for functional options to endpoint tracing middleware.
type EndpointOption func(*EndpointOptions)

// WithOptions sets all configuration options at once by use of the EndpointOptions struct.
func WithOptions(options EndpointOptions) EndpointOption {
return func(o *EndpointOptions) {
*o = options
}
}

// WithIgnoreBusinessError if set to true will not treat a business error
// identified through the endpoint.Failer interface as a span error.
func WithIgnoreBusinessError(ignoreBusinessError bool) EndpointOption {
return func(o *EndpointOptions) {
o.IgnoreBusinessError = ignoreBusinessError
}
}

// WithOperationNameFunc allows to set function that can set the span operation name based on the existing one
// for the endpoint and information in the context.
func WithOperationNameFunc(getOperationName func(ctx context.Context, name string) string) EndpointOption {
return func(o *EndpointOptions) {
o.GetOperationName = getOperationName
}
}

// WithTags adds default tags for the spans created by the Endpoint tracer.
func WithTags(tags opentracing.Tags) EndpointOption {
return func(o *EndpointOptions) {
if o.Tags == nil {
o.Tags = make(opentracing.Tags)
}

for key, value := range tags {
o.Tags[key] = value
}
}
}

// WithTagsFunc set the func to extracts additional tags from the context.
func WithTagsFunc(getTags func(ctx context.Context) opentracing.Tags) EndpointOption {
return func(o *EndpointOptions) {
o.GetTags = getTags
}
}