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