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  }