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

refactoring interfaces and apis #2317

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions ddtrace/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.

package ddtrace

import (
"context"
"testing"

"github.com/DataDog/dd-trace-go/v2/internal"

"github.com/stretchr/testify/assert"
)

func TestContextWithSpan(t *testing.T) {
want := &Span{SpanID: 123}
ctx := ContextWithSpan(context.Background(), want)
got, ok := ctx.Value(internal.ActiveSpanKey).(*Span)
assert := assert.New(t)
assert.True(ok)
assert.Equal(got, want)
}

func TestSpanFromContext(t *testing.T) {
t.Run("regular", func(t *testing.T) {
assert := assert.New(t)
want := &Span{SpanID: 123}
ctx := ContextWithSpan(context.Background(), want)
got, ok := SpanFromContext(ctx)
assert.True(ok)
assert.Equal(got, want)
})
t.Run("no-op", func(t *testing.T) {
assert := assert.New(t)
span, ok := SpanFromContext(context.Background())
assert.False(ok)
assert.Nil(span)
span, ok = SpanFromContext(nil)
assert.False(ok)
assert.Nil(span)
})
}

// TODO(kjn v2): Fix span context
// func TestStartSpanFromContext(t *testing.T) {
// _, _, _, stop := startTestTracer(t)
// defer stop()
//
// parent := &Span{context: &spanContext{spanID: 123, traceID: traceIDFrom64Bits(456)}}
// parent2 := &Span{context: &spanContext{spanID: 789, traceID: traceIDFrom64Bits(456)}}
// pctx := ContextWithSpan(context.Background(), parent)
// child, ctx := StartSpanFromContext(
// pctx,
// "http.request",
// ServiceName("gin"),
// ResourceName("/"),
// ChildOf(parent2.Context()), // we do this to assert that the span in pctx takes priority.
// )
// assert := assert.New(t)
//
// got, ok := child.(*Span)
// assert.True(ok)
// gotctx, ok := SpanFromContext(ctx)
// assert.True(ok)
// assert.Equal(gotctx, got)
// _, ok = gotctx.(*NoopSpan)
// assert.False(ok)
//
// assert.Equal(uint64(456), got.TraceID)
// assert.Equal(uint64(123), got.ParentID)
// assert.Equal("http.request", got.Name)
// assert.Equal("gin", got.Service)
// assert.Equal("/", got.Resource)
// }
//
// func TestStartSpanFromContextRace(t *testing.T) {
// _, _, _, stop := startTestTracer(t)
// defer stop()
//
// // Start 100 goroutines that create child spans with StartSpanFromContext in parallel,
// // with a shared options slice. The child spans should get parented to the correct spans
// const contextKey = "key"
// const numContexts = 100
// options := make([]StartSpanOption, 0, 3)
// outputValues := make(chan uint64, numContexts)
// var expectedTraceIDs []uint64
// for i := 0; i < numContexts; i++ {
// parent, childCtx := StartSpanFromContext(context.Background(), "parent")
// expectedTraceIDs = append(expectedTraceIDs, parent.Context().TraceID())
// go func() {
// span, _ := StartSpanFromContext(childCtx, "testoperation", options...)
// defer span.Finish()
// outputValues <- span.Context().TraceID()
// }()
// parent.Finish()
// }
//
// // collect the outputs
// var outputs []uint64
// for i := 0; i < numContexts; i++ {
// outputs = append(outputs, <-outputValues)
// }
// assert.Len(t, outputs, numContexts)
// assert.ElementsMatch(t, outputs, expectedTraceIDs)
// }
//
// func Test128(t *testing.T) {
// _, _, _, stop := startTestTracer(t)
// defer stop()
//
// span, _ := StartSpanFromContext(context.Background(), "http.request")
// assert.NotZero(t, span.Context().TraceID())
// w3cCtx, ok := span.Context().(SpanContextW3C)
// if !ok {
// assert.Fail(t, "couldn't cast to SpanContextW3C")
// }
// id128 := w3cCtx.TraceID128()
// assert.Len(t, id128, 32) // ensure there are enough leading zeros
// idBytes, err := hex.DecodeString(id128)
// assert.NoError(t, err)
// assert.Equal(t, uint64(0), binary.BigEndian.Uint64(idBytes[:8])) // high 64 bits should be 0
// assert.Equal(t, span.Context().TraceID(), binary.BigEndian.Uint64(idBytes[8:]))
//
// // Enable 128 bit trace ids
// t.Setenv("DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED", "true")
// span128, _ := StartSpanFromContext(context.Background(), "http.request")
// assert.NotZero(t, span128.Context().TraceID())
// w3cCtx, ok = span128.Context().(SpanContextW3C)
// if !ok {
// assert.Fail(t, "couldn't cast to SpanContextW3C")
// }
// id128bit := w3cCtx.TraceID128()
// assert.NotEmpty(t, id128bit)
// assert.Len(t, id128bit, 32)
// // Ensure that the lower order bits match the span's 64-bit trace id
// b, err := hex.DecodeString(id128bit)
// assert.NoError(t, err)
// assert.Equal(t, span128.Context().TraceID(), binary.BigEndian.Uint64(b[8:]))
// }
//
// func TestStartSpanFromNilContext(t *testing.T) {
// _, _, _, stop := startTestTracer(t)
// defer stop()
//
// child, ctx := StartSpanFromContext(nil, "http.request")
// assert := assert.New(t)
// // ensure the returned context works
// assert.Nil(ctx.Value("not_found_key"))
//
// internalSpan, ok := child.(*Span)
// assert.True(ok)
// assert.Equal("http.request", internalSpan.Name)
//
// // the returned context includes the span
// ctxSpan, ok := SpanFromContext(ctx)
// assert.True(ok)
// assert.Equal(child, ctxSpan)
// }
96 changes: 71 additions & 25 deletions ddtrace/ddtrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"time"

"github.com/DataDog/dd-trace-go/v2/internal"
"github.com/DataDog/dd-trace-go/v2/internal/log"
)

Expand All @@ -38,7 +39,8 @@ type SpanContextW3C interface {
// within the "tracer" package.
type Tracer interface {
// StartSpan starts a span with the given operation name and options.
StartSpan(operationName string, opts ...StartSpanOption) Span
StartSpan(operationName string, opts ...StartSpanOption) *Span
StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (*Span, context.Context)

// Extract extracts a span context from a given carrier. Note that baggage item
// keys will always be lower-cased to maintain consistency. It is impossible to
Expand All @@ -50,33 +52,37 @@ type Tracer interface {

// Stop stops the tracer. Calls to Stop should be idempotent.
Stop()
}

// Span represents a chunk of computation time. Spans have names, durations,
// timestamps and other metadata. A Tracer is used to create hierarchies of
// spans in a request, buffer and submit them to the server.
type Span interface {
// SetTag sets a key/value pair as metadata on the span.
SetTag(key string, value interface{})

// SetOperationName sets the operation name for this span. An operation name should be
// a representative name for a group of spans (e.g. "grpc.server" or "http.request").
SetOperationName(operationName string)

// BaggageItem returns the baggage item held by the given key.
BaggageItem(key string) string

// SetBaggageItem sets a new baggage item at the given key. The baggage
// item should propagate to all descendant spans, both in- and cross-process.
SetBaggageItem(key, val string)

// Finish finishes the current span with the given options. Finish calls should be idempotent.
Finish(opts ...FinishOption)

// Context returns the SpanContext of this Span.
Context() SpanContext
// Temporary hacks for v2 conversion
CanComputeStats() bool
PushChunk(*Chunk) // We will keep this, or some version of it to allow submitting traces to a tracer.
}

// // Span represents a chunk of computation time. Spans have names, durations,
// // timestamps and other metadata. A Tracer is used to create hierarchies of
// // spans in a request, buffer and submit them to the server.
// type Span interface {
// // SetTag sets a key/value pair as metadata on the span.
// SetTag(key string, value interface{})
//
// // SetOperationName sets the operation name for this span. An operation name should be
// // a representative name for a group of spans (e.g. "grpc.server" or "http.request").
// SetOperationName(operationName string)
//
// // BaggageItem returns the baggage item held by the given key.
// BaggageItem(key string) string
//
// // SetBaggageItem sets a new baggage item at the given key. The baggage
// // item should propagate to all descendant spans, both in- and cross-process.
// SetBaggageItem(key, val string)
//
// // Finish finishes the current span with the given options. Finish calls should be idempotent.
// Finish(opts ...FinishOption)
//
// // Context returns the SpanContext of this Span.
// Context() SpanContext
// }

// SpanContext represents a span state that can propagate to descendant spans
// and across process boundaries. It contains all the information needed to
// spawn a direct descendant of the span that it belongs to. It can be used
Expand Down Expand Up @@ -156,3 +162,43 @@ type Logger interface {
func UseLogger(l Logger) {
log.UseLogger(l)
}

// ContextWithSpan returns a copy of the given context which includes the span s.
func ContextWithSpan(ctx context.Context, s *Span) context.Context {
return context.WithValue(ctx, internal.ActiveSpanKey, s)
}

// StartSpan starts a new span with the given operation name and set of options.
// If the tracer is not started, calling this function is a no-op.
func StartSpan(operationName string, opts ...StartSpanOption) *Span {
return GetGlobalTracer().StartSpan(operationName, opts...)
}

// SpanFromContext returns the span contained in the given context. A second return
// value indicates if a span was found in the context. If no span is found, a no-op
// span is returned.
func SpanFromContext(ctx context.Context) (*Span, bool) {
if ctx == nil {
return nil, false
}
v := ctx.Value(internal.ActiveSpanKey)
if s, ok := v.(*Span); ok {
return s, true
}
return nil, false
}

// StartSpanFromContext returns a new span with the given operation name and options. If a span
// is found in the context, it will be used as the parent of the resulting span. If the ChildOf
// option is passed, it will only be used as the parent if there is no span found in `ctx`.
func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (*Span, context.Context) {
return GetGlobalTracer().StartSpanFromContext(ctx, operationName, opts...)
}

// ChildOf tells StartSpan to use the given span context as a parent for the
// created span.
func ChildOf(ctx SpanContext) StartSpanOption {
return func(cfg *StartSpanConfig) {
cfg.Parent = ctx
}
}
50 changes: 25 additions & 25 deletions ddtrace/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/DataDog/dd-trace-go/v2/ddtrace"
"github.com/DataDog/dd-trace-go/v2/ddtrace/ext"
"github.com/DataDog/dd-trace-go/v2/ddtrace/mocktracer"
"github.com/DataDog/dd-trace-go/v2/ddtrace/opentracer"
"github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"

Expand All @@ -28,11 +27,11 @@ func Example_datadog() {
defer tracer.Stop()

// Start a root span.
span := tracer.StartSpan("get.data")
span := ddtrace.StartSpan("get.data")
defer span.Finish()

// Create a child of it, computing the time needed to read a file.
child := tracer.StartSpan("read.file", tracer.ChildOf(span.Context()))
child := ddtrace.StartSpan("read.file", ddtrace.ChildOf(span.Context()))
child.SetTag(ext.ResourceName, "test.json")

// If you are using 128 bit trace ids and want to generate the high
Expand All @@ -47,7 +46,7 @@ func Example_datadog() {

// We may finish the child span using the returned error. If it's
// nil, it will be disregarded.
child.Finish(tracer.WithError(err))
child.Finish(ddtrace.WithError(err))
if err != nil {
log.Fatal(err)
}
Expand All @@ -66,24 +65,25 @@ func Example_opentracing() {
opentracing.SetGlobalTracer(t)
}

// The code below illustrates a scenario of how one could use a mock tracer in tests
// to assert that spans are created correctly.
func Example_mocking() {
// Setup the test environment: start the mock tracer.
mt := mocktracer.Start()
defer mt.Stop()

// Run test code: in this example we will simply create a span to illustrate.
tracer.StartSpan("test.span").Finish()

// Assert the results: query the mock tracer for finished spans.
spans := mt.FinishedSpans()
if len(spans) != 1 {
// fail
panic("expected 1 span")
}
if spans[0].OperationName() != "test.span" {
// fail
panic("unexpected operation name")
}
}
// TODO(kjn v2): Fix mocktracer
// // The code below illustrates a scenario of how one could use a mock tracer in tests
// // to assert that spans are created correctly.
// func Example_mocking() {
// // Setup the test environment: start the mock tracer.
// mt := mocktracer.Start()
// defer mt.Stop()
//
// // Run test code: in this example we will simply create a span to illustrate.
// tracer.StartSpan("test.span").Finish()
//
// // Assert the results: query the mock tracer for finished spans.
// spans := mt.FinishedSpans()
// if len(spans) != 1 {
// // fail
// panic("expected 1 span")
// }
// if spans[0].OperationName() != "test.span" {
// // fail
// panic("unexpected operation name")
// }
// }
Loading
Loading