github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/json_span.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2017
     3  
     4  package instana
     5  
     6  import (
     7  	"encoding/json"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/opentracing/opentracing-go/ext"
    12  )
    13  
    14  type typedSpanData interface {
    15  	Type() RegisteredSpanType
    16  	Kind() SpanKind
    17  }
    18  
    19  // SpanKind represents values of field `k` in OpenTracing span representation. It represents
    20  // the direction of the call associated with a span.
    21  type SpanKind uint8
    22  
    23  // Valid span kinds
    24  const (
    25  	// The kind of a span associated with an inbound call, this must be the first span in the trace.
    26  	EntrySpanKind SpanKind = iota + 1
    27  	// The kind of a span associated with an outbound call, e.g. an HTTP client request, posting to a message bus, etc.
    28  	ExitSpanKind
    29  	// The default kind for a span that is associated with a call within the same service.
    30  	IntermediateSpanKind
    31  )
    32  
    33  // String returns string representation of a span kind suitable for use as a value for `data.sdk.type`
    34  // tag of an SDK span. By default all spans are intermediate unless they are explicitly set to be "entry" or "exit"
    35  func (k SpanKind) String() string {
    36  	switch k {
    37  	case EntrySpanKind:
    38  		return "entry"
    39  	case ExitSpanKind:
    40  		return "exit"
    41  	default:
    42  		return "intermediate"
    43  	}
    44  }
    45  
    46  // Span represents the OpenTracing span document to be sent to the agent
    47  type Span struct {
    48  	TraceID         int64
    49  	TraceIDHi       int64
    50  	ParentID        int64
    51  	SpanID          int64
    52  	Ancestor        *TraceReference
    53  	Timestamp       uint64
    54  	Duration        uint64
    55  	Name            string
    56  	From            *fromS
    57  	Batch           *batchInfo
    58  	Kind            int
    59  	Ec              int
    60  	Data            typedSpanData
    61  	Synthetic       bool
    62  	CorrelationType string
    63  	CorrelationID   string
    64  	ForeignTrace    bool
    65  }
    66  
    67  func newSpan(span *spanS) Span {
    68  	data := RegisteredSpanType(span.Operation).extractData(span)
    69  	sp := Span{
    70  		TraceID:         span.context.TraceID,
    71  		TraceIDHi:       span.context.TraceIDHi,
    72  		ParentID:        span.context.ParentID,
    73  		SpanID:          span.context.SpanID,
    74  		Timestamp:       uint64(span.Start.UnixNano()) / uint64(time.Millisecond),
    75  		Duration:        uint64(span.Duration) / uint64(time.Millisecond),
    76  		Name:            string(data.Type()),
    77  		Ec:              span.ErrorCount,
    78  		CorrelationType: span.Correlation.Type,
    79  		CorrelationID:   span.Correlation.ID,
    80  		ForeignTrace:    span.context.ForeignTrace,
    81  		Kind:            int(data.Kind()),
    82  		Data:            data,
    83  	}
    84  
    85  	if bs, ok := span.Tags[batchSizeTag].(int); ok {
    86  		if bs > 1 {
    87  			sp.Batch = &batchInfo{Size: bs}
    88  		}
    89  		delete(span.Tags, batchSizeTag)
    90  	}
    91  
    92  	if syn, ok := span.Tags[syntheticCallTag].(bool); ok {
    93  		sp.Synthetic = syn
    94  		delete(span.Tags, syntheticCallTag)
    95  	}
    96  
    97  	if len(span.context.Links) > 0 {
    98  		ancestor := span.context.Links[0]
    99  		sp.Ancestor = &TraceReference{
   100  			TraceID:  ancestor.TraceID,
   101  			ParentID: ancestor.SpanID,
   102  		}
   103  	}
   104  
   105  	return sp
   106  }
   107  
   108  // TraceReference is used to reference a parent span
   109  type TraceReference struct {
   110  	TraceID  string `json:"t"`
   111  	ParentID string `json:"p,omitempty"`
   112  }
   113  
   114  // MarshalJSON serializes span to JSON for sending it to Instana
   115  func (sp Span) MarshalJSON() ([]byte, error) {
   116  	var parentID string
   117  	if sp.ParentID != 0 {
   118  		parentID = FormatID(sp.ParentID)
   119  	}
   120  
   121  	var longTraceID string
   122  	if sp.TraceIDHi != 0 && sp.Kind == int(EntrySpanKind) {
   123  		longTraceID = FormatLongID(sp.TraceIDHi, sp.TraceID)
   124  	}
   125  
   126  	return json.Marshal(struct {
   127  		TraceReference
   128  
   129  		SpanID          string          `json:"s"`
   130  		LongTraceID     string          `json:"lt,omitempty"`
   131  		Timestamp       uint64          `json:"ts"`
   132  		Duration        uint64          `json:"d"`
   133  		Name            string          `json:"n"`
   134  		From            *fromS          `json:"f"`
   135  		Batch           *batchInfo      `json:"b,omitempty"`
   136  		Kind            int             `json:"k"`
   137  		Ec              int             `json:"ec,omitempty"`
   138  		Data            typedSpanData   `json:"data"`
   139  		Synthetic       bool            `json:"sy,omitempty"`
   140  		CorrelationType string          `json:"crtp,omitempty"`
   141  		CorrelationID   string          `json:"crid,omitempty"`
   142  		ForeignTrace    bool            `json:"tp,omitempty"`
   143  		Ancestor        *TraceReference `json:"ia,omitempty"`
   144  	}{
   145  		TraceReference{
   146  			FormatID(sp.TraceID),
   147  			parentID,
   148  		},
   149  		FormatID(sp.SpanID),
   150  		longTraceID,
   151  		sp.Timestamp,
   152  		sp.Duration,
   153  		sp.Name,
   154  		sp.From,
   155  		sp.Batch,
   156  		sp.Kind,
   157  		sp.Ec,
   158  		sp.Data,
   159  		sp.Synthetic,
   160  		sp.CorrelationType,
   161  		sp.CorrelationID,
   162  		sp.ForeignTrace,
   163  		sp.Ancestor,
   164  	})
   165  }
   166  
   167  type batchInfo struct {
   168  	Size int `json:"s"`
   169  }
   170  
   171  // CustomSpanData holds user-defined span tags
   172  type CustomSpanData struct {
   173  	Tags map[string]interface{} `json:"tags,omitempty"`
   174  }
   175  
   176  func filterCustomSpanTags(tags map[string]interface{}, st RegisteredSpanType) map[string]interface{} {
   177  	knownTags := st.TagsNames()
   178  	customTags := make(map[string]interface{})
   179  
   180  	for k, v := range tags {
   181  		if k == string(ext.SpanKind) {
   182  			continue
   183  		}
   184  
   185  		if _, ok := knownTags[k]; ok {
   186  			continue
   187  		}
   188  
   189  		customTags[k] = v
   190  	}
   191  
   192  	return customTags
   193  }
   194  
   195  // SpanData contains fields to be sent in the `data` section of an OT span document. These fields are
   196  // common for all span types.
   197  type SpanData struct {
   198  	Service string          `json:"service,omitempty"`
   199  	Custom  *CustomSpanData `json:"sdk.custom,omitempty"`
   200  
   201  	st RegisteredSpanType
   202  }
   203  
   204  // NewSpanData initializes a new span data from tracer span
   205  func NewSpanData(span *spanS, st RegisteredSpanType) SpanData {
   206  	data := SpanData{
   207  		Service: span.Service,
   208  		st:      st,
   209  	}
   210  
   211  	if customTags := filterCustomSpanTags(span.Tags, st); len(customTags) > 0 {
   212  		data.Custom = &CustomSpanData{Tags: customTags}
   213  	}
   214  
   215  	return data
   216  }
   217  
   218  // Type returns the registered span type suitable for use as the value of `n` field.
   219  func (d SpanData) Type() RegisteredSpanType {
   220  	return d.st
   221  }
   222  
   223  // SDKSpanData represents the `data` section of an SDK span sent within an OT span document
   224  type SDKSpanData struct {
   225  	// Deprecated
   226  	SpanData `json:"-"`
   227  
   228  	Service string      `json:"service,omitempty"`
   229  	Tags    SDKSpanTags `json:"sdk"`
   230  
   231  	sk SpanKind
   232  }
   233  
   234  // newSDKSpanData initializes a new SDK span data from a tracer span
   235  func newSDKSpanData(span *spanS) SDKSpanData {
   236  	sk := IntermediateSpanKind
   237  
   238  	switch span.Tags[string(ext.SpanKind)] {
   239  	case ext.SpanKindRPCServerEnum, string(ext.SpanKindRPCServerEnum),
   240  		ext.SpanKindConsumerEnum, string(ext.SpanKindConsumerEnum),
   241  		"entry":
   242  		sk = EntrySpanKind
   243  	case ext.SpanKindRPCClientEnum, string(ext.SpanKindRPCClientEnum),
   244  		ext.SpanKindProducerEnum, string(ext.SpanKindProducerEnum),
   245  		"exit":
   246  		sk = ExitSpanKind
   247  	}
   248  
   249  	return SDKSpanData{
   250  		Service: span.Service,
   251  		Tags:    newSDKSpanTags(span, sk.String()),
   252  		sk:      sk,
   253  	}
   254  }
   255  
   256  // Type returns the registered span type suitable for use as the value of `n` field.
   257  func (d SDKSpanData) Type() RegisteredSpanType {
   258  	return SDKSpanType
   259  }
   260  
   261  // Kind returns the kind of the span. It handles the github.com/opentracing/opentracing-go/ext.SpanKindEnum
   262  // values as well as generic "entry" and "exit"
   263  func (d SDKSpanData) Kind() SpanKind {
   264  	return d.sk
   265  }
   266  
   267  // SDKSpanTags contains fields within the `data.sdk` section of an OT span document
   268  type SDKSpanTags struct {
   269  	Name      string                 `json:"name"`
   270  	Type      string                 `json:"type,omitempty"`
   271  	Arguments string                 `json:"arguments,omitempty"`
   272  	Return    string                 `json:"return,omitempty"`
   273  	Custom    map[string]interface{} `json:"custom,omitempty"`
   274  }
   275  
   276  // newSDKSpanTags extracts SDK span tags from a tracer span
   277  func newSDKSpanTags(span *spanS, spanType string) SDKSpanTags {
   278  	tags := SDKSpanTags{
   279  		Name:   span.Operation,
   280  		Type:   spanType,
   281  		Custom: map[string]interface{}{},
   282  	}
   283  
   284  	if len(span.Tags) != 0 {
   285  		tags.Custom["tags"] = cloneTags(span.Tags)
   286  	}
   287  
   288  	if len(span.context.Baggage) != 0 {
   289  		tags.Custom["baggage"] = cloneMapStringString(span.context.Baggage)
   290  	}
   291  
   292  	return tags
   293  }
   294  
   295  // readStringTag populates the &dst with the tag value if it's of either string or []byte type
   296  func readStringTag(dst *string, tag interface{}) {
   297  	switch s := tag.(type) {
   298  	case string:
   299  		*dst = s
   300  	case []byte:
   301  		*dst = string(s)
   302  	}
   303  }
   304  
   305  // readArrayStringTag populates &dst with the tag value if it's a slice of strings
   306  func readArrayStringTag(dst *[]string, tag interface{}) {
   307  	switch s := tag.(type) {
   308  	case []string:
   309  		*dst = s
   310  	default:
   311  		*dst = nil
   312  	}
   313  }
   314  
   315  // readMapOfStringSlicesTag populates &dst with the tag value if it's a map of slice of strings
   316  func readMapOfStringSlicesTag(dst *map[string][]string, tag interface{}) {
   317  	if m, ok := tag.(map[string][]string); ok {
   318  		*dst = m
   319  	} else {
   320  		*dst = nil
   321  	}
   322  }
   323  
   324  // readBoolTag populates the &dst with the tag value if it's either bool, string, byte array or numeric type
   325  func readBoolTag(dst *bool, tag interface{}) {
   326  	switch s := tag.(type) {
   327  	case string:
   328  		val := strings.ToLower(s)
   329  		if val == "true" || val == "1" {
   330  			*dst = true
   331  			return
   332  		}
   333  	case []byte:
   334  		val := strings.ToLower(string(s))
   335  		if val == "true" || val == "1" {
   336  			*dst = true
   337  			return
   338  		}
   339  	case bool:
   340  		*dst = s
   341  		return
   342  	case uint:
   343  		if s == 1 {
   344  			*dst = true
   345  			return
   346  		}
   347  	case uint8:
   348  		if s == 1 {
   349  			*dst = true
   350  			return
   351  		}
   352  	case uint16:
   353  		if s == 1 {
   354  			*dst = true
   355  			return
   356  		}
   357  	case uint32:
   358  		if s == 1 {
   359  			*dst = true
   360  			return
   361  		}
   362  	case uint64:
   363  		if s == 1 {
   364  			*dst = true
   365  			return
   366  		}
   367  	case int:
   368  		if s == 1 {
   369  			*dst = true
   370  			return
   371  		}
   372  	case int8:
   373  		if s == 1 {
   374  			*dst = true
   375  			return
   376  		}
   377  	case int16:
   378  		if s == 1 {
   379  			*dst = true
   380  			return
   381  		}
   382  	case int32:
   383  		if s == 1 {
   384  			*dst = true
   385  			return
   386  		}
   387  	case float32:
   388  		if s == 1 {
   389  			*dst = true
   390  			return
   391  		}
   392  	case float64:
   393  		if s == 1 {
   394  			*dst = true
   395  			return
   396  		}
   397  	}
   398  
   399  	*dst = false
   400  }
   401  
   402  // readIntTag populates the &dst with the tag value if it's of any kind of integer type
   403  func readIntTag(dst *int, tag interface{}) {
   404  	switch n := tag.(type) {
   405  	case int:
   406  		*dst = n
   407  	case int8:
   408  		*dst = int(n)
   409  	case int16:
   410  		*dst = int(n)
   411  	case int32:
   412  		*dst = int(n)
   413  	case int64:
   414  		*dst = int(n)
   415  	case uint:
   416  		*dst = int(n)
   417  	case uint8:
   418  		*dst = int(n)
   419  	case uint16:
   420  		*dst = int(n)
   421  	case uint32:
   422  		*dst = int(n)
   423  	case uint64:
   424  		*dst = int(n)
   425  	}
   426  }