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	"errors"
19	"fmt"
20	"strconv"
21	"testing"
22	"time"
23
24	"github.com/opentracing/opentracing-go"
25	"github.com/opentracing/opentracing-go/ext"
26	"github.com/opentracing/opentracing-go/log"
27	"github.com/stretchr/testify/assert"
28	"github.com/stretchr/testify/require"
29
30	"github.com/uber/jaeger-client-go/thrift-gen/zipkincore"
31	"github.com/uber/jaeger-client-go/utils"
32)
33
34func TestThriftFirstInProcessSpan(t *testing.T) {
35	tracer, closer := NewTracer("DOOP",
36		NewConstSampler(true),
37		NewNullReporter())
38	defer closer.Close()
39
40	sp1 := tracer.StartSpan("s1").(*Span)
41	sp2 := tracer.StartSpan("sp2", opentracing.ChildOf(sp1.Context())).(*Span)
42	sp2.Finish()
43	sp1.Finish()
44
45	tests := []struct {
46		span     *Span
47		wantTags bool
48	}{
49		{sp1, true},
50		{sp2, false},
51	}
52
53	for _, test := range tests {
54		var check func(assert.TestingT, interface{}, ...interface{}) bool
55		if test.wantTags {
56			check = assert.NotNil
57		} else {
58			check = assert.Nil
59		}
60		thriftSpan := BuildZipkinThrift(test.span)
61		version := findBinaryAnnotation(thriftSpan, JaegerClientVersionTagKey)
62		hostname := findBinaryAnnotation(thriftSpan, TracerHostnameTagKey)
63		check(t, version)
64		check(t, hostname)
65	}
66}
67
68func Test128bitTraceIDs(t *testing.T) {
69	tracer128, closer128 := NewTracer("OneTwentyEight",
70		NewConstSampler(true),
71		NewNullReporter(),
72		TracerOptions.Gen128Bit(true),
73	)
74	defer closer128.Close()
75
76	tracer64, closer64 := NewTracer("SixtyFour",
77		NewConstSampler(true),
78		NewNullReporter(),
79		TracerOptions.Gen128Bit(false),
80	)
81	defer closer64.Close()
82
83	sp1 := tracer128.StartSpan("s1").(*Span)
84	sp2 := tracer128.StartSpan("sp2", opentracing.ChildOf(sp1.Context())).(*Span)
85	sp2.Finish()
86	sp1.Finish()
87
88	thriftSpan1 := BuildZipkinThrift(sp1)
89	assert.NotNil(t, thriftSpan1.TraceIDHigh)
90
91	thriftSpan2 := BuildZipkinThrift(sp2)
92	assert.NotNil(t, thriftSpan2.TraceIDHigh)
93
94	sp3 := tracer64.StartSpan("s3").(*Span)
95	sp3.Finish()
96	thriftSpan3 := BuildZipkinThrift(sp3)
97	assert.Nil(t, thriftSpan3.TraceIDHigh)
98}
99
100func TestThriftForceSampled(t *testing.T) {
101	tracer, closer := NewTracer("DOOP",
102		NewConstSampler(false), // sample nothing
103		NewNullReporter())
104	defer closer.Close()
105
106	sp := tracer.StartSpan("s1").(*Span)
107	ext.SamplingPriority.Set(sp, 1)
108	assert.True(t, sp.context.IsSampled())
109	assert.True(t, sp.context.IsDebug())
110	thriftSpan := BuildZipkinThrift(sp)
111	assert.True(t, thriftSpan.Debug)
112}
113
114func TestThriftSpanLogs(t *testing.T) {
115	tracer, closer := NewTracer("DOOP",
116		NewConstSampler(true),
117		NewNullReporter())
118	defer closer.Close()
119	root := tracer.StartSpan("s1")
120
121	someTime := time.Now().Add(-time.Minute)
122	someTimeInt64 := utils.TimeToMicrosecondsSinceEpochInt64(someTime)
123
124	fields := func(fields ...log.Field) []log.Field {
125		return fields
126	}
127	tests := []struct {
128		fields            []log.Field
129		logFunc           func(sp opentracing.Span)
130		expected          string
131		expectedTimestamp int64
132		disableSampling   bool
133	}{
134		{fields: fields(log.String("event", "happened")), expected: "happened"},
135		{fields: fields(log.String("something", "happened")), expected: `{"something":"happened"}`},
136		{fields: fields(log.Bool("something", true)), expected: `{"something":"true"}`},
137		{fields: fields(log.Int("something", 123)), expected: `{"something":"123"}`},
138		{fields: fields(log.Int32("something", 123)), expected: `{"something":"123"}`},
139		{fields: fields(log.Int64("something", 123)), expected: `{"something":"123"}`},
140		{fields: fields(log.Uint32("something", 123)), expected: `{"something":"123"}`},
141		{fields: fields(log.Uint64("something", 123)), expected: `{"something":"123"}`},
142		{fields: fields(log.Float32("something", 123)), expected: `{"something":"123.000000"}`},
143		{fields: fields(log.Float64("something", 123)), expected: `{"something":"123.000000"}`},
144		{fields: fields(log.Error(errors.New("drugs are baaad, m-k"))),
145			expected: `{"error.object":"drugs are baaad, m-k"}`},
146		{fields: fields(log.Object("something", 123)), expected: `{"something":"123"}`},
147		{
148			fields: fields(log.Lazy(func(fv log.Encoder) {
149				fv.EmitBool("something", true)
150			})),
151			expected: `{"something":"true"}`,
152		},
153		{
154			logFunc: func(sp opentracing.Span) {
155				sp.LogKV("event", "something")
156			},
157			expected: "something",
158		},
159		{
160			logFunc: func(sp opentracing.Span) {
161				sp.LogKV("non-even number of arguments")
162			},
163			// this is a bit fragile, but ¯\_(ツ)_/¯
164			expected: `{"error.object":"non-even keyValues len: 1","function":"LogKV"}`,
165		},
166		{
167			logFunc: func(sp opentracing.Span) {
168				sp.LogEvent("something")
169			},
170			expected: "something",
171		},
172		{
173			logFunc: func(sp opentracing.Span) {
174				sp.LogEventWithPayload("something", "payload")
175			},
176			expected: `{"event":"something","payload":"payload"}`,
177		},
178		{
179			logFunc: func(sp opentracing.Span) {
180				sp.Log(opentracing.LogData{Event: "something"})
181			},
182			expected: "something",
183		},
184		{
185			logFunc: func(sp opentracing.Span) {
186				sp.Log(opentracing.LogData{Event: "something", Payload: "payload"})
187			},
188			expected: `{"event":"something","payload":"payload"}`,
189		},
190		{
191			logFunc: func(sp opentracing.Span) {
192				sp.FinishWithOptions(opentracing.FinishOptions{
193					LogRecords: []opentracing.LogRecord{
194						{
195							Timestamp: someTime,
196							Fields:    fields(log.String("event", "happened")),
197						},
198					},
199				})
200			},
201			expected:          "happened",
202			expectedTimestamp: someTimeInt64,
203		},
204		{
205			logFunc: func(sp opentracing.Span) {
206				sp.FinishWithOptions(opentracing.FinishOptions{
207					BulkLogData: []opentracing.LogData{
208						{
209							Timestamp: someTime,
210							Event:     "happened",
211						},
212					},
213				})
214			},
215			expected:          "happened",
216			expectedTimestamp: someTimeInt64,
217		},
218		{
219			logFunc: func(sp opentracing.Span) {
220				sp.FinishWithOptions(opentracing.FinishOptions{
221					BulkLogData: []opentracing.LogData{
222						{
223							Timestamp: someTime,
224							Event:     "happened",
225							Payload:   "payload",
226						},
227					},
228				})
229			},
230			expected:          `{"event":"happened","payload":"payload"}`,
231			expectedTimestamp: someTimeInt64,
232		},
233		{
234			disableSampling: true,
235			fields:          fields(log.String("event", "happened")),
236			expected:        "",
237		},
238		{
239			disableSampling: true,
240			logFunc: func(sp opentracing.Span) {
241				sp.LogKV("event", "something")
242			},
243			expected: "",
244		},
245	}
246
247	for i, test := range tests {
248		testName := fmt.Sprintf("test-%02d", i)
249		sp := tracer.StartSpan(testName, opentracing.ChildOf(root.Context()))
250		if test.disableSampling {
251			ext.SamplingPriority.Set(sp, 0)
252		}
253		if test.logFunc != nil {
254			test.logFunc(sp)
255		} else if len(test.fields) > 0 {
256			sp.LogFields(test.fields...)
257		}
258		thriftSpan := BuildZipkinThrift(sp.(*Span))
259		if test.disableSampling {
260			assert.Equal(t, 0, len(thriftSpan.Annotations), testName)
261			continue
262		}
263		assert.Equal(t, 1, len(thriftSpan.Annotations), testName)
264		assert.Equal(t, test.expected, thriftSpan.Annotations[0].Value, testName)
265		if test.expectedTimestamp != 0 {
266			assert.Equal(t, test.expectedTimestamp, thriftSpan.Annotations[0].Timestamp, testName)
267		}
268	}
269}
270
271func TestThriftLocalComponentSpan(t *testing.T) {
272	tracer, closer := NewTracer("DOOP",
273		NewConstSampler(true),
274		NewNullReporter())
275	defer closer.Close()
276
277	tests := []struct {
278		addComponentTag bool
279		wantAnnotation  string
280	}{
281		{false, "DOOP"}, // Without COMPONENT tag the value is the service name
282		{true, "c1"},
283	}
284
285	for _, test := range tests {
286		sp := tracer.StartSpan("s1").(*Span)
287		if test.addComponentTag {
288			ext.Component.Set(sp, "c1")
289		}
290		sp.Finish()
291		thriftSpan := BuildZipkinThrift(sp)
292
293		anno := findBinaryAnnotation(thriftSpan, "lc")
294		require.NotNil(t, anno)
295		assert.EqualValues(t, test.wantAnnotation, anno.Value)
296	}
297}
298
299func TestSpecialTags(t *testing.T) {
300	tracer, closer := NewTracer("DOOP",
301		NewConstSampler(true),
302		NewNullReporter())
303	defer closer.Close()
304
305	sp := tracer.StartSpan("s1").(*Span)
306	ext.SpanKindRPCServer.Set(sp)
307	ext.PeerService.Set(sp, "peer")
308	ext.PeerPort.Set(sp, 80)
309	ext.PeerHostIPv4.Set(sp, 2130706433)
310	sp.Finish()
311
312	thriftSpan := BuildZipkinThrift(sp)
313	// Special tags should not be copied over to binary annotations
314	assert.Nil(t, findBinaryAnnotation(thriftSpan, "span.kind"))
315	assert.Nil(t, findBinaryAnnotation(thriftSpan, "peer.service"))
316	assert.Nil(t, findBinaryAnnotation(thriftSpan, "peer.port"))
317	assert.Nil(t, findBinaryAnnotation(thriftSpan, "peer.ipv4"))
318	assert.Nil(t, findBinaryAnnotation(thriftSpan, "ip"))
319
320	anno := findBinaryAnnotation(thriftSpan, "ca")
321	assert.NotNil(t, anno)
322	assert.NotNil(t, anno.Host)
323	assert.EqualValues(t, 80, anno.Host.Port)
324	assert.EqualValues(t, 2130706433, anno.Host.Ipv4)
325	assert.EqualValues(t, "peer", anno.Host.ServiceName)
326
327	assert.NotNil(t, findAnnotation(thriftSpan, "sr"))
328	assert.NotNil(t, findAnnotation(thriftSpan, "ss"))
329}
330
331func TestBaggageLogs(t *testing.T) {
332	tracer, closer := NewTracer("DOOP",
333		NewConstSampler(true),
334		NewNullReporter())
335	defer closer.Close()
336
337	sp := tracer.StartSpan("s1").(*Span)
338	sp.SetBaggageItem("auth.token", "token")
339	ext.SpanKindRPCServer.Set(sp)
340	sp.Finish()
341
342	thriftSpan := BuildZipkinThrift(sp)
343	assert.NotNil(t, findAnnotation(thriftSpan, `{"event":"baggage","key":"auth.token","value":"token"}`))
344}
345
346func TestMaxTagValueLength(t *testing.T) {
347	value := make([]byte, 512)
348	tests := []struct {
349		tagValueLength int
350		value          []byte
351		expected       []byte
352	}{
353		{256, value, value[:256]},
354		{512, value, value},
355	}
356
357	for _, test := range tests {
358		t.Run(strconv.Itoa(test.tagValueLength), func(t *testing.T) {
359			tracer, closer := NewTracer("DOOP",
360				NewConstSampler(true),
361				NewNullReporter(),
362				TracerOptions.MaxTagValueLength(test.tagValueLength))
363			defer closer.Close()
364			sp := tracer.StartSpan("s1").(*Span)
365			sp.SetTag("tag.string", string(test.value))
366			sp.SetTag("tag.bytes", test.value)
367			sp.Finish()
368			thriftSpan := BuildZipkinThrift(sp)
369			assert.Equal(t, test.expected, findBinaryAnnotation(thriftSpan, "tag.string").Value)
370			assert.Equal(t, test.expected, findBinaryAnnotation(thriftSpan, "tag.bytes").Value)
371		})
372	}
373}
374
375func findAnnotation(span *zipkincore.Span, name string) *zipkincore.Annotation {
376	for _, a := range span.Annotations {
377		if a.Value == name {
378			return a
379		}
380	}
381	return nil
382}
383
384func findBinaryAnnotation(span *zipkincore.Span, name string) *zipkincore.BinaryAnnotation {
385	for _, a := range span.BinaryAnnotations {
386		if a.Key == name {
387			return a
388		}
389	}
390	return nil
391}
392