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 }