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

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package instana_test
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/instana/go-sensor/acceptor"
    17  	"github.com/instana/go-sensor/autoprofile"
    18  
    19  	instana "github.com/instana/go-sensor"
    20  	"github.com/instana/go-sensor/w3ctrace"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func BenchmarkTracingNamedHandlerFunc(b *testing.B) {
    26  	recorder := instana.NewTestRecorder()
    27  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
    28  		Service:     "go-sensor-test",
    29  		AgentClient: alwaysReadyClient{},
    30  	}, recorder))
    31  	defer instana.ShutdownSensor()
    32  
    33  	h := instana.TracingNamedHandlerFunc(s, "action", "/{action}", func(w http.ResponseWriter, req *http.Request) {
    34  		fmt.Fprintln(w, "Ok")
    35  	})
    36  
    37  	req := httptest.NewRequest(http.MethodGet, "/test?q=term", nil)
    38  
    39  	rec := httptest.NewRecorder()
    40  
    41  	b.ResetTimer()
    42  
    43  	for i := 0; i < b.N; i++ {
    44  		h.ServeHTTP(rec, req)
    45  	}
    46  }
    47  
    48  func TestTracingNamedHandlerFunc_Write(t *testing.T) {
    49  	opts := &instana.Options{
    50  		Service: "go-sensor-test",
    51  		Tracer: instana.TracerOptions{
    52  			CollectableHTTPHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
    53  		},
    54  		AgentClient: alwaysReadyClient{},
    55  	}
    56  
    57  	recorder := instana.NewTestRecorder()
    58  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(opts, recorder))
    59  	defer instana.ShutdownSensor()
    60  
    61  	h := instana.TracingNamedHandlerFunc(s, "action", "/{action}", func(w http.ResponseWriter, req *http.Request) {
    62  		w.Header().Set("X-Response", "true")
    63  		w.Header().Set("X-Custom-Header-2", "response")
    64  		fmt.Fprintln(w, "Ok")
    65  	})
    66  
    67  	req := httptest.NewRequest(http.MethodGet, "/test?q=term", nil)
    68  	req.Header.Set("Authorization", "Basic blah")
    69  	req.Header.Set("X-Custom-Header-1", "request")
    70  
    71  	rec := httptest.NewRecorder()
    72  	h.ServeHTTP(rec, req)
    73  
    74  	assert.Equal(t, http.StatusOK, rec.Code)
    75  	assert.Equal(t, "Ok\n", rec.Body.String())
    76  
    77  	spans := recorder.GetQueuedSpans()
    78  	require.Len(t, spans, 1)
    79  
    80  	span := spans[0]
    81  	assert.Equal(t, 0, span.Ec)
    82  	assert.EqualValues(t, instana.EntrySpanKind, span.Kind)
    83  	assert.False(t, span.Synthetic)
    84  	assert.Empty(t, span.CorrelationType)
    85  	assert.Empty(t, span.CorrelationID)
    86  	assert.False(t, span.ForeignTrace)
    87  	assert.Empty(t, span.Ancestor)
    88  
    89  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
    90  	data := span.Data.(instana.HTTPSpanData)
    91  
    92  	assert.Equal(t, instana.HTTPSpanTags{
    93  		Host:   "example.com",
    94  		Status: http.StatusOK,
    95  		Method: "GET",
    96  		Path:   "/test",
    97  		Params: "q=term",
    98  		Headers: map[string]string{
    99  			"x-custom-header-1": "request",
   100  			"x-custom-header-2": "response",
   101  		},
   102  		PathTemplate: "/{action}",
   103  		RouteID:      "action",
   104  	}, data.Tags)
   105  
   106  	// check whether the trace context has been sent back to the client
   107  	assert.Equal(t, instana.FormatID(span.TraceID), rec.Header().Get(instana.FieldT))
   108  	assert.Equal(t, instana.FormatID(span.SpanID), rec.Header().Get(instana.FieldS))
   109  
   110  	// w3c trace context
   111  	traceparent := rec.Header().Get(w3ctrace.TraceParentHeader)
   112  	assert.Contains(t, traceparent, instana.FormatLongID(span.TraceIDHi, span.TraceID))
   113  	assert.Contains(t, traceparent, instana.FormatID(span.SpanID))
   114  
   115  	tracestate := rec.Header().Get(w3ctrace.TraceStateHeader)
   116  	assert.True(t, strings.HasPrefix(
   117  		tracestate,
   118  		"in="+instana.FormatID(span.TraceID)+";"+instana.FormatID(span.SpanID),
   119  	), tracestate)
   120  }
   121  
   122  func TestTracingNamedHandlerFunc_InstanaFieldLPriorityOverTraceParentHeader(t *testing.T) {
   123  	type testCase struct {
   124  		headers                 http.Header
   125  		traceParentHeaderSuffix string
   126  	}
   127  
   128  	testCases := map[string]testCase{
   129  		"traceparent is suppressed, x-instana-l is not suppressed": {
   130  			headers: http.Header{
   131  				w3ctrace.TraceParentHeader: []string{"00-00000000000000000000000000000001-0000000000000001-00"},
   132  				instana.FieldL:             []string{"1"},
   133  			},
   134  			traceParentHeaderSuffix: "-01",
   135  		},
   136  		"traceparent is suppressed, x-instana-l is absent (is not suppressed by default)": {
   137  			headers: http.Header{
   138  				w3ctrace.TraceParentHeader: []string{"00-00000000000000000000000000000001-0000000000000001-00"},
   139  			},
   140  			traceParentHeaderSuffix: "-01",
   141  		},
   142  		"traceparent is not suppressed, x-instana-l is absent (tracing enabled by default)": {
   143  			headers: http.Header{
   144  				w3ctrace.TraceParentHeader: []string{"00-00000000000000000000000000000001-0000000000000001-01"},
   145  			},
   146  			traceParentHeaderSuffix: "-01",
   147  		},
   148  		"traceparent is not suppressed, x-instana-l is not suppressed": {
   149  			headers: http.Header{
   150  				w3ctrace.TraceParentHeader: []string{"00-00000000000000000000000000000001-0000000000000001-01"},
   151  				instana.FieldL:             []string{"1"},
   152  			},
   153  			traceParentHeaderSuffix: "-01",
   154  		},
   155  		"traceparent is suppressed, x-instana-l is suppressed": {
   156  			headers: http.Header{
   157  				w3ctrace.TraceParentHeader: []string{"00-00000000000000000000000000000001-0000000000000001-00"},
   158  				instana.FieldL:             []string{"0"},
   159  			},
   160  			traceParentHeaderSuffix: "-00",
   161  		},
   162  		"traceparent is not suppressed, x-instana-l is suppressed": {
   163  			headers: http.Header{
   164  				w3ctrace.TraceParentHeader: []string{"00-00000000000000000000000000000001-0000000000000001-01"},
   165  				instana.FieldL:             []string{"0"},
   166  			},
   167  			traceParentHeaderSuffix: "-00",
   168  		},
   169  	}
   170  
   171  	recorder := instana.NewTestRecorder()
   172  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
   173  		Service:     "go-sensor-test",
   174  		AgentClient: alwaysReadyClient{},
   175  	}, recorder))
   176  	defer instana.ShutdownSensor()
   177  
   178  	h := instana.TracingNamedHandlerFunc(s, "action", "/test", func(w http.ResponseWriter, req *http.Request) {})
   179  
   180  	for name, testCase := range testCases {
   181  		req := httptest.NewRequest(http.MethodGet, "/test", nil)
   182  		req.Header = testCase.headers
   183  
   184  		rec := httptest.NewRecorder()
   185  		h.ServeHTTP(rec, req)
   186  
   187  		assert.Equal(t, http.StatusOK, rec.Code)
   188  		assert.True(t, strings.HasSuffix(rec.Header().Get(w3ctrace.TraceParentHeader), testCase.traceParentHeaderSuffix), "case '"+name+"' failed")
   189  	}
   190  }
   191  
   192  func TestTracingNamedHandlerFunc_WriteHeaders(t *testing.T) {
   193  	recorder := instana.NewTestRecorder()
   194  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   195  	defer instana.ShutdownSensor()
   196  
   197  	h := instana.TracingNamedHandlerFunc(s, "test", "/test", func(w http.ResponseWriter, req *http.Request) {
   198  		w.WriteHeader(http.StatusNotFound)
   199  	})
   200  
   201  	rec := httptest.NewRecorder()
   202  	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/test?q=term", nil))
   203  
   204  	assert.Equal(t, http.StatusNotFound, rec.Code)
   205  
   206  	spans := recorder.GetQueuedSpans()
   207  	require.Len(t, spans, 1)
   208  
   209  	span := spans[0]
   210  	assert.Equal(t, 0, span.Ec)
   211  	assert.EqualValues(t, instana.EntrySpanKind, span.Kind)
   212  	assert.False(t, span.Synthetic)
   213  	assert.Empty(t, span.CorrelationType)
   214  	assert.Empty(t, span.CorrelationID)
   215  	assert.False(t, span.ForeignTrace)
   216  	assert.Empty(t, span.Ancestor)
   217  
   218  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   219  	data := span.Data.(instana.HTTPSpanData)
   220  
   221  	assert.Equal(t, instana.HTTPSpanTags{
   222  		Status:  http.StatusNotFound,
   223  		Method:  "GET",
   224  		Host:    "example.com",
   225  		Path:    "/test",
   226  		Params:  "q=term",
   227  		RouteID: "test",
   228  	}, data.Tags)
   229  
   230  	// check whether the trace context has been sent back to the client
   231  	assert.Equal(t, instana.FormatID(span.TraceID), rec.Header().Get(instana.FieldT))
   232  	assert.Equal(t, instana.FormatID(span.SpanID), rec.Header().Get(instana.FieldS))
   233  
   234  	// w3c trace context
   235  	traceparent := rec.Header().Get(w3ctrace.TraceParentHeader)
   236  	assert.Contains(t, traceparent, instana.FormatLongID(span.TraceIDHi, span.TraceID))
   237  	assert.Contains(t, traceparent, instana.FormatID(span.SpanID))
   238  
   239  	tracestate := rec.Header().Get(w3ctrace.TraceStateHeader)
   240  	assert.True(t, strings.HasPrefix(
   241  		tracestate,
   242  		"in="+instana.FormatID(span.TraceID)+";"+instana.FormatID(span.SpanID),
   243  	), tracestate)
   244  }
   245  
   246  func TestTracingNamedHandlerFunc_W3CTraceContext(t *testing.T) {
   247  	recorder := instana.NewTestRecorder()
   248  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   249  	defer instana.ShutdownSensor()
   250  
   251  	h := instana.TracingNamedHandlerFunc(s, "test", "/test", func(w http.ResponseWriter, req *http.Request) {
   252  		fmt.Fprintln(w, "Ok")
   253  	})
   254  
   255  	rec := httptest.NewRecorder()
   256  
   257  	req := httptest.NewRequest(http.MethodGet, "/test", nil)
   258  	req.Header.Set(w3ctrace.TraceParentHeader, "00-00000000000000010000000000000002-0000000000000003-01")
   259  	req.Header.Set(w3ctrace.TraceStateHeader, "in=1234;5678,rojo=00f067aa0ba902b7")
   260  
   261  	h.ServeHTTP(rec, req)
   262  
   263  	assert.Equal(t, http.StatusOK, rec.Code)
   264  
   265  	spans := recorder.GetQueuedSpans()
   266  	require.Len(t, spans, 1)
   267  
   268  	span := spans[0]
   269  
   270  	assert.EqualValues(t, 0x1, span.TraceIDHi)
   271  	assert.EqualValues(t, 0x2, span.TraceID)
   272  	assert.EqualValues(t, 0x3, span.ParentID)
   273  
   274  	assert.Equal(t, 0, span.Ec)
   275  	assert.EqualValues(t, instana.EntrySpanKind, span.Kind)
   276  	assert.False(t, span.Synthetic)
   277  	assert.Empty(t, span.CorrelationType)
   278  	assert.Empty(t, span.CorrelationID)
   279  	assert.True(t, span.ForeignTrace)
   280  	assert.Equal(t, &instana.TraceReference{
   281  		TraceID:  "1234",
   282  		ParentID: "5678",
   283  	}, span.Ancestor)
   284  
   285  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   286  	data := span.Data.(instana.HTTPSpanData)
   287  
   288  	assert.Equal(t, instana.HTTPSpanTags{
   289  		Host:    "example.com",
   290  		Status:  http.StatusOK,
   291  		Method:  "GET",
   292  		Path:    "/test",
   293  		RouteID: "test",
   294  	}, data.Tags)
   295  
   296  	// check whether the trace context has been sent back to the client
   297  	assert.Equal(t, instana.FormatID(span.TraceID), rec.Header().Get(instana.FieldT))
   298  	assert.Equal(t, instana.FormatID(span.SpanID), rec.Header().Get(instana.FieldS))
   299  
   300  	// w3c trace context
   301  	traceparent := rec.Header().Get(w3ctrace.TraceParentHeader)
   302  	assert.Contains(t, traceparent, instana.FormatLongID(span.TraceIDHi, span.TraceID))
   303  	assert.Contains(t, traceparent, instana.FormatID(span.SpanID))
   304  
   305  	tracestate := rec.Header().Get(w3ctrace.TraceStateHeader)
   306  	assert.True(t, strings.HasPrefix(
   307  		tracestate,
   308  		"in="+instana.FormatID(span.TraceID)+";"+instana.FormatID(span.SpanID),
   309  	), tracestate)
   310  }
   311  
   312  func TestTracingHandlerFunc_SecretsFiltering(t *testing.T) {
   313  	recorder := instana.NewTestRecorder()
   314  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{
   315  		Service:     "go-sensor-test",
   316  		AgentClient: alwaysReadyClient{},
   317  	}, recorder))
   318  	defer instana.ShutdownSensor()
   319  
   320  	h := instana.TracingNamedHandlerFunc(s, "action", "/{action}", func(w http.ResponseWriter, req *http.Request) {
   321  		fmt.Fprintln(w, "Ok")
   322  	})
   323  
   324  	req := httptest.NewRequest(http.MethodGet, "/test?q=term&sensitive_key=s3cr3t&myPassword=qwerty&SECRET_VALUE=1", nil)
   325  
   326  	rec := httptest.NewRecorder()
   327  	h.ServeHTTP(rec, req)
   328  
   329  	assert.Equal(t, http.StatusOK, rec.Code)
   330  	assert.Equal(t, "Ok\n", rec.Body.String())
   331  
   332  	spans := recorder.GetQueuedSpans()
   333  	require.Len(t, spans, 1)
   334  
   335  	span := spans[0]
   336  	assert.Equal(t, 0, span.Ec)
   337  	assert.EqualValues(t, instana.EntrySpanKind, span.Kind)
   338  	assert.False(t, span.Synthetic)
   339  	assert.Empty(t, span.CorrelationType)
   340  	assert.Empty(t, span.CorrelationID)
   341  
   342  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   343  	data := span.Data.(instana.HTTPSpanData)
   344  
   345  	assert.Equal(t, instana.HTTPSpanTags{
   346  		Host:         "example.com",
   347  		Status:       http.StatusOK,
   348  		Method:       "GET",
   349  		Path:         "/test",
   350  		Params:       "SECRET_VALUE=%3Credacted%3E&myPassword=%3Credacted%3E&q=term&sensitive_key=%3Credacted%3E",
   351  		PathTemplate: "/{action}",
   352  		RouteID:      "action",
   353  	}, data.Tags)
   354  
   355  	// check whether the trace context has been sent back to the client
   356  	assert.Equal(t, instana.FormatID(span.TraceID), rec.Header().Get(instana.FieldT))
   357  	assert.Equal(t, instana.FormatID(span.SpanID), rec.Header().Get(instana.FieldS))
   358  }
   359  
   360  func TestTracingHandlerFunc_Error(t *testing.T) {
   361  	recorder := instana.NewTestRecorder()
   362  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   363  	defer instana.ShutdownSensor()
   364  
   365  	h := instana.TracingNamedHandlerFunc(s, "test", "/test", func(w http.ResponseWriter, req *http.Request) {
   366  		http.Error(w, "something went wrong", http.StatusInternalServerError)
   367  	})
   368  
   369  	rec := httptest.NewRecorder()
   370  	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/test", nil))
   371  
   372  	assert.Equal(t, http.StatusInternalServerError, rec.Code)
   373  
   374  	spans := recorder.GetQueuedSpans()
   375  	require.Len(t, spans, 2)
   376  
   377  	span, logSpan := spans[0], spans[1]
   378  	assert.Equal(t, 1, span.Ec)
   379  	assert.EqualValues(t, instana.EntrySpanKind, span.Kind)
   380  	assert.False(t, span.Synthetic)
   381  
   382  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   383  	data := span.Data.(instana.HTTPSpanData)
   384  
   385  	assert.Equal(t, instana.HTTPSpanTags{
   386  		Status:  http.StatusInternalServerError,
   387  		Method:  "GET",
   388  		Host:    "example.com",
   389  		Path:    "/test",
   390  		RouteID: "test",
   391  		Error:   "Internal Server Error",
   392  	}, data.Tags)
   393  
   394  	assert.Equal(t, span.TraceID, logSpan.TraceID)
   395  	assert.Equal(t, span.SpanID, logSpan.ParentID)
   396  	assert.Equal(t, "log.go", logSpan.Name)
   397  
   398  	// assert that log message has been recorded within the span interval
   399  	assert.GreaterOrEqual(t, logSpan.Timestamp, span.Timestamp)
   400  	assert.LessOrEqual(t, logSpan.Duration, span.Duration)
   401  
   402  	require.IsType(t, instana.LogSpanData{}, logSpan.Data)
   403  	logData := logSpan.Data.(instana.LogSpanData)
   404  
   405  	assert.Equal(t, instana.LogSpanTags{
   406  		Level:   "ERROR",
   407  		Message: `error: "Internal Server Error"`,
   408  	}, logData.Tags)
   409  }
   410  
   411  func TestTracingHandlerFunc_SyntheticCall(t *testing.T) {
   412  	recorder := instana.NewTestRecorder()
   413  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   414  	defer instana.ShutdownSensor()
   415  
   416  	h := instana.TracingNamedHandlerFunc(s, "test-handler", "/", func(w http.ResponseWriter, req *http.Request) {
   417  		fmt.Fprintln(w, "Ok")
   418  	})
   419  
   420  	rec := httptest.NewRecorder()
   421  
   422  	req := httptest.NewRequest(http.MethodGet, "/test", nil)
   423  	req.Header.Set(instana.FieldSynthetic, "1")
   424  
   425  	h.ServeHTTP(rec, req)
   426  
   427  	assert.Equal(t, http.StatusOK, rec.Code)
   428  
   429  	spans := recorder.GetQueuedSpans()
   430  	require.Len(t, spans, 1)
   431  	assert.True(t, spans[0].Synthetic)
   432  }
   433  
   434  func TestTracingHandlerFunc_EUMCall(t *testing.T) {
   435  	recorder := instana.NewTestRecorder()
   436  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   437  	defer instana.ShutdownSensor()
   438  
   439  	h := instana.TracingNamedHandlerFunc(s, "test-handler", "/", func(w http.ResponseWriter, req *http.Request) {
   440  		fmt.Fprintln(w, "Ok")
   441  	})
   442  
   443  	rec := httptest.NewRecorder()
   444  
   445  	req := httptest.NewRequest(http.MethodGet, "/test", nil)
   446  	req.Header.Set(instana.FieldL, "1,correlationType=web;correlationId=eum correlation id")
   447  
   448  	h.ServeHTTP(rec, req)
   449  
   450  	assert.Equal(t, http.StatusOK, rec.Code)
   451  
   452  	spans := recorder.GetQueuedSpans()
   453  	require.Len(t, spans, 1)
   454  	assert.Equal(t, "web", spans[0].CorrelationType)
   455  	assert.Equal(t, "eum correlation id", spans[0].CorrelationID)
   456  }
   457  
   458  func TestTracingHandlerFunc_PanicHandling(t *testing.T) {
   459  	recorder := instana.NewTestRecorder()
   460  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   461  	defer instana.ShutdownSensor()
   462  
   463  	h := instana.TracingNamedHandlerFunc(s, "test", "/test", func(w http.ResponseWriter, req *http.Request) {
   464  		panic("something went wrong")
   465  	})
   466  
   467  	rec := httptest.NewRecorder()
   468  	assert.Panics(t, func() {
   469  		h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/test?q=term", nil))
   470  	})
   471  
   472  	spans := recorder.GetQueuedSpans()
   473  	require.Len(t, spans, 2)
   474  
   475  	span, logSpan := spans[0], spans[1]
   476  	assert.Equal(t, 1, span.Ec)
   477  	assert.EqualValues(t, instana.EntrySpanKind, span.Kind)
   478  	assert.False(t, span.Synthetic)
   479  
   480  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   481  	data := span.Data.(instana.HTTPSpanData)
   482  
   483  	assert.Equal(t, instana.HTTPSpanTags{
   484  		Status:  http.StatusInternalServerError,
   485  		Method:  "GET",
   486  		Host:    "example.com",
   487  		Path:    "/test",
   488  		Params:  "q=term",
   489  		RouteID: "test",
   490  		Error:   "something went wrong",
   491  	}, data.Tags)
   492  
   493  	assert.Equal(t, span.TraceID, logSpan.TraceID)
   494  	assert.Equal(t, span.SpanID, logSpan.ParentID)
   495  	assert.Equal(t, "log.go", logSpan.Name)
   496  
   497  	// assert that log message has been recorded within the span interval
   498  	assert.GreaterOrEqual(t, logSpan.Timestamp, span.Timestamp)
   499  	assert.LessOrEqual(t, logSpan.Duration, span.Duration)
   500  
   501  	require.IsType(t, instana.LogSpanData{}, logSpan.Data)
   502  	logData := logSpan.Data.(instana.LogSpanData)
   503  
   504  	assert.Equal(t, instana.LogSpanTags{
   505  		Level:   "ERROR",
   506  		Message: `error: "something went wrong"`,
   507  	}, logData.Tags)
   508  }
   509  
   510  func TestRoundTripper(t *testing.T) {
   511  	recorder := instana.NewTestRecorder()
   512  	opts := &instana.Options{
   513  		Service: TestServiceName,
   514  		Tracer: instana.TracerOptions{
   515  			CollectableHTTPHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
   516  		},
   517  		AgentClient: alwaysReadyClient{},
   518  	}
   519  	tracer := instana.NewTracerWithEverything(opts, recorder)
   520  	s := instana.NewSensorWithTracer(tracer)
   521  	defer instana.ShutdownSensor()
   522  
   523  	parentSpan := tracer.StartSpan("parent")
   524  
   525  	var traceIDHeader, spanIDHeader string
   526  	rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
   527  		traceIDHeader = req.Header.Get(instana.FieldT)
   528  		spanIDHeader = req.Header.Get(instana.FieldS)
   529  
   530  		return &http.Response{
   531  			Status:     http.StatusText(http.StatusNotImplemented),
   532  			StatusCode: http.StatusNotImplemented,
   533  			Header: http.Header{
   534  				"X-Response":        []string{"true"},
   535  				"X-Custom-Header-2": []string{"response"},
   536  			},
   537  		}, nil
   538  	}))
   539  
   540  	ctx := instana.ContextWithSpan(context.Background(), parentSpan)
   541  	req := httptest.NewRequest("GET", "http://user:password@example.com/hello?q=term&sensitive_key=s3cr3t&myPassword=qwerty&SECRET_VALUE=1", nil)
   542  	req.Header.Set("X-Custom-Header-1", "request")
   543  	req.Header.Set("Authorization", "Basic blah")
   544  
   545  	_, err := rt.RoundTrip(req.WithContext(ctx))
   546  	require.NoError(t, err)
   547  
   548  	parentSpan.Finish()
   549  
   550  	spans := recorder.GetQueuedSpans()
   551  	require.Len(t, spans, 2)
   552  
   553  	cSpan, pSpan := spans[0], spans[1]
   554  	assert.Equal(t, 0, cSpan.Ec)
   555  	assert.EqualValues(t, instana.ExitSpanKind, cSpan.Kind)
   556  
   557  	assert.Equal(t, pSpan.TraceID, cSpan.TraceID)
   558  	assert.Equal(t, pSpan.SpanID, cSpan.ParentID)
   559  
   560  	assert.Equal(t, instana.FormatID(cSpan.TraceID), traceIDHeader)
   561  	assert.Equal(t, instana.FormatID(cSpan.SpanID), spanIDHeader)
   562  
   563  	require.IsType(t, instana.HTTPSpanData{}, cSpan.Data)
   564  	data := cSpan.Data.(instana.HTTPSpanData)
   565  
   566  	assert.Equal(t, instana.HTTPSpanTags{
   567  		Method: "GET",
   568  		Status: http.StatusNotImplemented,
   569  		URL:    "http://example.com/hello",
   570  		Params: "SECRET_VALUE=%3Credacted%3E&myPassword=%3Credacted%3E&q=term&sensitive_key=%3Credacted%3E",
   571  		Headers: map[string]string{
   572  			"x-custom-header-1": "request",
   573  			"x-custom-header-2": "response",
   574  		},
   575  	}, data.Tags)
   576  }
   577  
   578  func TestRoundTripper_WithoutParentSpan(t *testing.T) {
   579  	recorder := instana.NewTestRecorder()
   580  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   581  	defer instana.ShutdownSensor()
   582  
   583  	rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
   584  		// These fields will be present, as an exit span would be created
   585  		// However the exit spans will not be recorded, as they are discarded before sending to the agent.
   586  		assert.NotEmpty(t, req.Header.Get(instana.FieldT))
   587  		assert.NotEmpty(t, req.Header.Get(instana.FieldS))
   588  
   589  		return &http.Response{
   590  			Status:     http.StatusText(http.StatusNotImplemented),
   591  			StatusCode: http.StatusNotImplemented,
   592  		}, nil
   593  	}))
   594  
   595  	resp, err := rt.RoundTrip(httptest.NewRequest("GET", "http://example.com/hello", nil))
   596  	require.NoError(t, err)
   597  	assert.Equal(t, http.StatusNotImplemented, resp.StatusCode)
   598  
   599  	assert.Empty(t, recorder.GetQueuedSpans())
   600  }
   601  
   602  func TestRoundTripper_AllowRootExitSpan(t *testing.T) {
   603  
   604  	os.Setenv("INSTANA_ALLOW_ROOT_EXIT_SPAN", "1")
   605  	defer func() {
   606  		os.Unsetenv("INSTANA_ALLOW_ROOT_EXIT_SPAN")
   607  	}()
   608  
   609  	recorder := instana.NewTestRecorder()
   610  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   611  	defer instana.ShutdownSensor()
   612  
   613  	rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
   614  		// These fields will be present as an exit span would be created
   615  		assert.NotEmpty(t, req.Header.Get(instana.FieldT))
   616  		assert.NotEmpty(t, req.Header.Get(instana.FieldS))
   617  
   618  		return &http.Response{
   619  			Status:     http.StatusText(http.StatusNotImplemented),
   620  			StatusCode: http.StatusNotImplemented,
   621  		}, nil
   622  	}))
   623  
   624  	resp, err := rt.RoundTrip(httptest.NewRequest("GET", "http://example.com/hello", nil))
   625  	require.NoError(t, err)
   626  	assert.Equal(t, http.StatusNotImplemented, resp.StatusCode)
   627  
   628  	// the spans are present in the recorder as INSTANA_ALLOW_ROOT_EXIT_SPAN is configured
   629  	spans := recorder.GetQueuedSpans()
   630  	require.Len(t, spans, 1)
   631  	span := spans[0]
   632  	assert.Equal(t, 0, span.Ec)
   633  	assert.EqualValues(t, instana.ExitSpanKind, span.Kind)
   634  }
   635  
   636  func TestRoundTripper_Error(t *testing.T) {
   637  	serverErr := errors.New("something went wrong")
   638  
   639  	recorder := instana.NewTestRecorder()
   640  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   641  	defer instana.ShutdownSensor()
   642  
   643  	rt := instana.RoundTripper(s, testRoundTripper(func(req *http.Request) (*http.Response, error) {
   644  		return nil, serverErr
   645  	}))
   646  
   647  	ctx := instana.ContextWithSpan(context.Background(), s.Tracer().StartSpan("parent"))
   648  	req := httptest.NewRequest("GET", "http://example.com/hello?q=term&key=s3cr3t", nil)
   649  
   650  	_, err := rt.RoundTrip(req.WithContext(ctx))
   651  	assert.Error(t, err)
   652  
   653  	spans := recorder.GetQueuedSpans()
   654  	require.Len(t, spans, 2)
   655  
   656  	span, logSpan := spans[0], spans[1]
   657  	assert.Equal(t, 1, span.Ec)
   658  	assert.EqualValues(t, instana.ExitSpanKind, span.Kind)
   659  
   660  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   661  	data := span.Data.(instana.HTTPSpanData)
   662  
   663  	assert.Equal(t, instana.HTTPSpanTags{
   664  		Method: "GET",
   665  		URL:    "http://example.com/hello",
   666  		Params: "key=%3Credacted%3E&q=term",
   667  		Error:  "something went wrong",
   668  	}, data.Tags)
   669  
   670  	assert.Equal(t, span.TraceID, logSpan.TraceID)
   671  	assert.Equal(t, span.SpanID, logSpan.ParentID)
   672  	assert.Equal(t, "log.go", logSpan.Name)
   673  
   674  	// assert that log message has been recorded within the span interval
   675  	assert.GreaterOrEqual(t, logSpan.Timestamp, span.Timestamp)
   676  	assert.LessOrEqual(t, logSpan.Duration, span.Duration)
   677  
   678  	require.IsType(t, instana.LogSpanData{}, logSpan.Data)
   679  	logData := logSpan.Data.(instana.LogSpanData)
   680  
   681  	assert.Equal(t, instana.LogSpanTags{
   682  		Level:   "ERROR",
   683  		Message: `error.object: "something went wrong"`,
   684  	}, logData.Tags)
   685  }
   686  
   687  func TestRoundTripper_DefaultTransport(t *testing.T) {
   688  	recorder := instana.NewTestRecorder()
   689  	s := instana.NewSensorWithTracer(instana.NewTracerWithEverything(&instana.Options{AgentClient: alwaysReadyClient{}}, recorder))
   690  	defer instana.ShutdownSensor()
   691  
   692  	var numCalls int
   693  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   694  		numCalls++
   695  
   696  		assert.NotEmpty(t, req.Header.Get(instana.FieldT))
   697  		assert.NotEmpty(t, req.Header.Get(instana.FieldS))
   698  
   699  		w.Write([]byte("OK"))
   700  	}))
   701  	defer ts.Close()
   702  
   703  	rt := instana.RoundTripper(s, nil)
   704  
   705  	ctx := instana.ContextWithSpan(context.Background(), s.Tracer().StartSpan("parent"))
   706  	req := httptest.NewRequest("GET", ts.URL+"/hello", nil)
   707  
   708  	resp, err := rt.RoundTrip(req.WithContext(ctx))
   709  	require.NoError(t, err)
   710  	assert.Equal(t, http.StatusOK, resp.StatusCode)
   711  
   712  	assert.Equal(t, 1, numCalls)
   713  
   714  	spans := recorder.GetQueuedSpans()
   715  	require.Len(t, spans, 1)
   716  
   717  	span := spans[0]
   718  	assert.Equal(t, 0, span.Ec)
   719  	assert.EqualValues(t, instana.ExitSpanKind, span.Kind)
   720  
   721  	require.IsType(t, instana.HTTPSpanData{}, span.Data)
   722  	data := span.Data.(instana.HTTPSpanData)
   723  
   724  	assert.Equal(t, instana.HTTPSpanTags{
   725  		Status: http.StatusOK,
   726  		Method: "GET",
   727  		URL:    ts.URL + "/hello",
   728  	}, data.Tags)
   729  }
   730  
   731  type testRoundTripper func(*http.Request) (*http.Response, error)
   732  
   733  func (rt testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   734  	return rt(req)
   735  }
   736  
   737  type alwaysReadyClient struct{}
   738  
   739  func (alwaysReadyClient) Ready() bool                                       { return true }
   740  func (alwaysReadyClient) SendMetrics(data acceptor.Metrics) error           { return nil }
   741  func (alwaysReadyClient) SendEvent(event *instana.EventData) error          { return nil }
   742  func (alwaysReadyClient) SendSpans(spans []instana.Span) error              { return nil }
   743  func (alwaysReadyClient) SendProfiles(profiles []autoprofile.Profile) error { return nil }
   744  func (alwaysReadyClient) Flush(context.Context) error                       { return nil }