github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/span_test.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2017
     3  
     4  package instana_test
     5  
     6  import (
     7  	"errors"
     8  	"os"
     9  	"testing"
    10  	"time"
    11  
    12  	instana "github.com/instana/go-sensor"
    13  	ot "github.com/opentracing/opentracing-go"
    14  	"github.com/opentracing/opentracing-go/ext"
    15  	"github.com/opentracing/opentracing-go/log"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestBasicSpan(t *testing.T) {
    21  	instana.InitSensor(&instana.Options{
    22  		Service: TestServiceName,
    23  		Tracer: instana.TracerOptions{
    24  			CollectableHTTPHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
    25  		},
    26  		AgentClient: alwaysReadyClient{},
    27  	})
    28  	defer instana.ShutdownSensor()
    29  
    30  	recorder := instana.NewTestRecorder()
    31  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
    32  
    33  	start := time.Now()
    34  	sp := tracer.StartSpan("test")
    35  	time.Sleep(10 * time.Millisecond)
    36  	sp.Finish()
    37  	elapsed := time.Since(start)
    38  
    39  	spans := recorder.GetQueuedSpans()
    40  	require.Len(t, spans, 1)
    41  	span := spans[0]
    42  
    43  	assert.NotEmpty(t, span.SpanID)
    44  	assert.NotEmpty(t, span.TraceID)
    45  	assert.NotEmpty(t, span.Timestamp)
    46  	assert.LessOrEqual(t, uint64(10), span.Duration)
    47  	assert.LessOrEqual(t, span.Duration, uint64(elapsed))
    48  	assert.Equal(t, "sdk", span.Name)
    49  
    50  	require.IsType(t, instana.SDKSpanData{}, span.Data)
    51  	data := span.Data.(instana.SDKSpanData)
    52  	assert.Equal(t, TestServiceName, data.Service)
    53  
    54  	assert.Equal(t, "test", data.Tags.Name)
    55  	assert.Nil(t, data.Tags.Custom["tags"])
    56  	assert.Nil(t, data.Tags.Custom["baggage"])
    57  }
    58  
    59  func TestSpanHeritage(t *testing.T) {
    60  	recorder := instana.NewTestRecorder()
    61  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
    62  	defer instana.ShutdownSensor()
    63  
    64  	parentSpan := tracer.StartSpan("parent")
    65  
    66  	childSpan := tracer.StartSpan("child", ot.ChildOf(parentSpan.Context()))
    67  	childSpan.Finish()
    68  
    69  	parentSpan.Finish()
    70  
    71  	spans := recorder.GetQueuedSpans()
    72  	require.Len(t, spans, 2)
    73  
    74  	cSpan, pSpan := spans[0], spans[1]
    75  
    76  	// Parent should not have a parent
    77  	assert.Empty(t, pSpan.ParentID)
    78  
    79  	// Child must have parent ID set as parent
    80  	assert.Equal(t, pSpan.SpanID, cSpan.ParentID)
    81  
    82  	// Must be root span
    83  	assert.Equal(t, pSpan.TraceID, pSpan.SpanID)
    84  
    85  	// Trace ID must be consistent across spans
    86  	assert.Equal(t, cSpan.TraceID, pSpan.TraceID)
    87  
    88  	require.IsType(t, cSpan.Data, instana.SDKSpanData{})
    89  	cData := cSpan.Data.(instana.SDKSpanData)
    90  	assert.Equal(t, "child", cData.Tags.Name)
    91  
    92  	require.IsType(t, pSpan.Data, instana.SDKSpanData{})
    93  	pData := pSpan.Data.(instana.SDKSpanData)
    94  	assert.Equal(t, "parent", pData.Tags.Name)
    95  }
    96  
    97  func TestSpanBaggage(t *testing.T) {
    98  	const op = "test"
    99  	recorder := instana.NewTestRecorder()
   100  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   101  	defer instana.ShutdownSensor()
   102  
   103  	sp := tracer.StartSpan(op)
   104  	sp.SetBaggageItem("foo", "bar")
   105  	sp.Finish()
   106  
   107  	spans := recorder.GetQueuedSpans()
   108  	require.Len(t, spans, 1)
   109  	span := spans[0]
   110  
   111  	require.IsType(t, instana.SDKSpanData{}, span.Data)
   112  	data := span.Data.(instana.SDKSpanData)
   113  
   114  	assert.Equal(t, map[string]string{"foo": "bar"}, data.Tags.Custom["baggage"])
   115  }
   116  
   117  func TestSpanTags(t *testing.T) {
   118  	const op = "test"
   119  	recorder := instana.NewTestRecorder()
   120  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   121  	defer instana.ShutdownSensor()
   122  
   123  	sp := tracer.StartSpan(op)
   124  	sp.SetTag("foo", "bar")
   125  	sp.Finish()
   126  
   127  	spans := recorder.GetQueuedSpans()
   128  	require.Len(t, spans, 1)
   129  	span := spans[0]
   130  
   131  	require.IsType(t, instana.SDKSpanData{}, span.Data)
   132  	data := span.Data.(instana.SDKSpanData)
   133  
   134  	assert.Equal(t, ot.Tags{"foo": "bar"}, data.Tags.Custom["tags"])
   135  }
   136  
   137  func TestOTLogError(t *testing.T) {
   138  	recorder := instana.NewTestRecorder()
   139  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   140  	defer instana.ShutdownSensor()
   141  
   142  	sp := tracer.StartSpan("test")
   143  	ext.Error.Set(sp, true)
   144  	sp.Finish()
   145  
   146  	spans := recorder.GetQueuedSpans()
   147  	require.Equal(t, len(spans), 1)
   148  
   149  	span := spans[0]
   150  	assert.Equal(t, 1, span.Ec)
   151  
   152  	require.IsType(t, instana.SDKSpanData{}, span.Data)
   153  	data := span.Data.(instana.SDKSpanData)
   154  
   155  	assert.Equal(t, map[string]interface{}{
   156  		"tags": ot.Tags{"error": true},
   157  	}, data.Tags.Custom)
   158  }
   159  
   160  func TestSpanErrorLogKV(t *testing.T) {
   161  	recorder := instana.NewTestRecorder()
   162  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   163  	defer instana.ShutdownSensor()
   164  
   165  	sp := tracer.StartSpan("test")
   166  	sp.LogKV("error", "simulated error")
   167  	sp.Finish()
   168  
   169  	spans := recorder.GetQueuedSpans()
   170  	require.Len(t, spans, 2)
   171  
   172  	span, logSpan := spans[0], spans[1]
   173  	assert.Equal(t, 1, span.Ec)
   174  
   175  	assert.Equal(t, span.TraceID, logSpan.TraceID)
   176  	assert.Equal(t, span.SpanID, logSpan.ParentID)
   177  	assert.Equal(t, "log.go", logSpan.Name)
   178  
   179  	// assert that log message has been recorded within the span interval
   180  	assert.GreaterOrEqual(t, logSpan.Timestamp, span.Timestamp)
   181  	assert.LessOrEqual(t, logSpan.Duration, span.Duration)
   182  
   183  	require.IsType(t, instana.LogSpanData{}, logSpan.Data)
   184  	logData := logSpan.Data.(instana.LogSpanData)
   185  
   186  	assert.Equal(t, instana.LogSpanTags{
   187  		Level:   "ERROR",
   188  		Message: `error: "simulated error"`,
   189  	}, logData.Tags)
   190  }
   191  
   192  func TestSpan_LogFields(t *testing.T) {
   193  	recorder := instana.NewTestRecorder()
   194  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   195  	defer instana.ShutdownSensor()
   196  
   197  	examples := map[string]struct {
   198  		Fields             []log.Field
   199  		ExpectedErrorCount int
   200  		ExpectedTags       instana.LogSpanTags
   201  	}{
   202  		"error object": {
   203  			Fields: []log.Field{
   204  				log.Error(errors.New("simulated error")),
   205  				log.String("function", "ErrorFunc"),
   206  			},
   207  			ExpectedErrorCount: 1,
   208  			ExpectedTags: instana.LogSpanTags{
   209  				Level:   "ERROR",
   210  				Message: `error.object: "simulated error" function: "ErrorFunc"`,
   211  			},
   212  		},
   213  		"error log": {
   214  			Fields: []log.Field{
   215  				log.String("error.object", "simulated error"),
   216  				log.String("function", "ErrorFunc"),
   217  			},
   218  			ExpectedErrorCount: 1,
   219  			ExpectedTags: instana.LogSpanTags{
   220  				Level:   "ERROR",
   221  				Message: `error.object: "simulated error" function: "ErrorFunc"`,
   222  			},
   223  		},
   224  		"warn log": {
   225  			Fields: []log.Field{
   226  				log.String("warn", "simulated warning"),
   227  				log.String("function", "WarnFunc"),
   228  			},
   229  			ExpectedTags: instana.LogSpanTags{
   230  				Level:   "WARN",
   231  				Message: `warn: "simulated warning" function: "WarnFunc"`,
   232  			},
   233  		},
   234  	}
   235  
   236  	for name, example := range examples {
   237  		t.Run(name, func(t *testing.T) {
   238  			sp := tracer.StartSpan("test")
   239  			sp.LogFields(example.Fields...)
   240  			sp.Finish()
   241  
   242  			spans := recorder.GetQueuedSpans()
   243  			require.Len(t, spans, 2)
   244  
   245  			span, logSpan := spans[0], spans[1]
   246  			assert.Equal(t, example.ExpectedErrorCount, span.Ec)
   247  
   248  			assert.Equal(t, span.TraceID, logSpan.TraceID)
   249  			assert.Equal(t, span.SpanID, logSpan.ParentID)
   250  			assert.Equal(t, "log.go", logSpan.Name)
   251  
   252  			// assert that log message has been recorded within the span interval
   253  			assert.GreaterOrEqual(t, logSpan.Timestamp, span.Timestamp)
   254  			assert.LessOrEqual(t, logSpan.Duration, span.Duration)
   255  
   256  			require.IsType(t, instana.LogSpanData{}, logSpan.Data)
   257  			logData := logSpan.Data.(instana.LogSpanData)
   258  
   259  			assert.Equal(t, example.ExpectedTags, logData.Tags)
   260  		})
   261  	}
   262  }
   263  
   264  func TestSpan_Suppressed_StartSpanOption(t *testing.T) {
   265  	recorder := instana.NewTestRecorder()
   266  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   267  	defer instana.ShutdownSensor()
   268  
   269  	sp := tracer.StartSpan("test", instana.SuppressTracing())
   270  	sp.Finish()
   271  
   272  	assert.Empty(t, recorder.GetQueuedSpans())
   273  }
   274  
   275  func TestSpan_Suppressed_SetTag(t *testing.T) {
   276  	recorder := instana.NewTestRecorder()
   277  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   278  	defer instana.ShutdownSensor()
   279  
   280  	sp := tracer.StartSpan("test")
   281  	instana.SuppressTracing().Set(sp)
   282  	sp.Finish()
   283  
   284  	assert.Empty(t, recorder.GetQueuedSpans())
   285  }
   286  
   287  func Test_tracerS_SuppressTracing(t *testing.T) {
   288  	opName := "my_operation"
   289  	suppressTracingTag := "suppress_tracing"
   290  	exitSpan := ext.SpanKindRPCClientEnum
   291  	entrySpan := ext.SpanKindRPCServerEnum
   292  	allowRootExitSpanEnv := "INSTANA_ALLOW_ROOT_EXIT_SPAN"
   293  
   294  	tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, nil)
   295  	parentSpan := tracer.StartSpan("parent-span")
   296  
   297  	getSpanTags := func(kind ext.SpanKindEnum, suppressTracing bool) ot.Tags {
   298  		return ot.Tags{
   299  			"span.kind":        kind,
   300  			suppressTracingTag: suppressTracing,
   301  		}
   302  	}
   303  
   304  	type args struct {
   305  		operationName string
   306  		opts          ot.StartSpanOptions
   307  	}
   308  	tests := []struct {
   309  		name      string
   310  		exportEnv bool
   311  		args      args
   312  		want      int
   313  	}{
   314  		{
   315  			name:      "env_unset_suppress_false_spanType_exit",
   316  			exportEnv: false,
   317  			args: args{
   318  				operationName: opName,
   319  				opts: ot.StartSpanOptions{
   320  					Tags: getSpanTags(exitSpan, false),
   321  				},
   322  			},
   323  			want: 0,
   324  		},
   325  		{
   326  			name:      "env_unset_suppress_true_spanType_exit",
   327  			exportEnv: false,
   328  			args: args{
   329  				operationName: opName,
   330  				opts: ot.StartSpanOptions{
   331  					Tags: getSpanTags(exitSpan, true),
   332  				},
   333  			},
   334  			want: 0,
   335  		},
   336  		{
   337  			name:      "env_set_suppress_false_spanType_exit",
   338  			exportEnv: true,
   339  			args: args{
   340  				operationName: opName,
   341  				opts: ot.StartSpanOptions{
   342  					Tags: getSpanTags(exitSpan, false),
   343  				},
   344  			},
   345  			want: 1,
   346  		},
   347  		{
   348  			name:      "env_set_suppress_true_spanType_exit",
   349  			exportEnv: true,
   350  			args: args{
   351  				operationName: opName,
   352  				opts: ot.StartSpanOptions{
   353  					Tags: getSpanTags(exitSpan, true),
   354  				},
   355  			},
   356  			want: 0,
   357  		},
   358  		{
   359  			name:      "env_unset_suppress_false_spanType_entry",
   360  			exportEnv: false,
   361  			args: args{
   362  				operationName: opName,
   363  				opts: ot.StartSpanOptions{
   364  					Tags: getSpanTags(entrySpan, false),
   365  				},
   366  			},
   367  			want: 1,
   368  		},
   369  		{
   370  			name:      "env_unset_suppress_true_spanType_entry",
   371  			exportEnv: false,
   372  			args: args{
   373  				operationName: opName,
   374  				opts: ot.StartSpanOptions{
   375  					Tags: getSpanTags(entrySpan, true),
   376  				},
   377  			},
   378  			want: 0,
   379  		},
   380  		{
   381  			name:      "env_set_suppress_false_spanType_entry",
   382  			exportEnv: true,
   383  			args: args{
   384  				operationName: opName,
   385  				opts: ot.StartSpanOptions{
   386  					Tags: getSpanTags(entrySpan, false),
   387  				},
   388  			},
   389  			want: 1,
   390  		},
   391  		{
   392  			name:      "env_set_suppress_true_spanType_entry",
   393  			exportEnv: true,
   394  			args: args{
   395  				operationName: opName,
   396  				opts: ot.StartSpanOptions{
   397  					Tags: getSpanTags(entrySpan, true),
   398  				},
   399  			},
   400  			want: 0,
   401  		},
   402  		{
   403  			name:      "env_unset_suppress_false_spanType_ExitSpanButNotRoot",
   404  			exportEnv: false,
   405  			args: args{
   406  				operationName: opName,
   407  				opts: ot.StartSpanOptions{
   408  					Tags: getSpanTags(exitSpan, false),
   409  					References: []ot.SpanReference{
   410  						ot.ChildOf(parentSpan.Context()),
   411  					},
   412  				},
   413  			},
   414  			want: 1,
   415  		},
   416  		{
   417  			name:      "env_set_suppress_false_spanType_ExitSpanButNotRoot",
   418  			exportEnv: true,
   419  			args: args{
   420  				operationName: opName,
   421  				opts: ot.StartSpanOptions{
   422  					Tags: getSpanTags(exitSpan, false),
   423  					References: []ot.SpanReference{
   424  						ot.ChildOf(parentSpan.Context()),
   425  					},
   426  				},
   427  			},
   428  			want: 1,
   429  		},
   430  	}
   431  	for _, tt := range tests {
   432  		t.Run(tt.name, func(t *testing.T) {
   433  			if tt.exportEnv {
   434  				os.Setenv(allowRootExitSpanEnv, "1")
   435  
   436  				defer func() {
   437  					os.Unsetenv(allowRootExitSpanEnv)
   438  				}()
   439  			}
   440  
   441  			recorder := instana.NewTestRecorder()
   442  			tracer := instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder)
   443  			sp := tracer.StartSpanWithOptions(tt.args.operationName, tt.args.opts)
   444  			sp.Finish()
   445  			assert.Equal(t, tt.want, len(recorder.GetQueuedSpans()))
   446  		})
   447  	}
   448  }