github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/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"] = span.Tags
   286  	}
   287  
   288  	if len(span.context.Baggage) != 0 {
   289  		tags.Custom["baggage"] = 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  func readArrayStringTag(dst *[]string, tag interface{}) {
   306  	switch s := tag.(type) {
   307  	case []string:
   308  		*dst = s
   309  	default:
   310  		*dst = nil
   311  	}
   312  }
   313  
   314  // readBoolTag populates the &dst with the tag value if it's either bool, string, byte array or numeric type
   315  func readBoolTag(dst *bool, tag interface{}) {
   316  	switch s := tag.(type) {
   317  	case string:
   318  		val := strings.ToLower(s)
   319  		if val == "true" || val == "1" {
   320  			*dst = true
   321  			return
   322  		}
   323  	case []byte:
   324  		val := strings.ToLower(string(s))
   325  		if val == "true" || val == "1" {
   326  			*dst = true
   327  			return
   328  		}
   329  	case bool:
   330  		*dst = s
   331  		return
   332  	case uint:
   333  		if s == 1 {
   334  			*dst = true
   335  			return
   336  		}
   337  	case uint8:
   338  		if s == 1 {
   339  			*dst = true
   340  			return
   341  		}
   342  	case uint16:
   343  		if s == 1 {
   344  			*dst = true
   345  			return
   346  		}
   347  	case uint32:
   348  		if s == 1 {
   349  			*dst = true
   350  			return
   351  		}
   352  	case uint64:
   353  		if s == 1 {
   354  			*dst = true
   355  			return
   356  		}
   357  	case int:
   358  		if s == 1 {
   359  			*dst = true
   360  			return
   361  		}
   362  	case int8:
   363  		if s == 1 {
   364  			*dst = true
   365  			return
   366  		}
   367  	case int16:
   368  		if s == 1 {
   369  			*dst = true
   370  			return
   371  		}
   372  	case int32:
   373  		if s == 1 {
   374  			*dst = true
   375  			return
   376  		}
   377  	case float32:
   378  		if s == 1 {
   379  			*dst = true
   380  			return
   381  		}
   382  	case float64:
   383  		if s == 1 {
   384  			*dst = true
   385  			return
   386  		}
   387  	}
   388  
   389  	*dst = false
   390  }
   391  
   392  // readIntTag populates the &dst with the tag value if it's of any kind of integer type
   393  func readIntTag(dst *int, tag interface{}) {
   394  	switch n := tag.(type) {
   395  	case int:
   396  		*dst = n
   397  	case int8:
   398  		*dst = int(n)
   399  	case int16:
   400  		*dst = int(n)
   401  	case int32:
   402  		*dst = int(n)
   403  	case int64:
   404  		*dst = int(n)
   405  	case uint:
   406  		*dst = int(n)
   407  	case uint8:
   408  		*dst = int(n)
   409  	case uint16:
   410  		*dst = int(n)
   411  	case uint32:
   412  		*dst = int(n)
   413  	case uint64:
   414  		*dst = int(n)
   415  	}
   416  }