1// Copyright The OpenTelemetry Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package trace 16 17import ( 18 "context" 19 "errors" 20 "fmt" 21 "math" 22 "strconv" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "testing" 27 "time" 28 29 "go.opentelemetry.io/otel" 30 "go.opentelemetry.io/otel/attribute" 31 "go.opentelemetry.io/otel/codes" 32 semconv "go.opentelemetry.io/otel/semconv/v1.7.0" 33 "go.opentelemetry.io/otel/trace" 34 35 "github.com/google/go-cmp/cmp" 36 "github.com/stretchr/testify/assert" 37 "github.com/stretchr/testify/require" 38 39 ottest "go.opentelemetry.io/otel/internal/internaltest" 40 41 "go.opentelemetry.io/otel/sdk/instrumentation" 42 "go.opentelemetry.io/otel/sdk/resource" 43) 44 45const envVar = "OTEL_RESOURCE_ATTRIBUTES" 46 47type storingHandler struct { 48 errs []error 49} 50 51func (s *storingHandler) Handle(err error) { 52 s.errs = append(s.errs, err) 53} 54 55func (s *storingHandler) Reset() { 56 s.errs = nil 57} 58 59var ( 60 tid trace.TraceID 61 sid trace.SpanID 62 sc trace.SpanContext 63 64 handler = &storingHandler{} 65) 66 67func init() { 68 tid, _ = trace.TraceIDFromHex("01020304050607080102040810203040") 69 sid, _ = trace.SpanIDFromHex("0102040810203040") 70 sc = trace.NewSpanContext(trace.SpanContextConfig{ 71 TraceID: tid, 72 SpanID: sid, 73 TraceFlags: 0x1, 74 }) 75 76 otel.SetErrorHandler(handler) 77} 78 79func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) { 80 harness := ottest.NewHarness(t) 81 82 harness.TestTracerProvider(func() trace.TracerProvider { 83 return NewTracerProvider(WithSampler(TraceIDRatioBased(0))) 84 }) 85 86 tp := NewTracerProvider(WithSampler(TraceIDRatioBased(0))) 87 harness.TestTracer(func() trace.Tracer { 88 return tp.Tracer("") 89 }) 90} 91 92type testExporter struct { 93 mu sync.RWMutex 94 idx map[string]int 95 spans []*snapshot 96} 97 98func NewTestExporter() *testExporter { 99 return &testExporter{idx: make(map[string]int)} 100} 101 102func (te *testExporter) ExportSpans(_ context.Context, spans []ReadOnlySpan) error { 103 te.mu.Lock() 104 defer te.mu.Unlock() 105 106 i := len(te.spans) 107 for _, s := range spans { 108 te.idx[s.Name()] = i 109 te.spans = append(te.spans, s.(*snapshot)) 110 i++ 111 } 112 return nil 113} 114 115func (te *testExporter) Spans() []*snapshot { 116 te.mu.RLock() 117 defer te.mu.RUnlock() 118 119 cp := make([]*snapshot, len(te.spans)) 120 copy(cp, te.spans) 121 return cp 122} 123 124func (te *testExporter) GetSpan(name string) (*snapshot, bool) { 125 te.mu.RLock() 126 defer te.mu.RUnlock() 127 i, ok := te.idx[name] 128 if !ok { 129 return nil, false 130 } 131 return te.spans[i], true 132} 133 134func (te *testExporter) Len() int { 135 te.mu.RLock() 136 defer te.mu.RUnlock() 137 return len(te.spans) 138} 139 140func (te *testExporter) Shutdown(context.Context) error { 141 te.Reset() 142 return nil 143} 144 145func (te *testExporter) Reset() { 146 te.mu.Lock() 147 defer te.mu.Unlock() 148 te.idx = make(map[string]int) 149 te.spans = te.spans[:0] 150} 151 152type testSampler struct { 153 callCount int 154 prefix string 155 t *testing.T 156} 157 158func (ts *testSampler) ShouldSample(p SamplingParameters) SamplingResult { 159 ts.callCount++ 160 ts.t.Logf("called sampler for name %q", p.Name) 161 decision := Drop 162 if strings.HasPrefix(p.Name, ts.prefix) { 163 decision = RecordAndSample 164 } 165 return SamplingResult{Decision: decision, Attributes: []attribute.KeyValue{attribute.Int("callCount", ts.callCount)}} 166} 167 168func (ts testSampler) Description() string { 169 return "testSampler" 170} 171 172func TestSetName(t *testing.T) { 173 tp := NewTracerProvider() 174 175 type testCase struct { 176 name string 177 newName string 178 } 179 for idx, tt := range []testCase{ 180 { // 0 181 name: "foobar", 182 newName: "foobaz", 183 }, 184 { // 1 185 name: "foobar", 186 newName: "barbaz", 187 }, 188 { // 2 189 name: "barbar", 190 newName: "barbaz", 191 }, 192 { // 3 193 name: "barbar", 194 newName: "foobar", 195 }, 196 } { 197 sp := startNamedSpan(tp, "SetName", tt.name) 198 if sdkspan, ok := sp.(*recordingSpan); ok { 199 if sdkspan.Name() != tt.name { 200 t.Errorf("%d: invalid name at span creation, expected %v, got %v", idx, tt.name, sdkspan.Name()) 201 } 202 } else { 203 t.Errorf("%d: unable to coerce span to SDK span, is type %T", idx, sp) 204 } 205 sp.SetName(tt.newName) 206 if sdkspan, ok := sp.(*recordingSpan); ok { 207 if sdkspan.Name() != tt.newName { 208 t.Errorf("%d: span name not changed, expected %v, got %v", idx, tt.newName, sdkspan.Name()) 209 } 210 } else { 211 t.Errorf("%d: unable to coerce span to SDK span, is type %T", idx, sp) 212 } 213 sp.End() 214 } 215} 216 217func TestSpanIsRecording(t *testing.T) { 218 t.Run("while Span active", func(t *testing.T) { 219 for name, tc := range map[string]struct { 220 sampler Sampler 221 want bool 222 }{ 223 "Always sample, recording on": {sampler: AlwaysSample(), want: true}, 224 "Never sample recording off": {sampler: NeverSample(), want: false}, 225 } { 226 tp := NewTracerProvider(WithSampler(tc.sampler)) 227 _, span := tp.Tracer(name).Start(context.Background(), "StartSpan") 228 defer span.End() 229 got := span.IsRecording() 230 assert.Equal(t, got, tc.want, name) 231 } 232 }) 233 234 t.Run("after Span end", func(t *testing.T) { 235 for name, tc := range map[string]Sampler{ 236 "Always Sample": AlwaysSample(), 237 "Never Sample": NeverSample(), 238 } { 239 tp := NewTracerProvider(WithSampler(tc)) 240 _, span := tp.Tracer(name).Start(context.Background(), "StartSpan") 241 span.End() 242 got := span.IsRecording() 243 assert.False(t, got, name) 244 } 245 }) 246} 247 248func TestSampling(t *testing.T) { 249 idg := defaultIDGenerator() 250 const total = 10000 251 for name, tc := range map[string]struct { 252 sampler Sampler 253 expect float64 254 parent bool 255 sampledParent bool 256 }{ 257 // Span w/o a parent 258 "NeverSample": {sampler: NeverSample(), expect: 0}, 259 "AlwaysSample": {sampler: AlwaysSample(), expect: 1.0}, 260 "TraceIdRatioBased_-1": {sampler: TraceIDRatioBased(-1.0), expect: 0}, 261 "TraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25}, 262 "TraceIdRatioBased_.50": {sampler: TraceIDRatioBased(0.50), expect: .5}, 263 "TraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75}, 264 "TraceIdRatioBased_2.0": {sampler: TraceIDRatioBased(2.0), expect: 1}, 265 266 // Spans w/o a parent and using ParentBased(DelegateSampler()) Sampler, receive DelegateSampler's sampling decision 267 "ParentNeverSample": {sampler: ParentBased(NeverSample()), expect: 0}, 268 "ParentAlwaysSample": {sampler: ParentBased(AlwaysSample()), expect: 1}, 269 "ParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: .5}, 270 271 // An unadorned TraceIDRatioBased sampler ignores parent spans 272 "UnsampledParentSpanWithTraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25, parent: true}, 273 "SampledParentSpanWithTraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25, parent: true, sampledParent: true}, 274 "UnsampledParentSpanWithTraceIdRatioBased_.50": {sampler: TraceIDRatioBased(0.50), expect: .5, parent: true}, 275 "SampledParentSpanWithTraceIdRatioBased_.50": {sampler: TraceIDRatioBased(0.50), expect: .5, parent: true, sampledParent: true}, 276 "UnsampledParentSpanWithTraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75, parent: true}, 277 "SampledParentSpanWithTraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75, parent: true, sampledParent: true}, 278 279 // Spans with a sampled parent but using NeverSample Sampler, are not sampled 280 "SampledParentSpanWithNeverSample": {sampler: NeverSample(), expect: 0, parent: true, sampledParent: true}, 281 282 // Spans with a sampled parent and using ParentBased(DelegateSampler()) Sampler, inherit the parent span's sampling status 283 "SampledParentSpanWithParentNeverSample": {sampler: ParentBased(NeverSample()), expect: 1, parent: true, sampledParent: true}, 284 "UnsampledParentSpanWithParentNeverSampler": {sampler: ParentBased(NeverSample()), expect: 0, parent: true, sampledParent: false}, 285 "SampledParentSpanWithParentAlwaysSampler": {sampler: ParentBased(AlwaysSample()), expect: 1, parent: true, sampledParent: true}, 286 "UnsampledParentSpanWithParentAlwaysSampler": {sampler: ParentBased(AlwaysSample()), expect: 0, parent: true, sampledParent: false}, 287 "SampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: 1, parent: true, sampledParent: true}, 288 "UnsampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: 0, parent: true, sampledParent: false}, 289 } { 290 tc := tc 291 t.Run(name, func(t *testing.T) { 292 t.Parallel() 293 p := NewTracerProvider(WithSampler(tc.sampler)) 294 tr := p.Tracer("test") 295 var sampled int 296 for i := 0; i < total; i++ { 297 ctx := context.Background() 298 if tc.parent { 299 tid, sid := idg.NewIDs(ctx) 300 psc := trace.NewSpanContext(trace.SpanContextConfig{ 301 TraceID: tid, 302 SpanID: sid, 303 }) 304 if tc.sampledParent { 305 psc = psc.WithTraceFlags(trace.FlagsSampled) 306 } 307 ctx = trace.ContextWithRemoteSpanContext(ctx, psc) 308 } 309 _, span := tr.Start(ctx, "test") 310 if span.SpanContext().IsSampled() { 311 sampled++ 312 } 313 } 314 tolerance := 0.0 315 got := float64(sampled) / float64(total) 316 317 if tc.expect > 0 && tc.expect < 1 { 318 // See https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval 319 const z = 4.75342 // This should succeed 99.9999% of the time 320 tolerance = z * math.Sqrt(got*(1-got)/total) 321 } 322 323 diff := math.Abs(got - tc.expect) 324 if diff > tolerance { 325 t.Errorf("got %f (diff: %f), expected %f (w/tolerance: %f)", got, diff, tc.expect, tolerance) 326 } 327 }) 328 } 329} 330 331func TestStartSpanWithParent(t *testing.T) { 332 tp := NewTracerProvider() 333 tr := tp.Tracer("SpanWithParent") 334 ctx := context.Background() 335 336 _, s1 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "span1-unsampled-parent1") 337 if err := checkChild(t, sc, s1); err != nil { 338 t.Error(err) 339 } 340 341 _, s2 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "span2-unsampled-parent1") 342 if err := checkChild(t, sc, s2); err != nil { 343 t.Error(err) 344 } 345 346 ts, err := trace.ParseTraceState("k=v") 347 if err != nil { 348 t.Error(err) 349 } 350 sc2 := sc.WithTraceState(ts) 351 _, s3 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc2), "span3-sampled-parent2") 352 if err := checkChild(t, sc2, s3); err != nil { 353 t.Error(err) 354 } 355 356 ctx2, s4 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc2), "span4-sampled-parent2") 357 if err := checkChild(t, sc2, s4); err != nil { 358 t.Error(err) 359 } 360 361 s4Sc := s4.SpanContext() 362 _, s5 := tr.Start(ctx2, "span5-implicit-childof-span4") 363 if err := checkChild(t, s4Sc, s5); err != nil { 364 t.Error(err) 365 } 366} 367 368func TestStartSpanNewRootNotSampled(t *testing.T) { 369 alwaysSampleTp := NewTracerProvider() 370 sampledTr := alwaysSampleTp.Tracer("AlwaysSampled") 371 neverSampleTp := NewTracerProvider(WithSampler(ParentBased(NeverSample()))) 372 neverSampledTr := neverSampleTp.Tracer("ParentBasedNeverSample") 373 ctx := context.Background() 374 375 ctx, s1 := sampledTr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "span1-sampled") 376 if err := checkChild(t, sc, s1); err != nil { 377 t.Error(err) 378 } 379 380 _, s2 := neverSampledTr.Start(ctx, "span2-no-newroot") 381 if !s2.SpanContext().IsSampled() { 382 t.Error(fmt.Errorf("got child span is not sampled, want child span with sampler: ParentBased(NeverSample()) to be sampled")) 383 } 384 385 // Adding WithNewRoot causes child spans to not sample based on parent context 386 _, s3 := neverSampledTr.Start(ctx, "span3-newroot", trace.WithNewRoot()) 387 if s3.SpanContext().IsSampled() { 388 t.Error(fmt.Errorf("got child span is sampled, want child span WithNewRoot() and with sampler: ParentBased(NeverSample()) to not be sampled")) 389 } 390} 391 392func TestSetSpanAttributesOnStart(t *testing.T) { 393 te := NewTestExporter() 394 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 395 span := startSpan(tp, 396 "StartSpanAttribute", 397 trace.WithAttributes(attribute.String("key1", "value1")), 398 trace.WithAttributes(attribute.String("key2", "value2")), 399 ) 400 got, err := endSpan(te, span) 401 if err != nil { 402 t.Fatal(err) 403 } 404 405 want := &snapshot{ 406 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 407 TraceID: tid, 408 TraceFlags: 0x1, 409 }), 410 parent: sc.WithRemote(true), 411 name: "span0", 412 attributes: []attribute.KeyValue{ 413 attribute.String("key1", "value1"), 414 attribute.String("key2", "value2"), 415 }, 416 spanKind: trace.SpanKindInternal, 417 instrumentationLibrary: instrumentation.Library{Name: "StartSpanAttribute"}, 418 } 419 if diff := cmpDiff(got, want); diff != "" { 420 t.Errorf("SetSpanAttributesOnStart: -got +want %s", diff) 421 } 422} 423 424func TestSetSpanAttributes(t *testing.T) { 425 te := NewTestExporter() 426 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 427 span := startSpan(tp, "SpanAttribute") 428 span.SetAttributes(attribute.String("key1", "value1")) 429 got, err := endSpan(te, span) 430 if err != nil { 431 t.Fatal(err) 432 } 433 434 want := &snapshot{ 435 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 436 TraceID: tid, 437 TraceFlags: 0x1, 438 }), 439 parent: sc.WithRemote(true), 440 name: "span0", 441 attributes: []attribute.KeyValue{ 442 attribute.String("key1", "value1"), 443 }, 444 spanKind: trace.SpanKindInternal, 445 instrumentationLibrary: instrumentation.Library{Name: "SpanAttribute"}, 446 } 447 if diff := cmpDiff(got, want); diff != "" { 448 t.Errorf("SetSpanAttributes: -got +want %s", diff) 449 } 450} 451 452func TestSamplerAttributesLocalChildSpan(t *testing.T) { 453 sampler := &testSampler{prefix: "span", t: t} 454 te := NewTestExporter() 455 tp := NewTracerProvider(WithSampler(sampler), WithSyncer(te), WithResource(resource.Empty())) 456 457 ctx := context.Background() 458 ctx, span := startLocalSpan(tp, ctx, "SpanOne", "span0") 459 _, spanTwo := startLocalSpan(tp, ctx, "SpanTwo", "span1") 460 461 spanTwo.End() 462 span.End() 463 464 got := te.Spans() 465 require.Len(t, got, 2) 466 // FILO order above means spanTwo <-> gotSpan0 and span <-> gotSpan1. 467 gotSpan0, gotSpan1 := got[0], got[1] 468 // Ensure sampler is called for local child spans by verifying the 469 // attributes set by the sampler are set on the child span. 470 assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 2)}, gotSpan0.Attributes()) 471 assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 1)}, gotSpan1.Attributes()) 472} 473 474func TestSetSpanAttributesOverLimit(t *testing.T) { 475 te := NewTestExporter() 476 tp := NewTracerProvider(WithSpanLimits(SpanLimits{AttributeCountLimit: 2}), WithSyncer(te), WithResource(resource.Empty())) 477 478 span := startSpan(tp, "SpanAttributesOverLimit") 479 span.SetAttributes( 480 attribute.Bool("key1", true), 481 attribute.String("key2", "value2"), 482 attribute.Bool("key1", false), // Replace key1. 483 attribute.Int64("key4", 4), // Remove key2 and add key4 484 ) 485 got, err := endSpan(te, span) 486 if err != nil { 487 t.Fatal(err) 488 } 489 490 want := &snapshot{ 491 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 492 TraceID: tid, 493 TraceFlags: 0x1, 494 }), 495 parent: sc.WithRemote(true), 496 name: "span0", 497 attributes: []attribute.KeyValue{ 498 attribute.Bool("key1", false), 499 attribute.Int64("key4", 4), 500 }, 501 spanKind: trace.SpanKindInternal, 502 droppedAttributeCount: 1, 503 instrumentationLibrary: instrumentation.Library{Name: "SpanAttributesOverLimit"}, 504 } 505 if diff := cmpDiff(got, want); diff != "" { 506 t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff) 507 } 508} 509 510func TestSetSpanAttributesWithInvalidKey(t *testing.T) { 511 te := NewTestExporter() 512 tp := NewTracerProvider(WithSpanLimits(SpanLimits{}), WithSyncer(te), WithResource(resource.Empty())) 513 514 span := startSpan(tp, "SpanToSetInvalidKeyOrValue") 515 span.SetAttributes( 516 attribute.Bool("", true), 517 attribute.Bool("key1", false), 518 ) 519 got, err := endSpan(te, span) 520 if err != nil { 521 t.Fatal(err) 522 } 523 524 want := &snapshot{ 525 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 526 TraceID: tid, 527 TraceFlags: 0x1, 528 }), 529 parent: sc.WithRemote(true), 530 name: "span0", 531 attributes: []attribute.KeyValue{ 532 attribute.Bool("key1", false), 533 }, 534 spanKind: trace.SpanKindInternal, 535 droppedAttributeCount: 0, 536 instrumentationLibrary: instrumentation.Library{Name: "SpanToSetInvalidKeyOrValue"}, 537 } 538 if diff := cmpDiff(got, want); diff != "" { 539 t.Errorf("SetSpanAttributesWithInvalidKey: -got +want %s", diff) 540 } 541} 542 543func TestEvents(t *testing.T) { 544 te := NewTestExporter() 545 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 546 547 span := startSpan(tp, "Events") 548 k1v1 := attribute.String("key1", "value1") 549 k2v2 := attribute.Bool("key2", true) 550 k3v3 := attribute.Int64("key3", 3) 551 552 span.AddEvent("foo", trace.WithAttributes(attribute.String("key1", "value1"))) 553 span.AddEvent("bar", trace.WithAttributes( 554 attribute.Bool("key2", true), 555 attribute.Int64("key3", 3), 556 )) 557 got, err := endSpan(te, span) 558 if err != nil { 559 t.Fatal(err) 560 } 561 562 for i := range got.Events() { 563 if !checkTime(&got.Events()[i].Time) { 564 t.Error("exporting span: expected nonzero Event Time") 565 } 566 } 567 568 want := &snapshot{ 569 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 570 TraceID: tid, 571 TraceFlags: 0x1, 572 }), 573 parent: sc.WithRemote(true), 574 name: "span0", 575 events: []Event{ 576 {Name: "foo", Attributes: []attribute.KeyValue{k1v1}}, 577 {Name: "bar", Attributes: []attribute.KeyValue{k2v2, k3v3}}, 578 }, 579 spanKind: trace.SpanKindInternal, 580 instrumentationLibrary: instrumentation.Library{Name: "Events"}, 581 } 582 if diff := cmpDiff(got, want); diff != "" { 583 t.Errorf("Message Events: -got +want %s", diff) 584 } 585} 586 587func TestEventsOverLimit(t *testing.T) { 588 te := NewTestExporter() 589 tp := NewTracerProvider(WithSpanLimits(SpanLimits{EventCountLimit: 2}), WithSyncer(te), WithResource(resource.Empty())) 590 591 span := startSpan(tp, "EventsOverLimit") 592 k1v1 := attribute.String("key1", "value1") 593 k2v2 := attribute.Bool("key2", false) 594 k3v3 := attribute.String("key3", "value3") 595 596 span.AddEvent("fooDrop", trace.WithAttributes(attribute.String("key1", "value1"))) 597 span.AddEvent("barDrop", trace.WithAttributes( 598 attribute.Bool("key2", true), 599 attribute.String("key3", "value3"), 600 )) 601 span.AddEvent("foo", trace.WithAttributes(attribute.String("key1", "value1"))) 602 span.AddEvent("bar", trace.WithAttributes( 603 attribute.Bool("key2", false), 604 attribute.String("key3", "value3"), 605 )) 606 got, err := endSpan(te, span) 607 if err != nil { 608 t.Fatal(err) 609 } 610 611 for i := range got.Events() { 612 if !checkTime(&got.Events()[i].Time) { 613 t.Error("exporting span: expected nonzero Event Time") 614 } 615 } 616 617 want := &snapshot{ 618 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 619 TraceID: tid, 620 TraceFlags: 0x1, 621 }), 622 parent: sc.WithRemote(true), 623 name: "span0", 624 events: []Event{ 625 {Name: "foo", Attributes: []attribute.KeyValue{k1v1}}, 626 {Name: "bar", Attributes: []attribute.KeyValue{k2v2, k3v3}}, 627 }, 628 droppedEventCount: 2, 629 spanKind: trace.SpanKindInternal, 630 instrumentationLibrary: instrumentation.Library{Name: "EventsOverLimit"}, 631 } 632 if diff := cmpDiff(got, want); diff != "" { 633 t.Errorf("Message Event over limit: -got +want %s", diff) 634 } 635} 636 637func TestLinks(t *testing.T) { 638 te := NewTestExporter() 639 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 640 641 k1v1 := attribute.String("key1", "value1") 642 k2v2 := attribute.String("key2", "value2") 643 k3v3 := attribute.String("key3", "value3") 644 645 sc1 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 646 sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 647 648 l1 := trace.Link{SpanContext: sc1, Attributes: []attribute.KeyValue{k1v1}} 649 l2 := trace.Link{SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2, k3v3}} 650 651 links := []trace.Link{l1, l2} 652 span := startSpan(tp, "Links", trace.WithLinks(links...)) 653 654 got, err := endSpan(te, span) 655 if err != nil { 656 t.Fatal(err) 657 } 658 659 want := &snapshot{ 660 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 661 TraceID: tid, 662 TraceFlags: 0x1, 663 }), 664 parent: sc.WithRemote(true), 665 name: "span0", 666 links: []Link{{l1.SpanContext, l1.Attributes, 0}, {l2.SpanContext, l2.Attributes, 0}}, 667 spanKind: trace.SpanKindInternal, 668 instrumentationLibrary: instrumentation.Library{Name: "Links"}, 669 } 670 if diff := cmpDiff(got, want); diff != "" { 671 t.Errorf("Link: -got +want %s", diff) 672 } 673 sc1 = trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 674 675 span1 := startSpan(tp, "name", trace.WithLinks([]trace.Link{ 676 {SpanContext: trace.SpanContext{}}, 677 {SpanContext: sc1}, 678 }...)) 679 680 sdkspan, _ := span1.(*recordingSpan) 681 require.Len(t, sdkspan.Links(), 1) 682} 683 684func TestLinksOverLimit(t *testing.T) { 685 te := NewTestExporter() 686 687 sc1 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 688 sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 689 sc3 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 690 691 tp := NewTracerProvider(WithSpanLimits(SpanLimits{LinkCountLimit: 2}), WithSyncer(te), WithResource(resource.Empty())) 692 693 span := startSpan(tp, "LinksOverLimit", 694 trace.WithLinks( 695 trace.Link{SpanContext: sc1, Attributes: []attribute.KeyValue{attribute.String("key1", "value1")}}, 696 trace.Link{SpanContext: sc2, Attributes: []attribute.KeyValue{attribute.String("key2", "value2")}}, 697 trace.Link{SpanContext: sc3, Attributes: []attribute.KeyValue{attribute.String("key3", "value3")}}, 698 ), 699 ) 700 701 k2v2 := attribute.String("key2", "value2") 702 k3v3 := attribute.String("key3", "value3") 703 704 got, err := endSpan(te, span) 705 if err != nil { 706 t.Fatal(err) 707 } 708 709 want := &snapshot{ 710 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 711 TraceID: tid, 712 TraceFlags: 0x1, 713 }), 714 parent: sc.WithRemote(true), 715 name: "span0", 716 links: []Link{ 717 {SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2}, DroppedAttributeCount: 0}, 718 {SpanContext: sc3, Attributes: []attribute.KeyValue{k3v3}, DroppedAttributeCount: 0}, 719 }, 720 droppedLinkCount: 1, 721 spanKind: trace.SpanKindInternal, 722 instrumentationLibrary: instrumentation.Library{Name: "LinksOverLimit"}, 723 } 724 if diff := cmpDiff(got, want); diff != "" { 725 t.Errorf("Link over limit: -got +want %s", diff) 726 } 727} 728 729func TestSetSpanName(t *testing.T) { 730 te := NewTestExporter() 731 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 732 ctx := context.Background() 733 734 want := "SpanName-1" 735 ctx = trace.ContextWithRemoteSpanContext(ctx, sc) 736 _, span := tp.Tracer("SetSpanName").Start(ctx, "SpanName-1") 737 got, err := endSpan(te, span) 738 if err != nil { 739 t.Fatal(err) 740 } 741 742 if got.Name() != want { 743 t.Errorf("span.Name: got %q; want %q", got.Name(), want) 744 } 745} 746 747func TestSetSpanStatus(t *testing.T) { 748 te := NewTestExporter() 749 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 750 751 span := startSpan(tp, "SpanStatus") 752 span.SetStatus(codes.Error, "Error") 753 got, err := endSpan(te, span) 754 if err != nil { 755 t.Fatal(err) 756 } 757 758 want := &snapshot{ 759 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 760 TraceID: tid, 761 TraceFlags: 0x1, 762 }), 763 parent: sc.WithRemote(true), 764 name: "span0", 765 spanKind: trace.SpanKindInternal, 766 status: Status{ 767 Code: codes.Error, 768 Description: "Error", 769 }, 770 instrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, 771 } 772 if diff := cmpDiff(got, want); diff != "" { 773 t.Errorf("SetSpanStatus: -got +want %s", diff) 774 } 775} 776 777func TestSetSpanStatusWithoutMessageWhenStatusIsNotError(t *testing.T) { 778 te := NewTestExporter() 779 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 780 781 span := startSpan(tp, "SpanStatus") 782 span.SetStatus(codes.Ok, "This message will be ignored") 783 got, err := endSpan(te, span) 784 if err != nil { 785 t.Fatal(err) 786 } 787 788 want := &snapshot{ 789 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 790 TraceID: tid, 791 TraceFlags: 0x1, 792 }), 793 parent: sc.WithRemote(true), 794 name: "span0", 795 spanKind: trace.SpanKindInternal, 796 status: Status{ 797 Code: codes.Ok, 798 Description: "", 799 }, 800 instrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, 801 } 802 if diff := cmpDiff(got, want); diff != "" { 803 t.Errorf("SetSpanStatus: -got +want %s", diff) 804 } 805} 806 807func cmpDiff(x, y interface{}) string { 808 return cmp.Diff(x, y, 809 cmp.AllowUnexported(snapshot{}), 810 cmp.AllowUnexported(attribute.Value{}), 811 cmp.AllowUnexported(Event{}), 812 cmp.AllowUnexported(trace.TraceState{})) 813} 814 815// checkChild is test utility function that tests that c has fields set appropriately, 816// given that it is a child span of p. 817func checkChild(t *testing.T, p trace.SpanContext, apiSpan trace.Span) error { 818 s := apiSpan.(*recordingSpan) 819 if s == nil { 820 return fmt.Errorf("got nil child span, want non-nil") 821 } 822 if got, want := s.spanContext.TraceID().String(), p.TraceID().String(); got != want { 823 return fmt.Errorf("got child trace ID %s, want %s", got, want) 824 } 825 if childID, parentID := s.spanContext.SpanID().String(), p.SpanID().String(); childID == parentID { 826 return fmt.Errorf("got child span ID %s, parent span ID %s; want unequal IDs", childID, parentID) 827 } 828 if got, want := s.spanContext.TraceFlags(), p.TraceFlags(); got != want { 829 return fmt.Errorf("got child trace options %d, want %d", got, want) 830 } 831 got, want := s.spanContext.TraceState(), p.TraceState() 832 assert.Equal(t, want, got) 833 return nil 834} 835 836// startSpan starts a span with a name "span0". See startNamedSpan for 837// details. 838func startSpan(tp *TracerProvider, trName string, args ...trace.SpanStartOption) trace.Span { 839 return startNamedSpan(tp, trName, "span0", args...) 840} 841 842// startNamed Span is a test utility func that starts a span with a 843// passed name and with remote span context as parent. The remote span 844// context contains TraceFlags with sampled bit set. This allows the 845// span to be automatically sampled. 846func startNamedSpan(tp *TracerProvider, trName, name string, args ...trace.SpanStartOption) trace.Span { 847 _, span := tp.Tracer(trName).Start( 848 trace.ContextWithRemoteSpanContext(context.Background(), sc), 849 name, 850 args..., 851 ) 852 return span 853} 854 855// startLocalSpan is a test utility func that starts a span with a 856// passed name and with the passed context. The context is returned 857// along with the span so this parent can be used to create child 858// spans. 859func startLocalSpan(tp *TracerProvider, ctx context.Context, trName, name string, args ...trace.SpanStartOption) (context.Context, trace.Span) { 860 ctx, span := tp.Tracer(trName).Start( 861 ctx, 862 name, 863 args..., 864 ) 865 return ctx, span 866} 867 868// endSpan is a test utility function that ends the span in the context and 869// returns the exported span. 870// It requires that span be sampled using one of these methods 871// 1. Passing parent span context in context 872// 2. Use WithSampler(AlwaysSample()) 873// 3. Configuring AlwaysSample() as default sampler 874// 875// It also does some basic tests on the span. 876// It also clears spanID in the to make the comparison easier. 877func endSpan(te *testExporter, span trace.Span) (*snapshot, error) { 878 if !span.IsRecording() { 879 return nil, fmt.Errorf("IsRecording: got false, want true") 880 } 881 if !span.SpanContext().IsSampled() { 882 return nil, fmt.Errorf("IsSampled: got false, want true") 883 } 884 span.End() 885 if te.Len() != 1 { 886 return nil, fmt.Errorf("got %d exported spans, want one span", te.Len()) 887 } 888 got := te.Spans()[0] 889 if !got.SpanContext().SpanID().IsValid() { 890 return nil, fmt.Errorf("exporting span: expected nonzero SpanID") 891 } 892 got.spanContext = got.SpanContext().WithSpanID(trace.SpanID{}) 893 if !checkTime(&got.startTime) { 894 return nil, fmt.Errorf("exporting span: expected nonzero StartTime") 895 } 896 if !checkTime(&got.endTime) { 897 return nil, fmt.Errorf("exporting span: expected nonzero EndTime") 898 } 899 return got, nil 900} 901 902// checkTime checks that a nonzero time was set in x, then clears it. 903func checkTime(x *time.Time) bool { 904 if x.IsZero() { 905 return false 906 } 907 *x = time.Time{} 908 return true 909} 910 911func TestEndSpanTwice(t *testing.T) { 912 te := NewTestExporter() 913 tp := NewTracerProvider(WithSyncer(te)) 914 915 st := time.Now() 916 et1 := st.Add(100 * time.Millisecond) 917 et2 := st.Add(200 * time.Millisecond) 918 919 span := startSpan(tp, "EndSpanTwice", trace.WithTimestamp(st)) 920 span.End(trace.WithTimestamp(et1)) 921 span.End(trace.WithTimestamp(et2)) 922 923 if te.Len() != 1 { 924 t.Fatalf("expected only a single span, got %#v", te.Spans()) 925 } 926 927 ro := span.(ReadOnlySpan) 928 if ro.EndTime() != et1 { 929 t.Fatalf("2nd call to End() should not modify end time") 930 } 931} 932 933func TestStartSpanAfterEnd(t *testing.T) { 934 te := NewTestExporter() 935 tp := NewTracerProvider(WithSampler(AlwaysSample()), WithSyncer(te)) 936 ctx := context.Background() 937 938 tr := tp.Tracer("SpanAfterEnd") 939 ctx, span0 := tr.Start(trace.ContextWithRemoteSpanContext(ctx, sc), "parent") 940 ctx1, span1 := tr.Start(ctx, "span-1") 941 span1.End() 942 // Start a new span with the context containing span-1 943 // even though span-1 is ended, we still add this as a new child of span-1 944 _, span2 := tr.Start(ctx1, "span-2") 945 span2.End() 946 span0.End() 947 if got, want := te.Len(), 3; got != want { 948 t.Fatalf("len(%#v) = %d; want %d", te.Spans(), got, want) 949 } 950 951 gotParent, ok := te.GetSpan("parent") 952 if !ok { 953 t.Fatal("parent not recorded") 954 } 955 gotSpan1, ok := te.GetSpan("span-1") 956 if !ok { 957 t.Fatal("span-1 not recorded") 958 } 959 gotSpan2, ok := te.GetSpan("span-2") 960 if !ok { 961 t.Fatal("span-2 not recorded") 962 } 963 964 if got, want := gotSpan1.SpanContext().TraceID(), gotParent.SpanContext().TraceID(); got != want { 965 t.Errorf("span-1.TraceID=%q; want %q", got, want) 966 } 967 if got, want := gotSpan2.SpanContext().TraceID(), gotParent.SpanContext().TraceID(); got != want { 968 t.Errorf("span-2.TraceID=%q; want %q", got, want) 969 } 970 if got, want := gotSpan1.Parent().SpanID(), gotParent.SpanContext().SpanID(); got != want { 971 t.Errorf("span-1.ParentSpanID=%q; want %q (parent.SpanID)", got, want) 972 } 973 if got, want := gotSpan2.Parent().SpanID(), gotSpan1.SpanContext().SpanID(); got != want { 974 t.Errorf("span-2.ParentSpanID=%q; want %q (span1.SpanID)", got, want) 975 } 976} 977 978func TestChildSpanCount(t *testing.T) { 979 te := NewTestExporter() 980 tp := NewTracerProvider(WithSampler(AlwaysSample()), WithSyncer(te)) 981 982 tr := tp.Tracer("ChidSpanCount") 983 ctx, span0 := tr.Start(context.Background(), "parent") 984 ctx1, span1 := tr.Start(ctx, "span-1") 985 _, span2 := tr.Start(ctx1, "span-2") 986 span2.End() 987 span1.End() 988 989 _, span3 := tr.Start(ctx, "span-3") 990 span3.End() 991 span0.End() 992 if got, want := te.Len(), 4; got != want { 993 t.Fatalf("len(%#v) = %d; want %d", te.Spans(), got, want) 994 } 995 996 gotParent, ok := te.GetSpan("parent") 997 if !ok { 998 t.Fatal("parent not recorded") 999 } 1000 gotSpan1, ok := te.GetSpan("span-1") 1001 if !ok { 1002 t.Fatal("span-1 not recorded") 1003 } 1004 gotSpan2, ok := te.GetSpan("span-2") 1005 if !ok { 1006 t.Fatal("span-2 not recorded") 1007 } 1008 gotSpan3, ok := te.GetSpan("span-3") 1009 if !ok { 1010 t.Fatal("span-3 not recorded") 1011 } 1012 1013 if got, want := gotSpan3.ChildSpanCount(), 0; got != want { 1014 t.Errorf("span-3.ChildSpanCount=%d; want %d", got, want) 1015 } 1016 if got, want := gotSpan2.ChildSpanCount(), 0; got != want { 1017 t.Errorf("span-2.ChildSpanCount=%d; want %d", got, want) 1018 } 1019 if got, want := gotSpan1.ChildSpanCount(), 1; got != want { 1020 t.Errorf("span-1.ChildSpanCount=%d; want %d", got, want) 1021 } 1022 if got, want := gotParent.ChildSpanCount(), 2; got != want { 1023 t.Errorf("parent.ChildSpanCount=%d; want %d", got, want) 1024 } 1025} 1026 1027func TestNilSpanEnd(t *testing.T) { 1028 var span *recordingSpan 1029 span.End() 1030} 1031 1032func TestNonRecordingSpanDoesNotTrackRuntimeTracerTask(t *testing.T) { 1033 tp := NewTracerProvider(WithSampler(NeverSample())) 1034 tr := tp.Tracer("TestNonRecordingSpanDoesNotTrackRuntimeTracerTask") 1035 1036 _, apiSpan := tr.Start(context.Background(), "foo") 1037 if _, ok := apiSpan.(runtimeTracer); ok { 1038 t.Fatalf("non recording span implements runtime trace task tracking") 1039 } 1040} 1041 1042func TestRecordingSpanRuntimeTracerTaskEnd(t *testing.T) { 1043 tp := NewTracerProvider(WithSampler(AlwaysSample())) 1044 tr := tp.Tracer("TestRecordingSpanRuntimeTracerTaskEnd") 1045 1046 var n uint64 1047 executionTracerTaskEnd := func() { 1048 atomic.AddUint64(&n, 1) 1049 } 1050 _, apiSpan := tr.Start(context.Background(), "foo") 1051 s, ok := apiSpan.(*recordingSpan) 1052 if !ok { 1053 t.Fatal("recording span not returned from always sampled Tracer") 1054 } 1055 1056 s.executionTracerTaskEnd = executionTracerTaskEnd 1057 s.End() 1058 1059 if n != 1 { 1060 t.Error("recording span did not end runtime trace task") 1061 } 1062} 1063 1064func TestCustomStartEndTime(t *testing.T) { 1065 te := NewTestExporter() 1066 tp := NewTracerProvider(WithSyncer(te), WithSampler(AlwaysSample())) 1067 1068 startTime := time.Date(2019, time.August, 27, 14, 42, 0, 0, time.UTC) 1069 endTime := startTime.Add(time.Second * 20) 1070 _, span := tp.Tracer("Custom Start and End time").Start( 1071 context.Background(), 1072 "testspan", 1073 trace.WithTimestamp(startTime), 1074 ) 1075 span.End(trace.WithTimestamp(endTime)) 1076 1077 if te.Len() != 1 { 1078 t.Fatalf("got %d exported spans, want one span", te.Len()) 1079 } 1080 got := te.Spans()[0] 1081 if got.StartTime() != startTime { 1082 t.Errorf("expected start time to be %s, got %s", startTime, got.StartTime()) 1083 } 1084 if got.EndTime() != endTime { 1085 t.Errorf("expected end time to be %s, got %s", endTime, got.EndTime()) 1086 } 1087} 1088 1089func TestRecordError(t *testing.T) { 1090 scenarios := []struct { 1091 err error 1092 typ string 1093 msg string 1094 }{ 1095 { 1096 err: ottest.NewTestError("test error"), 1097 typ: "go.opentelemetry.io/otel/internal/internaltest.TestError", 1098 msg: "test error", 1099 }, 1100 { 1101 err: errors.New("test error 2"), 1102 typ: "*errors.errorString", 1103 msg: "test error 2", 1104 }, 1105 } 1106 1107 for _, s := range scenarios { 1108 te := NewTestExporter() 1109 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 1110 span := startSpan(tp, "RecordError") 1111 1112 errTime := time.Now() 1113 span.RecordError(s.err, trace.WithTimestamp(errTime)) 1114 1115 got, err := endSpan(te, span) 1116 if err != nil { 1117 t.Fatal(err) 1118 } 1119 1120 want := &snapshot{ 1121 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1122 TraceID: tid, 1123 TraceFlags: 0x1, 1124 }), 1125 parent: sc.WithRemote(true), 1126 name: "span0", 1127 status: Status{Code: codes.Unset}, 1128 spanKind: trace.SpanKindInternal, 1129 events: []Event{ 1130 { 1131 Name: semconv.ExceptionEventName, 1132 Time: errTime, 1133 Attributes: []attribute.KeyValue{ 1134 semconv.ExceptionTypeKey.String(s.typ), 1135 semconv.ExceptionMessageKey.String(s.msg), 1136 }, 1137 }, 1138 }, 1139 instrumentationLibrary: instrumentation.Library{Name: "RecordError"}, 1140 } 1141 if diff := cmpDiff(got, want); diff != "" { 1142 t.Errorf("SpanErrorOptions: -got +want %s", diff) 1143 } 1144 } 1145} 1146 1147func TestRecordErrorWithStackTrace(t *testing.T) { 1148 err := ottest.NewTestError("test error") 1149 typ := "go.opentelemetry.io/otel/internal/internaltest.TestError" 1150 msg := "test error" 1151 1152 te := NewTestExporter() 1153 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 1154 span := startSpan(tp, "RecordError") 1155 1156 errTime := time.Now() 1157 span.RecordError(err, trace.WithTimestamp(errTime), trace.WithStackTrace(true)) 1158 1159 got, err := endSpan(te, span) 1160 if err != nil { 1161 t.Fatal(err) 1162 } 1163 1164 want := &snapshot{ 1165 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1166 TraceID: tid, 1167 TraceFlags: 0x1, 1168 }), 1169 parent: sc.WithRemote(true), 1170 name: "span0", 1171 status: Status{Code: codes.Unset}, 1172 spanKind: trace.SpanKindInternal, 1173 events: []Event{ 1174 { 1175 Name: semconv.ExceptionEventName, 1176 Time: errTime, 1177 Attributes: []attribute.KeyValue{ 1178 semconv.ExceptionTypeKey.String(typ), 1179 semconv.ExceptionMessageKey.String(msg), 1180 }, 1181 }, 1182 }, 1183 instrumentationLibrary: instrumentation.Library{Name: "RecordError"}, 1184 } 1185 1186 assert.Equal(t, got.spanContext, want.spanContext) 1187 assert.Equal(t, got.parent, want.parent) 1188 assert.Equal(t, got.name, want.name) 1189 assert.Equal(t, got.status, want.status) 1190 assert.Equal(t, got.spanKind, want.spanKind) 1191 assert.Equal(t, got.events[0].Attributes[0].Value.AsString(), want.events[0].Attributes[0].Value.AsString()) 1192 assert.Equal(t, got.events[0].Attributes[1].Value.AsString(), want.events[0].Attributes[1].Value.AsString()) 1193 gotStackTraceFunctionName := strings.Split(got.events[0].Attributes[2].Value.AsString(), "\n") 1194 1195 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[1], "go.opentelemetry.io/otel/sdk/trace.recordStackTrace"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.recordStackTrace", gotStackTraceFunctionName[1]) 1196 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[3], "go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).RecordError"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).RecordError", gotStackTraceFunctionName[3]) 1197} 1198 1199func TestRecordErrorNil(t *testing.T) { 1200 te := NewTestExporter() 1201 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 1202 span := startSpan(tp, "RecordErrorNil") 1203 1204 span.RecordError(nil) 1205 1206 got, err := endSpan(te, span) 1207 if err != nil { 1208 t.Fatal(err) 1209 } 1210 1211 want := &snapshot{ 1212 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1213 TraceID: tid, 1214 TraceFlags: 0x1, 1215 }), 1216 parent: sc.WithRemote(true), 1217 name: "span0", 1218 spanKind: trace.SpanKindInternal, 1219 status: Status{ 1220 Code: codes.Unset, 1221 Description: "", 1222 }, 1223 instrumentationLibrary: instrumentation.Library{Name: "RecordErrorNil"}, 1224 } 1225 if diff := cmpDiff(got, want); diff != "" { 1226 t.Errorf("SpanErrorOptions: -got +want %s", diff) 1227 } 1228} 1229 1230func TestWithSpanKind(t *testing.T) { 1231 te := NewTestExporter() 1232 tp := NewTracerProvider(WithSyncer(te), WithSampler(AlwaysSample()), WithResource(resource.Empty())) 1233 tr := tp.Tracer("withSpanKind") 1234 1235 _, span := tr.Start(context.Background(), "WithoutSpanKind") 1236 spanData, err := endSpan(te, span) 1237 if err != nil { 1238 t.Error(err.Error()) 1239 } 1240 1241 if spanData.SpanKind() != trace.SpanKindInternal { 1242 t.Errorf("Default value of Spankind should be Internal: got %+v, want %+v\n", spanData.SpanKind(), trace.SpanKindInternal) 1243 } 1244 1245 sks := []trace.SpanKind{ 1246 trace.SpanKindInternal, 1247 trace.SpanKindServer, 1248 trace.SpanKindClient, 1249 trace.SpanKindProducer, 1250 trace.SpanKindConsumer, 1251 } 1252 1253 for _, sk := range sks { 1254 te.Reset() 1255 1256 _, span := tr.Start(context.Background(), fmt.Sprintf("SpanKind-%v", sk), trace.WithSpanKind(sk)) 1257 spanData, err := endSpan(te, span) 1258 if err != nil { 1259 t.Error(err.Error()) 1260 } 1261 1262 if spanData.SpanKind() != sk { 1263 t.Errorf("WithSpanKind check: got %+v, want %+v\n", spanData.SpanKind(), sks) 1264 } 1265 } 1266} 1267 1268func mergeResource(t *testing.T, r1, r2 *resource.Resource) *resource.Resource { 1269 r, err := resource.Merge(r1, r2) 1270 assert.NoError(t, err) 1271 return r 1272} 1273 1274func TestWithResource(t *testing.T) { 1275 store, err := ottest.SetEnvVariables(map[string]string{ 1276 envVar: "key=value,rk5=7", 1277 }) 1278 require.NoError(t, err) 1279 defer func() { require.NoError(t, store.Restore()) }() 1280 1281 cases := []struct { 1282 name string 1283 options []TracerProviderOption 1284 want *resource.Resource 1285 msg string 1286 }{ 1287 { 1288 name: "explicitly empty resource", 1289 options: []TracerProviderOption{WithResource(resource.Empty())}, 1290 want: resource.Environment(), 1291 }, 1292 { 1293 name: "uses default if no resource option", 1294 options: []TracerProviderOption{}, 1295 want: resource.Default(), 1296 }, 1297 { 1298 name: "explicit resource", 1299 options: []TracerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))}, 1300 want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))), 1301 }, 1302 { 1303 name: "last resource wins", 1304 options: []TracerProviderOption{ 1305 WithResource(resource.NewSchemaless(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))), 1306 WithResource(resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10)))}, 1307 want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))), 1308 }, 1309 { 1310 name: "overlapping attributes with environment resource", 1311 options: []TracerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))}, 1312 want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))), 1313 }, 1314 } 1315 for _, tc := range cases { 1316 tc := tc 1317 t.Run(tc.name, func(t *testing.T) { 1318 te := NewTestExporter() 1319 defaultOptions := []TracerProviderOption{WithSyncer(te), WithSampler(AlwaysSample())} 1320 tp := NewTracerProvider(append(defaultOptions, tc.options...)...) 1321 span := startSpan(tp, "WithResource") 1322 span.SetAttributes(attribute.String("key1", "value1")) 1323 got, err := endSpan(te, span) 1324 if err != nil { 1325 t.Error(err.Error()) 1326 } 1327 want := &snapshot{ 1328 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1329 TraceID: tid, 1330 TraceFlags: 0x1, 1331 }), 1332 parent: sc.WithRemote(true), 1333 name: "span0", 1334 attributes: []attribute.KeyValue{ 1335 attribute.String("key1", "value1"), 1336 }, 1337 spanKind: trace.SpanKindInternal, 1338 resource: tc.want, 1339 instrumentationLibrary: instrumentation.Library{Name: "WithResource"}, 1340 } 1341 if diff := cmpDiff(got, want); diff != "" { 1342 t.Errorf("WithResource:\n -got +want %s", diff) 1343 } 1344 }) 1345 } 1346} 1347 1348func TestWithInstrumentationVersionAndSchema(t *testing.T) { 1349 te := NewTestExporter() 1350 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 1351 1352 ctx := context.Background() 1353 ctx = trace.ContextWithRemoteSpanContext(ctx, sc) 1354 _, span := tp.Tracer( 1355 "WithInstrumentationVersion", 1356 trace.WithInstrumentationVersion("v0.1.0"), 1357 trace.WithSchemaURL("https://opentelemetry.io/schemas/1.2.0"), 1358 ).Start(ctx, "span0") 1359 got, err := endSpan(te, span) 1360 if err != nil { 1361 t.Error(err.Error()) 1362 } 1363 1364 want := &snapshot{ 1365 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1366 TraceID: tid, 1367 TraceFlags: 0x1, 1368 }), 1369 parent: sc.WithRemote(true), 1370 name: "span0", 1371 spanKind: trace.SpanKindInternal, 1372 instrumentationLibrary: instrumentation.Library{ 1373 Name: "WithInstrumentationVersion", 1374 Version: "v0.1.0", 1375 SchemaURL: "https://opentelemetry.io/schemas/1.2.0", 1376 }, 1377 } 1378 if diff := cmpDiff(got, want); diff != "" { 1379 t.Errorf("WithResource:\n -got +want %s", diff) 1380 } 1381} 1382 1383func TestSpanCapturesPanic(t *testing.T) { 1384 te := NewTestExporter() 1385 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 1386 _, span := tp.Tracer("CatchPanic").Start( 1387 context.Background(), 1388 "span", 1389 ) 1390 1391 f := func() { 1392 defer span.End() 1393 panic(errors.New("error message")) 1394 } 1395 require.PanicsWithError(t, "error message", f) 1396 spans := te.Spans() 1397 require.Len(t, spans, 1) 1398 require.Len(t, spans[0].Events(), 1) 1399 assert.Equal(t, spans[0].Events()[0].Name, semconv.ExceptionEventName) 1400 assert.Equal(t, spans[0].Events()[0].Attributes, []attribute.KeyValue{ 1401 semconv.ExceptionTypeKey.String("*errors.errorString"), 1402 semconv.ExceptionMessageKey.String("error message"), 1403 }) 1404} 1405 1406func TestSpanCapturesPanicWithStackTrace(t *testing.T) { 1407 te := NewTestExporter() 1408 tp := NewTracerProvider(WithSyncer(te), WithResource(resource.Empty())) 1409 _, span := tp.Tracer("CatchPanic").Start( 1410 context.Background(), 1411 "span", 1412 ) 1413 1414 f := func() { 1415 defer span.End(trace.WithStackTrace(true)) 1416 panic(errors.New("error message")) 1417 } 1418 require.PanicsWithError(t, "error message", f) 1419 spans := te.Spans() 1420 require.Len(t, spans, 1) 1421 require.Len(t, spans[0].Events(), 1) 1422 assert.Equal(t, spans[0].Events()[0].Name, semconv.ExceptionEventName) 1423 assert.Equal(t, spans[0].Events()[0].Attributes[0].Value.AsString(), "*errors.errorString") 1424 assert.Equal(t, spans[0].Events()[0].Attributes[1].Value.AsString(), "error message") 1425 1426 gotStackTraceFunctionName := strings.Split(spans[0].Events()[0].Attributes[2].Value.AsString(), "\n") 1427 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[1], "go.opentelemetry.io/otel/sdk/trace.recordStackTrace"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.recordStackTrace", gotStackTraceFunctionName[1]) 1428 assert.Truef(t, strings.HasPrefix(gotStackTraceFunctionName[3], "go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End"), "%q not prefixed with go.opentelemetry.io/otel/sdk/trace.(*recordingSpan).End", gotStackTraceFunctionName[3]) 1429} 1430 1431func TestReadOnlySpan(t *testing.T) { 1432 kv := attribute.String("foo", "bar") 1433 1434 tp := NewTracerProvider(WithResource(resource.NewSchemaless(kv))) 1435 tr := tp.Tracer("ReadOnlySpan", trace.WithInstrumentationVersion("3")) 1436 1437 // Initialize parent context. 1438 tID, sID := tp.idGenerator.NewIDs(context.Background()) 1439 parent := trace.NewSpanContext(trace.SpanContextConfig{ 1440 TraceID: tID, 1441 SpanID: sID, 1442 TraceFlags: 0x1, 1443 Remote: true, 1444 }) 1445 ctx := trace.ContextWithRemoteSpanContext(context.Background(), parent) 1446 1447 // Initialize linked context. 1448 tID, sID = tp.idGenerator.NewIDs(context.Background()) 1449 linked := trace.NewSpanContext(trace.SpanContextConfig{ 1450 TraceID: tID, 1451 SpanID: sID, 1452 TraceFlags: 0x1, 1453 }) 1454 1455 st := time.Now() 1456 ctx, s := tr.Start(ctx, "foo", trace.WithTimestamp(st), 1457 trace.WithLinks(trace.Link{SpanContext: linked})) 1458 s.SetAttributes(kv) 1459 s.AddEvent("foo", trace.WithAttributes(kv)) 1460 s.SetStatus(codes.Ok, "foo") 1461 1462 // Verify span implements ReadOnlySpan. 1463 ro, ok := s.(ReadOnlySpan) 1464 require.True(t, ok) 1465 1466 assert.Equal(t, "foo", ro.Name()) 1467 assert.Equal(t, trace.SpanContextFromContext(ctx), ro.SpanContext()) 1468 assert.Equal(t, parent, ro.Parent()) 1469 assert.Equal(t, trace.SpanKindInternal, ro.SpanKind()) 1470 assert.Equal(t, st, ro.StartTime()) 1471 assert.True(t, ro.EndTime().IsZero()) 1472 assert.Equal(t, kv.Key, ro.Attributes()[0].Key) 1473 assert.Equal(t, kv.Value, ro.Attributes()[0].Value) 1474 assert.Equal(t, linked, ro.Links()[0].SpanContext) 1475 assert.Equal(t, kv.Key, ro.Events()[0].Attributes[0].Key) 1476 assert.Equal(t, kv.Value, ro.Events()[0].Attributes[0].Value) 1477 assert.Equal(t, codes.Ok, ro.Status().Code) 1478 assert.Equal(t, "", ro.Status().Description) 1479 assert.Equal(t, "ReadOnlySpan", ro.InstrumentationLibrary().Name) 1480 assert.Equal(t, "3", ro.InstrumentationLibrary().Version) 1481 assert.Equal(t, kv.Key, ro.Resource().Attributes()[0].Key) 1482 assert.Equal(t, kv.Value, ro.Resource().Attributes()[0].Value) 1483 1484 // Verify changes to the original span are reflected in the ReadOnlySpan. 1485 s.SetName("bar") 1486 assert.Equal(t, "bar", ro.Name()) 1487 1488 // Verify snapshot() returns snapshots that are independent from the 1489 // original span and from one another. 1490 d1 := s.(*recordingSpan).snapshot() 1491 s.AddEvent("baz") 1492 d2 := s.(*recordingSpan).snapshot() 1493 for _, e := range d1.Events() { 1494 if e.Name == "baz" { 1495 t.Errorf("Didn't expect to find 'baz' event") 1496 } 1497 } 1498 var exists bool 1499 for _, e := range d2.Events() { 1500 if e.Name == "baz" { 1501 exists = true 1502 } 1503 } 1504 if !exists { 1505 t.Errorf("Expected to find 'baz' event") 1506 } 1507 1508 et := st.Add(time.Millisecond) 1509 s.End(trace.WithTimestamp(et)) 1510 assert.Equal(t, et, ro.EndTime()) 1511} 1512 1513func TestReadWriteSpan(t *testing.T) { 1514 tp := NewTracerProvider(WithResource(resource.Empty())) 1515 tr := tp.Tracer("ReadWriteSpan") 1516 1517 // Initialize parent context. 1518 tID, sID := tp.idGenerator.NewIDs(context.Background()) 1519 parent := trace.NewSpanContext(trace.SpanContextConfig{ 1520 TraceID: tID, 1521 SpanID: sID, 1522 TraceFlags: 0x1, 1523 }) 1524 ctx := trace.ContextWithRemoteSpanContext(context.Background(), parent) 1525 1526 _, span := tr.Start(ctx, "foo") 1527 defer span.End() 1528 1529 // Verify span implements ReadOnlySpan. 1530 rw, ok := span.(ReadWriteSpan) 1531 require.True(t, ok) 1532 1533 // Verify the span can be read from. 1534 assert.False(t, rw.StartTime().IsZero()) 1535 1536 // Verify the span can be written to. 1537 rw.SetName("bar") 1538 assert.Equal(t, "bar", rw.Name()) 1539 1540 // NOTE: This function tests ReadWriteSpan which is an interface which 1541 // embeds trace.Span and ReadOnlySpan. Since both of these interfaces have 1542 // their own tests, there is no point in testing all the possible methods 1543 // available via ReadWriteSpan as doing so would mean creating a lot of 1544 // duplication. 1545} 1546 1547func TestAddEventsWithMoreAttributesThanLimit(t *testing.T) { 1548 te := NewTestExporter() 1549 tp := NewTracerProvider( 1550 WithSpanLimits(SpanLimits{AttributePerEventCountLimit: 2}), 1551 WithSyncer(te), 1552 WithResource(resource.Empty()), 1553 ) 1554 1555 span := startSpan(tp, "AddSpanEventWithOverLimitedAttributes") 1556 span.AddEvent("test1", trace.WithAttributes( 1557 attribute.Bool("key1", true), 1558 attribute.String("key2", "value2"), 1559 )) 1560 // Parts of the attribute should be discard 1561 span.AddEvent("test2", trace.WithAttributes( 1562 attribute.Bool("key1", true), 1563 attribute.String("key2", "value2"), 1564 attribute.String("key3", "value3"), 1565 attribute.String("key4", "value4"), 1566 )) 1567 got, err := endSpan(te, span) 1568 if err != nil { 1569 t.Fatal(err) 1570 } 1571 1572 for i := range got.Events() { 1573 if !checkTime(&got.Events()[i].Time) { 1574 t.Error("exporting span: expected nonzero Event Time") 1575 } 1576 } 1577 1578 want := &snapshot{ 1579 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1580 TraceID: tid, 1581 TraceFlags: 0x1, 1582 }), 1583 parent: sc.WithRemote(true), 1584 name: "span0", 1585 attributes: nil, 1586 events: []Event{ 1587 { 1588 Name: "test1", 1589 Attributes: []attribute.KeyValue{ 1590 attribute.Bool("key1", true), 1591 attribute.String("key2", "value2"), 1592 }, 1593 }, 1594 { 1595 Name: "test2", 1596 Attributes: []attribute.KeyValue{ 1597 attribute.Bool("key1", true), 1598 attribute.String("key2", "value2"), 1599 }, 1600 DroppedAttributeCount: 2, 1601 }, 1602 }, 1603 spanKind: trace.SpanKindInternal, 1604 instrumentationLibrary: instrumentation.Library{Name: "AddSpanEventWithOverLimitedAttributes"}, 1605 } 1606 if diff := cmpDiff(got, want); diff != "" { 1607 t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff) 1608 } 1609} 1610 1611func TestAddLinksWithMoreAttributesThanLimit(t *testing.T) { 1612 te := NewTestExporter() 1613 tp := NewTracerProvider( 1614 WithSpanLimits(SpanLimits{AttributePerLinkCountLimit: 1}), 1615 WithSyncer(te), 1616 WithResource(resource.Empty()), 1617 ) 1618 1619 k1v1 := attribute.String("key1", "value1") 1620 k2v2 := attribute.String("key2", "value2") 1621 k3v3 := attribute.String("key3", "value3") 1622 k4v4 := attribute.String("key4", "value4") 1623 1624 sc1 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 1625 sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}}) 1626 1627 span := startSpan(tp, "Links", trace.WithLinks([]trace.Link{ 1628 {SpanContext: sc1, Attributes: []attribute.KeyValue{k1v1, k2v2}}, 1629 {SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2, k3v3, k4v4}}, 1630 }...)) 1631 1632 got, err := endSpan(te, span) 1633 if err != nil { 1634 t.Fatal(err) 1635 } 1636 1637 want := &snapshot{ 1638 spanContext: trace.NewSpanContext(trace.SpanContextConfig{ 1639 TraceID: tid, 1640 TraceFlags: 0x1, 1641 }), 1642 parent: sc.WithRemote(true), 1643 name: "span0", 1644 links: []Link{ 1645 { 1646 SpanContext: sc1, 1647 Attributes: []attribute.KeyValue{k1v1}, 1648 DroppedAttributeCount: 1, 1649 }, 1650 { 1651 SpanContext: sc2, 1652 Attributes: []attribute.KeyValue{k2v2}, 1653 DroppedAttributeCount: 2, 1654 }, 1655 }, 1656 spanKind: trace.SpanKindInternal, 1657 instrumentationLibrary: instrumentation.Library{Name: "Links"}, 1658 } 1659 if diff := cmpDiff(got, want); diff != "" { 1660 t.Errorf("Link: -got +want %s", diff) 1661 } 1662} 1663 1664type stateSampler struct { 1665 prefix string 1666 f func(trace.TraceState) trace.TraceState 1667} 1668 1669func (s *stateSampler) ShouldSample(p SamplingParameters) SamplingResult { 1670 decision := Drop 1671 if strings.HasPrefix(p.Name, s.prefix) { 1672 decision = RecordAndSample 1673 } 1674 ts := s.f(trace.SpanContextFromContext(p.ParentContext).TraceState()) 1675 return SamplingResult{Decision: decision, Tracestate: ts} 1676} 1677 1678func (s stateSampler) Description() string { 1679 return "stateSampler" 1680} 1681 1682// Check that a new span propagates the SamplerResult.TraceState 1683func TestSamplerTraceState(t *testing.T) { 1684 mustTS := func(ts trace.TraceState, err error) trace.TraceState { 1685 require.NoError(t, err) 1686 return ts 1687 } 1688 makeInserter := func(k, v, prefix string) Sampler { 1689 return &stateSampler{ 1690 prefix: prefix, 1691 f: func(t trace.TraceState) trace.TraceState { return mustTS(t.Insert(k, v)) }, 1692 } 1693 } 1694 makeDeleter := func(k, prefix string) Sampler { 1695 return &stateSampler{ 1696 prefix: prefix, 1697 f: func(t trace.TraceState) trace.TraceState { return t.Delete(k) }, 1698 } 1699 } 1700 clearer := func(prefix string) Sampler { 1701 return &stateSampler{ 1702 prefix: prefix, 1703 f: func(t trace.TraceState) trace.TraceState { return trace.TraceState{} }, 1704 } 1705 } 1706 1707 tests := []struct { 1708 name string 1709 sampler Sampler 1710 spanName string 1711 input trace.TraceState 1712 want trace.TraceState 1713 exportSpan bool 1714 }{ 1715 { 1716 name: "alwaysOn", 1717 sampler: AlwaysSample(), 1718 input: mustTS(trace.ParseTraceState("k1=v1")), 1719 want: mustTS(trace.ParseTraceState("k1=v1")), 1720 exportSpan: true, 1721 }, 1722 { 1723 name: "alwaysOff", 1724 sampler: NeverSample(), 1725 input: mustTS(trace.ParseTraceState("k1=v1")), 1726 want: mustTS(trace.ParseTraceState("k1=v1")), 1727 exportSpan: false, 1728 }, 1729 { 1730 name: "insertKeySampled", 1731 sampler: makeInserter("k2", "v2", "span"), 1732 spanName: "span0", 1733 input: mustTS(trace.ParseTraceState("k1=v1")), 1734 want: mustTS(trace.ParseTraceState("k2=v2,k1=v1")), 1735 exportSpan: true, 1736 }, 1737 { 1738 name: "insertKeyDropped", 1739 sampler: makeInserter("k2", "v2", "span"), 1740 spanName: "nospan0", 1741 input: mustTS(trace.ParseTraceState("k1=v1")), 1742 want: mustTS(trace.ParseTraceState("k2=v2,k1=v1")), 1743 exportSpan: false, 1744 }, 1745 { 1746 name: "deleteKeySampled", 1747 sampler: makeDeleter("k1", "span"), 1748 spanName: "span0", 1749 input: mustTS(trace.ParseTraceState("k1=v1,k2=v2")), 1750 want: mustTS(trace.ParseTraceState("k2=v2")), 1751 exportSpan: true, 1752 }, 1753 { 1754 name: "deleteKeyDropped", 1755 sampler: makeDeleter("k1", "span"), 1756 spanName: "nospan0", 1757 input: mustTS(trace.ParseTraceState("k1=v1,k2=v2,k3=v3")), 1758 want: mustTS(trace.ParseTraceState("k2=v2,k3=v3")), 1759 exportSpan: false, 1760 }, 1761 { 1762 name: "clearer", 1763 sampler: clearer("span"), 1764 spanName: "span0", 1765 input: mustTS(trace.ParseTraceState("k1=v1,k3=v3")), 1766 want: mustTS(trace.ParseTraceState("")), 1767 exportSpan: true, 1768 }, 1769 } 1770 1771 for _, ts := range tests { 1772 ts := ts 1773 t.Run(ts.name, func(t *testing.T) { 1774 te := NewTestExporter() 1775 tp := NewTracerProvider(WithSampler(ts.sampler), WithSyncer(te), WithResource(resource.Empty())) 1776 tr := tp.Tracer("TraceState") 1777 1778 sc1 := trace.NewSpanContext(trace.SpanContextConfig{ 1779 TraceID: tid, 1780 SpanID: sid, 1781 TraceFlags: trace.FlagsSampled, 1782 TraceState: ts.input, 1783 }) 1784 ctx := trace.ContextWithRemoteSpanContext(context.Background(), sc1) 1785 _, span := tr.Start(ctx, ts.spanName) 1786 1787 // span's TraceState should be set regardless of Sampled/NonSampled state. 1788 require.Equal(t, ts.want, span.SpanContext().TraceState()) 1789 1790 span.End() 1791 1792 got := te.Spans() 1793 if len(got) > 0 != ts.exportSpan { 1794 t.Errorf("unexpected number of exported spans %d", len(got)) 1795 } 1796 if len(got) == 0 { 1797 return 1798 } 1799 1800 receivedState := got[0].SpanContext().TraceState() 1801 1802 if diff := cmpDiff(receivedState, ts.want); diff != "" { 1803 t.Errorf("TraceState not propagated: -got +want %s", diff) 1804 } 1805 }) 1806 } 1807 1808} 1809 1810type testIDGenerator struct { 1811 traceID int 1812 spanID int 1813} 1814 1815func (gen *testIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { 1816 traceIDHex := fmt.Sprintf("%032x", gen.traceID) 1817 traceID, _ := trace.TraceIDFromHex(traceIDHex) 1818 gen.traceID++ 1819 1820 spanID := gen.NewSpanID(ctx, traceID) 1821 return traceID, spanID 1822} 1823 1824func (gen *testIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { 1825 spanIDHex := fmt.Sprintf("%016x", gen.spanID) 1826 spanID, _ := trace.SpanIDFromHex(spanIDHex) 1827 gen.spanID++ 1828 return spanID 1829} 1830 1831var _ IDGenerator = (*testIDGenerator)(nil) 1832 1833func TestWithIDGenerator(t *testing.T) { 1834 const ( 1835 startTraceID = 1 1836 startSpanID = 1 1837 numSpan = 10 1838 ) 1839 1840 gen := &testIDGenerator{traceID: startSpanID, spanID: startSpanID} 1841 1842 for i := 0; i < numSpan; i++ { 1843 te := NewTestExporter() 1844 tp := NewTracerProvider( 1845 WithSyncer(te), 1846 WithIDGenerator(gen), 1847 ) 1848 span := startSpan(tp, "TestWithIDGenerator") 1849 got, err := strconv.ParseUint(span.SpanContext().SpanID().String(), 16, 64) 1850 require.NoError(t, err) 1851 want := uint64(startSpanID + i) 1852 assert.Equal(t, got, want) 1853 _, err = endSpan(te, span) 1854 require.NoError(t, err) 1855 } 1856} 1857