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 }