1
0
Fork 0

Merge branch v3.0 into master

This commit is contained in:
Fernandez Ludovic 2024-04-03 20:30:13 +02:00
commit 1ffbffb26a
326 changed files with 8850 additions and 9322 deletions

View file

@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"net/url"
"slices"
"strconv"
"strings"
)
@ -59,13 +60,9 @@ func (c *searchCriterion) searchIn(values ...string) bool {
return true
}
for _, v := range values {
if strings.Contains(strings.ToLower(v), strings.ToLower(c.Search)) {
return true
}
}
return false
return slices.ContainsFunc(values, func(v string) bool {
return strings.Contains(strings.ToLower(v), strings.ToLower(c.Search))
})
}
func (c *searchCriterion) filterService(name string) bool {

View file

@ -39,7 +39,7 @@ func Append(router *mux.Router, customAssets fs.FS) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
w.Header().Del("Content-Type")
http.StripPrefix("/dashboard/", http.FileServer(http.FS(assets))).ServeHTTP(w, r)
http.StripPrefix("/dashboard/", http.FileServerFS(assets)).ServeHTTP(w, r)
})
}
@ -56,7 +56,7 @@ func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
w.Header().Del("Content-Type")
http.FileServer(http.FS(assets)).ServeHTTP(w, r)
http.FileServerFS(assets).ServeHTTP(w, r)
}
func safePrefix(req *http.Request) string {

View file

@ -42,7 +42,6 @@ func Test_safePrefix(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -91,7 +90,6 @@ func Test_ContentSecurityPolicy(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -149,7 +149,7 @@ func getProviderName(id string) string {
func extractType(element interface{}) string {
v := reflect.ValueOf(element).Elem()
for i := 0; i < v.NumField(); i++ {
for i := range v.NumField() {
field := v.Field(i)
if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(dynamic.PluginConf{}) {

View file

@ -210,7 +210,6 @@ func TestHandler_EntryPoints(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -256,7 +255,7 @@ func TestHandler_EntryPoints(t *testing.T) {
func generateEntryPoints(nb int) map[string]*static.EntryPoint {
eps := make(map[string]*static.EntryPoint, nb)
for i := 0; i < nb; i++ {
for i := range nb {
eps[fmt.Sprintf("ep%2d", i)] = &static.EntryPoint{
Address: ":" + strconv.Itoa(i),
}

View file

@ -998,7 +998,6 @@ func TestHandler_HTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -1050,7 +1049,7 @@ func TestHandler_HTTP(t *testing.T) {
func generateHTTPRouters(nbRouters int) map[string]*runtime.RouterInfo {
routers := make(map[string]*runtime.RouterInfo, nbRouters)
for i := 0; i < nbRouters; i++ {
for i := range nbRouters {
routers[fmt.Sprintf("bar%2d@myprovider", i)] = &runtime.RouterInfo{
Router: &dynamic.Router{
EntryPoints: []string{"web"},

View file

@ -226,7 +226,7 @@ func getProviders(conf static.Configuration) []string {
var providers []string
v := reflect.ValueOf(conf.Providers).Elem()
for i := 0; i < v.NumField(); i++ {
for i := range v.NumField() {
field := v.Field(i)
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
if !field.IsNil() {
@ -256,7 +256,7 @@ func getMetrics(conf static.Configuration) string {
}
v := reflect.ValueOf(conf.Metrics).Elem()
for i := 0; i < v.NumField(); i++ {
for i := range v.NumField() {
field := v.Field(i)
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
if !field.IsNil() {
@ -274,7 +274,7 @@ func getTracing(conf static.Configuration) string {
}
v := reflect.ValueOf(conf.Tracing).Elem()
for i := 0; i < v.NumField(); i++ {
for i := range v.NumField() {
field := v.Field(i)
if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
if !field.IsNil() {

View file

@ -269,7 +269,6 @@ func TestHandler_Overview(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -874,7 +874,6 @@ func TestHandler_TCP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -127,7 +127,6 @@ func TestHandler_RawData(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -252,7 +251,6 @@ func TestHandler_GetMiddleware(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -564,7 +564,6 @@ func TestHandler_UDP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -828,7 +828,6 @@ func TestSortRouters(t *testing.T) {
},
}
for _, test := range testCases {
test := test
t.Run(fmt.Sprintf("%s-%s", test.direction, test.sortBy), func(t *testing.T) {
t.Parallel()
@ -1339,7 +1338,6 @@ func TestSortServices(t *testing.T) {
},
}
for _, test := range testCases {
test := test
t.Run(fmt.Sprintf("%s-%s", test.direction, test.sortBy), func(t *testing.T) {
t.Parallel()
@ -1674,7 +1672,6 @@ func TestSortMiddlewares(t *testing.T) {
},
}
for _, test := range testCases {
test := test
t.Run(fmt.Sprintf("%s-%s", test.direction, test.sortBy), func(t *testing.T) {
t.Parallel()

View file

@ -267,7 +267,6 @@ func TestDeprecationNotice(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -385,7 +384,6 @@ func TestLoad(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
tconfig := cmd.NewTraefikConfiguration()
c := &cli.Command{Configuration: tconfig}

View file

@ -65,7 +65,7 @@ func createBody(staticConfiguration *static.Configuration) (*bytes.Buffer, error
}
buf := new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(data) //nolint:musttag // cannot be changed for historical reasons.
err = json.NewEncoder(buf).Encode(data)
if err != nil {
return nil, err
}

View file

@ -56,7 +56,7 @@ func fill(field reflect.Value) error {
case reflect.Int64:
switch field.Type() {
case reflect.TypeOf(types.Duration(time.Second)):
setTyped(field, int64(defaultNumber*int(time.Second)))
setTyped(field, types.Duration(defaultNumber*time.Second))
default:
setTyped(field, int64(defaultNumber))
}
@ -93,12 +93,12 @@ func setTyped(field reflect.Value, i interface{}) {
func setMap(field reflect.Value) error {
field.Set(reflect.MakeMap(field.Type()))
for i := 0; i < mapItemNumber; i++ {
for i := range mapItemNumber {
baseKeyName := makeKeyName(field.Type().Elem())
key := reflect.ValueOf(fmt.Sprintf("%s%d", baseKeyName, i))
// generate value
ptrType := reflect.PtrTo(field.Type().Elem())
ptrType := reflect.PointerTo(field.Type().Elem())
ptrValue := reflect.New(ptrType)
if err := fill(ptrValue); err != nil {
return err
@ -125,7 +125,7 @@ func makeKeyName(typ reflect.Type) string {
}
func setStruct(field reflect.Value) error {
for i := 0; i < field.NumField(); i++ {
for i := range field.NumField() {
fld := field.Field(i)
stFld := field.Type().Field(i)
@ -142,7 +142,7 @@ func setStruct(field reflect.Value) error {
func setSlice(field reflect.Value) error {
field.Set(reflect.MakeSlice(field.Type(), sliceItemNumber, sliceItemNumber))
for j := 0; j < field.Len(); j++ {
for j := range field.Len() {
if err := fill(field.Index(j)); err != nil {
return err
}

View file

@ -24,7 +24,7 @@ type Configuration struct {
HTTP *HTTPConfiguration `json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
TCP *TCPConfiguration `json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty" export:"true"`
UDP *UDPConfiguration `json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty" export:"true"`
TLS *TLSConfiguration `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"-" export:"true"`
TLS *TLSConfiguration `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true
@ -32,6 +32,6 @@ type Configuration struct {
// TLSConfiguration contains all the configuration parameters of a TLS connection.
type TLSConfiguration struct {
Certificates []*tls.CertAndStores `json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" label:"-" export:"true"`
Options map[string]tls.Options `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty" export:"true"`
Options map[string]tls.Options `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty" label:"-" export:"true"`
Stores map[string]tls.Store `json:"stores,omitempty" toml:"stores,omitempty" yaml:"stores,omitempty" export:"true"`
}

View file

@ -55,7 +55,7 @@ func getFieldNames(rootName string, rootType reflect.Type) []string {
return nil
}
for i := 0; i < rootType.NumField(); i++ {
for i := range rootType.NumField() {
field := rootType.Field(i)
if !parser.IsExported(field) {

View file

@ -244,7 +244,6 @@ func TestDecodeToNode(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -87,7 +87,6 @@ func TestDecode(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -12,9 +12,11 @@ func DecodeConfiguration(labels map[string]string) (*dynamic.Configuration, erro
HTTP: &dynamic.HTTPConfiguration{},
TCP: &dynamic.TCPConfiguration{},
UDP: &dynamic.UDPConfiguration{},
TLS: &dynamic.TLSConfiguration{},
}
err := parser.Decode(labels, conf, parser.DefaultRootName, "traefik.http", "traefik.tcp", "traefik.udp")
// When decoding the TLS configuration we are making sure that only the default TLS store can be configured.
err := parser.Decode(labels, conf, parser.DefaultRootName, "traefik.http", "traefik.tcp", "traefik.udp", "traefik.tls.stores.default")
if err != nil {
return nil, err
}

View file

@ -9,6 +9,8 @@ import (
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
)
func Bool(v bool) *bool { return &v }
@ -217,6 +219,10 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.udp.routers.Router1.service": "foobar",
"traefik.udp.services.Service0.loadbalancer.server.Port": "42",
"traefik.udp.services.Service1.loadbalancer.server.Port": "42",
"traefik.tls.stores.default.defaultgeneratedcert.resolver": "foobar",
"traefik.tls.stores.default.defaultgeneratedcert.domain.main": "foobar",
"traefik.tls.stores.default.defaultgeneratedcert.domain.sans": "foobar, fiibar",
}
configuration, err := DecodeConfiguration(labels)
@ -719,6 +725,19 @@ func TestDecodeConfiguration(t *testing.T) {
},
},
},
TLS: &dynamic.TLSConfiguration{
Stores: map[string]tls.Store{
"default": {
DefaultGeneratedCert: &tls.GeneratedCert{
Resolver: "foobar",
Domain: &types.Domain{
Main: "foobar",
SANs: []string{"foobar", "fiibar"},
},
},
},
},
},
}
assert.Nil(t, configuration.HTTP.ServersTransports)
@ -1216,6 +1235,19 @@ func TestEncodeConfiguration(t *testing.T) {
},
},
},
TLS: &dynamic.TLSConfiguration{
Stores: map[string]tls.Store{
"default": {
DefaultGeneratedCert: &tls.GeneratedCert{
Resolver: "foobar",
Domain: &types.Domain{
Main: "foobar",
SANs: []string{"foobar", "fiibar"},
},
},
},
},
},
}
labels, err := EncodeConfiguration(configuration)
@ -1415,6 +1447,10 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.TCP.Services.Service1.LoadBalancer.ServersTransport": "foo",
"traefik.TCP.Services.Service1.LoadBalancer.TerminationDelay": "42",
"traefik.TLS.Stores.default.DefaultGeneratedCert.Resolver": "foobar",
"traefik.TLS.Stores.default.DefaultGeneratedCert.Domain.Main": "foobar",
"traefik.TLS.Stores.default.DefaultGeneratedCert.Domain.SANs": "foobar, fiibar",
"traefik.UDP.Routers.Router0.EntryPoints": "foobar, fiibar",
"traefik.UDP.Routers.Router0.Service": "foobar",
"traefik.UDP.Routers.Router1.EntryPoints": "foobar, fiibar",

View file

@ -208,7 +208,6 @@ func TestGetRoutersByEntryPoints(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConfig := NewConfig(test.conf)

View file

@ -208,7 +208,6 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConfig := NewConfig(test.conf)

View file

@ -665,8 +665,6 @@ func TestPopulateUsedBy(t *testing.T) {
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -189,7 +189,6 @@ func TestGetUDPRoutersByEntryPoints(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
runtimeConfig := NewConfig(test.conf)

View file

@ -97,7 +97,7 @@ type RedirectEntryPoint struct {
func (r *RedirectEntryPoint) SetDefaults() {
r.Scheme = "https"
r.Permanent = true
r.Priority = math.MaxInt32 - 1
r.Priority = math.MaxInt - 1
}
// TLSConfig is the default TLS configuration for all the routers associated to the concerned entry point.

View file

@ -193,10 +193,12 @@ func (a *LifeCycle) SetDefaults() {
// Tracing holds the tracing configuration.
type Tracing struct {
ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"`
SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"`
AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"`
ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
GlobalAttributes map[string]string `description:"Defines additional attributes (key:value) on all spans." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"`
CapturedRequestHeaders []string `description:"Request headers to add as attributes for server and client spans." json:"capturedRequestHeaders,omitempty" toml:"capturedRequestHeaders,omitempty" yaml:"capturedRequestHeaders,omitempty" export:"true"`
CapturedResponseHeaders []string `description:"Response headers to add as attributes for server and client spans." json:"capturedResponseHeaders,omitempty" toml:"capturedResponseHeaders,omitempty" yaml:"capturedResponseHeaders,omitempty" export:"true"`
SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"`
AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"`
OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
}
@ -286,6 +288,10 @@ func (c *Configuration) SetEffectiveConfiguration() {
entryPoints[epName] = gateway.Entrypoint{Address: entryPoint.GetAddress(), HasHTTPTLSConf: entryPoint.HTTP.TLS != nil}
}
if c.Providers.KubernetesCRD != nil {
c.Providers.KubernetesCRD.FillExtensionBuilderRegistry(c.Providers.KubernetesGateway)
}
c.Providers.KubernetesGateway.EntryPoints = entryPoints
}

View file

@ -26,7 +26,6 @@ func TestHasEntrypoint(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -243,7 +243,6 @@ func TestServiceHealthChecker_newRequest(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -405,7 +404,6 @@ func TestServiceHealthChecker_Launch(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -36,7 +36,6 @@ func TestIsAuthorized(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -117,7 +116,6 @@ func TestNew(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -289,7 +287,6 @@ func TestContainsIsAllowed(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -21,7 +21,6 @@ func TestRemoteAddrStrategy_GetIP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -61,7 +60,6 @@ func TestDepthStrategy_GetIP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -109,7 +107,6 @@ func TestTrustedIPsStrategy_GetIP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -67,7 +67,6 @@ func TestDatadog_parseDatadogAddress(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -30,6 +30,74 @@ var (
openTelemetryGaugeCollector *gaugeCollector
)
// SetMeterProvider sets the meter provider for the tests.
func SetMeterProvider(meterProvider *sdkmetric.MeterProvider) {
openTelemetryMeterProvider = meterProvider
otel.SetMeterProvider(meterProvider)
}
// SemConvMetricsRegistry holds stables semantic conventions metric instruments.
type SemConvMetricsRegistry struct {
// server metrics
httpServerRequestDuration metric.Float64Histogram
// client metrics
httpClientRequestDuration metric.Float64Histogram
}
// NewSemConvMetricRegistry registers all stables semantic conventions metrics.
func NewSemConvMetricRegistry(ctx context.Context, config *types.OTLP) (*SemConvMetricsRegistry, error) {
if openTelemetryMeterProvider == nil {
var err error
if openTelemetryMeterProvider, err = newOpenTelemetryMeterProvider(ctx, config); err != nil {
log.Ctx(ctx).Err(err).Msg("Unable to create OpenTelemetry meter provider")
return nil, nil
}
}
meter := otel.Meter("github.com/traefik/traefik",
metric.WithInstrumentationVersion(version.Version))
httpServerRequestDuration, err := meter.Float64Histogram("http.server.request.duration",
metric.WithDescription("Duration of HTTP server requests."),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries(config.ExplicitBoundaries...))
if err != nil {
return nil, fmt.Errorf("can't build httpServerRequestDuration histogram: %w", err)
}
httpClientRequestDuration, err := meter.Float64Histogram("http.client.request.duration",
metric.WithDescription("Duration of HTTP client requests."),
metric.WithUnit("s"),
metric.WithExplicitBucketBoundaries(config.ExplicitBoundaries...))
if err != nil {
return nil, fmt.Errorf("can't build httpClientRequestDuration histogram: %w", err)
}
return &SemConvMetricsRegistry{
httpServerRequestDuration: httpServerRequestDuration,
httpClientRequestDuration: httpClientRequestDuration,
}, nil
}
// HTTPServerRequestDuration returns the HTTP server request duration histogram.
func (s *SemConvMetricsRegistry) HTTPServerRequestDuration() metric.Float64Histogram {
if s == nil {
return nil
}
return s.httpServerRequestDuration
}
// HTTPClientRequestDuration returns the HTTP client request duration histogram.
func (s *SemConvMetricsRegistry) HTTPClientRequestDuration() metric.Float64Histogram {
if s == nil {
return nil
}
return s.httpClientRequestDuration
}
// RegisterOpenTelemetry registers all OpenTelemetry metrics.
func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry {
if openTelemetryMeterProvider == nil {

View file

@ -89,8 +89,6 @@ func TestOpenTelemetry_labels(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -181,8 +179,6 @@ func TestOpenTelemetry_GaugeCollectorAdd(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -275,8 +271,6 @@ func TestOpenTelemetry_GaugeCollectorSet(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -361,7 +355,7 @@ func TestOpenTelemetry(t *testing.T) {
expectedEntryPoints := []string{
`({"name":"traefik_entrypoint_requests_total","description":"How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_entrypoint_requests_tls_total","description":"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test2"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_entrypoint_request_duration_seconds","description":"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test3"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_entrypoint_request_duration_seconds","description":"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test3"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_entrypoint_requests_bytes_total","description":"The total size of requests in bytes handled by an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_entrypoint_responses_bytes_total","description":"The total size of responses in bytes handled by an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
}
@ -377,7 +371,7 @@ func TestOpenTelemetry(t *testing.T) {
expectedRouters := []string{
`({"name":"traefik_router_requests_total","description":"How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_router_requests_tls_total","description":"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_router_request_duration_seconds","description":"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_router_request_duration_seconds","description":"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_router_requests_bytes_total","description":"The total size of requests in bytes handled by a router, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_router_responses_bytes_total","description":"The total size of responses in bytes handled by a router, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
}
@ -394,7 +388,7 @@ func TestOpenTelemetry(t *testing.T) {
expectedServices := []string{
`({"name":"traefik_service_requests_total","description":"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_service_requests_tls_total","description":"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
`({"name":"traefik_service_requests_bytes_total","description":"The total size of requests in bytes received by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_service_responses_bytes_total","description":"The total size of responses in bytes returned by a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"404"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
@ -426,7 +420,7 @@ func TestOpenTelemetry(t *testing.T) {
// and as soon as the EntryPointReqDurationHistogram.Observe is called,
// it adds a new dataPoint to the histogram.
expectedEntryPointReqDuration := []string{
`({"attributes":\[{"key":"entrypoint","value":{"stringValue":"myEntrypoint"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"2","sum":30000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","2"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":20000})`,
`({"attributes":\[{"key":"entrypoint","value":{"stringValue":"myEntrypoint"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"2","sum":30000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","0","0","0","2"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.075,0.1,0.25,0.5,0.75,1,2.5,5,7.5,10\],"min":10000,"max":20000})`,
}
registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(10000)

View file

@ -64,7 +64,6 @@ func TestRegisterPromState(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
actualNbRegistries := 0
for _, prom := range test.prometheusSlice {
@ -381,7 +380,6 @@ func TestPrometheus(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.name, func(t *testing.T) {
family := findMetricFamily(test.name, metricsFamilies)
if family == nil {

View file

@ -86,7 +86,6 @@ func TestCommonLogFormatter_Format(t *testing.T) {
t.Setenv("TZ", "Etc/GMT+9")
for _, test := range testCases {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
@ -150,7 +149,6 @@ func Test_toLog(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -76,7 +76,7 @@ func TestLogRotation(t *testing.T) {
halfDone := make(chan bool)
writeDone := make(chan bool)
go func() {
for i := 0; i < iterations; i++ {
for i := range iterations {
handler.ServeHTTP(recorder, req)
if i == iterations/2 {
halfDone <- true
@ -180,7 +180,6 @@ func TestLoggerHeaderFields(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
logFile, err := os.CreateTemp(t.TempDir(), "*.log")
require.NoError(t, err)
@ -469,7 +468,6 @@ func TestLoggerJSON(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -687,7 +685,6 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
// NOTE: It is not possible to run these cases in parallel because we capture Stdout

View file

@ -60,7 +60,6 @@ func TestParseAccessLog(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -28,8 +28,6 @@ func TestSaveRetries(t *testing.T) {
}
for _, test := range tests {
test := test
t.Run(fmt.Sprintf("%d retries", test.requestAttempt), func(t *testing.T) {
t.Parallel()
saveRetries := &SaveRetries{}

View file

@ -29,7 +29,6 @@ func TestNewAddPrefix(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -75,7 +74,6 @@ func TestAddPrefix(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -11,7 +11,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
@ -77,7 +77,7 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !ok {
logger.Debug().Msg("Authentication failed")
tracing.SetStatusErrorf(req.Context(), "Authentication failed")
observability.SetStatusErrorf(req.Context(), "Authentication failed")
b.auth.RequireAuth(rw, req)
return

View file

@ -210,7 +210,6 @@ func TestBasicAuthUsersFromFile(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -11,7 +11,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
@ -78,13 +78,13 @@ func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if authinfo != nil && *authinfo == "stale" {
logger.Debug().Msg("Digest authentication failed, possibly because out of order requests")
tracing.SetStatusErrorf(req.Context(), "Digest authentication failed, possibly because out of order requests")
observability.SetStatusErrorf(req.Context(), "Digest authentication failed, possibly because out of order requests")
d.auth.RequireAuthStale(rw, req)
return
}
logger.Debug().Msg("Digest authentication failed")
tracing.SetStatusErrorf(req.Context(), "Digest authentication failed")
observability.SetStatusErrorf(req.Context(), "Digest authentication failed")
d.auth.RequireAuth(rw, req)
return
}

View file

@ -88,7 +88,6 @@ func TestDigestAuthUsersFromFile(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -14,6 +14,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/connectionheader"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/vulcand/oxy/v2/forward"
@ -126,13 +127,16 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if err != nil {
logMessage := fmt.Sprintf("Error calling %s. Cause %s", fa.address, err)
logger.Debug().Msg(logMessage)
tracing.SetStatusErrorf(req.Context(), logMessage)
observability.SetStatusErrorf(req.Context(), logMessage)
rw.WriteHeader(http.StatusInternalServerError)
return
}
writeHeader(req, forwardReq, fa.trustForwardHeader, fa.authRequestHeaders)
var forwardSpan trace.Span
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
var tracer *tracing.Tracer
if tracer = tracing.TracerFromContext(req.Context()); tracer != nil {
var tracingCtx context.Context
tracingCtx, forwardSpan = tracer.Start(req.Context(), "AuthRequest", trace.WithSpanKind(trace.SpanKindClient))
defer forwardSpan.End()
@ -140,16 +144,14 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
forwardReq = forwardReq.WithContext(tracingCtx)
tracing.InjectContextIntoCarrier(forwardReq)
tracing.LogClientRequest(forwardSpan, forwardReq)
tracer.CaptureClientRequest(forwardSpan, forwardReq)
}
writeHeader(req, forwardReq, fa.trustForwardHeader, fa.authRequestHeaders)
forwardResponse, forwardErr := fa.client.Do(forwardReq)
if forwardErr != nil {
logMessage := fmt.Sprintf("Error calling %s. Cause: %s", fa.address, forwardErr)
logger.Debug().Msg(logMessage)
tracing.SetStatusErrorf(forwardReq.Context(), logMessage)
observability.SetStatusErrorf(forwardReq.Context(), logMessage)
rw.WriteHeader(http.StatusInternalServerError)
return
@ -160,7 +162,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if readError != nil {
logMessage := fmt.Sprintf("Error reading body %s. Cause: %s", fa.address, readError)
logger.Debug().Msg(logMessage)
tracing.SetStatusErrorf(forwardReq.Context(), logMessage)
observability.SetStatusErrorf(forwardReq.Context(), logMessage)
rw.WriteHeader(http.StatusInternalServerError)
return
@ -187,7 +189,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !errors.Is(err, http.ErrNoLocation) {
logMessage := fmt.Sprintf("Error reading response location header %s. Cause: %s", fa.address, err)
logger.Debug().Msg(logMessage)
tracing.SetStatusErrorf(forwardReq.Context(), logMessage)
observability.SetStatusErrorf(forwardReq.Context(), logMessage)
rw.WriteHeader(http.StatusInternalServerError)
return
@ -197,7 +199,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Location", redirectURL.String())
}
tracing.LogResponseCode(forwardSpan, forwardResponse.StatusCode, trace.SpanKindClient)
tracer.CaptureResponse(forwardSpan, forwardResponse.Header, forwardResponse.StatusCode, trace.SpanKindClient)
rw.WriteHeader(forwardResponse.StatusCode)
if _, err = rw.Write(body); err != nil {
@ -228,7 +230,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
}
tracing.LogResponseCode(forwardSpan, forwardResponse.StatusCode, trace.SpanKindClient)
tracer.CaptureResponse(forwardSpan, forwardResponse.Header, forwardResponse.StatusCode, trace.SpanKindClient)
req.RequestURI = req.URL.RequestURI()

View file

@ -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
}

View file

@ -48,7 +48,6 @@ func TestBuffering(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -149,7 +149,7 @@ func BenchmarkCapture(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(test.size))
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range b.N {
runBenchmark(b, test.size, req, handlers)
}
})
@ -170,7 +170,7 @@ func runBenchmark(b *testing.B, size int, req *http.Request, handler http.Handle
func generateBytes(length int) []byte {
var value []byte
for i := 0; i < length; i++ {
for i := range length {
value = append(value, 0x61+byte(i%26))
}
return value

View file

@ -10,7 +10,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/vulcand/oxy/v2/cbreaker"
"go.opentelemetry.io/otel/trace"
)
@ -34,7 +34,7 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
cbOpts := []cbreaker.Option{
cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
tracing.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression)
observability.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression)
rw.WriteHeader(responseCode)
if _, err := rw.Write([]byte(http.StatusText(responseCode))); err != nil {

View file

@ -88,7 +88,6 @@ func Test_NoBody(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -347,7 +346,6 @@ func Test_ExcludedContentTypes(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -452,7 +450,6 @@ func Test_IncludedContentTypes(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -557,7 +554,6 @@ func Test_FlushExcludedContentTypes(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -676,7 +672,6 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -831,8 +826,6 @@ func TestParseContentType_equals(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -74,8 +74,6 @@ func TestNegotiation(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -299,7 +297,6 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -351,7 +348,6 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -547,8 +543,6 @@ func TestMinResponseBodyBytes(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
@ -720,7 +714,7 @@ func BenchmarkCompress(b *testing.B) {
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range b.N {
runBenchmark(b, req, handler)
}
})
@ -741,7 +735,7 @@ func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
func generateBytes(length int) []byte {
var value []byte
for i := 0; i < length; i++ {
for i := range length {
value = append(value, 0x61+byte(i))
}
return value

View file

@ -47,7 +47,6 @@ func TestRemover(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -46,7 +46,6 @@ func TestAutoDetection(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -12,7 +12,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/vulcand/oxy/v2/utils"
"go.opentelemetry.io/otel/trace"
@ -71,7 +71,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if c.backendHandler == nil {
logger.Error().Msg("Error pages: no backend handler.")
tracing.SetStatusErrorf(req.Context(), "Error pages: no backend handler.")
observability.SetStatusErrorf(req.Context(), "Error pages: no backend handler.")
c.next.ServeHTTP(rw, req)
return
}
@ -96,7 +96,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
pageReq, err := newRequest("http://" + req.Host + query)
if err != nil {
logger.Error().Err(err).Send()
tracing.SetStatusErrorf(req.Context(), err.Error())
observability.SetStatusErrorf(req.Context(), err.Error())
http.Error(rw, http.StatusText(code), code)
return
}

View file

@ -157,7 +157,6 @@ func TestHandler(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -272,7 +272,6 @@ func TestServeHTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -369,7 +368,6 @@ func Test_isWebsocketRequest(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -52,7 +52,6 @@ func TestNewHeader_customRequestHeader(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -59,7 +59,6 @@ func TestNew_allowedHosts(t *testing.T) {
require.NoError(t, err)
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -67,7 +67,6 @@ func Test_newSecure_modifyResponse(t *testing.T) {
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -10,7 +10,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
@ -78,7 +78,7 @@ func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if err != nil {
msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err)
logger.Debug().Msg(msg)
tracing.SetStatusErrorf(req.Context(), msg)
observability.SetStatusErrorf(req.Context(), msg)
reject(ctx, al.rejectStatusCode, rw)
return
}

View file

@ -41,7 +41,6 @@ func TestNewIPAllowLister(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -102,7 +101,6 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -10,7 +10,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/ip"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
@ -68,7 +68,7 @@ func (wl *ipWhiteLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if err != nil {
msg := fmt.Sprintf("Rejecting IP %s: %v", clientIP, err)
logger.Debug().Msg(msg)
tracing.SetStatusErrorf(req.Context(), msg)
observability.SetStatusErrorf(req.Context(), msg)
reject(ctx, rw)
return
}

View file

@ -33,7 +33,6 @@ func TestNewIPWhiteLister(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -76,7 +75,6 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -3,6 +3,7 @@ package metrics
import (
"context"
"net/http"
"slices"
"strconv"
"strings"
"time"
@ -14,9 +15,9 @@ import (
"github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/retry"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/codes"
)
@ -144,7 +145,7 @@ func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request)
}
logger := with.Logger()
logger.Error().Err(err).Msg("Could not get Capture")
tracing.SetStatusErrorf(req.Context(), "Could not get Capture")
observability.SetStatusErrorf(req.Context(), "Could not get Capture")
return
}
@ -216,12 +217,10 @@ func grpcStatusCode(rw http.ResponseWriter) int {
func containsHeader(req *http.Request, name, value string) bool {
items := strings.Split(req.Header.Get(name), ",")
for _, item := range items {
if value == strings.ToLower(strings.TrimSpace(item)) {
return true
}
}
return false
return slices.ContainsFunc(items, func(item string) bool {
return value == strings.ToLower(strings.TrimSpace(item))
})
}
// getMethod returns the request's method.

View file

@ -80,7 +80,6 @@ func Test_getMethod(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.method, func(t *testing.T) {
t.Parallel()
@ -132,7 +131,6 @@ func Test_getRequestProtocol(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -175,7 +173,6 @@ func Test_grpcStatusCode(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -0,0 +1,98 @@
package observability
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/containous/alice"
"github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
const (
entryPointTypeName = "TracingEntryPoint"
)
type entryPointTracing struct {
tracer *tracing.Tracer
entryPoint string
next http.Handler
semConvMetricRegistry *metrics.SemConvMetricsRegistry
}
// WrapEntryPointHandler Wraps tracing to alice.Constructor.
func WrapEntryPointHandler(ctx context.Context, tracer *tracing.Tracer, semConvMetricRegistry *metrics.SemConvMetricsRegistry, entryPointName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
if tracer == nil {
tracer = tracing.NewTracer(noop.Tracer{}, nil, nil)
}
return newEntryPoint(ctx, tracer, semConvMetricRegistry, entryPointName, next), nil
}
}
// newEntryPoint creates a new tracing middleware for incoming requests.
func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, semConvMetricRegistry *metrics.SemConvMetricsRegistry, entryPointName string, next http.Handler) http.Handler {
middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware")
if tracer == nil {
tracer = tracing.NewTracer(noop.Tracer{}, nil, nil)
}
return &entryPointTracing{
entryPoint: entryPointName,
tracer: tracer,
semConvMetricRegistry: semConvMetricRegistry,
next: next,
}
}
func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
tracingCtx := tracing.ExtractCarrierIntoContext(req.Context(), req.Header)
start := time.Now()
tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start))
req = req.WithContext(tracingCtx)
span.SetAttributes(attribute.String("entry_point", e.entryPoint))
e.tracer.CaptureServerRequest(span, req)
recorder := newStatusCodeRecorder(rw, http.StatusOK)
e.next.ServeHTTP(recorder, req)
e.tracer.CaptureResponse(span, recorder.Header(), recorder.Status(), trace.SpanKindServer)
end := time.Now()
span.End(trace.WithTimestamp(end))
if e.semConvMetricRegistry != nil && e.semConvMetricRegistry.HTTPServerRequestDuration() != nil {
var attrs []attribute.KeyValue
if recorder.Status() < 100 || recorder.Status() >= 600 {
attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code ; %d", recorder.Status())))
} else if recorder.Status() >= 400 {
attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(recorder.Status())))
}
attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method))
attrs = append(attrs, semconv.HTTPResponseStatusCode(recorder.Status()))
attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto)))
attrs = append(attrs, semconv.NetworkProtocolVersion(Proto(req.Proto)))
attrs = append(attrs, semconv.ServerAddress(req.Host))
attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto")))
e.semConvMetricRegistry.HTTPServerRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...))
}
}

View file

@ -0,0 +1,184 @@
package observability
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/metrics"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/otel/attribute"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
)
func TestEntryPointMiddleware_tracing(t *testing.T) {
type expected struct {
name string
attributes []attribute.KeyValue
}
testCases := []struct {
desc string
entryPoint string
expected expected
}{
{
desc: "basic test",
entryPoint: "test",
expected: expected{
name: "EntryPoint",
attributes: []attribute.KeyValue{
attribute.String("span.kind", "server"),
attribute.String("entry_point", "test"),
attribute.String("http.request.method", "GET"),
attribute.String("network.protocol.version", "1.1"),
attribute.Int64("http.request.body.size", int64(0)),
attribute.String("url.path", "/search"),
attribute.String("url.query", "q=Opentelemetry"),
attribute.String("url.scheme", "http"),
attribute.String("user_agent.original", "entrypoint-test"),
attribute.String("server.address", "www.test.com"),
attribute.String("network.peer.address", "10.0.0.1"),
attribute.String("network.peer.port", "1234"),
attribute.String("client.address", "10.0.0.1"),
attribute.Int64("client.port", int64(1234)),
attribute.String("client.socket.address", ""),
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"}),
},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
rw := httptest.NewRecorder()
req.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "entrypoint-test")
req.Header.Set("X-Forwarded-Proto", "http")
req.Header.Set("X-Foo", "foo")
req.Header.Add("X-Foo", "bar")
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("X-Bar", "foo")
rw.Header().Add("X-Bar", "bar")
rw.WriteHeader(http.StatusNotFound)
})
tracer := &mockTracer{}
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}), nil, test.entryPoint, next)
handler.ServeHTTP(rw, req)
for _, span := range tracer.spans {
assert.Equal(t, test.expected.name, span.name)
assert.Equal(t, test.expected.attributes, span.attributes)
}
})
}
}
func TestEntryPointMiddleware_metrics(t *testing.T) {
tests := []struct {
desc string
statusCode int
wantAttributes attribute.Set
}{
{
desc: "not found status",
statusCode: http.StatusNotFound,
wantAttributes: attribute.NewSet(
attribute.Key("error.type").String("404"),
attribute.Key("http.request.method").String("GET"),
attribute.Key("http.response.status_code").Int(404),
attribute.Key("network.protocol.name").String("http/1.1"),
attribute.Key("network.protocol.version").String("1.1"),
attribute.Key("server.address").String("www.test.com"),
attribute.Key("url.scheme").String("http"),
),
},
{
desc: "created status",
statusCode: http.StatusCreated,
wantAttributes: attribute.NewSet(
attribute.Key("http.request.method").String("GET"),
attribute.Key("http.response.status_code").Int(201),
attribute.Key("network.protocol.name").String("http/1.1"),
attribute.Key("network.protocol.version").String("1.1"),
attribute.Key("server.address").String("www.test.com"),
attribute.Key("url.scheme").String("http"),
),
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var cfg types.OTLP
(&cfg).SetDefaults()
cfg.AddRoutersLabels = true
cfg.PushInterval = ptypes.Duration(10 * time.Millisecond)
rdr := sdkmetric.NewManualReader()
meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(rdr))
// force the meter provider with manual reader to collect metrics for the test.
metrics.SetMeterProvider(meterProvider)
semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg)
require.NoError(t, err)
require.NotNil(t, semConvMetricRegistry)
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
rw := httptest.NewRecorder()
req.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "entrypoint-test")
req.Header.Set("X-Forwarded-Proto", "http")
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(test.statusCode)
})
handler := newEntryPoint(context.Background(), nil, semConvMetricRegistry, "test", next)
handler.ServeHTTP(rw, req)
got := metricdata.ResourceMetrics{}
err = rdr.Collect(context.Background(), &got)
require.NoError(t, err)
require.Len(t, got.ScopeMetrics, 1)
expected := metricdata.Metrics{
Name: "http.server.request.duration",
Description: "Duration of HTTP server requests.",
Unit: "s",
Data: metricdata.Histogram[float64]{
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: test.wantAttributes,
Count: 1,
Bounds: []float64{0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10},
BucketCounts: []uint64{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Min: metricdata.NewExtrema[float64](1),
Max: metricdata.NewExtrema[float64](1),
Sum: 1,
},
},
Temporality: metricdata.CumulativeTemporality,
},
}
metricdatatest.AssertEqual[metricdata.Metrics](t, expected, got.ScopeMetrics[0].Metrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue())
})
}
}

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"context"

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"context"
@ -47,7 +47,9 @@ type mockSpan struct {
var _ trace.Span = &mockSpan{}
func (*mockSpan) SpanContext() trace.SpanContext { return trace.SpanContext{} }
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) {
@ -59,4 +61,6 @@ func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {}
func (s *mockSpan) SetName(name string) { s.name = name }
func (*mockSpan) TracerProvider() trace.TracerProvider { return mockTracerProvider{} }
func (s *mockSpan) TracerProvider() trace.TracerProvider {
return nil
}

View file

@ -0,0 +1,31 @@
package observability
import (
"context"
"fmt"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
// SetStatusErrorf flags the span as in error and log an event.
func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) {
if span := trace.SpanFromContext(ctx); span != nil {
span.SetStatus(codes.Error, fmt.Sprintf(format, args...))
}
}
func Proto(proto string) string {
switch proto {
case "HTTP/1.0":
return "1.0"
case "HTTP/1.1":
return "1.1"
case "HTTP/2":
return "2"
case "HTTP/3":
return "3"
default:
return proto
}
}

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"context"

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"context"

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"context"

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"context"

View file

@ -1,4 +1,4 @@
package tracing
package observability
import (
"bufio"

View file

@ -310,7 +310,6 @@ func TestPassTLSClientCert_PEM(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -533,7 +532,6 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -604,7 +602,6 @@ WqeUSNGYV//RunTeuRDAf5OxehERb1srzBXhRZ3cZdzXbgR/`,
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -645,7 +642,6 @@ func Test_getSANs(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -12,7 +12,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/vulcand/oxy/v2/utils"
"go.opentelemetry.io/otel/trace"
"golang.org/x/time/rate"
@ -153,14 +153,14 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// as the expiryTime is supposed to reflect the activity (or lack thereof) on that source.
if err := rl.buckets.Set(source, bucket, rl.ttl); err != nil {
logger.Error().Err(err).Msg("Could not insert/update bucket")
tracing.SetStatusErrorf(req.Context(), "Could not insert/update bucket")
observability.SetStatusErrorf(req.Context(), "Could not insert/update bucket")
http.Error(rw, "could not insert/update bucket", http.StatusInternalServerError)
return
}
res := bucket.Reserve()
if !res.OK() {
tracing.SetStatusErrorf(req.Context(), "No bursty traffic allowed")
observability.SetStatusErrorf(req.Context(), "No bursty traffic allowed")
http.Error(rw, "No bursty traffic allowed", http.StatusTooManyRequests)
return
}

View file

@ -89,7 +89,6 @@ func TestNewRateLimiter(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -248,7 +247,6 @@ func TestRateLimit(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
if test.loadDuration >= time.Minute && testing.Short() {
t.Skip("skipping test in short mode.")

View file

@ -154,7 +154,6 @@ func TestRedirectRegexHandler(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -283,8 +283,6 @@ func TestRedirectSchemeHandler(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -7,7 +7,7 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"go.opentelemetry.io/otel/trace"
)
@ -52,7 +52,7 @@ func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
req.URL.Path, err = url.PathUnescape(req.URL.RawPath)
if err != nil {
middlewares.GetLogger(context.Background(), r.name, typeName).Error().Err(err).Send()
tracing.SetStatusErrorf(req.Context(), err.Error())
observability.SetStatusErrorf(req.Context(), err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}

View file

@ -10,8 +10,8 @@ import (
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
"github.com/traefik/traefik/v3/pkg/middlewares/replacepath"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/trace"
)
@ -52,7 +52,7 @@ func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
currentPath = req.URL.EscapedPath()
}
if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) {
if rp.regexp != nil && rp.regexp.MatchString(currentPath) {
req.Header.Add(replacepath.ReplacedPathHeader, currentPath)
req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement)
@ -63,7 +63,7 @@ func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
req.URL.Path, err = url.PathUnescape(req.URL.RawPath)
if err != nil {
middlewares.GetLogger(context.Background(), rp.name, typeName).Error().Err(err).Send()
tracing.SetStatusErrorf(req.Context(), err.Error())
observability.SetStatusErrorf(req.Context(), err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}

View file

@ -44,6 +44,28 @@ func TestReplacePathRegex(t *testing.T) {
expectedRawPath: "/who-am-i/and/who-am-i",
expectedHeader: "/whoami/and/whoami",
},
{
desc: "empty replacement",
path: "/whoami/and/whoami",
config: dynamic.ReplacePathRegex{
Replacement: "",
Regex: `/whoami`,
},
expectedPath: "/and",
expectedRawPath: "/and",
expectedHeader: "/whoami/and/whoami",
},
{
desc: "empty trimmed replacement",
path: "/whoami/and/whoami",
config: dynamic.ReplacePathRegex{
Replacement: " ",
Regex: `/whoami`,
},
expectedPath: "/and",
expectedRawPath: "/and",
expectedHeader: "/whoami/and/whoami",
},
{
desc: "no match",
path: "/whoami/and/whoami",

View file

@ -49,7 +49,7 @@ func (hr *Resolver) CNAMEFlatten(ctx context.Context, host string) string {
logger := log.Ctx(ctx)
cacheDuration := 0 * time.Second
for depth := 0; depth < hr.ResolvDepth; depth++ {
for depth := range hr.ResolvDepth {
resolv, err := cnameResolve(ctx, request, hr.ResolvConfig)
if err != nil {
logger.Error().Err(err).Send()

View file

@ -35,7 +35,6 @@ func TestCNAMEFlatten(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -38,7 +38,6 @@ func TestRequestHost(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -80,7 +79,6 @@ func TestRequestFlattening(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -173,7 +171,6 @@ func Test_parseHost(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -101,7 +101,6 @@ func TestRetry(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -203,7 +202,7 @@ func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) {
}
// Validate that we don't have headers from previous attempts
for i := 0; i < attempt; i++ {
for i := range attempt {
headerName := fmt.Sprintf("X-Foo-Test-%d", i)
headerValue = responseRecorder.Header().Get("headerName")
if headerValue != "" {
@ -272,7 +271,6 @@ func TestRetryWebsocket(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -37,7 +37,6 @@ func TestSNICheck_ServeHTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -134,7 +134,6 @@ func TestStripPrefix(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -108,7 +108,6 @@ func TestStripPrefixRegex(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.path, func(t *testing.T) {
t.Parallel()

View file

@ -39,7 +39,6 @@ func TestNewIPAllowLister(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -81,7 +80,6 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -39,7 +39,6 @@ func TestNewIPWhiteLister(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -81,7 +80,6 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -1,57 +0,0 @@
package tracing
import (
"context"
"net/http"
"github.com/containous/alice"
"github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/tracing"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
const (
entryPointTypeName = "TracingEntryPoint"
)
type entryPointTracing struct {
tracer trace.Tracer
entryPoint string
next http.Handler
}
// WrapEntryPointHandler Wraps tracing to alice.Constructor.
func WrapEntryPointHandler(ctx context.Context, tracer trace.Tracer, entryPointName string) alice.Constructor {
return func(next http.Handler) (http.Handler, error) {
return newEntryPoint(ctx, tracer, entryPointName, next), nil
}
}
// newEntryPoint creates a new tracing middleware for incoming requests.
func newEntryPoint(ctx context.Context, tracer trace.Tracer, entryPointName string, next http.Handler) http.Handler {
middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware")
return &entryPointTracing{
entryPoint: entryPointName,
tracer: tracer,
next: next,
}
}
func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
tracingCtx := tracing.ExtractCarrierIntoContext(req.Context(), req.Header)
tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
req = req.WithContext(tracingCtx)
span.SetAttributes(attribute.String("entry_point", e.entryPoint))
tracing.LogServerRequest(span, req)
recorder := newStatusCodeRecorder(rw, http.StatusOK)
e.next.ServeHTTP(recorder, req)
tracing.LogResponseCode(span, recorder.Status(), trace.SpanKindServer)
}

View file

@ -1,74 +0,0 @@
package tracing
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
)
func TestEntryPointMiddleware(t *testing.T) {
type expected struct {
name string
attributes []attribute.KeyValue
}
testCases := []struct {
desc string
entryPoint string
expected expected
}{
{
desc: "basic test",
entryPoint: "test",
expected: expected{
name: "EntryPoint",
attributes: []attribute.KeyValue{
attribute.String("span.kind", "server"),
attribute.String("entry_point", "test"),
attribute.String("http.request.method", "GET"),
attribute.String("network.protocol.version", "1.1"),
attribute.Int64("http.request.body.size", int64(0)),
attribute.String("url.path", "/search"),
attribute.String("url.query", "q=Opentelemetry"),
attribute.String("url.scheme", "http"),
attribute.String("user_agent.original", "entrypoint-test"),
attribute.String("server.address", "www.test.com"),
attribute.String("network.peer.address", "10.0.0.1"),
attribute.String("network.peer.port", "1234"),
attribute.String("client.address", "10.0.0.1"),
attribute.Int64("client.port", int64(1234)),
attribute.String("client.socket.address", ""),
attribute.Int64("http.response.status_code", int64(404)),
},
},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil)
rw := httptest.NewRecorder()
req.RemoteAddr = "10.0.0.1:1234"
req.Header.Set("User-Agent", "entrypoint-test")
req.Header.Set("X-Forwarded-Proto", "http")
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusNotFound)
})
tracer := &mockTracer{}
handler := newEntryPoint(context.Background(), tracer, test.entryPoint, next)
handler.ServeHTTP(rw, req)
for _, span := range tracer.spans {
assert.Equal(t, test.expected.name, span.name)
assert.Equal(t, test.expected.attributes, span.attributes)
}
})
}
}

View file

@ -65,7 +65,6 @@ func TestClientIPMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -139,7 +138,6 @@ func TestMethodMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -257,7 +255,6 @@ func TestHostMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -357,7 +354,6 @@ func TestHostRegexpMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -431,7 +427,6 @@ func TestPathMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -524,7 +519,6 @@ func TestPathRegexpMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -596,7 +590,6 @@ func TestPathPrefixMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -683,8 +676,6 @@ func TestHeaderMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -792,7 +783,6 @@ func TestHeaderRegexpMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -881,7 +871,6 @@ func TestQueryMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -995,7 +984,6 @@ func TestQueryRegexpMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -25,15 +25,21 @@ var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{
}
func pathV2(tree *matchersTree, paths ...string) error {
var routes []*mux.Route
for _, path := range paths {
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path %q does not start with a '/'", path)
route := mux.NewRouter().NewRoute()
if err := route.Path(path).GetError(); err != nil {
return err
}
routes = append(routes, route)
}
tree.matcher = func(req *http.Request) bool {
for _, path := range paths {
if req.URL.Path == path {
for _, route := range routes {
if route.Match(req, &mux.RouteMatch{}) {
return true
}
}
@ -45,15 +51,21 @@ func pathV2(tree *matchersTree, paths ...string) error {
}
func pathPrefixV2(tree *matchersTree, paths ...string) error {
var routes []*mux.Route
for _, path := range paths {
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path %q does not start with a '/'", path)
route := mux.NewRouter().NewRoute()
if err := route.PathPrefix(path).GetError(); err != nil {
return err
}
routes = append(routes, route)
}
tree.matcher = func(req *http.Request) bool {
for _, path := range paths {
if strings.HasPrefix(req.URL.Path, path) {
for _, route := range routes {
if route.Match(req, &mux.RouteMatch{}) {
return true
}
}

View file

@ -69,7 +69,6 @@ func TestClientIPV2Matcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -146,7 +145,6 @@ func TestMethodV2Matcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -271,7 +269,6 @@ func TestHostV2Matcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -374,7 +371,6 @@ func TestHostRegexpV2Matcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -454,10 +450,21 @@ func TestPathV2Matcher(t *testing.T) {
"https://example.com/css/main.css": http.StatusNotFound,
},
},
{
desc: "valid Path matcher with regexp",
rule: "Path(`/css{path:(/.*)?}`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/css/main.css": http.StatusOK,
"https://example.org/css/main.css": http.StatusOK,
"https://example.com/css/components/component.css": http.StatusOK,
"https://example.com/css.css": http.StatusNotFound,
"https://example.com/js/main.js": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -535,10 +542,21 @@ func TestPathPrefixV2Matcher(t *testing.T) {
"https://example.com/css/main.css": http.StatusOK,
},
},
{
desc: "valid PathPrefix matcher with regexp",
rule: "PathPrefix(`/css-{name:[0-9]?}`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/css-1/main.css": http.StatusOK,
"https://example.org/css-222/main.css": http.StatusOK,
"https://example.com/css-333333/components/component.css": http.StatusOK,
"https://example.com/css.css": http.StatusNotFound,
"https://example.com/js/main.js": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -625,8 +643,6 @@ func TestHeadersMatcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -734,7 +750,6 @@ func TestHeaderRegexpV2Matcher(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -827,7 +842,6 @@ func TestHostRegexp(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -1495,8 +1509,6 @@ func Test_addRoute(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -222,8 +222,6 @@ func TestMuxer(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -378,14 +376,12 @@ func Test_addRoutePriority(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
require.NoError(t, err)
for _, route := range test.cases {
route := route
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-From", route.xFrom)
})
@ -446,7 +442,6 @@ func TestParseDomains(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.expression, func(t *testing.T) {
t.Parallel()
@ -511,7 +506,6 @@ func TestEmptyHost(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -550,7 +544,6 @@ func TestGetRulePriority(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -31,7 +31,6 @@ func Test_HostSNICatchAll(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -137,7 +136,6 @@ func Test_HostSNI(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -220,7 +218,6 @@ func Test_HostSNIRegexp(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -292,7 +289,6 @@ func Test_ClientIP(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -356,7 +352,6 @@ func Test_ALPN(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

View file

@ -461,8 +461,6 @@ func Test_addTCPRouteV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -561,7 +559,6 @@ func TestParseHostSNIV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.expression, func(t *testing.T) {
t.Parallel()
@ -606,8 +603,6 @@ func Test_HostSNICatchAllV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -708,8 +703,6 @@ func Test_HostSNIV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -823,8 +816,6 @@ func Test_HostSNIRegexpV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -916,8 +907,6 @@ func Test_ClientIPV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
@ -985,8 +974,6 @@ func Test_ALPNV2(t *testing.T) {
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()

Some files were not shown because too many files have changed in this diff Show more