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 }