1// Copyright (c) 2017 Uber Technologies, Inc. 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 jaeger 16 17import ( 18 "io" 19 "net/http" 20 "testing" 21 "time" 22 23 "github.com/opentracing/opentracing-go" 24 "github.com/opentracing/opentracing-go/ext" 25 "github.com/opentracing/opentracing-go/harness" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 "github.com/stretchr/testify/suite" 29 "github.com/uber/jaeger-lib/metrics/metricstest" 30 31 "github.com/uber/jaeger-client-go/internal/baggage" 32 "github.com/uber/jaeger-client-go/log" 33 "github.com/uber/jaeger-client-go/utils" 34) 35 36type tracerSuite struct { 37 suite.Suite 38 tracer opentracing.Tracer 39 closer io.Closer 40 metricsFactory *metricstest.Factory 41} 42 43func (s *tracerSuite) SetupTest() { 44 s.metricsFactory = metricstest.NewFactory(0) 45 metrics := NewMetrics(s.metricsFactory, nil) 46 47 s.tracer, s.closer = NewTracer("DOOP", // respect the classics, man! 48 NewConstSampler(true), 49 NewNullReporter(), 50 TracerOptions.Metrics(metrics), 51 TracerOptions.ZipkinSharedRPCSpan(true), 52 TracerOptions.BaggageRestrictionManager(baggage.NewDefaultRestrictionManager(0)), 53 TracerOptions.PoolSpans(false), 54 ) 55 s.NotNil(s.tracer) 56} 57 58func (s *tracerSuite) TearDownTest() { 59 if s.tracer != nil { 60 s.closer.Close() 61 s.tracer = nil 62 } 63} 64 65func TestTracerSuite(t *testing.T) { 66 suite.Run(t, new(tracerSuite)) 67} 68 69func (s *tracerSuite) TestBeginRootSpan() { 70 s.metricsFactory.Clear() 71 startTime := time.Now() 72 s.tracer.(*Tracer).timeNow = func() time.Time { return startTime } 73 someID := uint64(12345) 74 s.tracer.(*Tracer).randomNumber = func() uint64 { return someID } 75 76 sp := s.tracer.StartSpan("get_name") 77 ext.SpanKindRPCServer.Set(sp) 78 ext.PeerService.Set(sp, "peer-service") 79 s.NotNil(sp) 80 ss := sp.(*Span) 81 s.NotNil(ss.tracer, "Tracer must be referenced from span") 82 s.Equal("get_name", ss.operationName) 83 s.Len(ss.tags, 4, "Span should have 2 sampler tags, span.kind tag and peer.service tag") 84 s.EqualValues(Tag{key: "span.kind", value: ext.SpanKindRPCServerEnum}, ss.tags[2], "Span must be server-side") 85 s.EqualValues(Tag{key: "peer.service", value: "peer-service"}, ss.tags[3], "Client is 'peer-service'") 86 87 s.EqualValues(someID, ss.context.traceID.Low) 88 s.EqualValues(0, ss.context.parentID) 89 90 s.Equal(startTime, ss.startTime) 91 92 sp.Finish() 93 s.NotNil(ss.duration) 94 95 s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{ 96 {Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 1}, 97 {Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 1}, 98 {Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 1}, 99 }...) 100} 101 102func (s *tracerSuite) TestStartRootSpanWithOptions() { 103 ts := time.Now() 104 sp := s.tracer.StartSpan("get_address", opentracing.StartTime(ts)) 105 ss := sp.(*Span) 106 s.Equal("get_address", ss.operationName) 107 s.Equal(ts, ss.startTime) 108} 109 110func (s *tracerSuite) TestStartChildSpan() { 111 s.metricsFactory.Clear() 112 sp1 := s.tracer.StartSpan("get_address") 113 sp2 := s.tracer.StartSpan("get_street", opentracing.ChildOf(sp1.Context())) 114 s.Equal(sp1.(*Span).context.spanID, sp2.(*Span).context.parentID) 115 sp2.Finish() 116 s.NotNil(sp2.(*Span).duration) 117 sp1.Finish() 118 s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{ 119 {Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 2}, 120 {Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 1}, 121 {Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 2}, 122 }...) 123} 124 125type nonJaegerSpanContext struct{} 126 127func (c nonJaegerSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} 128 129func (s *tracerSuite) TestStartSpanWithMultipleReferences() { 130 s.metricsFactory.Clear() 131 sp1 := s.tracer.StartSpan("A") 132 sp2 := s.tracer.StartSpan("B") 133 sp3 := s.tracer.StartSpan("C") 134 sp4 := s.tracer.StartSpan( 135 "D", 136 opentracing.ChildOf(sp1.Context()), 137 opentracing.ChildOf(sp2.Context()), 138 opentracing.FollowsFrom(sp3.Context()), 139 opentracing.FollowsFrom(nonJaegerSpanContext{}), 140 opentracing.FollowsFrom(SpanContext{}), // Empty span context should be excluded 141 ) 142 // Should use the first ChildOf ref span as the parent 143 s.Equal(sp1.(*Span).context.spanID, sp4.(*Span).context.parentID) 144 sp4.Finish() 145 s.NotNil(sp4.(*Span).duration) 146 sp3.Finish() 147 sp2.Finish() 148 sp1.Finish() 149 s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{ 150 {Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 4}, 151 {Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 3}, 152 {Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 4}, 153 }...) 154 assert.Len(s.T(), sp4.(*Span).references, 3) 155} 156 157func (s *tracerSuite) TestStartSpanWithOnlyFollowFromReference() { 158 s.metricsFactory.Clear() 159 sp1 := s.tracer.StartSpan("A") 160 sp2 := s.tracer.StartSpan( 161 "B", 162 opentracing.FollowsFrom(sp1.Context()), 163 ) 164 // Should use the first ChildOf ref span as the parent 165 s.Equal(sp1.(*Span).context.spanID, sp2.(*Span).context.parentID) 166 sp2.Finish() 167 s.NotNil(sp2.(*Span).duration) 168 sp1.Finish() 169 s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{ 170 {Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": "y"}, Value: 2}, 171 {Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": "y", "state": "started"}, Value: 1}, 172 {Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": "y"}, Value: 2}, 173 }...) 174 assert.Len(s.T(), sp2.(*Span).references, 1) 175} 176 177func (s *tracerSuite) TestTraceStartedOrJoinedMetrics() { 178 tests := []struct { 179 sampled bool 180 label string 181 }{ 182 {true, "y"}, 183 {false, "n"}, 184 } 185 for _, test := range tests { 186 s.metricsFactory.Clear() 187 s.tracer.(*Tracer).sampler = NewConstSampler(test.sampled) 188 sp1 := s.tracer.StartSpan("parent", ext.RPCServerOption(nil)) 189 sp2 := s.tracer.StartSpan("child1", opentracing.ChildOf(sp1.Context())) 190 sp3 := s.tracer.StartSpan("child2", ext.RPCServerOption(sp2.Context())) 191 s.Equal(sp2.(*Span).context.spanID, sp3.(*Span).context.spanID) 192 s.Equal(sp2.(*Span).context.parentID, sp3.(*Span).context.parentID) 193 sp3.Finish() 194 sp2.Finish() 195 sp1.Finish() 196 s.Equal(test.sampled, sp1.Context().(SpanContext).IsSampled()) 197 s.Equal(test.sampled, sp2.Context().(SpanContext).IsSampled()) 198 199 s.metricsFactory.AssertCounterMetrics(s.T(), []metricstest.ExpectedMetric{ 200 {Name: "jaeger.tracer.started_spans", Tags: map[string]string{"sampled": test.label}, Value: 3}, 201 {Name: "jaeger.tracer.finished_spans", Tags: map[string]string{"sampled": test.label}, Value: 3}, 202 {Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": test.label, "state": "started"}, Value: 1}, 203 {Name: "jaeger.tracer.traces", Tags: map[string]string{"sampled": test.label, "state": "joined"}, Value: 1}, 204 }...) 205 } 206} 207 208func (s *tracerSuite) TestSetOperationName() { 209 sp1 := s.tracer.StartSpan("get_address") 210 sp1.SetOperationName("get_street") 211 s.Equal("get_street", sp1.(*Span).operationName) 212} 213 214func (s *tracerSuite) TestSamplerEffects() { 215 s.tracer.(*Tracer).sampler = NewConstSampler(true) 216 sp := s.tracer.StartSpan("test") 217 s.True(sp.(*Span).context.IsSampled()) 218 219 s.tracer.(*Tracer).sampler = NewConstSampler(false) 220 sp = s.tracer.StartSpan("test") 221 s.False(sp.(*Span).context.IsSampled()) 222} 223 224func (s *tracerSuite) TestRandomIDNotZero() { 225 val := uint64(0) 226 s.tracer.(*Tracer).randomNumber = func() (r uint64) { 227 r = val 228 val++ 229 return 230 } 231 sp := s.tracer.StartSpan("get_name").(*Span) 232 s.EqualValues(TraceID{Low: 1}, sp.context.traceID) 233 234 rng := utils.NewRand(0) 235 rng.Seed(1) // for test coverage 236} 237 238func (s *tracerSuite) TestReferenceSelfUsesProvidedContext() { 239 ctx := NewSpanContext( 240 TraceID{ 241 High: 1, 242 Low: 2, 243 }, 244 SpanID(2), 245 SpanID(1), 246 false, 247 nil, 248 ) 249 sp1 := s.tracer.StartSpan( 250 "continued_span", 251 SelfRef(ctx), 252 ) 253 s.Equal(ctx, sp1.(*Span).context) 254} 255 256func TestTracerOptions(t *testing.T) { 257 t1, e := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00") 258 assert.NoError(t, e) 259 260 timeNow := func() time.Time { 261 return t1 262 } 263 rnd := func() uint64 { 264 return 1 265 } 266 isPoolAllocator := func(allocator SpanAllocator) bool { 267 _, ok := allocator.(*syncPollSpanAllocator) 268 return ok 269 } 270 271 openTracer, closer := NewTracer("DOOP", // respect the classics, man! 272 NewConstSampler(true), 273 NewNullReporter(), 274 TracerOptions.Logger(log.StdLogger), 275 TracerOptions.TimeNow(timeNow), 276 TracerOptions.RandomNumber(rnd), 277 TracerOptions.PoolSpans(true), 278 TracerOptions.Tag("tag_key", "tag_value"), 279 TracerOptions.NoDebugFlagOnForcedSampling(true), 280 ) 281 defer closer.Close() 282 283 tracer := openTracer.(*Tracer) 284 assert.Equal(t, log.StdLogger, tracer.logger) 285 assert.Equal(t, t1, tracer.timeNow()) 286 assert.Equal(t, uint64(1), tracer.randomNumber()) 287 assert.Equal(t, uint64(1), tracer.randomNumber()) 288 assert.Equal(t, uint64(1), tracer.randomNumber()) // always 1 289 assert.Equal(t, true, isPoolAllocator(tracer.spanAllocator)) 290 assert.Equal(t, opentracing.Tag{Key: "tag_key", Value: "tag_value"}, tracer.Tags()[0]) 291 assert.True(t, tracer.options.noDebugFlagOnForcedSampling) 292} 293 294func TestInjectorExtractorOptions(t *testing.T) { 295 tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), 296 TracerOptions.Injector("dummy", &dummyPropagator{}), 297 TracerOptions.Extractor("dummy", &dummyPropagator{}), 298 ) 299 defer tc.Close() 300 301 sp := tracer.StartSpan("x") 302 c := &dummyCarrier{} 303 err := tracer.Inject(sp.Context(), "dummy", []int{}) 304 assert.Equal(t, opentracing.ErrInvalidCarrier, err) 305 err = tracer.Inject(sp.Context(), "dummy", c) 306 assert.NoError(t, err) 307 assert.True(t, c.ok) 308 309 c.ok = false 310 _, err = tracer.Extract("dummy", []int{}) 311 assert.Equal(t, opentracing.ErrInvalidCarrier, err) 312 _, err = tracer.Extract("dummy", c) 313 assert.Equal(t, opentracing.ErrSpanContextNotFound, err) 314 c.ok = true 315 _, err = tracer.Extract("dummy", c) 316 assert.NoError(t, err) 317} 318 319func TestEmptySpanContextAsParent(t *testing.T) { 320 tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter()) 321 defer tc.Close() 322 323 span := tracer.StartSpan("test", opentracing.ChildOf(emptyContext)) 324 ctx := span.Context().(SpanContext) 325 assert.True(t, ctx.traceID.IsValid()) 326 assert.True(t, ctx.IsValid()) 327} 328 329func TestGen128Bit(t *testing.T) { 330 tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.Gen128Bit(true)) 331 defer tc.Close() 332 333 span := tracer.StartSpan("test", opentracing.ChildOf(emptyContext)) 334 defer span.Finish() 335 traceID := span.Context().(SpanContext).TraceID() 336 assert.True(t, traceID.High != 0) 337 assert.True(t, traceID.Low != 0) 338} 339 340func TestZipkinSharedRPCSpan(t *testing.T) { 341 tracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.ZipkinSharedRPCSpan(false)) 342 343 sp1 := tracer.StartSpan("client", ext.SpanKindRPCClient) 344 sp2 := tracer.StartSpan("server", opentracing.ChildOf(sp1.Context()), ext.SpanKindRPCServer) 345 assert.Equal(t, sp1.(*Span).context.spanID, sp2.(*Span).context.parentID) 346 assert.NotEqual(t, sp1.(*Span).context.spanID, sp2.(*Span).context.spanID) 347 sp2.Finish() 348 sp1.Finish() 349 tc.Close() 350 351 tracer, tc = NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.ZipkinSharedRPCSpan(true)) 352 353 sp1 = tracer.StartSpan("client", ext.SpanKindRPCClient) 354 sp2 = tracer.StartSpan("server", opentracing.ChildOf(sp1.Context()), ext.SpanKindRPCServer) 355 assert.Equal(t, sp1.(*Span).context.spanID, sp2.(*Span).context.spanID) 356 assert.Equal(t, sp1.(*Span).context.parentID, sp2.(*Span).context.parentID) 357 sp2.Finish() 358 sp1.Finish() 359 tc.Close() 360} 361 362type testDebugThrottler struct { 363 process Process 364} 365 366func (t *testDebugThrottler) SetProcess(process Process) { 367 t.process = process 368} 369 370func (t *testDebugThrottler) Close() error { 371 return nil 372} 373 374func (t *testDebugThrottler) IsAllowed(operation string) bool { 375 return true 376} 377 378func TestDebugThrottler(t *testing.T) { 379 throttler := &testDebugThrottler{} 380 opentracingTracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.DebugThrottler(throttler)) 381 assert.NoError(t, tc.Close()) 382 tracer := opentracingTracer.(*Tracer) 383 assert.Equal(t, tracer.process, throttler.process) 384} 385 386func TestThrottling_SamplingPriority(t *testing.T) { 387 tracer, closer := NewTracer("DOOP", NewConstSampler(true), NewNullReporter()) 388 389 sp1 := tracer.StartSpan("s1", opentracing.Tags{string(ext.SamplingPriority): 0}).(*Span) 390 assert.False(t, sp1.context.IsDebug()) 391 392 sp1 = tracer.StartSpan("s1", opentracing.Tags{string(ext.SamplingPriority): uint16(1)}).(*Span) 393 assert.True(t, sp1.context.IsDebug()) 394 395 assert.NotNil(t, findDomainTag(sp1, "sampling.priority"), "sampling.priority tag should be added") 396 closer.Close() 397 398 tracer, closer = NewTracer("DOOP", NewConstSampler(true), NewNullReporter(), 399 TracerOptions.DebugThrottler(testThrottler{allowAll: false})) 400 defer closer.Close() 401 402 sp1 = tracer.StartSpan("s1", opentracing.Tags{string(ext.SamplingPriority): uint16(1)}).(*Span) 403 ext.SamplingPriority.Set(sp1, 1) 404 assert.False(t, sp1.context.IsDebug(), "debug should not be allowed by the throttler") 405} 406 407func TestThrottling_DebugHeader(t *testing.T) { 408 tracer, closer := NewTracer("DOOP", NewConstSampler(true), NewNullReporter()) 409 410 h := http.Header{} 411 h.Add(JaegerDebugHeader, "x") 412 ctx, err := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(h)) 413 require.NoError(t, err) 414 415 sp := tracer.StartSpan("root", opentracing.ChildOf(ctx)).(*Span) 416 assert.True(t, sp.context.IsDebug()) 417 assert.Len(t, sp.References(), 0) 418 closer.Close() 419 420 tracer, closer = NewTracer("DOOP", NewConstSampler(true), NewNullReporter(), 421 TracerOptions.DebugThrottler(testThrottler{allowAll: false})) 422 defer closer.Close() 423 424 sp = tracer.StartSpan("root", opentracing.ChildOf(ctx)).(*Span) 425 assert.False(t, sp.context.IsDebug(), "debug should not be allowed by the throttler") 426} 427 428func TestSetGetTag(t *testing.T) { 429 opentracer, tc := NewTracer("x", NewConstSampler(true), NewNullReporter()) 430 tracer := opentracer.(*Tracer) 431 defer tc.Close() 432 value, ok := tracer.getTag(TracerIPTagKey) 433 assert.True(t, ok) 434 _, ok = value.(string) 435 assert.True(t, ok) 436 assert.True(t, tracer.hostIPv4 != 0) 437 438 ipStr := "11.22.33.44" 439 opentracer, tc = NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.Tag(TracerIPTagKey, ipStr)) 440 tracer = opentracer.(*Tracer) 441 defer tc.Close() 442 value, ok = tracer.getTag(TracerIPTagKey) 443 assert.True(t, ok) 444 assert.True(t, value == ipStr) 445 assert.True(t, tracer.hostIPv4 != 0) 446 447 ipStrInvalid := "an invalid input" 448 opentracer, tc = NewTracer("x", NewConstSampler(true), NewNullReporter(), TracerOptions.Tag(TracerIPTagKey, ipStrInvalid)) 449 tracer = opentracer.(*Tracer) 450 defer tc.Close() 451 value, ok = tracer.getTag(TracerIPTagKey) 452 assert.True(t, ok) 453 assert.True(t, value == ipStrInvalid) 454 assert.True(t, tracer.hostIPv4 == 0) 455} 456 457func TestTracerGetSampler(t *testing.T) { 458 sampler := NewRateLimitingSampler(1) 459 tracer, closer := NewTracer("service", sampler, NewNullReporter()) 460 defer closer.Close() 461 462 assert.Same(t, sampler, tracer.(*Tracer).Sampler()) 463} 464 465type dummyPropagator struct{} 466type dummyCarrier struct { 467 ok bool 468} 469 470func (p *dummyPropagator) Inject(ctx SpanContext, carrier interface{}) error { 471 c, ok := carrier.(*dummyCarrier) 472 if !ok { 473 return opentracing.ErrInvalidCarrier 474 } 475 c.ok = true 476 return nil 477} 478 479func (p *dummyPropagator) Extract(carrier interface{}) (SpanContext, error) { 480 c, ok := carrier.(*dummyCarrier) 481 if !ok { 482 return emptyContext, opentracing.ErrInvalidCarrier 483 } 484 if c.ok { 485 return emptyContext, nil 486 } 487 return emptyContext, opentracing.ErrSpanContextNotFound 488} 489 490func TestAPI(t *testing.T) { 491 harness.RunAPIChecks( 492 t, 493 func() (opentracing.Tracer, func()) { 494 tracer, closer := NewTracer("DOOP", // respect the classics, man! 495 NewConstSampler(true), 496 NewNullReporter(), 497 ) 498 499 return tracer, func() { closer.Close() } 500 }, 501 harness.CheckEverything(), 502 harness.UseProbe(&jaegerProbe{}), 503 ) 504} 505 506type jaegerProbe struct{} 507 508// SameTrace helps tests assert that this tracer's spans are from the same trace. 509func (jp *jaegerProbe) SameTrace(first, second opentracing.Span) bool { 510 firstCtx := first.Context().(SpanContext) 511 secondCtx := second.Context().(SpanContext) 512 return firstCtx.traceID == secondCtx.traceID 513} 514 515// SameSpanContext helps tests assert that a span and a context are from the same trace and span. 516func (jp *jaegerProbe) SameSpanContext(first opentracing.Span, second opentracing.SpanContext) bool { 517 firstCtx := first.Context().(SpanContext) 518 secondCtx := second.(SpanContext) 519 return firstCtx.traceID == secondCtx.traceID && firstCtx.spanID == secondCtx.spanID 520} 521