github.com/rudderlabs/rudder-go-kit@v0.30.0/stats/traces_test.go (about) 1 package stats 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/ory/dockertest/v3" 12 "github.com/stretchr/testify/require" 13 "go.opentelemetry.io/otel" 14 15 "github.com/rudderlabs/rudder-go-kit/config" 16 "github.com/rudderlabs/rudder-go-kit/logger" 17 "github.com/rudderlabs/rudder-go-kit/stats/metric" 18 "github.com/rudderlabs/rudder-go-kit/stats/testhelper/tracemodel" 19 "github.com/rudderlabs/rudder-go-kit/testhelper/assert" 20 "github.com/rudderlabs/rudder-go-kit/testhelper/docker/resource/zipkin" 21 ) 22 23 func TestSpanFromContext(t *testing.T) { 24 pool, err := dockertest.NewPool("") 25 require.NoError(t, err) 26 27 zipkinContainer, err := zipkin.Setup(pool, t) 28 require.NoError(t, err) 29 30 zipkinURL := "http://localhost:" + zipkinContainer.Port + "/api/v2/spans" 31 zipkinTracesURL := "http://localhost:" + zipkinContainer.Port + "/api/v2/traces?serviceName=" + t.Name() 32 33 c := config.New() 34 c.Set("INSTANCE_ID", t.Name()) 35 c.Set("OpenTelemetry.enabled", true) 36 c.Set("RuntimeStats.enabled", false) 37 c.Set("OpenTelemetry.traces.endpoint", zipkinURL) 38 c.Set("OpenTelemetry.traces.samplingRate", 1.0) 39 c.Set("OpenTelemetry.traces.withSyncer", true) 40 c.Set("OpenTelemetry.traces.withZipkin", true) 41 stats := NewStats(c, logger.NewFactory(c), metric.NewManager(), 42 WithServiceName(t.Name()), WithServiceVersion("1.2.3"), 43 ) 44 t.Cleanup(stats.Stop) 45 46 require.NoError(t, stats.Start(context.Background(), DefaultGoRoutineFactory)) 47 48 tracer := stats.NewTracer("my-tracer") 49 50 ctx, span := tracer.Start(context.Background(), "my-span-01", SpanKindInternal) 51 spanFromCtx := tracer.SpanFromContext(ctx) 52 require.Equalf(t, span, spanFromCtx, "SpanFromContext should return the same span as the one from Start()") 53 54 // let's add the attributes to the span from the ctx, we should see them on zipkin for the same span 55 spanFromCtx.SetStatus(SpanStatusError, "some bad error") 56 spanFromCtx.SetAttributes(Tags{"key1": "value1"}) 57 spanFromCtx.AddEvent("some-event", 58 SpanWithTags(Tags{"key2": "value2"}), 59 SpanWithTimestamp(time.Date(2020, 1, 2, 3, 4, 5, 6, time.UTC)), 60 ) 61 span.End() 62 63 getTracesReq, err := http.NewRequest(http.MethodGet, zipkinTracesURL, nil) 64 require.NoError(t, err) 65 66 spansBody := assert.RequireEventuallyStatusCode(t, http.StatusOK, getTracesReq) 67 68 var traces [][]tracemodel.ZipkinTrace 69 require.NoError(t, json.Unmarshal([]byte(spansBody), &traces)) 70 71 require.Len(t, traces, 1) 72 require.Len(t, traces[0], 1) 73 require.Equal(t, traces[0][0].Name, "my-span-01") 74 require.Equal(t, map[string]string{ 75 "error": "some bad error", // this is coming from the span that we got from the ctx 76 "key1": "value1", // this is coming from the span that we got from the ctx 77 "instanceName": t.Name(), 78 "service.name": t.Name(), 79 "service.version": "1.2.3", 80 "otel.library.name": "my-tracer", 81 "otel.library.version": "1.2.3", 82 "otel.scope.name": "my-tracer", 83 "otel.scope.version": "1.2.3", 84 "otel.status_code": "ERROR", // this is coming from the span that we got from the ctx 85 "telemetry.sdk.language": "go", 86 "telemetry.sdk.name": "opentelemetry", 87 "telemetry.sdk.version": otel.Version(), 88 }, traces[0][0].Tags) 89 90 // checking the annotations coming from the AddEvent() call 91 require.Len(t, traces[0][0].Annotations, 1) 92 require.EqualValues(t, traces[0][0].Annotations[0].Timestamp, 1577934245000000) 93 require.Equal(t, traces[0][0].Annotations[0].Value, `some-event: {"key2":"value2"}`) 94 } 95 96 func TestAsyncTracePropagation(t *testing.T) { 97 pool, err := dockertest.NewPool("") 98 require.NoError(t, err) 99 100 zipkinContainer, err := zipkin.Setup(pool, t) 101 require.NoError(t, err) 102 103 zipkinURL := "http://localhost:" + zipkinContainer.Port + "/api/v2/spans" 104 zipkinTracesURL := "http://localhost:" + zipkinContainer.Port + "/api/v2/traces?serviceName=" + t.Name() 105 106 c := config.New() 107 c.Set("INSTANCE_ID", t.Name()) 108 c.Set("OpenTelemetry.enabled", true) 109 c.Set("RuntimeStats.enabled", false) 110 c.Set("OpenTelemetry.traces.endpoint", zipkinURL) 111 c.Set("OpenTelemetry.traces.samplingRate", 1.0) 112 c.Set("OpenTelemetry.traces.withSyncer", true) 113 c.Set("OpenTelemetry.traces.withZipkin", true) 114 stats := NewStats(c, logger.NewFactory(c), metric.NewManager(), WithServiceName(t.Name())) 115 t.Cleanup(stats.Stop) 116 117 require.NoError(t, stats.Start(context.Background(), DefaultGoRoutineFactory)) 118 119 tracer := stats.NewTracer("my-tracer") 120 121 // let's use an anonymous function to avoid sharing the context between the two spans 122 traceParent := func() string { 123 ctx, span := tracer.Start(context.Background(), "my-span-01", SpanKindInternal) 124 time.Sleep(123 * time.Millisecond) 125 span.End() 126 127 return GetTraceParentFromContext(ctx) 128 }() 129 130 require.NotEmpty(t, traceParent, "traceParent should not be empty") 131 t.Logf("traceParent my-span-01: %s", traceParent) 132 133 // we are not sharing any context here, let's say we stored the traceParent in a database 134 // now we want to continue the trace from the traceParent 135 136 ctx := InjectTraceParentIntoContext(context.Background(), traceParent) 137 // this span should show as a child of my-span-01 on zipkin 138 _, span := tracer.Start(ctx, "my-span-02", SpanKindInternal) 139 time.Sleep(234 * time.Millisecond) 140 span.End() 141 142 // let's verify that the two spans have the same traceID even though we did not share the context 143 t.Logf("my-span-02 trace ID: %v", span.SpanContext().TraceID()) 144 require.Equalf(t, 0, 145 strings.Index(traceParent, "00-"+span.SpanContext().TraceID().String()), 146 "The 2nd span traceID should be the same as the 1st span traceID", 147 ) 148 149 // let's check that the spans have the expected hierarchy on zipkin as well 150 getTracesReq, err := http.NewRequest(http.MethodGet, zipkinTracesURL, nil) 151 require.NoError(t, err) 152 153 spansBody := assert.RequireEventuallyStatusCode(t, http.StatusOK, getTracesReq) 154 155 var traces [][]tracemodel.ZipkinTrace 156 require.NoError(t, json.Unmarshal([]byte(spansBody), &traces)) 157 158 require.Len(t, traces, 1) 159 require.Len(t, traces[0], 2) 160 require.NotEmpty(t, traces[0][1].ParentID) 161 require.Equal(t, traces[0][0].ID, traces[0][1].ParentID) 162 } 163 164 func TestZipkinDownIsNotBlocking(t *testing.T) { 165 pool, err := dockertest.NewPool("") 166 require.NoError(t, err) 167 168 zipkinContainer, err := zipkin.Setup(pool, t) 169 require.NoError(t, err) 170 171 zipkinURL := "http://localhost:" + zipkinContainer.Port + "/api/v2/spans" 172 173 c := config.New() 174 c.Set("INSTANCE_ID", t.Name()) 175 c.Set("OpenTelemetry.enabled", true) 176 c.Set("RuntimeStats.enabled", false) 177 c.Set("OpenTelemetry.traces.endpoint", zipkinURL) 178 c.Set("OpenTelemetry.traces.samplingRate", 1.0) 179 c.Set("OpenTelemetry.traces.withSyncer", true) 180 c.Set("OpenTelemetry.traces.withZipkin", true) 181 stats := NewStats(c, logger.NewFactory(c), metric.NewManager(), WithServiceName(t.Name())) 182 t.Cleanup(stats.Stop) 183 require.NoError(t, stats.Start(context.Background(), DefaultGoRoutineFactory)) 184 185 tracer := stats.NewTracer("my-tracer") 186 187 done := make(chan struct{}) 188 go func() { 189 defer close(done) 190 _, span := tracer.Start(context.Background(), "my-span-01", SpanKindInternal) 191 require.NoError(t, zipkinContainer.Purge()) 192 span.End() 193 }() 194 195 select { 196 case <-done: 197 case <-time.After(500 * time.Millisecond): 198 t.Fatal("The Tracing API should not block if Zipkin is down") 199 } 200 }