1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-01-03 22:52:30 +02:00

Replace span relationship with a potentially remote parent context (#451)

This PR removes the non-compliant ChildOf and FollowsFrom interfaces
and the Relation type, which were inherited from OpenTracing via the
initial prototype. Instead allow adding a span context to the go
context as a remote span context and use a simple algorithm for
figuring out an actual parent of the new span, which was proposed for
the OpenTelemetry specification.

Also add a way to ignore current span and remote span context in go
context, so we can force the tracer to create a new root span - a span
with a new trace ID.

That required some moderate changes in the opentracing bridge - first
reference with ChildOfRef reference type becomes a local parent, the
rest become links. This also fixes links handling in the meantime. The
downside of the approach proposed here is that we can only set the
remote parent when creating a span through the opentracing API.

Co-authored-by: Joshua MacDonald <jmacd@users.noreply.github.com>
This commit is contained in:
Krzesimir Nowak 2020-02-04 17:55:03 +01:00 committed by GitHub
parent 574463c9ef
commit 942713a02d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 322 additions and 233 deletions

View File

@ -125,35 +125,53 @@ func (h *Harness) TestTracer(subjectFactory func() trace.Tracer) {
e.Expect(csc.SpanID).NotToEqual(psc.SpanID)
})
t.Run("propagates a parent's trace ID through `ChildOf`", func(t *testing.T) {
t.Run("ignores parent's trace ID when new root is requested", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
_, parent := subject.Start(context.Background(), "parent")
_, child := subject.Start(context.Background(), "child", trace.ChildOf(parent.SpanContext()))
ctx, parent := subject.Start(context.Background(), "parent")
_, child := subject.Start(ctx, "child", trace.WithNewRoot())
psc := parent.SpanContext()
csc := child.SpanContext()
e.Expect(csc.TraceID).NotToEqual(psc.TraceID)
e.Expect(csc.SpanID).NotToEqual(psc.SpanID)
})
t.Run("propagates remote parent's trace ID through the context", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
_, remoteParent := subject.Start(context.Background(), "remote parent")
parentCtx := trace.ContextWithRemoteSpanContext(context.Background(), remoteParent.SpanContext())
_, child := subject.Start(parentCtx, "child")
psc := remoteParent.SpanContext()
csc := child.SpanContext()
e.Expect(csc.TraceID).ToEqual(psc.TraceID)
e.Expect(csc.SpanID).NotToEqual(psc.SpanID)
})
t.Run("propagates a parent's trace ID through `FollowsFrom`", func(t *testing.T) {
t.Run("ignores remote parent's trace ID when new root is requested", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
_, parent := subject.Start(context.Background(), "parent")
_, child := subject.Start(context.Background(), "child", trace.FollowsFrom(parent.SpanContext()))
_, remoteParent := subject.Start(context.Background(), "remote parent")
parentCtx := trace.ContextWithRemoteSpanContext(context.Background(), remoteParent.SpanContext())
_, child := subject.Start(parentCtx, "child", trace.WithNewRoot())
psc := parent.SpanContext()
psc := remoteParent.SpanContext()
csc := child.SpanContext()
e.Expect(csc.TraceID).ToEqual(psc.TraceID)
e.Expect(csc.TraceID).NotToEqual(psc.TraceID)
e.Expect(csc.SpanID).NotToEqual(psc.SpanID)
})
})

View File

@ -100,26 +100,11 @@ type StartConfig struct {
Attributes []core.KeyValue
StartTime time.Time
Links []Link
Relation Relation
Record bool
NewRoot bool
SpanKind SpanKind
}
// Relation is used to establish relationship between newly created span and the
// other span. The other span could be related as a parent or linked or any other
// future relationship type.
type Relation struct {
core.SpanContext
RelationshipType
}
type RelationshipType int
const (
ChildOfRelationship RelationshipType = iota
FollowsFromRelationship
)
// Link is used to establish relationship between two spans within the same Trace or
// across different Traces. Few examples of Link usage.
// 1. Batch Processing: A batch of elements may contain elements associated with one
@ -216,23 +201,14 @@ func WithRecord() StartOption {
}
}
// ChildOf. TODO: do we need this?.
func ChildOf(sc core.SpanContext) StartOption {
// WithNewRoot specifies that the current span or remote span context
// in context passed to `Start` should be ignored when deciding about
// a parent, which effectively means creating a span with new trace
// ID. The current span and the remote span context may be added as
// links to the span by the implementation.
func WithNewRoot() StartOption {
return func(c *StartConfig) {
c.Relation = Relation{
SpanContext: sc,
RelationshipType: ChildOfRelationship,
}
}
}
// FollowsFrom. TODO: do we need this?.
func FollowsFrom(sc core.SpanContext) StartOption {
return func(c *StartConfig) {
c.Relation = Relation{
SpanContext: sc,
RelationshipType: FollowsFromRelationship,
}
c.NewRoot = true
}
}

View File

@ -16,19 +16,42 @@ package trace
import (
"context"
"go.opentelemetry.io/otel/api/core"
)
type currentSpanKeyType struct{}
type traceContextKeyType int
var currentSpanKey = &currentSpanKeyType{}
const (
currentSpanKey traceContextKeyType = iota
remoteContextKey
)
// ContextWithSpan creates a new context with a current span set to
// the passed span.
func ContextWithSpan(ctx context.Context, span Span) context.Context {
return context.WithValue(ctx, currentSpanKey, span)
}
// SpanFromContext returns the current span stored in the context.
func SpanFromContext(ctx context.Context) Span {
if span, has := ctx.Value(currentSpanKey).(Span); has {
return span
}
return NoopSpan{}
}
// ContextWithRemoteSpanContext creates a new context with a remote
// span context set to the passed span context.
func ContextWithRemoteSpanContext(ctx context.Context, sc core.SpanContext) context.Context {
return context.WithValue(ctx, remoteContextKey, sc)
}
// RemoteSpanContextFromContext returns the remote span context stored
// in the context.
func RemoteSpanContextFromContext(ctx context.Context) core.SpanContext {
if sc, ok := ctx.Value(remoteContextKey).(core.SpanContext); ok {
return sc
}
return core.EmptySpanContext()
}

View File

@ -102,10 +102,9 @@ func BenchmarkInjectB3(b *testing.B) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := context.Background()
if tt.parentSc.IsValid() {
ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(tt.parentSc))
} else {
ctx, _ = mockTracer.Start(ctx, "inject")
ctx = trace.ContextWithRemoteSpanContext(ctx, tt.parentSc)
}
ctx, _ = mockTracer.Start(ctx, "inject")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {

View File

@ -104,10 +104,9 @@ func TestInjectB3(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := context.Background()
if tt.parentSc.IsValid() {
ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(tt.parentSc))
} else {
ctx, _ = mockTracer.Start(ctx, "inject")
ctx = trace.ContextWithRemoteSpanContext(ctx, tt.parentSc)
}
ctx, _ = mockTracer.Start(ctx, "inject")
propagator.Inject(ctx, req.Header)
for h, v := range tt.wantHeaders {

View File

@ -38,8 +38,8 @@ func injectSubBenchmarks(b *testing.B, fn func(context.Context, *testing.B)) {
SpanID: spanID,
TraceFlags: core.TraceFlagsSampled,
}
ctx := context.Background()
ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(sc))
ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc)
ctx, _ = mockTracer.Start(ctx, "inject")
fn(ctx, b)
})

View File

@ -273,7 +273,8 @@ func TestInjectTraceContextToHTTPReq(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com", nil)
ctx := context.Background()
if tt.sc.IsValid() {
ctx, _ = mockTracer.Start(ctx, "inject", trace.ChildOf(tt.sc))
ctx = trace.ContextWithRemoteSpanContext(ctx, tt.sc)
ctx, _ = mockTracer.Start(ctx, "inject")
}
propagator.Inject(ctx, req.Header)

View File

@ -21,6 +21,8 @@ import (
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/internal/trace/parent"
)
var _ trace.Tracer = (*Tracer)(nil)
@ -52,10 +54,9 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...trace.StartOpti
var traceID core.TraceID
var parentSpanID core.SpanID
if parentSpanContext := c.Relation.SpanContext; parentSpanContext.IsValid() {
traceID = parentSpanContext.TraceID
parentSpanID = parentSpanContext.SpanID
} else if parentSpanContext := trace.SpanFromContext(ctx).SpanContext(); parentSpanContext.IsValid() {
parentSpanContext, _, links := parent.GetSpanContextAndLinks(ctx, c.NewRoot)
if parentSpanContext.IsValid() {
traceID = parentSpanContext.TraceID
parentSpanID = parentSpanContext.SpanID
} else {
@ -86,6 +87,9 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...trace.StartOpti
span.SetName(name)
span.SetAttributes(c.Attributes...)
for _, link := range links {
span.links[link.SpanContext] = link.Attributes
}
for _, link := range c.Links {
span.links[link.SpanContext] = link.Attributes
}

View File

@ -21,6 +21,7 @@ import (
"time"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/testharness"
"go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/api/trace/testtrace"
@ -74,17 +75,17 @@ func TestTracer(t *testing.T) {
e.Expect(attributes[attr2.Key]).ToEqual(attr2.Value)
})
t.Run("uses the parent's span context from ChildOf", func(t *testing.T) {
t.Run("uses the current span from context as parent", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := testtrace.NewTracer()
_, parent := subject.Start(context.Background(), "parent")
parentSpanContext := parent.SpanContext()
parent, parentSpan := subject.Start(context.Background(), "parent")
parentSpanContext := parentSpan.SpanContext()
_, span := subject.Start(context.Background(), "child", trace.ChildOf(parentSpanContext))
_, span := subject.Start(parent, "child")
testSpan, ok := span.(*testtrace.Span)
e.Expect(ok).ToBeTrue()
@ -95,18 +96,19 @@ func TestTracer(t *testing.T) {
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
})
t.Run("defers to ChildOf if the provided context also contains a parent span", func(t *testing.T) {
t.Run("uses the current span from context as parent, even if it has remote span context", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := testtrace.NewTracer()
_, parent := subject.Start(context.Background(), "parent")
parentSpanContext := parent.SpanContext()
parent, parentSpan := subject.Start(context.Background(), "parent")
_, remoteParentSpan := subject.Start(context.Background(), "remote not-a-parent")
parent = trace.ContextWithRemoteSpanContext(parent, remoteParentSpan.SpanContext())
parentSpanContext := parentSpan.SpanContext()
ctx, _ := subject.Start(context.Background(), "should be ignored")
_, span := subject.Start(ctx, "child", trace.ChildOf(parentSpanContext))
_, span := subject.Start(parent, "child")
testSpan, ok := span.(*testtrace.Span)
e.Expect(ok).ToBeTrue()
@ -117,47 +119,101 @@ func TestTracer(t *testing.T) {
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
})
t.Run("uses the parent's span context from FollowsFrom", func(t *testing.T) {
t.Run("uses the remote span context from context as parent, if current span is missing", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := testtrace.NewTracer()
_, parent := subject.Start(context.Background(), "parent")
parentSpanContext := parent.SpanContext()
_, remoteParentSpan := subject.Start(context.Background(), "remote parent")
parent := trace.ContextWithRemoteSpanContext(context.Background(), remoteParentSpan.SpanContext())
remoteParentSpanContext := remoteParentSpan.SpanContext()
_, span := subject.Start(context.Background(), "child", trace.FollowsFrom(parentSpanContext))
_, span := subject.Start(parent, "child")
testSpan, ok := span.(*testtrace.Span)
e.Expect(ok).ToBeTrue()
childSpanContext := testSpan.SpanContext()
e.Expect(childSpanContext.TraceID).ToEqual(parentSpanContext.TraceID)
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
e.Expect(childSpanContext.TraceID).ToEqual(remoteParentSpanContext.TraceID)
e.Expect(childSpanContext.SpanID).NotToEqual(remoteParentSpanContext.SpanID)
e.Expect(testSpan.ParentSpanID()).ToEqual(remoteParentSpanContext.SpanID)
})
t.Run("defers to FollowsFrom if the provided context also contains a parent span", func(t *testing.T) {
t.Run("creates new root when both current span and remote span context are missing", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := testtrace.NewTracer()
_, parent := subject.Start(context.Background(), "parent")
parentSpanContext := parent.SpanContext()
_, parentSpan := subject.Start(context.Background(), "not-a-parent")
_, remoteParentSpan := subject.Start(context.Background(), "remote not-a-parent")
parentSpanContext := parentSpan.SpanContext()
remoteParentSpanContext := remoteParentSpan.SpanContext()
ctx, _ := subject.Start(context.Background(), "should be ignored")
_, span := subject.Start(ctx, "child", trace.FollowsFrom(parentSpanContext))
_, span := subject.Start(context.Background(), "child")
testSpan, ok := span.(*testtrace.Span)
e.Expect(ok).ToBeTrue()
childSpanContext := testSpan.SpanContext()
e.Expect(childSpanContext.TraceID).ToEqual(parentSpanContext.TraceID)
e.Expect(childSpanContext.TraceID).NotToEqual(parentSpanContext.TraceID)
e.Expect(childSpanContext.TraceID).NotToEqual(remoteParentSpanContext.TraceID)
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
e.Expect(testSpan.ParentSpanID()).ToEqual(parentSpanContext.SpanID)
e.Expect(childSpanContext.SpanID).NotToEqual(remoteParentSpanContext.SpanID)
e.Expect(testSpan.ParentSpanID().IsValid()).ToBeFalse()
})
t.Run("creates new root when requested, even if both current span and remote span context are in context", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := testtrace.NewTracer()
parentCtx, parentSpan := subject.Start(context.Background(), "not-a-parent")
_, remoteParentSpan := subject.Start(context.Background(), "remote not-a-parent")
parentSpanContext := parentSpan.SpanContext()
remoteParentSpanContext := remoteParentSpan.SpanContext()
parentCtx = trace.ContextWithRemoteSpanContext(parentCtx, remoteParentSpanContext)
_, span := subject.Start(parentCtx, "child", trace.WithNewRoot())
testSpan, ok := span.(*testtrace.Span)
e.Expect(ok).ToBeTrue()
childSpanContext := testSpan.SpanContext()
e.Expect(childSpanContext.TraceID).NotToEqual(parentSpanContext.TraceID)
e.Expect(childSpanContext.TraceID).NotToEqual(remoteParentSpanContext.TraceID)
e.Expect(childSpanContext.SpanID).NotToEqual(parentSpanContext.SpanID)
e.Expect(childSpanContext.SpanID).NotToEqual(remoteParentSpanContext.SpanID)
e.Expect(testSpan.ParentSpanID().IsValid()).ToBeFalse()
expectedLinks := []trace.Link{
{
SpanContext: parentSpanContext,
Attributes: []core.KeyValue{
key.String("ignored-on-demand", "current"),
},
},
{
SpanContext: remoteParentSpanContext,
Attributes: []core.KeyValue{
key.String("ignored-on-demand", "remote"),
},
},
}
tsLinks := testSpan.Links()
gotLinks := make([]trace.Link, 0, len(tsLinks))
for sc, attributes := range tsLinks {
gotLinks = append(gotLinks, trace.Link{
SpanContext: sc,
Attributes: attributes,
})
}
e.Expect(gotLinks).ToMatchInAnyOrder(expectedLinks)
})
t.Run("uses the links provided through LinkedTo", func(t *testing.T) {

View File

@ -28,6 +28,7 @@ import (
otlog "github.com/opentracing/opentracing-go/log"
otelcore "go.opentelemetry.io/otel/api/core"
otelkey "go.opentelemetry.io/otel/api/key"
oteltrace "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/bridge/opentracing/migration"
@ -309,15 +310,18 @@ func (t *BridgeTracer) StartSpan(operationName string, opts ...ot.StartSpanOptio
for _, opt := range opts {
opt.Apply(&sso)
}
// TODO: handle links, needs SpanData to be in the API first?
bRelation, _ := otSpanReferencesToBridgeRelationAndLinks(sso.References)
parentBridgeSC, links := otSpanReferencesToParentAndLinks(sso.References)
attributes, kind, hadTrueErrorTag := otTagsToOtelAttributesKindAndError(sso.Tags)
checkCtx := migration.WithDeferredSetup(context.Background())
if parentBridgeSC != nil {
checkCtx = oteltrace.ContextWithRemoteSpanContext(checkCtx, parentBridgeSC.otelSpanContext)
}
checkCtx2, otelSpan := t.setTracer.tracer().Start(checkCtx, operationName, func(opts *oteltrace.StartConfig) {
opts.Attributes = attributes
opts.StartTime = sso.StartTime
opts.Relation = bRelation.ToOtelRelation()
opts.Links = links
opts.Record = true
opts.NewRoot = false
opts.SpanKind = kind
})
if checkCtx != checkCtx2 {
@ -328,9 +332,15 @@ func (t *BridgeTracer) StartSpan(operationName string, opts ...ot.StartSpanOptio
if hadTrueErrorTag {
otelSpan.SetStatus(codes.Unknown)
}
// One does not simply pass a concrete pointer to function
// that takes some interface. In case of passing nil concrete
// pointer, we get an interface with non-nil type (because the
// pointer type is known) and a nil value. Which means
// interface is not nil, but calling some interface function
// on it will most likely result in nil pointer dereference.
var otSpanContext ot.SpanContext
if bRelation.spanContext != nil {
otSpanContext = bRelation.spanContext
if parentBridgeSC != nil {
otSpanContext = parentBridgeSC
}
sctx := newBridgeSpanContext(otelSpan.SpanContext(), otSpanContext)
span := &bridgeSpan{
@ -440,53 +450,59 @@ func otTagToOtelCoreKey(k string) otelcore.Key {
return otelcore.Key(k)
}
type bridgeRelation struct {
spanContext *bridgeSpanContext
relationshipType oteltrace.RelationshipType
func otSpanReferencesToParentAndLinks(references []ot.SpanReference) (*bridgeSpanContext, []oteltrace.Link) {
var (
parent *bridgeSpanContext
links []oteltrace.Link
)
for _, reference := range references {
bridgeSC, ok := reference.ReferencedContext.(*bridgeSpanContext)
if !ok {
// We ignore foreign ot span contexts,
// sorry. We have no way of getting any
// TraceID and SpanID out of it for form a
// otelcore.SpanContext for otelcore.Link. And
// we can't make it a parent - it also needs a
// valid otelcore.SpanContext.
continue
}
if parent != nil {
links = append(links, otSpanReferenceToOtelLink(bridgeSC, reference.Type))
} else {
if reference.Type == ot.ChildOfRef {
parent = bridgeSC
} else {
links = append(links, otSpanReferenceToOtelLink(bridgeSC, reference.Type))
}
}
}
return parent, links
}
func (r bridgeRelation) ToOtelRelation() oteltrace.Relation {
if r.spanContext == nil {
return oteltrace.Relation{}
}
return oteltrace.Relation{
SpanContext: r.spanContext.otelSpanContext,
RelationshipType: r.relationshipType,
func otSpanReferenceToOtelLink(bridgeSC *bridgeSpanContext, refType ot.SpanReferenceType) oteltrace.Link {
return oteltrace.Link{
SpanContext: bridgeSC.otelSpanContext,
Attributes: otSpanReferenceTypeToOtelLinkAttributes(refType),
}
}
func otSpanReferencesToBridgeRelationAndLinks(references []ot.SpanReference) (bridgeRelation, []*bridgeSpanContext) {
if len(references) == 0 {
return bridgeRelation{}, nil
func otSpanReferenceTypeToOtelLinkAttributes(refType ot.SpanReferenceType) []otelcore.KeyValue {
return []otelcore.KeyValue{
otelkey.String("ot-span-reference-type", otSpanReferenceTypeToString(refType)),
}
first := references[0]
relation := bridgeRelation{
spanContext: mustGetBridgeSpanContext(first.ReferencedContext),
relationshipType: otSpanReferenceTypeToOtelRelationshipType(first.Type),
}
var links []*bridgeSpanContext
for _, reference := range references[1:] {
links = append(links, mustGetBridgeSpanContext(reference.ReferencedContext))
}
return relation, links
}
func mustGetBridgeSpanContext(ctx ot.SpanContext) *bridgeSpanContext {
ourCtx, ok := ctx.(*bridgeSpanContext)
if !ok {
panic("oops, some foreign span context here")
}
return ourCtx
}
func otSpanReferenceTypeToOtelRelationshipType(srt ot.SpanReferenceType) oteltrace.RelationshipType {
switch srt {
func otSpanReferenceTypeToString(refType ot.SpanReferenceType) string {
switch refType {
case ot.ChildOfRef:
return oteltrace.ChildOfRelationship
// "extra", because first child-of reference is used
// as a parent, so this function isn't even called for
// it.
return "extra-child-of"
case ot.FollowsFromRef:
return oteltrace.FollowsFromRelationship
return "follows-from-ref"
default:
panic("fix yer code, it uses bogus opentracing reference type")
return fmt.Sprintf("unknown-%d", int(refType))
}
}

View File

@ -26,6 +26,7 @@ import (
otelcorrelation "go.opentelemetry.io/otel/api/correlation"
otelkey "go.opentelemetry.io/otel/api/key"
oteltrace "go.opentelemetry.io/otel/api/trace"
otelparent "go.opentelemetry.io/otel/internal/trace/parent"
"go.opentelemetry.io/otel/bridge/opentracing/migration"
)
@ -146,14 +147,8 @@ func (t *MockTracer) getParentSpanID(ctx context.Context, spanOpts *oteltrace.St
}
func (t *MockTracer) getParentSpanContext(ctx context.Context, spanOpts *oteltrace.StartConfig) otelcore.SpanContext {
if spanOpts.Relation.RelationshipType == oteltrace.ChildOfRelationship &&
spanOpts.Relation.SpanContext.IsValid() {
return spanOpts.Relation.SpanContext
}
if parentSpanContext := oteltrace.SpanFromContext(ctx).SpanContext(); parentSpanContext.IsValid() {
return parentSpanContext
}
return otelcore.EmptySpanContext()
spanCtx, _, _ := otelparent.GetSpanContextAndLinks(ctx, spanOpts.NewRoot)
return spanCtx
}
func (t *MockTracer) getSpanID() otelcore.SpanID {

View File

@ -50,10 +50,9 @@ func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.Una
tr := global.TraceProvider().Tracer("example/grpc")
ctx, span := tr.Start(
ctx,
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
"hello-api-op",
trace.WithAttributes(serverSpanAttrs...),
trace.ChildOf(spanCtx),
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()

View File

@ -63,10 +63,9 @@ func main() {
})))
ctx, span := tr.Start(
req.Context(),
trace.ContextWithRemoteSpanContext(req.Context(), spanCtx),
"hello",
trace.WithAttributes(attrs...),
trace.ChildOf(spanCtx),
)
defer span.End()

View File

@ -57,10 +57,9 @@ func main() {
})))
ctx, span := tr.Start(
req.Context(),
trace.ContextWithRemoteSpanContext(req.Context(), spanCtx),
"hello",
trace.WithAttributes(attrs...),
trace.ChildOf(spanCtx),
)
defer span.End()

View File

@ -22,6 +22,7 @@ import (
"go.opentelemetry.io/otel/api/core"
apitrace "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/internal/trace/parent"
)
// MockTracer is a simple tracer used for testing purpose only.
@ -45,9 +46,9 @@ func (mt *MockTracer) WithSpan(ctx context.Context, name string, body func(conte
return body(ctx)
}
// Start starts a MockSpan. It creates a new Span based on Relation SpanContext option.
// TracdID is used from Relation Span Context and SpanID is assigned.
// If Relation SpanContext option is not specified then random TraceID is used.
// Start starts a MockSpan. It creates a new Span based on Parent SpanContext option.
// TracdID is used from Parent Span Context and SpanID is assigned.
// If Parent SpanContext option is not specified then random TraceID is used.
// No other options are supported.
func (mt *MockTracer) Start(ctx context.Context, name string, o ...apitrace.StartOption) (context.Context, apitrace.Span) {
var opts apitrace.StartConfig
@ -56,14 +57,17 @@ func (mt *MockTracer) Start(ctx context.Context, name string, o ...apitrace.Star
}
var span *MockSpan
var sc core.SpanContext
if !opts.Relation.SpanContext.IsValid() {
parentSpanContext, _, _ := parent.GetSpanContextAndLinks(ctx, opts.NewRoot)
if !parentSpanContext.IsValid() {
sc = core.SpanContext{}
_, _ = rand.Read(sc.TraceID[:])
if mt.Sampled {
sc.TraceFlags = core.TraceFlagsSampled
}
} else {
sc = opts.Relation.SpanContext
sc = parentSpanContext
}
binary.BigEndian.PutUint64(sc.SpanID[:], atomic.AddUint64(mt.StartSpanID, 1))

View File

@ -0,0 +1,41 @@
package parent
import (
"context"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/trace"
)
func GetSpanContextAndLinks(ctx context.Context, ignoreContext bool) (core.SpanContext, bool, []trace.Link) {
lsctx := trace.SpanFromContext(ctx).SpanContext()
rsctx := trace.RemoteSpanContextFromContext(ctx)
if ignoreContext {
links := addLinkIfValid(nil, lsctx, "current")
links = addLinkIfValid(links, rsctx, "remote")
return core.EmptySpanContext(), false, links
}
if lsctx.IsValid() {
links := addLinkIfValid(nil, rsctx, "remote")
return lsctx, false, links
}
if rsctx.IsValid() {
return rsctx, true, nil
}
return core.EmptySpanContext(), false, nil
}
func addLinkIfValid(links []trace.Link, sc core.SpanContext, kind string) []trace.Link {
if !sc.IsValid() {
return links
}
return append(links, trace.Link{
SpanContext: sc,
Attributes: []core.KeyValue{
key.String("ignored-on-demand", kind),
},
})
}

View File

@ -146,19 +146,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// TODO: do something with the correlation context
sc, _ := h.prop.Extract(r.Context(), r.Header)
ctx := r.Context()
if sc.IsValid() { // not a valid span context, so no link / parent relationship to establish
var opt trace.StartOption
if h.public {
// If the endpoint is a public endpoint, it should start a new trace
// and incoming remote sctx should be added as a link.
opt = trace.LinkedTo(sc)
opts = append(opts, opt)
} else { // not a private endpoint, so assume child relationship
opt = trace.ChildOf(sc)
ctx = trace.ContextWithRemoteSpanContext(ctx, sc)
}
opts = append(opts, opt)
}
ctx, span := h.tracer.Start(r.Context(), h.operation, opts...)
ctx, span := h.tracer.Start(ctx, h.operation, opts...)
defer span.End()
readRecordFunc := func(int64) {}

View File

@ -187,7 +187,8 @@ func generateSpan(t *testing.T, tr apitrace.Tracer, option testOption) {
for i := 0; i < option.genNumSpans; i++ {
binary.BigEndian.PutUint64(sc.TraceID[0:8], uint64(i+1))
_, span := tr.Start(context.Background(), option.name, apitrace.ChildOf(sc))
ctx := apitrace.ContextWithRemoteSpanContext(context.Background(), sc)
_, span := tr.Start(ctx, option.name)
span.End()
}
}

View File

@ -65,7 +65,8 @@ func TestSimpleSpanProcessorOnEnd(t *testing.T) {
SpanID: sid,
TraceFlags: 0x1,
}
_, span := tr.Start(context.Background(), "OnEnd", apitrace.ChildOf(sc))
ctx := apitrace.ContextWithRemoteSpanContext(context.Background(), sc)
_, span := tr.Start(ctx, "OnEnd")
span.End()
wantTraceID := tid

View File

@ -175,7 +175,7 @@ func TestSampling(t *testing.T) {
tr := p.Tracer("test")
var sampled int
for i := 0; i < total; i++ {
var opts []apitrace.StartOption
ctx := context.Background()
if tc.parent {
psc := core.SpanContext{
TraceID: idg.NewTraceID(),
@ -184,9 +184,9 @@ func TestSampling(t *testing.T) {
if tc.sampledParent {
psc.TraceFlags = core.TraceFlagsSampled
}
opts = append(opts, apitrace.ChildOf(psc))
ctx = apitrace.ContextWithRemoteSpanContext(ctx, psc)
}
_, span := tr.Start(context.Background(), "test", opts...)
_, span := tr.Start(ctx, "test")
if span.SpanContext().IsSampled() {
sampled++
}
@ -208,21 +208,22 @@ func TestSampling(t *testing.T) {
}
}
func TestStartSpanWithChildOf(t *testing.T) {
func TestStartSpanWithParent(t *testing.T) {
tp, _ := NewProvider()
tr := tp.Tracer("SpanWith ChildOf")
tr := tp.Tracer("SpanWithParent")
ctx := context.Background()
sc1 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 0x0,
}
_, s1 := tr.Start(context.Background(), "span1-unsampled-parent1", apitrace.ChildOf(sc1))
_, s1 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc1), "span1-unsampled-parent1")
if err := checkChild(sc1, s1); err != nil {
t.Error(err)
}
_, s2 := tr.Start(context.Background(), "span2-unsampled-parent1", apitrace.ChildOf(sc1))
_, s2 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc1), "span2-unsampled-parent1")
if err := checkChild(sc1, s2); err != nil {
t.Error(err)
}
@ -233,60 +234,18 @@ func TestStartSpanWithChildOf(t *testing.T) {
TraceFlags: 0x1,
//Tracestate: testTracestate,
}
_, s3 := tr.Start(context.Background(), "span3-sampled-parent2", apitrace.ChildOf(sc2))
_, s3 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc2), "span3-sampled-parent2")
if err := checkChild(sc2, s3); err != nil {
t.Error(err)
}
ctx, s4 := tr.Start(context.Background(), "span4-sampled-parent2", apitrace.ChildOf(sc2))
ctx2, s4 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, sc2), "span4-sampled-parent2")
if err := checkChild(sc2, s4); err != nil {
t.Error(err)
}
s4Sc := s4.SpanContext()
_, s5 := tr.Start(ctx, "span5-implicit-childof-span4")
if err := checkChild(s4Sc, s5); err != nil {
t.Error(err)
}
}
func TestStartSpanWithFollowsFrom(t *testing.T) {
tp, _ := NewProvider()
tr := tp.Tracer("SpanWith FollowsFrom")
sc1 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 0x0,
}
_, s1 := tr.Start(context.Background(), "span1-unsampled-parent1", apitrace.FollowsFrom(sc1))
if err := checkChild(sc1, s1); err != nil {
t.Error(err)
}
_, s2 := tr.Start(context.Background(), "span2-unsampled-parent1", apitrace.FollowsFrom(sc1))
if err := checkChild(sc1, s2); err != nil {
t.Error(err)
}
sc2 := core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 0x1,
//Tracestate: testTracestate,
}
_, s3 := tr.Start(context.Background(), "span3-sampled-parent2", apitrace.FollowsFrom(sc2))
if err := checkChild(sc2, s3); err != nil {
t.Error(err)
}
ctx, s4 := tr.Start(context.Background(), "span4-sampled-parent2", apitrace.FollowsFrom(sc2))
if err := checkChild(sc2, s4); err != nil {
t.Error(err)
}
s4Sc := s4.SpanContext()
_, s5 := tr.Start(ctx, "span5-implicit-childof-span4")
_, s5 := tr.Start(ctx2, "span5-implicit-childof-span4")
if err := checkChild(s4Sc, s5); err != nil {
t.Error(err)
}
@ -574,15 +533,15 @@ func TestLinksOverLimit(t *testing.T) {
func TestSetSpanName(t *testing.T) {
te := &testExporter{}
tp, _ := NewProvider(WithSyncer(te))
ctx := context.Background()
want := "SpanName-1"
_, span := tp.Tracer("SetSpanName").Start(context.Background(), "SpanName-1",
apitrace.ChildOf(core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 1,
}),
)
ctx = apitrace.ContextWithRemoteSpanContext(ctx, core.SpanContext{
TraceID: tid,
SpanID: sid,
TraceFlags: 1,
})
_, span := tp.Tracer("SetSpanName").Start(ctx, "SpanName-1")
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
@ -662,13 +621,15 @@ func startSpan(tp *Provider, trName string, args ...apitrace.StartOption) apitra
}
// startNamed Span is a test utility func that starts a span with a
// passed name and with ChildOf option. remote span context contains
// TraceFlags with sampled bit set. This allows the span to be
// automatically sampled.
// passed name and with remote span context as parent. The remote span
// context contains TraceFlags with sampled bit set. This allows the
// span to be automatically sampled.
func startNamedSpan(tp *Provider, trName, name string, args ...apitrace.StartOption) apitrace.Span {
args = append(args, apitrace.ChildOf(remoteSpanContext()), apitrace.WithRecord())
ctx := context.Background()
ctx = apitrace.ContextWithRemoteSpanContext(ctx, remoteSpanContext())
args = append(args, apitrace.WithRecord())
_, span := tp.Tracer(trName).Start(
context.Background(),
ctx,
name,
args...,
)
@ -678,7 +639,7 @@ func startNamedSpan(tp *Provider, trName, name string, args ...apitrace.StartOpt
// endSpan is a test utility function that ends the span in the context and
// returns the exported export.SpanData.
// It requires that span be sampled using one of these methods
// 1. Passing parent span context using ChildOf option
// 1. Passing parent span context in context
// 2. Use WithSampler(AlwaysSample())
// 3. Configuring AlwaysSample() as default sampler
//
@ -739,9 +700,10 @@ func TestEndSpanTwice(t *testing.T) {
func TestStartSpanAfterEnd(t *testing.T) {
spans := make(fakeExporter)
tp, _ := NewProvider(WithConfig(Config{DefaultSampler: AlwaysSample()}), WithSyncer(spans))
ctx := context.Background()
tr := tp.Tracer("SpanAfterEnd")
ctx, span0 := tr.Start(context.Background(), "parent", apitrace.ChildOf(remoteSpanContext()))
ctx, span0 := tr.Start(apitrace.ContextWithRemoteSpanContext(ctx, remoteSpanContext()), "parent")
ctx1, span1 := tr.Start(ctx, "span-1")
span1.End()
// Start a new span with the context containing span-1
@ -852,17 +814,18 @@ func TestExecutionTracerTaskEnd(t *testing.T) {
tID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f")
sID, _ := core.SpanIDFromHex("0001020304050607")
ctx := context.Background()
ctx = apitrace.ContextWithRemoteSpanContext(ctx,
core.SpanContext{
TraceID: tID,
SpanID: sID,
TraceFlags: 0,
},
)
_, apiSpan = tr.Start(
context.Background(),
ctx,
"foo",
apitrace.ChildOf(
core.SpanContext{
TraceID: tID,
SpanID: sID,
TraceFlags: 0,
},
),
)
s = apiSpan.(*span)
s.executionTracerTaskEnd = executionTracerTaskEnd

View File

@ -17,8 +17,8 @@ package trace
import (
"context"
"go.opentelemetry.io/otel/api/core"
apitrace "go.opentelemetry.io/otel/api/trace"
"go.opentelemetry.io/otel/internal/trace/parent"
)
type tracer struct {
@ -30,29 +30,23 @@ var _ apitrace.Tracer = &tracer{}
func (tr *tracer) Start(ctx context.Context, name string, o ...apitrace.StartOption) (context.Context, apitrace.Span) {
var opts apitrace.StartConfig
var parent core.SpanContext
var remoteParent bool
//TODO [rghetia] : Add new option for parent. If parent is configured then use that parent.
for _, op := range o {
op(&opts)
}
if relation := opts.Relation; relation.SpanContext != core.EmptySpanContext() {
switch relation.RelationshipType {
case apitrace.ChildOfRelationship, apitrace.FollowsFromRelationship:
parent = relation.SpanContext
remoteParent = true
default:
// Future relationship types may have different behavior,
// e.g., adding a `Link` instead of setting the `parent`
parentSpanContext, remoteParent, links := parent.GetSpanContextAndLinks(ctx, opts.NewRoot)
if p := apitrace.SpanFromContext(ctx); p != nil {
if sdkSpan, ok := p.(*span); ok {
sdkSpan.addChild()
}
} else if p, ok := apitrace.SpanFromContext(ctx).(*span); ok {
p.addChild()
parent = p.spanContext
}
span := startSpanInternal(tr, name, parent, remoteParent, opts)
span := startSpanInternal(tr, name, parentSpanContext, remoteParent, opts)
for _, l := range links {
span.addLink(l)
}
for _, l := range opts.Links {
span.addLink(l)
}