Skip to content

Commit

Permalink
contrib/google.golang.org/grpc: Fallback to dynamic service names if …
Browse files Browse the repository at this point in the history
…no global is found (#2051)

Co-authored-by: Peter Kalmakis <peter.kalmakis@gmail.com>
Co-authored-by: Katie Hockman <katie@hockman.dev>
Co-authored-by: Diana Shevchenko <40775148+dianashevchenko@users.noreply.github.com>
  • Loading branch information
4 people committed Jun 20, 2023
1 parent 61c3018 commit 254acfa
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 6 deletions.
4 changes: 2 additions & 2 deletions contrib/google.golang.org/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ func (cfg *config) startSpanOptions(opts ...tracer.StartSpanOption) []tracer.Sta
}

func startSpanFromContext(
ctx context.Context, method, operation, service string, opts ...tracer.StartSpanOption,
ctx context.Context, method, operation string, serviceFn func() string, opts ...tracer.StartSpanOption,
) (ddtrace.Span, context.Context) {
methodElements := strings.SplitN(strings.TrimPrefix(method, "/"), "/", 2)
opts = append(opts,
tracer.ServiceName(service),
tracer.ServiceName(serviceFn()),
tracer.ResourceName(method),
tracer.Tag(tagMethodName, method),
spanTypeRPC,
Expand Down
86 changes: 86 additions & 0 deletions contrib/google.golang.org/grpc/grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
package grpc

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strings"
"sync/atomic"
"testing"
Expand All @@ -23,9 +27,11 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinylib/msgp/msgp"
context "golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
Expand Down Expand Up @@ -1203,3 +1209,83 @@ func BenchmarkUnaryServerInterceptor(b *testing.B) {
}
})
}

type roundTripper struct {
assertSpanFromRequest func(r *http.Request)
}

func (rt *roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
rt.assertSpanFromRequest(r)
return http.DefaultTransport.RoundTrip(r)
}

func TestIssue2050(t *testing.T) {
// https://github.com/DataDog/dd-trace-go/issues/2050
t.Setenv("DD_SERVICE", "some-dd-service")

spansFound := make(chan bool, 1)

httpClient := &http.Client{
Transport: &roundTripper{
assertSpanFromRequest: func(r *http.Request) {
if r.URL.Path != "/v0.4/traces" {
return
}
req := r.Clone(context.Background())
defer req.Body.Close()

buf, err := io.ReadAll(req.Body)
require.NoError(t, err)

var payload bytes.Buffer
_, err = msgp.UnmarshalAsJSON(&payload, buf)
require.NoError(t, err)

var trace [][]map[string]interface{}
err = json.Unmarshal(payload.Bytes(), &trace)
require.NoError(t, err)

if len(trace) == 0 {
return
}
require.Len(t, trace, 2)
s0 := trace[0][0]
s1 := trace[1][0]

assert.Equal(t, "server", s0["meta"].(map[string]interface{})["span.kind"])
assert.Equal(t, "some-dd-service", s0["service"])

assert.Equal(t, "client", s1["meta"].(map[string]interface{})["span.kind"])
assert.Equal(t, "grpc.client", s1["service"])
close(spansFound)
},
},
}
serverInterceptors := []grpc.ServerOption{
grpc.UnaryInterceptor(UnaryServerInterceptor()),
grpc.StreamInterceptor(StreamServerInterceptor()),
}
clientInterceptors := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(UnaryClientInterceptor()),
grpc.WithStreamInterceptor(StreamClientInterceptor()),
}
rig, err := newRigWithInterceptors(serverInterceptors, clientInterceptors)
require.NoError(t, err)
defer rig.Close()

// call tracer.Start after integration is initialized, to reproduce the issue
tracer.Start(tracer.WithHTTPClient(httpClient))
defer tracer.Stop()

_, err = rig.client.Ping(context.Background(), &FixtureRequest{Name: "pass"})
require.NoError(t, err)

select {
case <-spansFound:
return

case <-time.After(5 * time.Second):
assert.Fail(t, "spans not found")
}
}
21 changes: 17 additions & 4 deletions contrib/google.golang.org/grpc/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema"

"google.golang.org/grpc/codes"
Expand All @@ -24,7 +26,7 @@ const (
type Option func(*config)

type config struct {
serviceName string
serviceName func() string
spanName string
nonErrorCodes map[codes.Code]bool
traceStreamCalls bool
Expand Down Expand Up @@ -60,24 +62,35 @@ func defaults(cfg *config) {
}

func clientDefaults(cfg *config) {
cfg.serviceName = namingschema.NewDefaultServiceName(
sn := namingschema.NewDefaultServiceName(
defaultClientServiceName,
namingschema.WithOverrideV0(defaultClientServiceName),
).GetName()
cfg.serviceName = func() string { return sn }
cfg.spanName = namingschema.NewGRPCClientOp().GetName()
defaults(cfg)
}

func serverDefaults(cfg *config) {
cfg.serviceName = namingschema.NewDefaultServiceName(defaultServerServiceName).GetName()
// We check for a configured service name, so we don't break users who are incorrectly creating their server
// before the call `tracer.Start()`
if globalconfig.ServiceName() != "" {
sn := namingschema.NewDefaultServiceName(defaultServerServiceName).GetName()
cfg.serviceName = func() string { return sn }
} else {
log.Warn("No global service name was detected. GRPC Server may have been created before calling tracer.Start(). Will dynamically fetch service name for every span. " +
"Note this may have a slight performance cost, it is always recommended to start the tracer before initializing any traced packages.\n")
ns := namingschema.NewDefaultServiceName(defaultServerServiceName)
cfg.serviceName = ns.GetName
}
cfg.spanName = namingschema.NewGRPCServerOp().GetName()
defaults(cfg)
}

// WithServiceName sets the given service name for the intercepted client.
func WithServiceName(name string) Option {
return func(cfg *config) {
cfg.serviceName = name
cfg.serviceName = func() string { return name }
}
}

Expand Down

0 comments on commit 254acfa

Please sign in to comment.