github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/tracing/tracer_span.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tracing 12 13 import ( 14 "bytes" 15 "encoding/json" 16 "fmt" 17 "regexp" 18 "sort" 19 "strconv" 20 "strings" 21 "sync/atomic" 22 "time" 23 24 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 25 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 26 "github.com/cockroachdb/errors" 27 "github.com/cockroachdb/logtags" 28 proto "github.com/gogo/protobuf/proto" 29 "github.com/gogo/protobuf/types" 30 jaegerjson "github.com/jaegertracing/jaeger/model/json" 31 opentracing "github.com/opentracing/opentracing-go" 32 otlog "github.com/opentracing/opentracing-go/log" 33 "golang.org/x/net/trace" 34 ) 35 36 // spanMeta stores span information that is common to span and spanContext. 37 type spanMeta struct { 38 // A probabilistically unique identifier for a [multi-span] trace. 39 TraceID uint64 40 41 // A probabilistically unique identifier for a span. 42 SpanID uint64 43 } 44 45 type spanContext struct { 46 spanMeta 47 48 // Underlying shadow tracer info and context (optional). 49 shadowTr *shadowTracer 50 shadowCtx opentracing.SpanContext 51 52 // If set, all spans derived from this context are being recorded as a group. 53 recordingGroup *spanGroup 54 recordingType RecordingType 55 56 // The span's associated baggage. 57 Baggage map[string]string 58 } 59 60 const ( 61 // TagPrefix is prefixed to all tags that should be output in SHOW TRACE. 62 TagPrefix = "cockroach." 63 // StatTagPrefix is prefixed to all stats output in span tags. 64 StatTagPrefix = TagPrefix + "stat." 65 ) 66 67 // SpanStats are stats that can be added to a span. 68 type SpanStats interface { 69 proto.Message 70 // Stats returns the stats that the object represents as a map from stat name 71 // to value to be added to span tags. The keys will be prefixed with 72 // StatTagPrefix. 73 Stats() map[string]string 74 } 75 76 var _ opentracing.SpanContext = &spanContext{} 77 78 // ForeachBaggageItem is part of the opentracing.SpanContext interface. 79 func (sc *spanContext) ForeachBaggageItem(handler func(k, v string) bool) { 80 for k, v := range sc.Baggage { 81 if !handler(k, v) { 82 break 83 } 84 } 85 } 86 87 // RecordingType is the type of recording that a span might be performing. 88 type RecordingType int 89 90 const ( 91 // NoRecording means that the span isn't recording. 92 NoRecording RecordingType = iota 93 // SnowballRecording means that remote child spans (generally opened through 94 // RPCs) are also recorded. 95 SnowballRecording 96 // SingleNodeRecording means that only spans on the current node are recorded. 97 SingleNodeRecording 98 ) 99 100 type span struct { 101 spanMeta 102 103 parentSpanID uint64 104 105 tracer *Tracer 106 107 // x/net/trace.Trace instance; nil if not tracing to x/net/trace. 108 netTr trace.Trace 109 // Shadow tracer and span; nil if not using a shadow tracer. 110 shadowTr *shadowTracer 111 shadowSpan opentracing.Span 112 113 operation string 114 startTime time.Time 115 116 // logTags are set to the log tags that were available when this span was 117 // created, so that there's no need to eagerly copy all of those log tags into 118 // this span's tags. If the span's tags are actually requested, these logTags 119 // will be copied out at that point. 120 // Note that these tags have not gone through the log tag -> span tag 121 // remapping procedure; tagName() needs to be called before exposing each 122 // tag's key to a user. 123 logTags *logtags.Buffer 124 125 // Atomic flag used to avoid taking the mutex in the hot path. 126 recording int32 127 128 mu struct { 129 syncutil.Mutex 130 // duration is initialized to -1 and set on Finish(). 131 duration time.Duration 132 133 recordingGroup *spanGroup 134 recordingType RecordingType 135 recordedLogs []opentracing.LogRecord 136 // tags are only set when recording. These are tags that have been added to 137 // this span, and will be appended to the tags in logTags when someone 138 // needs to actually observe the total set of tags that is a part of this 139 // span. 140 // TODO(radu): perhaps we want a recording to capture all the tags (even 141 // those that were set before recording started)? 142 tags opentracing.Tags 143 144 stats SpanStats 145 146 // The span's associated baggage. 147 Baggage map[string]string 148 } 149 } 150 151 var _ opentracing.Span = &span{} 152 153 func (s *span) isRecording() bool { 154 return atomic.LoadInt32(&s.recording) != 0 155 } 156 157 // IsRecording returns true if the span is recording its events. 158 func IsRecording(s opentracing.Span) bool { 159 if _, noop := s.(*noopSpan); noop { 160 return false 161 } 162 return s.(*span).isRecording() 163 } 164 165 func (s *span) enableRecording(group *spanGroup, recType RecordingType) { 166 if group == nil { 167 panic("no spanGroup") 168 } 169 s.mu.Lock() 170 atomic.StoreInt32(&s.recording, 1) 171 s.mu.recordingGroup = group 172 s.mu.recordingType = recType 173 if recType == SnowballRecording { 174 s.setBaggageItemLocked(Snowball, "1") 175 } 176 // Clear any previously recorded logs. 177 s.mu.recordedLogs = nil 178 s.mu.Unlock() 179 180 group.addSpan(s) 181 } 182 183 // StartRecording enables recording on the span. Events from this point forward 184 // are recorded; also, all direct and indirect child spans started from now on 185 // will be part of the same recording. 186 // 187 // Recording is not supported by noop spans; to ensure a real span is always 188 // created, use the Recordable option to StartSpan. 189 // 190 // If recording was already started on this span (either directly or because a 191 // parent span is recording), the old recording is lost. 192 func StartRecording(os opentracing.Span, recType RecordingType) { 193 if recType == NoRecording { 194 panic("StartRecording called with NoRecording") 195 } 196 if _, noop := os.(*noopSpan); noop { 197 panic("StartRecording called on NoopSpan; use the Recordable option for StartSpan") 198 } 199 os.(*span).enableRecording(new(spanGroup), recType) 200 } 201 202 // StopRecording disables recording on this span. Child spans that were created 203 // since recording was started will continue to record until they finish. 204 // 205 // Calling this after StartRecording is not required; the recording will go away 206 // when all the spans finish. 207 // 208 // StopRecording() can be called on a Finish()ed span. 209 func StopRecording(os opentracing.Span) { 210 os.(*span).disableRecording() 211 } 212 213 func (s *span) disableRecording() { 214 s.mu.Lock() 215 atomic.StoreInt32(&s.recording, 0) 216 s.mu.recordingGroup = nil 217 // We test the duration as a way to check if the span has been finished. If it 218 // has, we don't want to do the call below as it might crash (at least if 219 // there's a netTr). 220 if (s.mu.duration == -1) && (s.mu.recordingType == SnowballRecording) { 221 // Clear the Snowball baggage item, assuming that it was set by 222 // enableRecording(). 223 s.setBaggageItemLocked(Snowball, "") 224 } 225 s.mu.Unlock() 226 } 227 228 // IsRecordable returns true if {Start,Stop}Recording() can be called on this 229 // span. 230 // 231 // In other words, this tests if the span is our custom type, and not a noopSpan 232 // or anything else. 233 func IsRecordable(os opentracing.Span) bool { 234 _, isCockroachSpan := os.(*span) 235 return isCockroachSpan 236 } 237 238 // Recording represents a group of RecordedSpans, as returned by GetRecording. 239 type Recording []RecordedSpan 240 241 // GetRecording retrieves the current recording, if the span has recording 242 // enabled. This can be called while spans that are part of the record are 243 // still open; it can run concurrently with operations on those spans. 244 func GetRecording(os opentracing.Span) Recording { 245 if _, noop := os.(*noopSpan); noop { 246 return nil 247 } 248 s := os.(*span) 249 if !s.isRecording() { 250 return nil 251 } 252 s.mu.Lock() 253 group := s.mu.recordingGroup 254 s.mu.Unlock() 255 if group == nil { 256 return nil 257 } 258 return group.getSpans() 259 } 260 261 type traceLogData struct { 262 opentracing.LogRecord 263 depth int 264 // timeSincePrev represents the duration since the previous log line (previous in the 265 // set of log lines that this is part of). This is always computed relative to a log line 266 // from the same span, except for start of span in which case the duration is computed relative 267 // to the last log in the parent occurring before this start. For example: 268 // start span A 269 // log 1 // duration relative to "start span A" 270 // start span B // duration relative to "log 1" 271 // log 2 // duration relative to "start span B" 272 // log 3 // duration relative to "log 1" 273 timeSincePrev time.Duration 274 } 275 276 // String formats the given spans for human consumption, showing the 277 // relationship using nesting and times as both relative to the previous event 278 // and cumulative. Logs from the same span are kept together, before logs from 279 // children spans. Each log line show the time since the beginning of the trace 280 // and since the previous log line. Span starts are shown with special "=== 281 // <operation>" lines. For a span start, the time since the relative log line 282 // can be negative when the span start follows a message from the parent that 283 // was generated after the child span started (or even after the child 284 // finished). 285 // 286 // TODO(andrei): this should be unified with 287 // SessionTracing.GenerateSessionTraceVTable. 288 func (r Recording) String() string { 289 var logs []traceLogData 290 var start time.Time 291 for _, sp := range r { 292 if sp.ParentSpanID == 0 { 293 if start == (time.Time{}) { 294 start = sp.StartTime 295 } 296 logs = append(logs, r.visitSpan(sp, 0 /* depth */)...) 297 } 298 } 299 300 var buf strings.Builder 301 for _, entry := range logs { 302 fmt.Fprintf(&buf, "% 10.3fms % 10.3fms%s", 303 1000*entry.Timestamp.Sub(start).Seconds(), 304 1000*entry.timeSincePrev.Seconds(), 305 strings.Repeat(" ", entry.depth+1)) 306 for i, f := range entry.Fields { 307 if i != 0 { 308 buf.WriteByte(' ') 309 } 310 fmt.Fprintf(&buf, "%s:%v", f.Key(), f.Value()) 311 } 312 buf.WriteByte('\n') 313 } 314 return buf.String() 315 } 316 317 // FindLogMessage returns the first log message in the recording that matches 318 // the given regexp. The bool return value is true if such a message is found. 319 func (r Recording) FindLogMessage(pattern string) (string, bool) { 320 re := regexp.MustCompile(pattern) 321 for _, sp := range r { 322 for _, l := range sp.Logs { 323 msg := l.Msg() 324 if re.MatchString(msg) { 325 return msg, true 326 } 327 } 328 } 329 return "", false 330 } 331 332 // visitSpan returns the log messages for sp, and all of sp's children. 333 // 334 // All messages from a span are kept together. Sibling spans are ordered within 335 // the parent in their start order. 336 func (r Recording) visitSpan(sp RecordedSpan, depth int) []traceLogData { 337 ownLogs := make([]traceLogData, 0, len(sp.Logs)+1) 338 339 conv := func(l opentracing.LogRecord, ref time.Time) traceLogData { 340 var timeSincePrev time.Duration 341 if ref != (time.Time{}) { 342 timeSincePrev = l.Timestamp.Sub(ref) 343 } 344 return traceLogData{ 345 LogRecord: l, 346 depth: depth, 347 timeSincePrev: timeSincePrev, 348 } 349 } 350 351 // Add a log line representing the start of the span. 352 lr := opentracing.LogRecord{ 353 Timestamp: sp.StartTime, 354 Fields: []otlog.Field{otlog.String("=== operation", sp.Operation)}, 355 } 356 if len(sp.Tags) > 0 { 357 tags := make([]string, 0, len(sp.Tags)) 358 for k := range sp.Tags { 359 tags = append(tags, k) 360 } 361 sort.Strings(tags) 362 for _, k := range tags { 363 lr.Fields = append(lr.Fields, otlog.String(k, sp.Tags[k])) 364 } 365 } 366 ownLogs = append(ownLogs, conv( 367 lr, 368 // ref - this entries timeSincePrev will be computed when we merge it into the parent 369 time.Time{})) 370 371 for _, l := range sp.Logs { 372 lr := opentracing.LogRecord{ 373 Timestamp: l.Time, 374 Fields: make([]otlog.Field, len(l.Fields)), 375 } 376 for i, f := range l.Fields { 377 lr.Fields[i] = otlog.String(f.Key, f.Value) 378 } 379 lastLog := ownLogs[len(ownLogs)-1] 380 ownLogs = append(ownLogs, conv(lr, lastLog.Timestamp)) 381 } 382 383 childSpans := make([][]traceLogData, 0) 384 for _, osp := range r { 385 if osp.ParentSpanID != sp.SpanID { 386 continue 387 } 388 childSpans = append(childSpans, r.visitSpan(osp, depth+1)) 389 } 390 391 // Merge ownLogs with childSpans. 392 mergedLogs := make([]traceLogData, 0, len(ownLogs)) 393 timeMax := time.Date(2200, 0, 0, 0, 0, 0, 0, time.UTC) 394 i, j := 0, 0 395 var lastTimestamp time.Time 396 for i < len(ownLogs) || j < len(childSpans) { 397 if len(mergedLogs) > 0 { 398 lastTimestamp = mergedLogs[len(mergedLogs)-1].Timestamp 399 } 400 nextLog, nextChild := timeMax, timeMax 401 if i < len(ownLogs) { 402 nextLog = ownLogs[i].Timestamp 403 } 404 if j < len(childSpans) { 405 nextChild = childSpans[j][0].Timestamp 406 } 407 if nextLog.After(nextChild) { 408 // Fill in timeSincePrev for the first one of the child's entries. 409 if lastTimestamp != (time.Time{}) { 410 childSpans[j][0].timeSincePrev = childSpans[j][0].Timestamp.Sub(lastTimestamp) 411 } 412 mergedLogs = append(mergedLogs, childSpans[j]...) 413 lastTimestamp = childSpans[j][0].Timestamp 414 j++ 415 } else { 416 mergedLogs = append(mergedLogs, ownLogs[i]) 417 lastTimestamp = ownLogs[i].Timestamp 418 i++ 419 } 420 } 421 422 return mergedLogs 423 } 424 425 // ToJaegerJSON returns the trace as a JSON that can be imported into Jaeger for 426 // visualization. 427 // 428 // The format is described here: https://github.com/jaegertracing/jaeger-ui/issues/381#issuecomment-494150826 429 // 430 // The statement is passed in so it can be included in the trace. 431 func (r Recording) ToJaegerJSON(stmt string) (string, error) { 432 if len(r) == 0 { 433 return "", nil 434 } 435 436 cpy := make(Recording, len(r)) 437 copy(cpy, r) 438 r = cpy 439 tagsCopy := make(map[string]string) 440 for k, v := range r[0].Tags { 441 tagsCopy[k] = v 442 } 443 tagsCopy["statement"] = stmt 444 r[0].Tags = tagsCopy 445 446 toJaegerSpanID := func(spanID uint64) jaegerjson.SpanID { 447 return jaegerjson.SpanID(strconv.FormatUint(spanID, 10)) 448 } 449 450 // Each span in Jaeger belongs to a "process" that generated it. Spans 451 // belonging to different colors are colored differently in Jaeger. We're 452 // going to map our different nodes to different processes. 453 processes := make(map[jaegerjson.ProcessID]jaegerjson.Process) 454 // getProcessID figures out what "process" a span belongs to. It looks for an 455 // "node: <node id>" tag. The processes map is populated with an entry for every 456 // node present in the trace. 457 getProcessID := func(sp RecordedSpan) jaegerjson.ProcessID { 458 node := "unknown node" 459 for k, v := range sp.Tags { 460 if k == "node" { 461 node = fmt.Sprintf("node %s", v) 462 break 463 } 464 } 465 pid := jaegerjson.ProcessID(node) 466 if _, ok := processes[pid]; !ok { 467 processes[pid] = jaegerjson.Process{ 468 ServiceName: node, 469 Tags: nil, 470 } 471 } 472 return pid 473 } 474 475 var t jaegerjson.Trace 476 t.TraceID = jaegerjson.TraceID(strconv.FormatUint(r[0].TraceID, 10)) 477 t.Processes = processes 478 479 for _, sp := range r { 480 var s jaegerjson.Span 481 482 s.TraceID = t.TraceID 483 s.Duration = uint64(sp.Duration.Microseconds()) 484 s.StartTime = uint64(sp.StartTime.UnixNano() / 1000) 485 s.SpanID = toJaegerSpanID(sp.SpanID) 486 s.OperationName = sp.Operation 487 s.ProcessID = getProcessID(sp) 488 489 if sp.ParentSpanID != 0 { 490 s.References = []jaegerjson.Reference{{ 491 RefType: jaegerjson.ChildOf, 492 TraceID: s.TraceID, 493 SpanID: toJaegerSpanID(sp.ParentSpanID), 494 }} 495 } 496 497 for k, v := range sp.Tags { 498 s.Tags = append(s.Tags, jaegerjson.KeyValue{ 499 Key: k, 500 Value: v, 501 Type: "STRING", 502 }) 503 } 504 for _, l := range sp.Logs { 505 jl := jaegerjson.Log{Timestamp: uint64(l.Time.UnixNano() / 1000)} 506 for _, field := range l.Fields { 507 jl.Fields = append(jl.Fields, jaegerjson.KeyValue{ 508 Key: field.Key, 509 Value: field.Value, 510 Type: "STRING", 511 }) 512 } 513 s.Logs = append(s.Logs, jl) 514 } 515 t.Spans = append(t.Spans, s) 516 } 517 518 data := TraceCollection{ 519 Data: []jaegerjson.Trace{t}, 520 // Add a comment that will show-up at the top of the JSON file, is someone opens the file. 521 // NOTE: This comment is scarce on newlines because they appear as \n in the 522 // generated file doing more harm than good. 523 Comment: fmt.Sprintf(`This is a trace for SQL statement: %s 524 This trace can be imported into Jaeger for visualization. From the Jaeger Search screen, select JSON File. 525 Jaeger can be started using docker with: docker run -d --name jaeger -p 16686:16686 jaegertracing/all-in-one:1.17 526 The UI can then be accessed at http://localhost:16686/search`, 527 stmt), 528 } 529 json, err := json.MarshalIndent(data, "" /* prefix */, "\t" /* indent */) 530 if err != nil { 531 return "", err 532 } 533 return string(json), nil 534 } 535 536 // TraceCollection is the format accepted by the Jaegar upload feature, as per 537 // https://github.com/jaegertracing/jaeger-ui/issues/381#issuecomment-494150826 538 type TraceCollection struct { 539 // Comment is a dummy field we use to put instructions on how to load the trace. 540 Comment string `json:"_comment"` 541 Data []jaegerjson.Trace `json:"data"` 542 } 543 544 // ImportRemoteSpans adds RecordedSpan data to the recording of the given span; 545 // these spans will be part of the result of GetRecording. Used to import 546 // recorded traces from other nodes. 547 func ImportRemoteSpans(os opentracing.Span, remoteSpans []RecordedSpan) error { 548 s := os.(*span) 549 s.mu.Lock() 550 group := s.mu.recordingGroup 551 s.mu.Unlock() 552 if group == nil { 553 return errors.New("adding Raw Spans to a span that isn't recording") 554 } 555 group.Lock() 556 group.remoteSpans = append(group.remoteSpans, remoteSpans...) 557 group.Unlock() 558 return nil 559 } 560 561 // IsBlackHoleSpan returns true if events for this span are just dropped. This 562 // is the case when tracing is disabled and we're not recording. Tracing clients 563 // can use this method to figure out if they can short-circuit some 564 // tracing-related work that would be discarded anyway. 565 func IsBlackHoleSpan(s opentracing.Span) bool { 566 // There are two types of black holes: instances of noopSpan and, when tracing 567 // is disabled, real spans that are not recording. 568 if _, noop := s.(*noopSpan); noop { 569 return true 570 } 571 sp := s.(*span) 572 return !sp.isRecording() && sp.netTr == nil && sp.shadowTr == nil 573 } 574 575 // IsNoopContext returns true if the span context is from a "no-op" span. If 576 // this is true, any span derived from this context will be a "black hole span". 577 func IsNoopContext(spanCtx opentracing.SpanContext) bool { 578 _, noop := spanCtx.(noopSpanContext) 579 return noop 580 } 581 582 // SetSpanStats sets the stats on a span. stats.Stats() will also be added to 583 // the span tags. 584 func SetSpanStats(os opentracing.Span, stats SpanStats) { 585 s := os.(*span) 586 s.mu.Lock() 587 s.mu.stats = stats 588 for name, value := range stats.Stats() { 589 s.setTagInner(StatTagPrefix+name, value, true /* locked */) 590 } 591 s.mu.Unlock() 592 } 593 594 // Finish is part of the opentracing.Span interface. 595 func (s *span) Finish() { 596 s.FinishWithOptions(opentracing.FinishOptions{}) 597 } 598 599 // FinishWithOptions is part of the opentracing.Span interface. 600 func (s *span) FinishWithOptions(opts opentracing.FinishOptions) { 601 finishTime := opts.FinishTime 602 if finishTime.IsZero() { 603 finishTime = time.Now() 604 } 605 s.mu.Lock() 606 s.mu.duration = finishTime.Sub(s.startTime) 607 s.mu.Unlock() 608 if s.shadowTr != nil { 609 s.shadowSpan.Finish() 610 } 611 if s.netTr != nil { 612 s.netTr.Finish() 613 } 614 } 615 616 // Context is part of the opentracing.Span interface. 617 // 618 // TODO(andrei, radu): Should this return noopSpanContext for a Recordable span 619 // that's not currently recording? That might save work and allocations when 620 // creating child spans. 621 func (s *span) Context() opentracing.SpanContext { 622 s.mu.Lock() 623 defer s.mu.Unlock() 624 baggageCopy := make(map[string]string, len(s.mu.Baggage)) 625 for k, v := range s.mu.Baggage { 626 baggageCopy[k] = v 627 } 628 sc := &spanContext{ 629 spanMeta: s.spanMeta, 630 Baggage: baggageCopy, 631 } 632 if s.shadowTr != nil { 633 sc.shadowTr = s.shadowTr 634 sc.shadowCtx = s.shadowSpan.Context() 635 } 636 637 if s.isRecording() { 638 sc.recordingGroup = s.mu.recordingGroup 639 sc.recordingType = s.mu.recordingType 640 } 641 return sc 642 } 643 644 // SetOperationName is part of the opentracing.Span interface. 645 func (s *span) SetOperationName(operationName string) opentracing.Span { 646 if s.shadowTr != nil { 647 s.shadowSpan.SetOperationName(operationName) 648 } 649 s.operation = operationName 650 return s 651 } 652 653 // SetTag is part of the opentracing.Span interface. 654 func (s *span) SetTag(key string, value interface{}) opentracing.Span { 655 return s.setTagInner(key, value, false /* locked */) 656 } 657 658 func (s *span) setTagInner(key string, value interface{}, locked bool) opentracing.Span { 659 if s.shadowTr != nil { 660 s.shadowSpan.SetTag(key, value) 661 } 662 if s.netTr != nil { 663 s.netTr.LazyPrintf("%s:%v", key, value) 664 } 665 // The internal tags will be used if we start a recording on this span. 666 if !locked { 667 s.mu.Lock() 668 } 669 if s.mu.tags == nil { 670 s.mu.tags = make(opentracing.Tags) 671 } 672 s.mu.tags[key] = value 673 if !locked { 674 s.mu.Unlock() 675 } 676 return s 677 } 678 679 // LogFields is part of the opentracing.Span interface. 680 func (s *span) LogFields(fields ...otlog.Field) { 681 if s.shadowTr != nil { 682 s.shadowSpan.LogFields(fields...) 683 } 684 if s.netTr != nil { 685 // TODO(radu): when LightStep supports arbitrary fields, we should make 686 // the formatting of the message consistent with that. Until then we treat 687 // legacy events that just have an "event" key specially. 688 if len(fields) == 1 && fields[0].Key() == LogMessageField { 689 s.netTr.LazyPrintf("%s", fields[0].Value()) 690 } else { 691 var buf bytes.Buffer 692 for i, f := range fields { 693 if i > 0 { 694 buf.WriteByte(' ') 695 } 696 fmt.Fprintf(&buf, "%s:%v", f.Key(), f.Value()) 697 } 698 699 s.netTr.LazyPrintf("%s", buf.String()) 700 } 701 } 702 if s.isRecording() { 703 s.mu.Lock() 704 if len(s.mu.recordedLogs) < maxLogsPerSpan { 705 s.mu.recordedLogs = append(s.mu.recordedLogs, opentracing.LogRecord{ 706 Timestamp: time.Now(), 707 Fields: fields, 708 }) 709 } 710 s.mu.Unlock() 711 } 712 } 713 714 // LogKV is part of the opentracing.Span interface. 715 func (s *span) LogKV(alternatingKeyValues ...interface{}) { 716 fields, err := otlog.InterleavedKVToFields(alternatingKeyValues...) 717 if err != nil { 718 s.LogFields(otlog.Error(err), otlog.String("function", "LogKV")) 719 return 720 } 721 s.LogFields(fields...) 722 } 723 724 // SetBaggageItem is part of the opentracing.Span interface. 725 func (s *span) SetBaggageItem(restrictedKey, value string) opentracing.Span { 726 s.mu.Lock() 727 defer s.mu.Unlock() 728 return s.setBaggageItemLocked(restrictedKey, value) 729 } 730 731 func (s *span) setBaggageItemLocked(restrictedKey, value string) opentracing.Span { 732 if oldVal, ok := s.mu.Baggage[restrictedKey]; ok && oldVal == value { 733 // No-op. 734 return s 735 } 736 if s.mu.Baggage == nil { 737 s.mu.Baggage = make(map[string]string) 738 } 739 s.mu.Baggage[restrictedKey] = value 740 741 if s.shadowTr != nil { 742 s.shadowSpan.SetBaggageItem(restrictedKey, value) 743 } 744 // Also set a tag so it shows up in the Lightstep UI or x/net/trace. 745 s.setTagInner(restrictedKey, value, true /* locked */) 746 return s 747 } 748 749 // BaggageItem is part of the opentracing.Span interface. 750 func (s *span) BaggageItem(restrictedKey string) string { 751 s.mu.Lock() 752 defer s.mu.Unlock() 753 return s.mu.Baggage[restrictedKey] 754 } 755 756 // Tracer is part of the opentracing.Span interface. 757 func (s *span) Tracer() opentracing.Tracer { 758 return s.tracer 759 } 760 761 // LogEvent is part of the opentracing.Span interface. Deprecated. 762 func (s *span) LogEvent(event string) { 763 s.LogFields(otlog.String(LogMessageField, event)) 764 } 765 766 // LogEventWithPayload is part of the opentracing.Span interface. Deprecated. 767 func (s *span) LogEventWithPayload(event string, payload interface{}) { 768 s.LogFields(otlog.String(LogMessageField, event), otlog.Object("payload", payload)) 769 } 770 771 // Log is part of the opentracing.Span interface. Deprecated. 772 func (s *span) Log(data opentracing.LogData) { 773 panic("unimplemented") 774 } 775 776 // getRecording returns the span's recording. 777 func (s *span) getRecording() RecordedSpan { 778 s.mu.Lock() 779 defer s.mu.Unlock() 780 781 rs := RecordedSpan{ 782 TraceID: s.TraceID, 783 SpanID: s.SpanID, 784 ParentSpanID: s.parentSpanID, 785 Operation: s.operation, 786 StartTime: s.startTime, 787 Duration: s.mu.duration, 788 } 789 790 addTag := func(k, v string) { 791 if rs.Tags == nil { 792 rs.Tags = make(map[string]string) 793 } 794 rs.Tags[k] = v 795 } 796 797 switch rs.Duration { 798 case -1: 799 // -1 indicates an unfinished span. For a recording it's better to put some 800 // duration in it, otherwise tools get confused. For example, we export 801 // recordings to Jaeger, and spans with a zero duration don't look nice. 802 rs.Duration = timeutil.Now().Sub(rs.StartTime) 803 addTag("unfinished", "") 804 } 805 806 if s.mu.stats != nil { 807 stats, err := types.MarshalAny(s.mu.stats) 808 if err != nil { 809 panic(err) 810 } 811 rs.Stats = stats 812 } 813 814 if len(s.mu.Baggage) > 0 { 815 rs.Baggage = make(map[string]string) 816 for k, v := range s.mu.Baggage { 817 rs.Baggage[k] = v 818 } 819 } 820 if s.logTags != nil { 821 tags := s.logTags.Get() 822 for i := range tags { 823 tag := &tags[i] 824 addTag(tagName(tag.Key()), tag.ValueStr()) 825 } 826 } 827 if len(s.mu.tags) > 0 { 828 for k, v := range s.mu.tags { 829 // We encode the tag values as strings. 830 addTag(k, fmt.Sprint(v)) 831 } 832 } 833 rs.Logs = make([]LogRecord, len(s.mu.recordedLogs)) 834 for i, r := range s.mu.recordedLogs { 835 rs.Logs[i].Time = r.Timestamp 836 rs.Logs[i].Fields = make([]LogRecord_Field, len(r.Fields)) 837 for j, f := range r.Fields { 838 rs.Logs[i].Fields[j] = LogRecord_Field{ 839 Key: f.Key(), 840 Value: fmt.Sprint(f.Value()), 841 } 842 } 843 } 844 845 return rs 846 } 847 848 // spanGroup keeps track of all the spans that are being recorded as a group (i.e. 849 // the span for which recording was enabled and all direct or indirect child 850 // spans since then). 851 type spanGroup struct { 852 syncutil.Mutex 853 // spans keeps track of all the local spans. A span is inserted in this slice 854 // as soon as it is opened; the first element is the span passed to 855 // StartRecording(). 856 spans []*span 857 // remoteSpans stores spans obtained from another host that we want to associate 858 // with the record for this group. 859 remoteSpans Recording 860 } 861 862 func (ss *spanGroup) addSpan(s *span) { 863 ss.Lock() 864 ss.spans = append(ss.spans, s) 865 ss.Unlock() 866 } 867 868 // getSpans returns all the local and remote spans accumulated in this group. 869 // The first result is the first local span - i.e. the span originally passed to 870 // StartRecording(). 871 func (ss *spanGroup) getSpans() Recording { 872 ss.Lock() 873 spans := ss.spans 874 remoteSpans := ss.remoteSpans 875 ss.Unlock() 876 877 result := make(Recording, 0, len(spans)+len(remoteSpans)) 878 for _, s := range spans { 879 rs := s.getRecording() 880 result = append(result, rs) 881 } 882 return append(result, remoteSpans...) 883 } 884 885 type noopSpanContext struct{} 886 887 var _ opentracing.SpanContext = noopSpanContext{} 888 889 func (n noopSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} 890 891 type noopSpan struct { 892 tracer *Tracer 893 } 894 895 var _ opentracing.Span = &noopSpan{} 896 897 func (n *noopSpan) Context() opentracing.SpanContext { return noopSpanContext{} } 898 func (n *noopSpan) BaggageItem(key string) string { return "" } 899 func (n *noopSpan) SetTag(key string, value interface{}) opentracing.Span { return n } 900 func (n *noopSpan) Finish() {} 901 func (n *noopSpan) FinishWithOptions(opts opentracing.FinishOptions) {} 902 func (n *noopSpan) SetOperationName(operationName string) opentracing.Span { return n } 903 func (n *noopSpan) Tracer() opentracing.Tracer { return n.tracer } 904 func (n *noopSpan) LogFields(fields ...otlog.Field) {} 905 func (n *noopSpan) LogKV(keyVals ...interface{}) {} 906 func (n *noopSpan) LogEvent(event string) {} 907 func (n *noopSpan) LogEventWithPayload(event string, payload interface{}) {} 908 func (n *noopSpan) Log(data opentracing.LogData) {} 909 910 func (n *noopSpan) SetBaggageItem(key, val string) opentracing.Span { 911 if key == Snowball { 912 panic("attempting to set Snowball on a noop span; use the Recordable option to StartSpan") 913 } 914 return n 915 }