github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/span_context.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package instana
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/mier85/go-sensor/w3ctrace"
    10  )
    11  
    12  // EUMCorrelationData represents the data sent by the Instana End-User Monitoring script
    13  // integrated into frontend
    14  type EUMCorrelationData struct {
    15  	Type string
    16  	ID   string
    17  }
    18  
    19  // SpanReference is a reference to a span, possibly belonging to another trace, that is relevant
    20  // to the span context
    21  type SpanReference struct {
    22  	TraceID string
    23  	SpanID  string
    24  }
    25  
    26  // SpanContext holds the basic Span metadata.
    27  type SpanContext struct {
    28  	// The higher 4 bytes of a 128-bit trace ID
    29  	TraceIDHi int64
    30  	// A probabilistically unique identifier for a [multi-span] trace.
    31  	TraceID int64
    32  	// A probabilistically unique identifier for a span.
    33  	SpanID int64
    34  	// An optional parent span ID, 0 if this is the root span context.
    35  	ParentID int64
    36  	// Optional links to traces relevant to this context, i.e. an indirect parent
    37  	Links []SpanReference
    38  	// Whether the trace is sampled.
    39  	Sampled bool
    40  	// Whether the trace is suppressed and should not be sent to the agent.
    41  	Suppressed bool
    42  	// The span's associated baggage.
    43  	Baggage map[string]string // initialized on first use
    44  	// The W3C trace context
    45  	W3CContext w3ctrace.Context
    46  	// Whether the used trace ID came from 3rd party, e.g. W3C Trace Context
    47  	ForeignTrace bool
    48  	// Correlation is the correlation data sent by the frontend EUM script
    49  	Correlation EUMCorrelationData
    50  }
    51  
    52  // NewRootSpanContext initializes a new root span context issuing a new trace ID
    53  func NewRootSpanContext() SpanContext {
    54  	spanID := randomID()
    55  
    56  	c := SpanContext{
    57  		TraceID: spanID,
    58  		SpanID:  spanID,
    59  	}
    60  
    61  	c.W3CContext = newW3CTraceContext(c)
    62  
    63  	return c
    64  }
    65  
    66  // NewSpanContext initializes a new child span context from its parent. It will
    67  // ignore the parent context if it contains neither Instana trace and span IDs
    68  // nor a W3C trace context
    69  func NewSpanContext(parent SpanContext) SpanContext {
    70  	var foreignTrace bool
    71  	if parent.TraceIDHi == 0 && parent.TraceID == 0 && parent.SpanID == 0 {
    72  		parent = restoreFromW3CTraceContext(parent)
    73  		foreignTrace = !sensor.options.disableW3CTraceCorrelation
    74  	}
    75  
    76  	if parent.TraceIDHi == 0 && parent.TraceID == 0 && parent.SpanID == 0 {
    77  		c := NewRootSpanContext()
    78  		c.Suppressed = parent.Suppressed
    79  
    80  		// preserve the W3C trace context even if it was not used
    81  		if !parent.W3CContext.IsZero() {
    82  			c.W3CContext = parent.W3CContext
    83  		}
    84  
    85  		return c
    86  	}
    87  
    88  	c := parent.Clone()
    89  	c.SpanID, c.ParentID = randomID(), parent.SpanID
    90  	c.ForeignTrace = foreignTrace
    91  
    92  	// initialize W3C trace context if it's not set already
    93  	if c.W3CContext.IsZero() {
    94  		c.W3CContext = newW3CTraceContext(c)
    95  		return c
    96  	}
    97  
    98  	// update W3C trace context parent
    99  	w3cParent := c.W3CContext.Parent()
   100  	w3cParent.ParentID = FormatID(c.SpanID)
   101  	c.W3CContext.RawParent = w3cParent.String()
   102  
   103  	// check if there is Instana state stored in the W3C tracestate header
   104  	if foreignTrace {
   105  		w3cState := c.W3CContext.State()
   106  		if ancestor, ok := w3cState.FetchInstanaTraceStateValue(); ok {
   107  			if ref, ok := parseW3CInstanaState(ancestor); ok {
   108  				c.Links = append(c.Links, ref)
   109  			}
   110  		}
   111  	}
   112  
   113  	return c
   114  }
   115  
   116  // IsZero returns true if an instance of SpanContext is a zero-value
   117  func (sc SpanContext) IsZero() bool {
   118  	return sc.TraceIDHi == 0 && sc.TraceID == 0 && sc.SpanID == 0 && sc.W3CContext.IsZero() && !sc.Suppressed
   119  }
   120  
   121  func restoreFromW3CTraceContext(parent SpanContext) SpanContext {
   122  	if parent.W3CContext.IsZero() {
   123  		return parent
   124  	}
   125  
   126  	traceparent := parent.W3CContext.Parent()
   127  
   128  	if sensor.options.disableW3CTraceCorrelation {
   129  		restored := restoreFromW3CTraceState(parent.W3CContext)
   130  		restored.Suppressed = parent.Suppressed
   131  
   132  		return restored
   133  	}
   134  
   135  	traceIDHi, traceIDLo, err := ParseLongID(traceparent.TraceID)
   136  	if err != nil {
   137  		return parent
   138  	}
   139  
   140  	parentID, err := ParseID(traceparent.ParentID)
   141  	if err != nil {
   142  		return parent
   143  	}
   144  
   145  	return SpanContext{
   146  		TraceIDHi:  traceIDHi,
   147  		TraceID:    traceIDLo,
   148  		SpanID:     parentID,
   149  		Suppressed: parent.Suppressed,
   150  		W3CContext: parent.W3CContext,
   151  	}
   152  }
   153  
   154  func restoreFromW3CTraceState(trCtx w3ctrace.Context) SpanContext {
   155  	if trCtx.IsZero() {
   156  		return SpanContext{}
   157  	}
   158  
   159  	c := SpanContext{
   160  		W3CContext: trCtx,
   161  	}
   162  
   163  	state, ok := trCtx.State().FetchInstanaTraceStateValue()
   164  	if !ok {
   165  		return c
   166  	}
   167  
   168  	ref, ok := parseW3CInstanaState(state)
   169  	if !ok {
   170  		return c
   171  	}
   172  
   173  	traceIDHi, traceIDLo, err := ParseLongID(ref.TraceID)
   174  	if err != nil {
   175  		return c
   176  	}
   177  
   178  	parentID, err := ParseID(ref.SpanID)
   179  	if err != nil {
   180  		return c
   181  	}
   182  
   183  	c.TraceIDHi, c.TraceID, c.SpanID, c.Suppressed = traceIDHi, traceIDLo, parentID, !trCtx.Parent().Flags.Sampled
   184  
   185  	return c
   186  }
   187  
   188  // ForeachBaggageItem belongs to the opentracing.SpanContext interface
   189  func (sc SpanContext) ForeachBaggageItem(handler func(k, v string) bool) {
   190  	for k, v := range sc.Baggage {
   191  		if !handler(k, v) {
   192  			break
   193  		}
   194  	}
   195  }
   196  
   197  // WithBaggageItem returns an entirely new SpanContext with the
   198  // given key:value baggage pair set.
   199  func (sc SpanContext) WithBaggageItem(key, val string) SpanContext {
   200  	res := sc.Clone()
   201  
   202  	if res.Baggage == nil {
   203  		res.Baggage = make(map[string]string, 1)
   204  	}
   205  	res.Baggage[key] = val
   206  
   207  	return res
   208  }
   209  
   210  // Clone returns a deep copy of a SpanContext
   211  func (sc SpanContext) Clone() SpanContext {
   212  	res := SpanContext{
   213  		TraceIDHi:  sc.TraceIDHi,
   214  		TraceID:    sc.TraceID,
   215  		SpanID:     sc.SpanID,
   216  		ParentID:   sc.ParentID,
   217  		Sampled:    sc.Sampled,
   218  		Suppressed: sc.Suppressed,
   219  		W3CContext: sc.W3CContext,
   220  	}
   221  
   222  	if sc.Baggage != nil {
   223  		res.Baggage = make(map[string]string, len(sc.Baggage))
   224  		for k, v := range sc.Baggage {
   225  			res.Baggage[k] = v
   226  		}
   227  	}
   228  
   229  	return res
   230  }
   231  
   232  func newW3CTraceContext(c SpanContext) w3ctrace.Context {
   233  	return w3ctrace.New(w3ctrace.Parent{
   234  		Version:  w3ctrace.Version_Max,
   235  		TraceID:  FormatLongID(c.TraceIDHi, c.TraceID),
   236  		ParentID: FormatID(c.SpanID),
   237  		Flags: w3ctrace.Flags{
   238  			Sampled: !c.Suppressed,
   239  		},
   240  	})
   241  }
   242  
   243  func parseW3CInstanaState(vendorData string) (ancestor SpanReference, ok bool) {
   244  	ind := strings.IndexByte(vendorData, ';')
   245  	if ind < 0 {
   246  		return SpanReference{}, false
   247  	}
   248  
   249  	return SpanReference{
   250  		TraceID: vendorData[:ind],
   251  		SpanID:  vendorData[ind+1:],
   252  	}, true
   253  }