Add captured headers options for tracing
Co-authored-by: Baptiste Mayelle <baptiste.mayelle@traefik.io>
This commit is contained in:
parent
86be0a4e6f
commit
709ff6fb09
15 changed files with 520 additions and 119 deletions
|
@ -4,27 +4,25 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
tracingMiddleware "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/opentelemetry"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
"github.com/traefik/traefik/v3/pkg/version"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
"go.opentelemetry.io/contrib/propagators/autoprop"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/embedded"
|
||||
)
|
||||
|
||||
func TestForwardAuthFail(t *testing.T) {
|
||||
|
@ -466,64 +464,146 @@ func Test_writeHeader(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestForwardAuthUsesTracing(t *testing.T) {
|
||||
func TestForwardAuthTracing(t *testing.T) {
|
||||
type expected struct {
|
||||
name string
|
||||
attributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Traceparent") == "" {
|
||||
t.Errorf("expected Traceparent header to be present in request")
|
||||
}
|
||||
|
||||
w.Header().Set("X-Bar", "foo")
|
||||
w.Header().Add("X-Bar", "bar")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
next := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
auth := dynamic.ForwardAuth{
|
||||
Address: server.URL,
|
||||
}
|
||||
|
||||
exporter := tracetest.NewInMemoryExporter()
|
||||
|
||||
tres, err := resource.New(context.Background(),
|
||||
resource.WithAttributes(semconv.ServiceNameKey.String("traefik")),
|
||||
resource.WithAttributes(semconv.ServiceVersionKey.String(version.Version)),
|
||||
resource.WithFromEnv(),
|
||||
resource.WithTelemetrySDK(),
|
||||
)
|
||||
parse, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
tracerProvider := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||
sdktrace.WithResource(tres),
|
||||
sdktrace.WithBatcher(exporter),
|
||||
)
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
_, serverPort, err := net.SplitHostPort(parse.Host)
|
||||
require.NoError(t, err)
|
||||
|
||||
config := &static.Tracing{
|
||||
ServiceName: "testApp",
|
||||
SampleRate: 1,
|
||||
OTLP: &opentelemetry.Config{
|
||||
HTTP: &types.OtelHTTP{
|
||||
Endpoint: "http://127.0.0.1:8080",
|
||||
serverPortInt, err := strconv.Atoi(serverPort)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
expected []expected
|
||||
}{
|
||||
{
|
||||
desc: "basic test",
|
||||
expected: []expected{
|
||||
{
|
||||
name: "initial",
|
||||
attributes: []attribute.KeyValue{
|
||||
attribute.String("span.kind", "unspecified"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AuthRequest",
|
||||
attributes: []attribute.KeyValue{
|
||||
attribute.String("span.kind", "client"),
|
||||
attribute.String("http.request.method", "GET"),
|
||||
attribute.String("network.protocol.version", "1.1"),
|
||||
attribute.String("url.full", server.URL),
|
||||
attribute.String("url.scheme", "http"),
|
||||
attribute.String("user_agent.original", ""),
|
||||
attribute.String("network.peer.address", "127.0.0.1"),
|
||||
attribute.String("network.peer.port", serverPort),
|
||||
attribute.String("server.address", "127.0.0.1"),
|
||||
attribute.Int64("server.port", int64(serverPortInt)),
|
||||
attribute.StringSlice("http.request.header.x-foo", []string{"foo", "bar"}),
|
||||
attribute.Int64("http.response.status_code", int64(404)),
|
||||
attribute.StringSlice("http.response.header.x-bar", []string{"foo", "bar"}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
tr, closer, err := tracing.NewTracing(config)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = closer.Close()
|
||||
})
|
||||
|
||||
next, err = NewForward(context.Background(), next, auth, "authTest")
|
||||
require.NoError(t, err)
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
next := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
chain := alice.New(tracingMiddleware.WrapEntryPointHandler(context.Background(), tr, "tracingTest"))
|
||||
next, err = chain.Then(next)
|
||||
require.NoError(t, err)
|
||||
auth := dynamic.ForwardAuth{
|
||||
Address: server.URL,
|
||||
AuthRequestHeaders: []string{"X-Foo"},
|
||||
}
|
||||
next, err := NewForward(context.Background(), next, auth, "authTest")
|
||||
require.NoError(t, err)
|
||||
|
||||
ts := httptest.NewServer(next)
|
||||
t.Cleanup(ts.Close)
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
|
||||
req.RemoteAddr = "10.0.0.1:1234"
|
||||
req.Header.Set("User-Agent", "forward-test")
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
req.Header.Set("X-Foo", "foo")
|
||||
req.Header.Add("X-Foo", "bar")
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
|
||||
|
||||
mockTracer := &mockTracer{}
|
||||
tracer := tracing.NewTracer(mockTracer, []string{"X-Foo"}, []string{"X-Bar"})
|
||||
initialCtx, initialSpan := tracer.Start(req.Context(), "initial")
|
||||
defer initialSpan.End()
|
||||
req = req.WithContext(initialCtx)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
next.ServeHTTP(recorder, req)
|
||||
|
||||
for i, span := range mockTracer.spans {
|
||||
assert.Equal(t, test.expected[i].name, span.name)
|
||||
assert.Equal(t, test.expected[i].attributes, span.attributes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockTracer struct {
|
||||
embedded.Tracer
|
||||
|
||||
spans []*mockSpan
|
||||
}
|
||||
|
||||
var _ trace.Tracer = &mockTracer{}
|
||||
|
||||
func (t *mockTracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
|
||||
config := trace.NewSpanStartConfig(opts...)
|
||||
span := &mockSpan{}
|
||||
span.SetName(name)
|
||||
span.SetAttributes(attribute.String("span.kind", config.SpanKind().String()))
|
||||
span.SetAttributes(config.Attributes()...)
|
||||
t.spans = append(t.spans, span)
|
||||
return trace.ContextWithSpan(ctx, span), span
|
||||
}
|
||||
|
||||
// mockSpan is an implementation of Span that preforms no operations.
|
||||
type mockSpan struct {
|
||||
embedded.Span
|
||||
|
||||
name string
|
||||
attributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
var _ trace.Span = &mockSpan{}
|
||||
|
||||
func (*mockSpan) SpanContext() trace.SpanContext {
|
||||
return trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID{1}, SpanID: trace.SpanID{1}})
|
||||
}
|
||||
func (*mockSpan) IsRecording() bool { return false }
|
||||
func (s *mockSpan) SetStatus(_ codes.Code, _ string) {}
|
||||
func (s *mockSpan) SetAttributes(kv ...attribute.KeyValue) {
|
||||
s.attributes = append(s.attributes, kv...)
|
||||
}
|
||||
func (s *mockSpan) End(...trace.SpanEndOption) {}
|
||||
func (s *mockSpan) RecordError(_ error, _ ...trace.EventOption) {}
|
||||
func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {}
|
||||
|
||||
func (s *mockSpan) SetName(name string) { s.name = name }
|
||||
|
||||
func (s *mockSpan) TracerProvider() trace.TracerProvider {
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue