1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-02-09 13:37:12 +02:00

Decouple instrumentation from SDK (#983)

* Remove otel/sdk dependency from grpctrace

Use otel/trace/testtrace instead and cleanup testing code.

* Update httptrace to not depend on the SDK

Update testing to use api/trace/testtrace instead.

* Add changes to Changelog

* Restore check for `http.local` attr on `http.getconn`
This commit is contained in:
Tyler Yahn 2020-07-28 19:59:04 -07:00 committed by GitHub
parent 42c2a86ea4
commit d6bf2fbfc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 142 additions and 306 deletions

View File

@ -45,6 +45,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Use `global.Handle` for span export errors in the OTLP exporter. (#946) - Use `global.Handle` for span export errors in the OTLP exporter. (#946)
- Correct Go language formatting in the README documentation. (#961) - Correct Go language formatting in the README documentation. (#961)
- Remove default SDK dependencies from the `go.opentelemetry.io/otel/api` package. (#977) - Remove default SDK dependencies from the `go.opentelemetry.io/otel/api` package. (#977)
- Remove default SDK dependencies from the `go.opentelemetry.io/otel/instrumentation` package. (#983)
- Move documented examples for `go.opentelemetry.io/otel/instrumentation/grpctrace` interceptors into Go example tests. (#984) - Move documented examples for `go.opentelemetry.io/otel/instrumentation/grpctrace` interceptors into Go example tests. (#984)
## [0.9.0] - 2020-07-20 ## [0.9.0] - 2020-07-20

View File

@ -20,6 +20,7 @@ import (
"time" "time"
"go.opentelemetry.io/otel/api/standard" "go.opentelemetry.io/otel/api/standard"
"go.opentelemetry.io/otel/api/trace/testtrace"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -32,19 +33,30 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"go.opentelemetry.io/otel/api/kv" "go.opentelemetry.io/otel/api/kv"
export "go.opentelemetry.io/otel/sdk/export/trace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
type testExporter struct { type SpanRecorder struct {
mu sync.Mutex mu sync.RWMutex
spanMap map[string]*export.SpanData spans map[string]*testtrace.Span
} }
func (t *testExporter) ExportSpan(ctx context.Context, s *export.SpanData) { func NewSpanRecorder() *SpanRecorder {
t.mu.Lock() return &SpanRecorder{spans: make(map[string]*testtrace.Span)}
defer t.mu.Unlock() }
t.spanMap[s.Name] = s
func (sr *SpanRecorder) OnStart(span *testtrace.Span) {}
func (sr *SpanRecorder) OnEnd(span *testtrace.Span) {
sr.mu.Lock()
defer sr.mu.Unlock()
sr.spans[span.Name()] = span
}
func (sr *SpanRecorder) Get(name string) (*testtrace.Span, bool) {
sr.mu.RLock()
defer sr.mu.RUnlock()
s, ok := sr.spans[name]
return s, ok
} }
type mockUICInvoker struct { type mockUICInvoker struct {
@ -69,18 +81,13 @@ func (mm *mockProtoMessage) ProtoMessage() {
} }
func TestUnaryClientInterceptor(t *testing.T) { func TestUnaryClientInterceptor(t *testing.T) {
exp := &testExporter{spanMap: make(map[string]*export.SpanData)}
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp),
sdktrace.WithConfig(sdktrace.Config{
DefaultSampler: sdktrace.AlwaysSample(),
},
))
clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure()) clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure())
if err != nil { if err != nil {
t.Fatalf("failed to create client connection: %v", err) t.Fatalf("failed to create client connection: %v", err)
} }
sr := NewSpanRecorder()
tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr))
tracer := tp.Tracer("grpctrace/client") tracer := tp.Tracer("grpctrace/client")
unaryInterceptor := UnaryClientInterceptor(tracer) unaryInterceptor := UnaryClientInterceptor(tracer)
@ -210,70 +217,26 @@ func TestUnaryClientInterceptor(t *testing.T) {
} }
for _, check := range checks { for _, check := range checks {
err = unaryInterceptor(context.Background(), check.method, req, reply, clientConn, uniInterceptorInvoker.invoker) if !assert.NoError(t, unaryInterceptor(context.Background(), check.method, req, reply, clientConn, uniInterceptorInvoker.invoker)) {
if err != nil {
t.Errorf("failed to run unary interceptor: %v", err)
continue continue
} }
span, ok := sr.Get(check.name)
spanData, ok := exp.spanMap[check.name] if !assert.True(t, ok, "missing span %q", check.name) {
if !ok {
t.Errorf("no span data found for name < %s >", check.name)
continue continue
} }
assert.Equal(t, check.expectedAttr, span.Attributes())
attrs := spanData.Attributes assert.Equal(t, check.eventsAttr, eventAttrMap(span.Events()))
if len(check.expectedAttr) > len(attrs) {
t.Errorf("attributes received are less than expected attributes, received %d, expected %d",
len(attrs), len(check.expectedAttr))
}
for _, attr := range attrs {
expectedAttr, ok := check.expectedAttr[attr.Key]
if ok {
if expectedAttr != attr.Value {
t.Errorf("name: %s invalid %s found. expected %s, actual %s", check.name, string(attr.Key),
expectedAttr.AsString(), attr.Value.AsString())
}
delete(check.expectedAttr, attr.Key)
} else {
t.Errorf("attribute %s not found in expected attributes map", string(attr.Key))
}
}
// Check if any expected attr not seen
if len(check.expectedAttr) > 0 {
for attr := range check.expectedAttr {
t.Errorf("missing attribute %s in span", string(attr))
}
}
events := spanData.MessageEvents
if len(check.eventsAttr) > len(events) {
t.Errorf("events received are less than expected events, received %d, expected %d",
len(events), len(check.eventsAttr))
}
for event := 0; event < len(check.eventsAttr); event++ {
for _, attr := range events[event].Attributes {
expectedAttr, ok := check.eventsAttr[event][attr.Key]
if ok {
if attr.Value != expectedAttr {
t.Errorf("invalid value for attribute %s in events, expected %s actual %s",
string(attr.Key), attr.Value.AsString(), expectedAttr.AsString())
}
delete(check.eventsAttr[event], attr.Key)
} else {
t.Errorf("attribute in event %s not found in expected attributes map", string(attr.Key))
}
}
if len(check.eventsAttr[event]) > 0 {
for attr := range check.eventsAttr[event] {
t.Errorf("missing attribute %s in span event", string(attr))
}
}
}
} }
} }
func eventAttrMap(events []testtrace.Event) []map[kv.Key]kv.Value {
maps := make([]map[kv.Key]kv.Value, len(events))
for i, event := range events {
maps[i] = event.Attributes
}
return maps
}
type mockClientStream struct { type mockClientStream struct {
Desc *grpc.StreamDesc Desc *grpc.StreamDesc
Ctx context.Context Ctx context.Context
@ -287,18 +250,14 @@ func (mockClientStream) Header() (metadata.MD, error) { return nil, nil }
func (mockClientStream) Trailer() metadata.MD { return nil } func (mockClientStream) Trailer() metadata.MD { return nil }
func TestStreamClientInterceptor(t *testing.T) { func TestStreamClientInterceptor(t *testing.T) {
exp := &testExporter{spanMap: make(map[string]*export.SpanData)}
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp),
sdktrace.WithConfig(sdktrace.Config{
DefaultSampler: sdktrace.AlwaysSample(),
},
))
clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure()) clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure())
if err != nil { if err != nil {
t.Fatalf("failed to create client connection: %v", err) t.Fatalf("failed to create client connection: %v", err)
} }
// tracer // tracer
sr := NewSpanRecorder()
tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr))
tracer := tp.Tracer("grpctrace/Server") tracer := tp.Tracer("grpctrace/Server")
streamCI := StreamClientInterceptor(tracer) streamCI := StreamClientInterceptor(tracer)
@ -306,7 +265,8 @@ func TestStreamClientInterceptor(t *testing.T) {
method := "/github.com.serviceName/bar" method := "/github.com.serviceName/bar"
name := "github.com.serviceName/bar" name := "github.com.serviceName/bar"
streamClient, err := streamCI(context.Background(), streamClient, err := streamCI(
context.Background(),
&grpc.StreamDesc{ServerStreams: true}, &grpc.StreamDesc{ServerStreams: true},
clientConn, clientConn,
method, method,
@ -317,16 +277,11 @@ func TestStreamClientInterceptor(t *testing.T) {
opts ...grpc.CallOption) (grpc.ClientStream, error) { opts ...grpc.CallOption) (grpc.ClientStream, error) {
mockClStr = mockClientStream{Desc: desc, Ctx: ctx} mockClStr = mockClientStream{Desc: desc, Ctx: ctx}
return mockClStr, nil return mockClStr, nil
}) },
)
if err != nil { require.NoError(t, err, "initialize grpc stream client")
t.Fatalf("failed to initialize grpc stream client: %v", err) _, ok := sr.Get(name)
} require.False(t, ok, "span should ended while stream is open")
// no span exported while stream is open
if _, ok := exp.spanMap[name]; ok {
t.Fatalf("span shouldn't end while stream is open")
}
req := &mockProtoMessage{} req := &mockProtoMessage{}
reply := &mockProtoMessage{} reply := &mockProtoMessage{}
@ -343,53 +298,36 @@ func TestStreamClientInterceptor(t *testing.T) {
_ = streamClient.RecvMsg(reply) _ = streamClient.RecvMsg(reply)
// added retry because span end is called in separate go routine // added retry because span end is called in separate go routine
var spanData *export.SpanData var span *testtrace.Span
for retry := 0; retry < 5; retry++ { for retry := 0; retry < 5; retry++ {
ok := false span, ok = sr.Get(name)
exp.mu.Lock()
spanData, ok = exp.spanMap[name]
exp.mu.Unlock()
if ok { if ok {
break break
} }
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
} }
if spanData == nil { require.True(t, ok, "missing span %s", name)
t.Fatalf("no span data found for name < %s >", name)
}
attrs := spanData.Attributes expectedAttr := map[kv.Key]kv.Value{
expectedAttr := map[kv.Key]string{ standard.RPCSystemKey: kv.StringValue("grpc"),
standard.RPCSystemKey: "grpc", standard.RPCServiceKey: kv.StringValue("github.com.serviceName"),
standard.RPCServiceKey: "github.com.serviceName", standard.RPCMethodKey: kv.StringValue("bar"),
standard.RPCMethodKey: "bar", standard.NetPeerIPKey: kv.StringValue("fake"),
standard.NetPeerIPKey: "fake", standard.NetPeerPortKey: kv.StringValue("connection"),
standard.NetPeerPortKey: "connection",
} }
assert.Equal(t, expectedAttr, span.Attributes())
for _, attr := range attrs { events := span.Events()
expected, ok := expectedAttr[attr.Key] require.Len(t, events, 20)
if ok {
if expected != attr.Value.AsString() {
t.Errorf("name: %s invalid %s found. expected %s, actual %s", name, string(attr.Key),
expected, attr.Value.AsString())
}
}
}
events := spanData.MessageEvents
if len(events) != 20 {
t.Fatalf("incorrect number of events expected 20 got %d", len(events))
}
for i := 0; i < 20; i += 2 { for i := 0; i < 20; i += 2 {
msgID := i/2 + 1 msgID := i/2 + 1
validate := func(eventName string, attrs []kv.KeyValue) { validate := func(eventName string, attrs map[kv.Key]kv.Value) {
for _, attr := range attrs { for k, v := range attrs {
if attr.Key == standard.RPCMessageTypeKey && attr.Value.AsString() != eventName { if k == standard.RPCMessageTypeKey && v.AsString() != eventName {
t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, attr.Value.AsString()) t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString())
} }
if attr.Key == standard.RPCMessageIDKey && attr.Value != kv.IntValue(msgID) { if k == standard.RPCMessageIDKey && v != kv.IntValue(msgID) {
t.Errorf("invalid id for message event expected %d received %d", msgID, attr.Value.AsInt32()) t.Errorf("invalid id for message event expected %d received %d", msgID, v.AsInt32())
} }
} }
} }
@ -402,37 +340,30 @@ func TestStreamClientInterceptor(t *testing.T) {
} }
func TestServerInterceptorError(t *testing.T) { func TestServerInterceptorError(t *testing.T) {
exp := &testExporter{spanMap: make(map[string]*export.SpanData)} sr := NewSpanRecorder()
tp, err := sdktrace.NewProvider( tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr))
sdktrace.WithSyncer(exp),
sdktrace.WithConfig(sdktrace.Config{
DefaultSampler: sdktrace.AlwaysSample(),
}),
)
require.NoError(t, err)
tracer := tp.Tracer("grpctrace/Server") tracer := tp.Tracer("grpctrace/Server")
usi := UnaryServerInterceptor(tracer) usi := UnaryServerInterceptor(tracer)
deniedErr := status.Error(codes.PermissionDenied, "PERMISSION_DENIED_TEXT") deniedErr := status.Error(codes.PermissionDenied, "PERMISSION_DENIED_TEXT")
handler := func(_ context.Context, _ interface{}) (interface{}, error) { handler := func(_ context.Context, _ interface{}) (interface{}, error) {
return nil, deniedErr return nil, deniedErr
} }
_, err = usi(context.Background(), &mockProtoMessage{}, &grpc.UnaryServerInfo{}, handler) _, err := usi(context.Background(), &mockProtoMessage{}, &grpc.UnaryServerInfo{}, handler)
require.Error(t, err) require.Error(t, err)
assert.Equal(t, err, deniedErr) assert.Equal(t, err, deniedErr)
span, ok := exp.spanMap[""] span, ok := sr.Get("")
if !ok { if !ok {
t.Fatalf("failed to export error span") t.Fatalf("failed to export error span")
} }
assert.Equal(t, span.StatusCode, codes.PermissionDenied) assert.Equal(t, span.StatusCode(), codes.PermissionDenied)
assert.Contains(t, deniedErr.Error(), span.StatusMessage) assert.Contains(t, deniedErr.Error(), span.StatusMessage())
assert.Len(t, span.MessageEvents, 2) assert.Len(t, span.Events(), 2)
assert.Equal(t, []kv.KeyValue{ assert.Equal(t, map[kv.Key]kv.Value{
kv.String("message.type", "SENT"), kv.Key("message.type"): kv.StringValue("SENT"),
kv.Int("message.id", 1), kv.Key("message.id"): kv.IntValue(1),
kv.Int("message.uncompressed_size", 26), kv.Key("message.uncompressed_size"): kv.IntValue(26),
}, span.MessageEvents[1].Attributes) }, span.Events()[1].Attributes)
} }
func TestParseFullMethod(t *testing.T) { func TestParseFullMethod(t *testing.T) {

View File

@ -18,46 +18,26 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
nhtrace "net/http/httptrace" nhtrace "net/http/httptrace"
"sync"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/kv" "go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/trace/testtrace"
"go.opentelemetry.io/otel/instrumentation/httptrace" "go.opentelemetry.io/otel/instrumentation/httptrace"
export "go.opentelemetry.io/otel/sdk/export/trace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
type testExporter struct { type SpanRecorder map[string]*testtrace.Span
mu sync.Mutex
spanMap map[string][]*export.SpanData
}
func (t *testExporter) ExportSpan(ctx context.Context, s *export.SpanData) { func (sr *SpanRecorder) OnStart(span *testtrace.Span) {}
t.mu.Lock() func (sr *SpanRecorder) OnEnd(span *testtrace.Span) { (*sr)[span.Name()] = span }
defer t.mu.Unlock()
var spans []*export.SpanData
var ok bool
if spans, ok = t.spanMap[s.Name]; !ok {
spans = []*export.SpanData{}
t.spanMap[s.Name] = spans
}
spans = append(spans, s)
t.spanMap[s.Name] = spans
}
var _ export.SpanSyncer = (*testExporter)(nil)
func TestHTTPRequestWithClientTrace(t *testing.T) { func TestHTTPRequestWithClientTrace(t *testing.T) {
exp := &testExporter{ sr := SpanRecorder{}
spanMap: make(map[string][]*export.SpanData), tp := testtrace.NewProvider(testtrace.WithSpanRecorder(&sr))
}
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}))
global.SetTraceProvider(tp) global.SetTraceProvider(tp)
tr := tp.Tracer("httptrace/client") tr := tp.Tracer("httptrace/client")
// Mock http server // Mock http server
@ -86,34 +66,23 @@ func TestHTTPRequestWithClientTrace(t *testing.T) {
panic("unexpected error in http request: " + err.Error()) panic("unexpected error in http request: " + err.Error())
} }
getSpan := func(name string) *export.SpanData {
spans, ok := exp.spanMap[name]
if !ok {
t.Fatalf("no spans found with the name %s, %v", name, exp.spanMap)
}
if len(spans) != 1 {
t.Fatalf("Expected exactly one span for %s but found %d", name, len(spans))
}
return spans[0]
}
testLen := []struct { testLen := []struct {
name string name string
attributes []kv.KeyValue attributes map[kv.Key]kv.Value
parent string parent string
}{ }{
{ {
name: "http.connect", name: "http.connect",
attributes: []kv.KeyValue{kv.String("http.remote", address.String())}, attributes: map[kv.Key]kv.Value{
parent: "http.getconn", kv.Key("http.remote"): kv.StringValue(address.String()),
},
parent: "http.getconn",
}, },
{ {
name: "http.getconn", name: "http.getconn",
attributes: []kv.KeyValue{ attributes: map[kv.Key]kv.Value{
kv.String("http.remote", address.String()), kv.Key("http.remote"): kv.StringValue(address.String()),
kv.String("http.host", address.String()), kv.Key("http.host"): kv.StringValue(address.String()),
}, },
parent: "test", parent: "test",
}, },
@ -134,51 +103,42 @@ func TestHTTPRequestWithClientTrace(t *testing.T) {
}, },
} }
for _, tl := range testLen { for _, tl := range testLen {
span := getSpan(tl.name) if !assert.Contains(t, sr, tl.name) {
continue
}
span := sr[tl.name]
if tl.parent != "" { if tl.parent != "" {
parentSpan := getSpan(tl.parent) if assert.Contains(t, sr, tl.parent) {
assert.Equal(t, span.ParentSpanID(), sr[tl.parent].SpanContext().SpanID)
if span.ParentSpanID != parentSpan.SpanContext.SpanID {
t.Fatalf("[span %s] does not have expected parent span %s", tl.name, tl.parent)
} }
} }
if len(tl.attributes) > 0 {
actualAttrs := make(map[kv.Key]string) attrs := span.Attributes()
for _, attr := range span.Attributes { if tl.name == "http.getconn" {
actualAttrs[attr.Key] = attr.Value.Emit() // http.local attribute uses a non-deterministic port.
} local := kv.Key("http.local")
assert.Contains(t, attrs, local)
expectedAttrs := make(map[kv.Key]string) delete(attrs, local)
for _, attr := range tl.attributes {
expectedAttrs[attr.Key] = attr.Value.Emit()
}
if tl.name == "http.getconn" {
local := kv.Key("http.local")
// http.local attribute is not deterministic, just make sure it exists for `getconn`.
if _, ok := actualAttrs[local]; ok {
delete(actualAttrs, local)
} else {
t.Fatalf("[span %s] is missing attribute %v", tl.name, local)
} }
} assert.Equal(t, tl.attributes, attrs)
if diff := cmp.Diff(actualAttrs, expectedAttrs); diff != "" {
t.Fatalf("[span %s] Attributes are different: %v", tl.name, diff)
} }
} }
} }
type MultiSpanRecorder map[string][]*testtrace.Span
func (sr *MultiSpanRecorder) Reset() { (*sr) = MultiSpanRecorder{} }
func (sr *MultiSpanRecorder) OnStart(span *testtrace.Span) {}
func (sr *MultiSpanRecorder) OnEnd(span *testtrace.Span) {
(*sr)[span.Name()] = append((*sr)[span.Name()], span)
}
func TestConcurrentConnectionStart(t *testing.T) { func TestConcurrentConnectionStart(t *testing.T) {
exp := &testExporter{ sr := MultiSpanRecorder{}
spanMap: make(map[string][]*export.SpanData), global.SetTraceProvider(
} testtrace.NewProvider(testtrace.WithSpanRecorder(&sr)),
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()})) )
global.SetTraceProvider(tp)
ct := httptrace.NewClientTrace(context.Background()) ct := httptrace.NewClientTrace(context.Background())
tts := []struct { tts := []struct {
name string name string
run func() run func()
@ -186,8 +146,6 @@ func TestConcurrentConnectionStart(t *testing.T) {
{ {
name: "Open1Close1Open2Close2", name: "Open1Close1Open2Close2",
run: func() { run: func() {
exp.spanMap = make(map[string][]*export.SpanData)
ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "127.0.0.1:3000")
ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "[::1]:3000")
@ -197,8 +155,6 @@ func TestConcurrentConnectionStart(t *testing.T) {
{ {
name: "Open2Close2Open1Close1", name: "Open2Close2Open1Close1",
run: func() { run: func() {
exp.spanMap = make(map[string][]*export.SpanData)
ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "[::1]:3000")
ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil)
ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "127.0.0.1:3000")
@ -208,8 +164,6 @@ func TestConcurrentConnectionStart(t *testing.T) {
{ {
name: "Open1Open2Close1Close2", name: "Open1Open2Close1Close2",
run: func() { run: func() {
exp.spanMap = make(map[string][]*export.SpanData)
ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "127.0.0.1:3000")
ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "[::1]:3000")
ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
@ -219,8 +173,6 @@ func TestConcurrentConnectionStart(t *testing.T) {
{ {
name: "Open1Open2Close2Close1", name: "Open1Open2Close2Close1",
run: func() { run: func() {
exp.spanMap = make(map[string][]*export.SpanData)
ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "127.0.0.1:3000")
ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "[::1]:3000")
ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil)
@ -230,8 +182,6 @@ func TestConcurrentConnectionStart(t *testing.T) {
{ {
name: "Open2Open1Close1Close2", name: "Open2Open1Close1Close2",
run: func() { run: func() {
exp.spanMap = make(map[string][]*export.SpanData)
ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "[::1]:3000")
ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "127.0.0.1:3000")
ct.ConnectDone("tcp", "127.0.0.1:3000", nil) ct.ConnectDone("tcp", "127.0.0.1:3000", nil)
@ -241,8 +191,6 @@ func TestConcurrentConnectionStart(t *testing.T) {
{ {
name: "Open2Open1Close2Close1", name: "Open2Open1Close2Close1",
run: func() { run: func() {
exp.spanMap = make(map[string][]*export.SpanData)
ct.ConnectStart("tcp", "[::1]:3000") ct.ConnectStart("tcp", "[::1]:3000")
ct.ConnectStart("tcp", "127.0.0.1:3000") ct.ConnectStart("tcp", "127.0.0.1:3000")
ct.ConnectDone("tcp", "[::1]:3000", nil) ct.ConnectDone("tcp", "[::1]:3000", nil)
@ -251,70 +199,40 @@ func TestConcurrentConnectionStart(t *testing.T) {
}, },
} }
expectedRemotes := []kv.KeyValue{
kv.String("http.remote", "127.0.0.1:3000"),
kv.String("http.remote", "[::1]:3000"),
}
for _, tt := range tts { for _, tt := range tts {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
sr.Reset()
tt.run() tt.run()
spans := exp.spanMap["http.connect"] spans := sr["http.connect"]
require.Len(t, spans, 2)
if l := len(spans); l != 2 { var gotRemotes []kv.KeyValue
t.Fatalf("Expected 2 'http.connect' traces but found %d", l)
}
remotes := make(map[string]struct{})
for _, span := range spans { for _, span := range spans {
if l := len(span.Attributes); l != 1 { for k, v := range span.Attributes() {
t.Fatalf("Expected 1 attribute on each span but found %d", l) gotRemotes = append(gotRemotes, kv.Any(string(k), v.AsInterface()))
}
attr := span.Attributes[0]
if attr.Key != "http.remote" {
t.Fatalf("Expected attribute to be 'http.remote' but found %s", attr.Key)
}
remotes[attr.Value.Emit()] = struct{}{}
}
if l := len(remotes); l != 2 {
t.Fatalf("Expected 2 different 'http.remote' but found %d", l)
}
for _, remote := range []string{"127.0.0.1:3000", "[::1]:3000"} {
if _, ok := remotes[remote]; !ok {
t.Fatalf("Missing remote %s", remote)
} }
} }
assert.ElementsMatch(t, expectedRemotes, gotRemotes)
}) })
} }
} }
func TestEndBeforeStartCreatesSpan(t *testing.T) { func TestEndBeforeStartCreatesSpan(t *testing.T) {
exp := &testExporter{ sr := MultiSpanRecorder{}
spanMap: make(map[string][]*export.SpanData), global.SetTraceProvider(
} testtrace.NewProvider(testtrace.WithSpanRecorder(&sr)),
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()})) )
global.SetTraceProvider(tp)
tr := tp.Tracer("httptrace/client")
ctx, span := tr.Start(context.Background(), "test")
defer span.End()
ct := httptrace.NewClientTrace(ctx)
ct := httptrace.NewClientTrace(context.Background())
ct.DNSDone(nhtrace.DNSDoneInfo{}) ct.DNSDone(nhtrace.DNSDoneInfo{})
ct.DNSStart(nhtrace.DNSStartInfo{Host: "example.com"}) ct.DNSStart(nhtrace.DNSStartInfo{Host: "example.com"})
getSpan := func(name string) *export.SpanData { name := "http.dns"
spans, ok := exp.spanMap[name] require.Contains(t, sr, name)
if !ok { spans := sr[name]
t.Fatalf("no spans found with the name %s, %v", name, exp.spanMap) require.Len(t, spans, 1)
}
if len(spans) != 1 {
t.Fatalf("Expected exactly one span for %s but found %d", name, len(spans))
}
return spans[0]
}
getSpan("http.dns")
} }

View File

@ -24,23 +24,15 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"go.opentelemetry.io/otel/api/correlation" "go.opentelemetry.io/otel/api/correlation"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/kv" "go.opentelemetry.io/otel/api/kv"
"go.opentelemetry.io/otel/api/propagation" "go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/standard" "go.opentelemetry.io/otel/api/standard"
"go.opentelemetry.io/otel/api/trace/testtrace"
"go.opentelemetry.io/otel/instrumentation/httptrace" "go.opentelemetry.io/otel/instrumentation/httptrace"
export "go.opentelemetry.io/otel/sdk/export/trace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
) )
func TestRoundtrip(t *testing.T) { func TestRoundtrip(t *testing.T) {
exp := &testExporter{ tr := testtrace.NewProvider().Tracer("httptrace/client")
spanMap: make(map[string][]*export.SpanData),
}
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}))
global.SetTraceProvider(tp)
tr := tp.Tracer("httptrace/client")
var expectedAttrs map[kv.Key]string var expectedAttrs map[kv.Key]string
expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"} expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"}
@ -121,13 +113,7 @@ func TestRoundtrip(t *testing.T) {
} }
func TestSpecifyPropagators(t *testing.T) { func TestSpecifyPropagators(t *testing.T) {
exp := &testExporter{ tr := testtrace.NewProvider().Tracer("httptrace/client")
spanMap: make(map[string][]*export.SpanData),
}
tp, _ := sdktrace.NewProvider(sdktrace.WithSyncer(exp), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}))
global.SetTraceProvider(tp)
tr := tp.Tracer("httptrace/client")
expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"} expectedCorrs := map[kv.Key]string{kv.Key("foo"): "bar"}