github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/tracing/tracer.go (about) 1 // Copyright 2015 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 "context" 15 "fmt" 16 "math/rand" 17 "regexp" 18 "sort" 19 "strconv" 20 "strings" 21 "sync/atomic" 22 "time" 23 "unsafe" 24 25 "github.com/cockroachdb/cockroach/pkg/settings" 26 "github.com/cockroachdb/cockroach/pkg/util/caller" 27 "github.com/cockroachdb/cockroach/pkg/util/envutil" 28 "github.com/cockroachdb/errors" 29 "github.com/cockroachdb/logtags" 30 opentracing "github.com/opentracing/opentracing-go" 31 "golang.org/x/net/trace" 32 ) 33 34 // Snowball is set as Baggage on traces which are used for snowball tracing. 35 const Snowball = "sb" 36 37 // maxLogsPerSpan limits the number of logs in a Span; use a comfortable limit. 38 const maxLogsPerSpan = 1000 39 40 // These constants are used to form keys to represent tracing context 41 // information in carriers supporting opentracing.HTTPHeaders format. 42 const ( 43 prefixTracerState = "crdb-tracer-" 44 prefixBaggage = "crdb-baggage-" 45 // prefixShadow is prepended to the keys for the context of the shadow tracer 46 // (e.g. LightStep). 47 prefixShadow = "crdb-shadow-" 48 49 fieldNameTraceID = prefixTracerState + "traceid" 50 fieldNameSpanID = prefixTracerState + "spanid" 51 // fieldNameShadow is the name of the shadow tracer. 52 fieldNameShadowType = prefixTracerState + "shadowtype" 53 ) 54 55 var enableNetTrace = settings.RegisterPublicBoolSetting( 56 "trace.debug.enable", 57 "if set, traces for recent requests can be seen in the /debug page", 58 false, 59 ) 60 61 var lightstepToken = settings.RegisterPublicStringSetting( 62 "trace.lightstep.token", 63 "if set, traces go to Lightstep using this token", 64 envutil.EnvOrDefaultString("COCKROACH_TEST_LIGHTSTEP_TOKEN", ""), 65 ) 66 67 var zipkinCollector = settings.RegisterPublicStringSetting( 68 "trace.zipkin.collector", 69 "if set, traces go to the given Zipkin instance (example: '127.0.0.1:9411'); ignored if trace.lightstep.token is set", 70 envutil.EnvOrDefaultString("COCKROACH_TEST_ZIPKIN_COLLECTOR", ""), 71 ) 72 73 // Tracer is our own custom implementation of opentracing.Tracer. It supports: 74 // 75 // - forwarding events to x/net/trace instances 76 // 77 // - recording traces. Recording is started automatically for spans that have 78 // the Snowball baggage and can be started explicitly as well. Recorded 79 // events can be retrieved at any time. 80 // 81 // - lightstep traces. This is implemented by maintaining a "shadow" lightstep 82 // span inside each of our spans. 83 // 84 // Even when tracing is disabled, we still use this Tracer (with x/net/trace and 85 // lightstep disabled) because of its recording capability (snowball 86 // tracing needs to work in all cases). 87 // 88 // Tracer is currently stateless so we could have a single instance; however, 89 // this won't be the case if the cluster settings move away from using global 90 // state. 91 type Tracer struct { 92 // Preallocated noopSpan, used to avoid creating spans when we are not using 93 // x/net/trace or lightstep and we are not recording. 94 noopSpan noopSpan 95 96 // If forceRealSpans is set, this Tracer will always create real spans (never 97 // noopSpans), regardless of the recording or lightstep configuration. Used 98 // by tests for situations when they need to indirectly create spans and don't 99 // have the option of passing the Recordable option to their constructor. 100 forceRealSpans bool 101 102 // True if tracing to the debug/requests endpoint. Accessed via t.useNetTrace(). 103 _useNetTrace int32 // updated atomically 104 105 // Pointer to shadowTracer, if using one. 106 shadowTracer unsafe.Pointer 107 } 108 109 var _ opentracing.Tracer = &Tracer{} 110 111 // NewTracer creates a Tracer. It initially tries to run with minimal overhead 112 // and collects essentially nothing; use Configure() to enable various tracing 113 // backends. 114 func NewTracer() *Tracer { 115 t := &Tracer{} 116 t.noopSpan.tracer = t 117 return t 118 } 119 120 // Configure sets up the Tracer according to the cluster settings (and keeps 121 // it updated if they change). 122 func (t *Tracer) Configure(sv *settings.Values) { 123 reconfigure := func() { 124 if lsToken := lightstepToken.Get(sv); lsToken != "" { 125 t.setShadowTracer(createLightStepTracer(lsToken)) 126 } else if zipkinAddr := zipkinCollector.Get(sv); zipkinAddr != "" { 127 t.setShadowTracer(createZipkinTracer(zipkinAddr)) 128 } else { 129 t.setShadowTracer(nil, nil) 130 } 131 var nt int32 132 if enableNetTrace.Get(sv) { 133 nt = 1 134 } 135 atomic.StoreInt32(&t._useNetTrace, nt) 136 } 137 138 reconfigure() 139 140 enableNetTrace.SetOnChange(sv, reconfigure) 141 lightstepToken.SetOnChange(sv, reconfigure) 142 zipkinCollector.SetOnChange(sv, reconfigure) 143 } 144 145 func (t *Tracer) useNetTrace() bool { 146 return atomic.LoadInt32(&t._useNetTrace) != 0 147 } 148 149 // Close cleans up any resources associated with a Tracer. 150 func (t *Tracer) Close() { 151 // Clean up any shadow tracer. 152 t.setShadowTracer(nil, nil) 153 } 154 155 // SetForceRealSpans sets forceRealSpans option to v and returns the previous 156 // value. 157 func (t *Tracer) SetForceRealSpans(v bool) bool { 158 prevVal := t.forceRealSpans 159 t.forceRealSpans = v 160 return prevVal 161 } 162 163 func (t *Tracer) setShadowTracer(manager shadowTracerManager, tr opentracing.Tracer) { 164 var shadow *shadowTracer 165 if manager != nil { 166 shadow = &shadowTracer{ 167 Tracer: tr, 168 manager: manager, 169 } 170 } 171 if old := atomic.SwapPointer(&t.shadowTracer, unsafe.Pointer(shadow)); old != nil { 172 (*shadowTracer)(old).Close() 173 } 174 } 175 176 func (t *Tracer) getShadowTracer() *shadowTracer { 177 return (*shadowTracer)(atomic.LoadPointer(&t.shadowTracer)) 178 } 179 180 type recordableOption struct{} 181 182 // Apply is part of the opentracing.StartSpanOption interface. 183 func (recordableOption) Apply(*opentracing.StartSpanOptions) {} 184 185 // Recordable is a StartSpanOption that forces creation of a real span. 186 // 187 // When tracing is disabled all spans are noopSpans; these spans aren't 188 // capable of recording, so this option should be passed to StartSpan if the 189 // caller wants to be able to call StartRecording on the resulting span. 190 var Recordable opentracing.StartSpanOption = recordableOption{} 191 192 // StartSpan is part of the opentracing.Tracer interface. 193 // 194 // Avoid using this method in favor of Tracer.StartRootSpan() or 195 // tracing.StartChildSpan() (or higher-level methods like EnsureContext() or 196 // AmbientContext.AnnotateCtxWithSpan()) when possible. Those are more efficient 197 // because they don't have to use the StartSpanOption interface and so a bunch 198 // of allocations are avoided. However, we need to implement this standard 199 // StartSpan() too because grpc integrates with a generic opentracing.Tracer. 200 func (t *Tracer) StartSpan( 201 operationName string, opts ...opentracing.StartSpanOption, 202 ) opentracing.Span { 203 // Fast paths to avoid the allocation of StartSpanOptions below when tracing 204 // is disabled: if we have no options or a single SpanReference (the common 205 // case) with a noop context, return a noop span now. 206 if len(opts) == 1 { 207 if o, ok := opts[0].(opentracing.SpanReference); ok { 208 if IsNoopContext(o.ReferencedContext) { 209 return &t.noopSpan 210 } 211 } 212 } 213 214 shadowTr := t.getShadowTracer() 215 216 if len(opts) == 0 && !t.useNetTrace() && shadowTr == nil && !t.forceRealSpans { 217 return &t.noopSpan 218 } 219 220 var sso opentracing.StartSpanOptions 221 var recordable bool 222 var logTags *logtags.Buffer 223 for _, o := range opts { 224 switch to := o.(type) { 225 case recordableOption: 226 recordable = true 227 case *logTagsOption: 228 // NOTE: calling logTagsOption.Apply() would be functionally equivalent, 229 // but less efficient. 230 logTags = (*logtags.Buffer)(to) 231 default: 232 o.Apply(&sso) 233 } 234 } 235 236 var hasParent bool 237 var parentType opentracing.SpanReferenceType 238 var parentCtx *spanContext 239 var recordingGroup *spanGroup 240 var recordingType RecordingType 241 242 for _, r := range sso.References { 243 if r.Type != opentracing.ChildOfRef && r.Type != opentracing.FollowsFromRef { 244 continue 245 } 246 if r.ReferencedContext == nil { 247 continue 248 } 249 if IsNoopContext(r.ReferencedContext) { 250 continue 251 } 252 hasParent = true 253 parentType = r.Type 254 parentCtx = r.ReferencedContext.(*spanContext) 255 if parentCtx.recordingGroup != nil { 256 recordingGroup = parentCtx.recordingGroup 257 recordingType = parentCtx.recordingType 258 } else if parentCtx.Baggage[Snowball] != "" { 259 // Automatically enable recording if we have the Snowball baggage item. 260 recordingGroup = new(spanGroup) 261 recordingType = SnowballRecording 262 } 263 // TODO(radu): can we do something for multiple references? 264 break 265 } 266 if hasParent { 267 // We use the parent's shadow tracer, to avoid inconsistency inside a 268 // trace when the shadow tracer changes. 269 shadowTr = parentCtx.shadowTr 270 } 271 272 // If tracing is disabled, the Recordable option wasn't passed, and we're not 273 // part of a recording or snowball trace, avoid overhead and return a noop 274 // span. 275 if !recordable && recordingGroup == nil && shadowTr == nil && !t.useNetTrace() && !t.forceRealSpans { 276 return &t.noopSpan 277 } 278 279 s := &span{ 280 tracer: t, 281 operation: operationName, 282 startTime: sso.StartTime, 283 logTags: logTags, 284 } 285 if s.startTime.IsZero() { 286 s.startTime = time.Now() 287 } 288 s.mu.duration = -1 289 290 if !hasParent { 291 // No parent Span; allocate new trace id. 292 s.TraceID = uint64(rand.Int63()) 293 } else { 294 s.TraceID = parentCtx.TraceID 295 } 296 s.SpanID = uint64(rand.Int63()) 297 298 // Start recording if necessary. 299 if recordingGroup != nil { 300 s.enableRecording(recordingGroup, recordingType) 301 } 302 303 if t.useNetTrace() { 304 s.netTr = trace.New("tracing", operationName) 305 s.netTr.SetMaxEvents(maxLogsPerSpan) 306 } 307 308 if hasParent { 309 s.parentSpanID = parentCtx.SpanID 310 // Copy baggage from parent. 311 if l := len(parentCtx.Baggage); l > 0 { 312 s.mu.Baggage = make(map[string]string, l) 313 for k, v := range parentCtx.Baggage { 314 s.mu.Baggage[k] = v 315 } 316 } 317 } 318 319 for k, v := range sso.Tags { 320 s.SetTag(k, v) 321 } 322 323 // Copy baggage items to tags so they show up in the shadow tracer UI, 324 // x/net/trace, or recordings. 325 for k, v := range s.mu.Baggage { 326 s.SetTag(k, v) 327 } 328 329 if shadowTr != nil { 330 var parentShadowCtx opentracing.SpanContext 331 if hasParent { 332 parentShadowCtx = parentCtx.shadowCtx 333 } 334 linkShadowSpan(s, shadowTr, parentShadowCtx, parentType) 335 } 336 337 return s 338 } 339 340 // RecordableOpt specifies whether a root span should be recordable. 341 type RecordableOpt bool 342 343 const ( 344 // RecordableSpan means that the root span will be recordable. This means that 345 // a real span will be created (and so it carries a cost). 346 RecordableSpan RecordableOpt = true 347 // NonRecordableSpan means that the root span will not be recordable. This 348 // means that the static noop span might be returned. 349 NonRecordableSpan RecordableOpt = false 350 ) 351 352 // AlwaysTrace returns true if operations should be traced regardless of the 353 // context. 354 func (t *Tracer) AlwaysTrace() bool { 355 shadowTracer := t.getShadowTracer() 356 return t.useNetTrace() || shadowTracer != nil || t.forceRealSpans 357 } 358 359 // StartRootSpan creates a root span. This is functionally equivalent to: 360 // parentSpan.Tracer().(*Tracer).StartSpan(opName, LogTags(...), [Recordable]) 361 // Compared to that, it's more efficient, particularly in terms of memory 362 // allocations because the opentracing.StartSpanOption interface is not used. 363 // 364 // logTags can be nil. 365 func (t *Tracer) StartRootSpan( 366 opName string, logTags *logtags.Buffer, recordable RecordableOpt, 367 ) opentracing.Span { 368 // In the usual case, we return noopSpan. 369 if !t.AlwaysTrace() && recordable == NonRecordableSpan { 370 return &t.noopSpan 371 } 372 373 s := &span{ 374 spanMeta: spanMeta{ 375 TraceID: uint64(rand.Int63()), 376 SpanID: uint64(rand.Int63()), 377 }, 378 tracer: t, 379 operation: opName, 380 startTime: time.Now(), 381 logTags: logTags, 382 } 383 s.mu.duration = -1 384 385 shadowTracer := t.getShadowTracer() 386 if shadowTracer != nil { 387 linkShadowSpan( 388 s, shadowTracer, nil, /* parentShadowCtx */ 389 opentracing.SpanReferenceType(0) /* parentType - ignored*/) 390 } 391 392 if t.useNetTrace() { 393 var tags []logtags.Tag 394 if logTags != nil { 395 tags = logTags.Get() 396 } 397 398 s.netTr = trace.New("tracing", opName) 399 s.netTr.SetMaxEvents(maxLogsPerSpan) 400 for i := range tags { 401 tag := &tags[i] 402 s.netTr.LazyPrintf("%s:%v", tag.Key(), tag.Value()) 403 } 404 } 405 406 return s 407 } 408 409 // StartChildSpan creates a child span of the given parent span. This is 410 // functionally equivalent to: 411 // parentSpan.Tracer().(*Tracer).StartSpan(opName, opentracing.ChildOf(parentSpan.Context())) 412 // Compared to that, it's more efficient, particularly in terms of memory 413 // allocations; among others, it saves the call to parentSpan.Context. 414 // 415 // This only works for creating children of local parents (i.e. the caller needs 416 // to have a reference to the parent span). 417 // 418 // If separateRecording is true and the parent span is recording, we start a 419 // new recording for the child span. If separateRecording is false (the 420 // default), then the child span will be part of the same recording. 421 func StartChildSpan( 422 opName string, parentSpan opentracing.Span, logTags *logtags.Buffer, separateRecording bool, 423 ) opentracing.Span { 424 tr := parentSpan.Tracer().(*Tracer) 425 // If tracing is disabled, avoid overhead and return a noop span. 426 if IsBlackHoleSpan(parentSpan) { 427 return &tr.noopSpan 428 } 429 430 pSpan := parentSpan.(*span) 431 432 s := &span{ 433 tracer: tr, 434 operation: opName, 435 startTime: time.Now(), 436 parentSpanID: pSpan.SpanID, 437 logTags: logTags, 438 } 439 440 // Copy baggage from parent. 441 pSpan.mu.Lock() 442 if l := len(pSpan.mu.Baggage); l > 0 { 443 s.mu.Baggage = make(map[string]string, l) 444 for k, v := range pSpan.mu.Baggage { 445 s.mu.Baggage[k] = v 446 } 447 } 448 449 s.TraceID = pSpan.TraceID 450 s.SpanID = uint64(rand.Int63()) 451 452 if pSpan.shadowTr != nil { 453 linkShadowSpan(s, pSpan.shadowTr, pSpan.shadowSpan.Context(), opentracing.ChildOfRef) 454 } 455 456 // Start recording if necessary. 457 if pSpan.isRecording() { 458 recordingGroup := pSpan.mu.recordingGroup 459 if separateRecording { 460 recordingGroup = new(spanGroup) 461 } 462 s.enableRecording(recordingGroup, pSpan.mu.recordingType) 463 } 464 465 if pSpan.netTr != nil { 466 s.netTr = trace.New("tracing", opName) 467 s.netTr.SetMaxEvents(maxLogsPerSpan) 468 if startTags := s.logTags; startTags != nil { 469 tags := startTags.Get() 470 for i := range tags { 471 tag := &tags[i] 472 s.netTr.LazyPrintf("%s:%v", tagName(tag.Key()), tag.Value()) 473 } 474 } 475 } 476 477 if pSpan.netTr != nil || pSpan.shadowTr != nil { 478 // Copy baggage items to tags so they show up in the shadow tracer UI or x/net/trace. 479 for k, v := range s.mu.Baggage { 480 s.SetTag(k, v) 481 } 482 } 483 484 pSpan.mu.Unlock() 485 return s 486 } 487 488 type textMapWriterFn func(key, val string) 489 490 var _ opentracing.TextMapWriter = textMapWriterFn(nil) 491 492 // Set is part of the opentracing.TextMapWriter interface. 493 func (fn textMapWriterFn) Set(key, val string) { 494 fn(key, val) 495 } 496 497 // Inject is part of the opentracing.Tracer interface. 498 func (t *Tracer) Inject( 499 osc opentracing.SpanContext, format interface{}, carrier interface{}, 500 ) error { 501 if IsNoopContext(osc) { 502 // Fast path when tracing is disabled. Extract will accept an empty map as a 503 // noop context. 504 return nil 505 } 506 507 // We only support the HTTPHeaders/TextMap format. 508 if format != opentracing.HTTPHeaders && format != opentracing.TextMap { 509 return opentracing.ErrUnsupportedFormat 510 } 511 512 mapWriter, ok := carrier.(opentracing.TextMapWriter) 513 if !ok { 514 return opentracing.ErrInvalidCarrier 515 } 516 517 sc, ok := osc.(*spanContext) 518 if !ok { 519 return opentracing.ErrInvalidSpanContext 520 } 521 522 mapWriter.Set(fieldNameTraceID, strconv.FormatUint(sc.TraceID, 16)) 523 mapWriter.Set(fieldNameSpanID, strconv.FormatUint(sc.SpanID, 16)) 524 525 for k, v := range sc.Baggage { 526 mapWriter.Set(prefixBaggage+k, v) 527 } 528 529 if sc.shadowTr != nil { 530 mapWriter.Set(fieldNameShadowType, sc.shadowTr.Typ()) 531 // Encapsulate the shadow text map, prepending a prefix to the keys. 532 if err := sc.shadowTr.Inject(sc.shadowCtx, format, textMapWriterFn(func(key, val string) { 533 mapWriter.Set(prefixShadow+key, val) 534 })); err != nil { 535 return err 536 } 537 } 538 539 return nil 540 } 541 542 type textMapReaderFn func(handler func(key, val string) error) error 543 544 var _ opentracing.TextMapReader = textMapReaderFn(nil) 545 546 // ForeachKey is part of the opentracing.TextMapReader interface. 547 func (fn textMapReaderFn) ForeachKey(handler func(key, val string) error) error { 548 return fn(handler) 549 } 550 551 // Extract is part of the opentracing.Tracer interface. 552 // It always returns a valid context, even in error cases (this is assumed by the 553 // grpc-opentracing interceptor). 554 func (t *Tracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { 555 // We only support the HTTPHeaders/TextMap format. 556 if format != opentracing.HTTPHeaders && format != opentracing.TextMap { 557 return noopSpanContext{}, opentracing.ErrUnsupportedFormat 558 } 559 560 mapReader, ok := carrier.(opentracing.TextMapReader) 561 if !ok { 562 return noopSpanContext{}, opentracing.ErrInvalidCarrier 563 } 564 565 var sc spanContext 566 var shadowType string 567 var shadowCarrier opentracing.TextMapCarrier 568 569 err := mapReader.ForeachKey(func(k, v string) error { 570 switch k = strings.ToLower(k); k { 571 case fieldNameTraceID: 572 var err error 573 sc.TraceID, err = strconv.ParseUint(v, 16, 64) 574 if err != nil { 575 return opentracing.ErrSpanContextCorrupted 576 } 577 case fieldNameSpanID: 578 var err error 579 sc.SpanID, err = strconv.ParseUint(v, 16, 64) 580 if err != nil { 581 return opentracing.ErrSpanContextCorrupted 582 } 583 case fieldNameShadowType: 584 shadowType = v 585 default: 586 if strings.HasPrefix(k, prefixBaggage) { 587 if sc.Baggage == nil { 588 sc.Baggage = make(map[string]string) 589 } 590 sc.Baggage[strings.TrimPrefix(k, prefixBaggage)] = v 591 } else if strings.HasPrefix(k, prefixShadow) { 592 if shadowCarrier == nil { 593 shadowCarrier = make(opentracing.TextMapCarrier) 594 } 595 // We build a shadow textmap with the original shadow keys. 596 shadowCarrier.Set(strings.TrimPrefix(k, prefixShadow), v) 597 } 598 } 599 return nil 600 }) 601 if err != nil { 602 return noopSpanContext{}, err 603 } 604 if sc.TraceID == 0 && sc.SpanID == 0 { 605 return noopSpanContext{}, nil 606 } 607 608 if shadowType != "" { 609 // Using a shadow tracer only works if all hosts use the same shadow tracer. 610 // If that's not the case, ignore the shadow context. 611 if shadowTr := t.getShadowTracer(); shadowTr != nil && 612 strings.EqualFold(shadowType, shadowTr.Typ()) { 613 sc.shadowTr = shadowTr 614 // Extract the shadow context using the un-encapsulated textmap. 615 sc.shadowCtx, err = shadowTr.Extract(format, shadowCarrier) 616 if err != nil { 617 return noopSpanContext{}, err 618 } 619 } 620 } 621 622 return &sc, nil 623 } 624 625 // FinishSpan closes the given span (if not nil). It is a convenience wrapper 626 // for span.Finish() which tolerates nil spans. 627 func FinishSpan(span opentracing.Span) { 628 if span != nil { 629 span.Finish() 630 } 631 } 632 633 // ForkCtxSpan checks if ctx has a Span open; if it does, it creates a new Span 634 // that "follows from" the original Span. This allows the resulting context to be 635 // used in an async task that might outlive the original operation. 636 // 637 // Returns the new context and the new span (if any). The span should be 638 // closed via FinishSpan. 639 // 640 // See also ChildSpan() for a "parent-child relationship". 641 func ForkCtxSpan(ctx context.Context, opName string) (context.Context, opentracing.Span) { 642 if span := opentracing.SpanFromContext(ctx); span != nil { 643 if _, noop := span.(*noopSpan); noop { 644 // Optimization: avoid ContextWithSpan call if tracing is disabled. 645 return ctx, span 646 } 647 tr := span.Tracer() 648 if IsBlackHoleSpan(span) { 649 ns := &tr.(*Tracer).noopSpan 650 return opentracing.ContextWithSpan(ctx, ns), ns 651 } 652 newSpan := tr.StartSpan(opName, opentracing.FollowsFrom(span.Context()), LogTagsFromCtx(ctx)) 653 return opentracing.ContextWithSpan(ctx, newSpan), newSpan 654 } 655 return ctx, nil 656 } 657 658 // ChildSpan opens a span as a child of the current span in the context (if 659 // there is one). 660 // The span's tags are inherited from the ctx's log tags automatically. 661 // 662 // Returns the new context and the new span (if any). The span should be 663 // closed via FinishSpan. 664 func ChildSpan(ctx context.Context, opName string) (context.Context, opentracing.Span) { 665 return childSpan(ctx, opName, false /* separateRecording */) 666 } 667 668 // ChildSpanSeparateRecording is like ChildSpan but the new span has separate 669 // recording (see StartChildSpan). 670 func ChildSpanSeparateRecording( 671 ctx context.Context, opName string, 672 ) (context.Context, opentracing.Span) { 673 return childSpan(ctx, opName, true /* separateRecording */) 674 } 675 676 func childSpan( 677 ctx context.Context, opName string, separateRecording bool, 678 ) (context.Context, opentracing.Span) { 679 span := opentracing.SpanFromContext(ctx) 680 if span == nil { 681 return ctx, nil 682 } 683 if _, noop := span.(*noopSpan); noop { 684 // Optimization: avoid ContextWithSpan call if tracing is disabled. 685 return ctx, span 686 } 687 tr := span.Tracer() 688 if IsBlackHoleSpan(span) { 689 ns := &tr.(*Tracer).noopSpan 690 return opentracing.ContextWithSpan(ctx, ns), ns 691 } 692 newSpan := StartChildSpan(opName, span, logtags.FromContext(ctx), separateRecording) 693 return opentracing.ContextWithSpan(ctx, newSpan), newSpan 694 } 695 696 // EnsureContext checks whether the given context.Context contains a Span. If 697 // not, it creates one using the provided Tracer and wraps it in the returned 698 // Span. The returned closure must be called after the request has been fully 699 // processed. 700 // 701 // Note that, if there's already a span in the context, this method does nothing 702 // even if the current context's log tags are different from that span's tags. 703 func EnsureContext( 704 ctx context.Context, tracer opentracing.Tracer, opName string, 705 ) (context.Context, func()) { 706 if opentracing.SpanFromContext(ctx) == nil { 707 sp := tracer.(*Tracer).StartRootSpan(opName, logtags.FromContext(ctx), NonRecordableSpan) 708 return opentracing.ContextWithSpan(ctx, sp), sp.Finish 709 } 710 return ctx, func() {} 711 } 712 713 // EnsureChildSpan is the same as EnsureContext, except it creates a child 714 // span for the input context if the input context already has an active 715 // trace. 716 // 717 // The caller is responsible for closing the span (via Span.Finish). 718 func EnsureChildSpan( 719 ctx context.Context, tracer opentracing.Tracer, name string, 720 ) (context.Context, opentracing.Span) { 721 if opentracing.SpanFromContext(ctx) == nil { 722 sp := tracer.(*Tracer).StartRootSpan(name, logtags.FromContext(ctx), NonRecordableSpan) 723 return opentracing.ContextWithSpan(ctx, sp), sp 724 } 725 return ChildSpan(ctx, name) 726 } 727 728 // StartSnowballTrace takes in a context and returns a derived one with a 729 // "snowball span" in it. The caller takes ownership of this span from the 730 // returned context and is in charge of Finish()ing it. The span has recording 731 // enabled. 732 // 733 // TODO(andrei): remove this method once EXPLAIN(TRACE) is gone. 734 func StartSnowballTrace( 735 ctx context.Context, tracer opentracing.Tracer, opName string, 736 ) (context.Context, opentracing.Span) { 737 var span opentracing.Span 738 if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil { 739 span = parentSpan.Tracer().StartSpan( 740 opName, opentracing.ChildOf(parentSpan.Context()), Recordable, LogTagsFromCtx(ctx), 741 ) 742 } else { 743 span = tracer.StartSpan(opName, Recordable, LogTagsFromCtx(ctx)) 744 } 745 StartRecording(span, SnowballRecording) 746 return opentracing.ContextWithSpan(ctx, span), span 747 } 748 749 // TestingCheckRecordedSpans checks whether a recording looks like an expected 750 // one represented by a string with one line per expected span and one line per 751 // expected event (i.e. log message). 752 // 753 // Use with something like: 754 // if err := TestingCheckRecordedSpans(tracing.GetRecording(span), ` 755 // span root: 756 // event: a 757 // event: c 758 // span child: 759 // event: [ambient] b 760 // `); err != nil { 761 // t.Fatal(err) 762 // } 763 // 764 // The event lines can (and generally should) omit the file:line part that they 765 // might contain (depending on the level at which they were logged). 766 // 767 // Note: this test function is in this file because it needs to be used by 768 // both tests in the tracing package and tests outside of it, and the function 769 // itself depends on tracing. 770 func TestingCheckRecordedSpans(recSpans []RecordedSpan, expected string) error { 771 expected = strings.TrimSpace(expected) 772 var rows []string 773 row := func(format string, args ...interface{}) { 774 rows = append(rows, fmt.Sprintf(format, args...)) 775 } 776 777 for _, rs := range recSpans { 778 row("span %s:", rs.Operation) 779 if len(rs.Tags) > 0 { 780 var tags []string 781 for k, v := range rs.Tags { 782 tags = append(tags, fmt.Sprintf("%s=%v", k, v)) 783 } 784 sort.Strings(tags) 785 row(" tags: %s", strings.Join(tags, " ")) 786 } 787 for _, l := range rs.Logs { 788 msg := "" 789 for _, f := range l.Fields { 790 msg = msg + fmt.Sprintf(" %s: %v", f.Key, f.Value) 791 } 792 row("%s", msg) 793 } 794 } 795 var expRows []string 796 if expected != "" { 797 expRows = strings.Split(expected, "\n") 798 } 799 match := false 800 if len(expRows) == len(rows) { 801 match = true 802 for i := range expRows { 803 e := strings.Trim(expRows[i], " \t") 804 r := strings.Trim(rows[i], " \t") 805 if e != r && !matchesWithoutFileLine(r, e) { 806 match = false 807 break 808 } 809 } 810 } 811 if !match { 812 file, line, _ := caller.Lookup(1) 813 return errors.Errorf( 814 "%s:%d expected:\n%s\ngot:\n%s", 815 file, line, expected, strings.Join(rows, "\n")) 816 } 817 return nil 818 } 819 820 // matchesWithoutFileLine tries to match an event by stripping a file:line from 821 // it. For example: 822 // "event: util/log/trace_test.go:111 log" will match "event: log". 823 // 824 // Returns true if it matches. 825 func matchesWithoutFileLine(msg string, expected string) bool { 826 groups := regexp.MustCompile(`^(event: ).*:[0-9]* (.*)$`).FindStringSubmatch(msg) 827 return len(groups) == 3 && fmt.Sprintf("event: %s", groups[2]) == expected 828 } 829 830 // ContextWithRecordingSpan returns a context with an embedded trace span which 831 // returns its contents when getRecording is called and must be stopped by 832 // calling the cancel method when done with the context (getRecording() needs to 833 // be called before cancel()). 834 // 835 // Note that to convert the recorded spans into text, you can use 836 // Recording.String(). Tests can also use FindMsgInRecording(). 837 func ContextWithRecordingSpan( 838 ctx context.Context, opName string, 839 ) (retCtx context.Context, getRecording func() Recording, cancel func()) { 840 tr := NewTracer() 841 sp := tr.StartSpan(opName, Recordable, LogTagsFromCtx(ctx)) 842 StartRecording(sp, SnowballRecording) 843 ctx, cancelCtx := context.WithCancel(ctx) 844 ctx = opentracing.ContextWithSpan(ctx, sp) 845 846 getRecording = func() Recording { 847 return GetRecording(sp) 848 } 849 cancel = func() { 850 cancelCtx() 851 StopRecording(sp) 852 sp.Finish() 853 tr.Close() 854 } 855 return ctx, getRecording, cancel 856 }