Manage observability at entrypoint and router level
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
9588e51146
commit
b1934231ca
58 changed files with 1216 additions and 303 deletions
|
@ -36,11 +36,12 @@ type HTTPConfiguration struct {
|
|||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Model is a set of default router's values.
|
||||
// Model holds model configuration.
|
||||
type Model struct {
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Observability RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -57,14 +58,15 @@ type Service struct {
|
|||
|
||||
// Router holds the router configuration.
|
||||
type Router struct {
|
||||
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Observability *RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
@ -78,6 +80,15 @@ type RouterTLSConfig struct {
|
|||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// RouterObservabilityConfig holds the observability configuration for a router.
|
||||
type RouterObservabilityConfig struct {
|
||||
AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
|
||||
Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
|
||||
Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Mirroring holds the Mirroring configuration.
|
||||
type Mirroring struct {
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
|
|
|
@ -1023,6 +1023,7 @@ func (in *Model) DeepCopyInto(out *Model) {
|
|||
*out = new(RouterTLSConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Observability.DeepCopyInto(&out.Observability)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1249,6 +1250,11 @@ func (in *Router) DeepCopyInto(out *Router) {
|
|||
*out = new(RouterTLSConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Observability != nil {
|
||||
in, out := &in.Observability, &out.Observability
|
||||
*out = new(RouterObservabilityConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1262,6 +1268,37 @@ func (in *Router) DeepCopy() *Router {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouterObservabilityConfig) DeepCopyInto(out *RouterObservabilityConfig) {
|
||||
*out = *in
|
||||
if in.AccessLogs != nil {
|
||||
in, out := &in.AccessLogs, &out.AccessLogs
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Tracing != nil {
|
||||
in, out := &in.Tracing, &out.Tracing
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Metrics != nil {
|
||||
in, out := &in.Metrics, &out.Metrics
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouterObservabilityConfig.
|
||||
func (in *RouterObservabilityConfig) DeepCopy() *RouterObservabilityConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RouterObservabilityConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) {
|
||||
*out = *in
|
||||
|
|
|
@ -880,6 +880,11 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
"Router1": {
|
||||
EntryPoints: []string{
|
||||
|
@ -893,6 +898,11 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
Service: "foobar",
|
||||
Rule: "foobar",
|
||||
Priority: 42,
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
|
@ -1405,17 +1415,23 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
|
||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",
|
||||
|
||||
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router0.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.Service": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.TLS": "true",
|
||||
"traefik.HTTP.Routers.Router1.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router1.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router1.Service": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router0.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router0.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.Service": "foobar",
|
||||
"traefik.HTTP.Routers.Router0.TLS": "true",
|
||||
"traefik.HTTP.Routers.Router0.Observability.AccessLogs": "true",
|
||||
"traefik.HTTP.Routers.Router0.Observability.Tracing": "true",
|
||||
"traefik.HTTP.Routers.Router0.Observability.Metrics": "true",
|
||||
"traefik.HTTP.Routers.Router1.EntryPoints": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Routers.Router1.Priority": "42",
|
||||
"traefik.HTTP.Routers.Router1.Rule": "foobar",
|
||||
"traefik.HTTP.Routers.Router1.Service": "foobar",
|
||||
"traefik.HTTP.Routers.Router1.Observability.AccessLogs": "true",
|
||||
"traefik.HTTP.Routers.Router1.Observability.Tracing": "true",
|
||||
"traefik.HTTP.Routers.Router1.Observability.Metrics": "true",
|
||||
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name0": "foobar",
|
||||
"traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar",
|
||||
|
|
|
@ -23,6 +23,7 @@ type EntryPoint struct {
|
|||
HTTP2 *HTTP2Config `description:"HTTP/2 configuration." json:"http2,omitempty" toml:"http2,omitempty" yaml:"http2,omitempty" export:"true"`
|
||||
HTTP3 *HTTP3Config `description:"HTTP/3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
|
||||
Observability *ObservabilityConfig `description:"Observability configuration." json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// GetAddress strips any potential protocol part of the address field of the
|
||||
|
@ -59,6 +60,8 @@ func (ep *EntryPoint) SetDefaults() {
|
|||
ep.HTTP.SetDefaults()
|
||||
ep.HTTP2 = &HTTP2Config{}
|
||||
ep.HTTP2.SetDefaults()
|
||||
ep.Observability = &ObservabilityConfig{}
|
||||
ep.Observability.SetDefaults()
|
||||
}
|
||||
|
||||
// HTTPConfig is the HTTP configuration of an entry point.
|
||||
|
@ -158,3 +161,17 @@ type UDPConfig struct {
|
|||
func (u *UDPConfig) SetDefaults() {
|
||||
u.Timeout = ptypes.Duration(DefaultUDPTimeout)
|
||||
}
|
||||
|
||||
// ObservabilityConfig holds the observability configuration for an entry point.
|
||||
type ObservabilityConfig struct {
|
||||
AccessLogs bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"`
|
||||
Tracing bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"`
|
||||
Metrics bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (o *ObservabilityConfig) SetDefaults() {
|
||||
o.AccessLogs = true
|
||||
o.Tracing = true
|
||||
o.Metrics = true
|
||||
}
|
||||
|
|
|
@ -77,6 +77,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
},
|
||||
|
@ -122,6 +127,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
CertificatesResolvers: map[string]CertificateResolver{
|
||||
|
@ -178,6 +188,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
CertificatesResolvers: map[string]CertificateResolver{
|
||||
|
@ -238,6 +253,11 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
UDP: &UDPConfig{
|
||||
Timeout: 3000000000,
|
||||
},
|
||||
Observability: &ObservabilityConfig{
|
||||
AccessLogs: true,
|
||||
Tracing: true,
|
||||
Metrics: true,
|
||||
},
|
||||
}},
|
||||
Providers: &Providers{},
|
||||
CertificatesResolvers: map[string]CertificateResolver{
|
||||
|
|
|
@ -119,6 +119,11 @@ func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanK
|
|||
}
|
||||
|
||||
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if val := req.Context().Value(observability.DisableMetricsKey); val != nil {
|
||||
m.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
proto := getRequestProtocol(req)
|
||||
|
||||
var labels []string
|
||||
|
|
|
@ -2,21 +2,15 @@ package observability
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
@ -28,24 +22,19 @@ const (
|
|||
type entryPointTracing struct {
|
||||
tracer *tracing.Tracer
|
||||
|
||||
entryPoint string
|
||||
next http.Handler
|
||||
semConvMetricRegistry *metrics.SemConvMetricsRegistry
|
||||
entryPoint string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// WrapEntryPointHandler Wraps tracing to alice.Constructor.
|
||||
func WrapEntryPointHandler(ctx context.Context, tracer *tracing.Tracer, semConvMetricRegistry *metrics.SemConvMetricsRegistry, entryPointName string) alice.Constructor {
|
||||
// EntryPointHandler Wraps tracing to alice.Constructor.
|
||||
func EntryPointHandler(ctx context.Context, tracer *tracing.Tracer, entryPointName string) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
if tracer == nil {
|
||||
tracer = tracing.NewTracer(noop.Tracer{}, nil, nil, nil)
|
||||
}
|
||||
|
||||
return newEntryPoint(ctx, tracer, semConvMetricRegistry, entryPointName, next), nil
|
||||
return newEntryPoint(ctx, tracer, 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 {
|
||||
func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, entryPointName string, next http.Handler) http.Handler {
|
||||
middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware")
|
||||
|
||||
if tracer == nil {
|
||||
|
@ -53,10 +42,9 @@ func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, semConvMetricReg
|
|||
}
|
||||
|
||||
return &entryPointTracing{
|
||||
entryPoint: entryPointName,
|
||||
tracer: tracer,
|
||||
semConvMetricRegistry: semConvMetricRegistry,
|
||||
next: next,
|
||||
entryPoint: entryPointName,
|
||||
tracer: tracer,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,23 +76,4 @@ func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
|||
|
||||
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...))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,11 @@ import (
|
|||
"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/middlewares/accesslog"
|
||||
"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) {
|
||||
|
@ -77,7 +69,7 @@ func TestEntryPointMiddleware_tracing(t *testing.T) {
|
|||
|
||||
tracer := &mockTracer{}
|
||||
|
||||
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), nil, test.entryPoint, next)
|
||||
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next)
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
for _, span := range tracer.spans {
|
||||
|
@ -88,101 +80,6 @@ func TestEntryPointMiddleware_tracing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/", http.NoBody)
|
||||
req = req.WithContext(
|
||||
|
@ -197,7 +94,7 @@ func TestEntryPointMiddleware_tracingInfoIntoLog(t *testing.T) {
|
|||
|
||||
tracer := &mockTracer{}
|
||||
|
||||
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{}, []string{}, []string{}), nil, "test", next)
|
||||
handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{}, []string{}, []string{}), "test", next)
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
expectedSpanCtx := tracer.spans[0].SpanContext()
|
||||
|
|
|
@ -8,6 +8,11 @@ import (
|
|||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
// DisableMetricsKey is a context key used to disable the metrics.
|
||||
const DisableMetricsKey contextKey = iota
|
||||
|
||||
// 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 {
|
||||
|
|
81
pkg/middlewares/observability/semconv.go
Normal file
81
pkg/middlewares/observability/semconv.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
|
||||
)
|
||||
|
||||
const (
|
||||
semConvServerMetricsTypeName = "SemConvServerMetrics"
|
||||
)
|
||||
|
||||
type semConvServerMetrics struct {
|
||||
next http.Handler
|
||||
semConvMetricRegistry *metrics.SemConvMetricsRegistry
|
||||
}
|
||||
|
||||
// SemConvServerMetricsHandler return the alice.Constructor for semantic conventions servers metrics.
|
||||
func SemConvServerMetricsHandler(ctx context.Context, semConvMetricRegistry *metrics.SemConvMetricsRegistry) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
return newServerMetricsSemConv(ctx, semConvMetricRegistry, next), nil
|
||||
}
|
||||
}
|
||||
|
||||
// newServerMetricsSemConv creates a new semConv server metrics middleware for incoming requests.
|
||||
func newServerMetricsSemConv(ctx context.Context, semConvMetricRegistry *metrics.SemConvMetricsRegistry, next http.Handler) http.Handler {
|
||||
middlewares.GetLogger(ctx, "tracing", semConvServerMetricsTypeName).Debug().Msg("Creating middleware")
|
||||
|
||||
return &semConvServerMetrics{
|
||||
semConvMetricRegistry: semConvMetricRegistry,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *semConvServerMetrics) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if e.semConvMetricRegistry == nil || e.semConvMetricRegistry.HTTPServerRequestDuration() == nil {
|
||||
e.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
e.next.ServeHTTP(rw, req)
|
||||
end := time.Now()
|
||||
|
||||
ctx := req.Context()
|
||||
capt, err := capture.FromContext(ctx)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Str(logs.MiddlewareType, semConvServerMetricsTypeName).Msg("Could not get Capture")
|
||||
return
|
||||
}
|
||||
|
||||
var attrs []attribute.KeyValue
|
||||
|
||||
if capt.StatusCode() < 100 || capt.StatusCode() >= 600 {
|
||||
attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code ; %d", capt.StatusCode())))
|
||||
} else if capt.StatusCode() >= 400 {
|
||||
attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(capt.StatusCode())))
|
||||
}
|
||||
|
||||
attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method))
|
||||
attrs = append(attrs, semconv.HTTPResponseStatusCode(capt.StatusCode()))
|
||||
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...))
|
||||
}
|
118
pkg/middlewares/observability/semconv_test.go
Normal file
118
pkg/middlewares/observability/semconv_test.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
"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 TestSemConvServerMetrics(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 := newServerMetricsSemConv(context.Background(), semConvMetricRegistry, next)
|
||||
|
||||
handler, err = capture.Wrap(handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -12,6 +12,10 @@ spec:
|
|||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
observability:
|
||||
accessLogs: true
|
||||
tracing: true
|
||||
metrics: true
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
|
|
|
@ -114,12 +114,13 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
}
|
||||
|
||||
r := &dynamic.Router{
|
||||
Middlewares: mds,
|
||||
Priority: route.Priority,
|
||||
RuleSyntax: route.Syntax,
|
||||
EntryPoints: ingressRoute.Spec.EntryPoints,
|
||||
Rule: route.Match,
|
||||
Service: serviceName,
|
||||
Middlewares: mds,
|
||||
Priority: route.Priority,
|
||||
RuleSyntax: route.Syntax,
|
||||
EntryPoints: ingressRoute.Spec.EntryPoints,
|
||||
Rule: route.Match,
|
||||
Service: serviceName,
|
||||
Observability: route.Observability,
|
||||
}
|
||||
|
||||
if ingressRoute.Spec.TLS != nil {
|
||||
|
|
|
@ -1688,6 +1688,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
|
|
|
@ -43,6 +43,9 @@ type Route struct {
|
|||
// Middlewares defines the list of references to Middleware resources.
|
||||
// More info: https://doc.traefik.io/traefik/v3.2/routing/providers/kubernetes-crd/#kind-middleware
|
||||
Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
|
||||
// Observability defines the observability configuration for a router.
|
||||
// More info: https://doc.traefik.io/traefik/v3.2/routing/routers/#observability
|
||||
Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty"`
|
||||
}
|
||||
|
||||
// TLS holds the TLS configuration.
|
||||
|
|
|
@ -1102,6 +1102,11 @@ func (in *Route) DeepCopyInto(out *Route) {
|
|||
*out = make([]MiddlewareRef, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Observability != nil {
|
||||
in, out := &in.Observability, &out.Observability
|
||||
*out = new(dynamic.RouterObservabilityConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -22,12 +22,13 @@ type RouterConfig struct {
|
|||
|
||||
// RouterIng is the router's configuration from annotations.
|
||||
type RouterIng struct {
|
||||
PathMatcher string `json:"pathMatcher,omitempty"`
|
||||
EntryPoints []string `json:"entryPoints,omitempty"`
|
||||
Middlewares []string `json:"middlewares,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty"`
|
||||
TLS *dynamic.RouterTLSConfig `json:"tls,omitempty" label:"allowEmpty"`
|
||||
PathMatcher string `json:"pathMatcher,omitempty"`
|
||||
EntryPoints []string `json:"entryPoints,omitempty"`
|
||||
Middlewares []string `json:"middlewares,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty"`
|
||||
TLS *dynamic.RouterTLSConfig `json:"tls,omitempty" label:"allowEmpty"`
|
||||
Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty" label:"allowEmpty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
|
|
|
@ -18,20 +18,23 @@ func Test_parseRouterConfig(t *testing.T) {
|
|||
{
|
||||
desc: "router annotations",
|
||||
annotations: map[string]string{
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.options": "foobar",
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.options": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.observability.accessLogs": "true",
|
||||
"traefik.ingress.kubernetes.io/router.observability.metrics": "true",
|
||||
"traefik.ingress.kubernetes.io/router.observability.tracing": "true",
|
||||
},
|
||||
expected: &RouterConfig{
|
||||
Router: &RouterIng{
|
||||
|
@ -54,6 +57,11 @@ func Test_parseRouterConfig(t *testing.T) {
|
|||
},
|
||||
Options: "foobar",
|
||||
},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -182,35 +190,41 @@ func Test_convertAnnotations(t *testing.T) {
|
|||
{
|
||||
desc: "router annotations",
|
||||
annotations: map[string]string{
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.options": "foobar",
|
||||
"ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/foo": "bar",
|
||||
"traefik.ingress.kubernetes.io/router.pathmatcher": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.entrypoints": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.middlewares": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.priority": "42",
|
||||
"traefik.ingress.kubernetes.io/router.rulesyntax": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls": "true",
|
||||
"traefik.ingress.kubernetes.io/router.tls.certresolver": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.0.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.main": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.domains.1.sans": "foobar,foobar",
|
||||
"traefik.ingress.kubernetes.io/router.tls.options": "foobar",
|
||||
"traefik.ingress.kubernetes.io/router.observability.accessLogs": "true",
|
||||
"traefik.ingress.kubernetes.io/router.observability.metrics": "true",
|
||||
"traefik.ingress.kubernetes.io/router.observability.tracing": "true",
|
||||
},
|
||||
expected: map[string]string{
|
||||
"traefik.foo": "bar",
|
||||
"traefik.router.pathmatcher": "foobar",
|
||||
"traefik.router.entrypoints": "foobar,foobar",
|
||||
"traefik.router.middlewares": "foobar,foobar",
|
||||
"traefik.router.priority": "42",
|
||||
"traefik.router.rulesyntax": "foobar",
|
||||
"traefik.router.tls": "true",
|
||||
"traefik.router.tls.certresolver": "foobar",
|
||||
"traefik.router.tls.domains[0].main": "foobar",
|
||||
"traefik.router.tls.domains[0].sans": "foobar,foobar",
|
||||
"traefik.router.tls.domains[1].main": "foobar",
|
||||
"traefik.router.tls.domains[1].sans": "foobar,foobar",
|
||||
"traefik.router.tls.options": "foobar",
|
||||
"traefik.foo": "bar",
|
||||
"traefik.router.pathmatcher": "foobar",
|
||||
"traefik.router.entrypoints": "foobar,foobar",
|
||||
"traefik.router.middlewares": "foobar,foobar",
|
||||
"traefik.router.priority": "42",
|
||||
"traefik.router.rulesyntax": "foobar",
|
||||
"traefik.router.tls": "true",
|
||||
"traefik.router.tls.certresolver": "foobar",
|
||||
"traefik.router.tls.domains[0].main": "foobar",
|
||||
"traefik.router.tls.domains[0].sans": "foobar,foobar",
|
||||
"traefik.router.tls.domains[1].main": "foobar",
|
||||
"traefik.router.tls.domains[1].sans": "foobar,foobar",
|
||||
"traefik.router.tls.options": "foobar",
|
||||
"traefik.router.observability.accessLogs": "true",
|
||||
"traefik.router.observability.metrics": "true",
|
||||
"traefik.router.observability.tracing": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -18,6 +18,9 @@ metadata:
|
|||
traefik.ingress.kubernetes.io/router.tls.domains.1.main: example.com
|
||||
traefik.ingress.kubernetes.io/router.tls.domains.1.sans: one.example.com,two.example.com
|
||||
traefik.ingress.kubernetes.io/router.tls.options: foobar
|
||||
traefik.ingress.kubernetes.io/router.observability.accesslogs: "true"
|
||||
traefik.ingress.kubernetes.io/router.observability.metrics: "true"
|
||||
traefik.ingress.kubernetes.io/router.observability.tracing: "true"
|
||||
|
||||
spec:
|
||||
rules:
|
||||
|
|
|
@ -293,6 +293,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
rt.EntryPoints = rtConfig.Router.EntryPoints
|
||||
rt.Middlewares = rtConfig.Router.Middlewares
|
||||
rt.TLS = rtConfig.Router.TLS
|
||||
rt.Observability = rtConfig.Router.Observability
|
||||
}
|
||||
|
||||
p.applyRouterTransform(ctxIngress, rt, ingress)
|
||||
|
@ -619,10 +620,8 @@ func (p *Provider) loadRouter(rule netv1.IngressRule, pa netv1.HTTPIngressPath,
|
|||
rt.Priority = rtConfig.Router.Priority
|
||||
rt.EntryPoints = rtConfig.Router.EntryPoints
|
||||
rt.Middlewares = rtConfig.Router.Middlewares
|
||||
|
||||
if rtConfig.Router.TLS != nil {
|
||||
rt.TLS = rtConfig.Router.TLS
|
||||
}
|
||||
rt.TLS = rtConfig.Router.TLS
|
||||
rt.Observability = rtConfig.Router.Observability
|
||||
}
|
||||
|
||||
var rules []string
|
||||
|
|
|
@ -115,6 +115,11 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
},
|
||||
Options: "foobar",
|
||||
},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"observability": {
|
||||
"accessLogs": false,
|
||||
"tracing": false,
|
||||
"metrics": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,6 +231,14 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
|||
Middlewares: ep.HTTP.Middlewares,
|
||||
}
|
||||
|
||||
if ep.Observability != nil {
|
||||
m.Observability = dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: &ep.Observability.AccessLogs,
|
||||
Tracing: &ep.Observability.Tracing,
|
||||
Metrics: &ep.Observability.Metrics,
|
||||
}
|
||||
}
|
||||
|
||||
if ep.HTTP.TLS != nil {
|
||||
m.TLS = &dynamic.RouterTLSConfig{
|
||||
Options: ep.HTTP.TLS.Options,
|
||||
|
|
|
@ -184,6 +184,11 @@ func Test_createConfiguration(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Observability: &static.ObservabilityConfig{
|
||||
AccessLogs: false,
|
||||
Tracing: false,
|
||||
Metrics: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -68,7 +68,7 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
span.End(trace.WithTimestamp(end))
|
||||
}
|
||||
|
||||
if t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil {
|
||||
if req.Context().Value(observability.DisableMetricsKey) == nil && t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil {
|
||||
var attrs []attribute.KeyValue
|
||||
|
||||
if statusCode < 100 || statusCode >= 600 {
|
||||
|
|
|
@ -57,6 +57,11 @@ func init() {
|
|||
},
|
||||
},
|
||||
},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"observability": {
|
||||
"accessLogs": true,
|
||||
"tracing": true,
|
||||
"metrics": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -327,7 +332,8 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"observability": {}
|
||||
}
|
||||
},
|
||||
"serversTransports": {
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"observability": {
|
||||
"accessLogs": true,
|
||||
"tracing": true,
|
||||
"metrics": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -330,7 +335,8 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"observability": {}
|
||||
}
|
||||
},
|
||||
"serversTransports": {
|
||||
|
|
|
@ -178,6 +178,22 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
|
||||
cp.Middlewares = append(m.Middlewares, cp.Middlewares...)
|
||||
|
||||
if cp.Observability == nil {
|
||||
cp.Observability = &dynamic.RouterObservabilityConfig{}
|
||||
}
|
||||
|
||||
if cp.Observability.AccessLogs == nil {
|
||||
cp.Observability.AccessLogs = m.Observability.AccessLogs
|
||||
}
|
||||
|
||||
if cp.Observability.Tracing == nil {
|
||||
cp.Observability.Tracing = m.Observability.Tracing
|
||||
}
|
||||
|
||||
if cp.Observability.Metrics == nil {
|
||||
cp.Observability.Metrics = m.Observability.Metrics
|
||||
}
|
||||
|
||||
rtName := name
|
||||
if len(eps) > 1 {
|
||||
rtName = epName + "-" + name
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
)
|
||||
|
||||
func pointer[T any](v T) *T { return &v }
|
||||
|
||||
func Test_mergeConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -558,9 +560,10 @@ func Test_applyModel(t *testing.T) {
|
|||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"test": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
|
@ -574,6 +577,60 @@ func Test_applyModel(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with model, one entry point with observability",
|
||||
input: dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"test": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
Models: map[string]*dynamic.Model{
|
||||
"websecure@internal": {
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"test": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
Services: make(map[string]*dynamic.Service),
|
||||
Models: map[string]*dynamic.Model{
|
||||
"websecure@internal": {
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: pointer(true),
|
||||
Tracing: pointer(true),
|
||||
Metrics: pointer(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with model, one entry point, and router with tls",
|
||||
input: dynamic.Configuration{
|
||||
|
@ -601,6 +658,11 @@ func Test_applyModel(t *testing.T) {
|
|||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{CertResolver: "router"},
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: nil,
|
||||
Tracing: nil,
|
||||
Metrics: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
|
@ -640,9 +702,10 @@ func Test_applyModel(t *testing.T) {
|
|||
EntryPoints: []string{"web"},
|
||||
},
|
||||
"websecure-test": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
EntryPoints: []string{"websecure"},
|
||||
Middlewares: []string{"test"},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
Observability: &dynamic.RouterObservabilityConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: make(map[string]*dynamic.Middleware),
|
||||
|
|
|
@ -8,12 +8,13 @@ import (
|
|||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
)
|
||||
|
@ -41,7 +42,7 @@ func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Re
|
|||
}
|
||||
|
||||
// BuildEPChain an observability middleware chain by entry point.
|
||||
func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string) alice.Chain {
|
||||
func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string, observabilityConfig *dynamic.RouterObservabilityConfig) alice.Chain {
|
||||
chain := alice.New()
|
||||
|
||||
if o == nil {
|
||||
|
@ -49,62 +50,101 @@ func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName stri
|
|||
}
|
||||
|
||||
if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) {
|
||||
if o.ShouldAddAccessLogs(resourceName) || o.ShouldAddMetrics(resourceName) {
|
||||
if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(capture.Wrap)
|
||||
}
|
||||
}
|
||||
|
||||
// As the Entry point observability middleware ensures that the tracing is added to the request and logger context,
|
||||
// it needs to be added before the access log middleware to ensure that the trace ID is logged.
|
||||
if (o.tracer != nil && o.ShouldAddTracing(resourceName)) || (o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName)) {
|
||||
chain = chain.Append(observability.WrapEntryPointHandler(ctx, o.tracer, o.semConvMetricRegistry, entryPointName))
|
||||
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName))
|
||||
}
|
||||
|
||||
if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName) {
|
||||
if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware))
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
})
|
||||
}
|
||||
|
||||
if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName) {
|
||||
metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName)
|
||||
// Semantic convention server metrics handler.
|
||||
if o.semConvMetricRegistry != nil && o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry))
|
||||
}
|
||||
|
||||
if o.tracer != nil && o.ShouldAddTracing(resourceName) {
|
||||
if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName, observabilityConfig) {
|
||||
metricsHandler := mmetrics.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName)
|
||||
|
||||
if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) {
|
||||
chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler))
|
||||
} else {
|
||||
chain = chain.Append(metricsHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject context keys to control whether to produce metrics further downstream (services, round-tripper),
|
||||
// because the router configuration cannot be evaluated during build time for services.
|
||||
if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics {
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true)))
|
||||
}), nil
|
||||
})
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given resource.
|
||||
func (o *ObservabilityMgr) ShouldAddAccessLogs(resourceName string) bool {
|
||||
// ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return o.config.AccessLog != nil && (o.config.AccessLog.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
if o.config.AccessLog == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.AccessLogs != nil && *observabilityConfig.AccessLogs
|
||||
}
|
||||
|
||||
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource.
|
||||
func (o *ObservabilityMgr) ShouldAddMetrics(resourceName string) bool {
|
||||
// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return o.config.Metrics != nil && (o.config.Metrics.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
if o.config.Metrics == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.Metrics != nil && *observabilityConfig.Metrics
|
||||
}
|
||||
|
||||
// ShouldAddTracing returns whether the tracing should be enabled for the given resource.
|
||||
func (o *ObservabilityMgr) ShouldAddTracing(resourceName string) bool {
|
||||
// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config.
|
||||
func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool {
|
||||
if o == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return o.config.Tracing != nil && (o.config.Tracing.AddInternals || !strings.HasSuffix(resourceName, "@internal"))
|
||||
if o.config.Tracing == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasSuffix(serviceName, "@internal") && !o.config.Tracing.AddInternals {
|
||||
return false
|
||||
}
|
||||
|
||||
return observabilityConfig == nil || observabilityConfig.Tracing != nil && *observabilityConfig.Tracing
|
||||
}
|
||||
|
||||
// MetricsRegistry is an accessor to the metrics registry.
|
||||
|
|
|
@ -91,12 +91,12 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
|||
continue
|
||||
}
|
||||
|
||||
handler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "").Then(BuildDefaultHTTPRouter())
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
continue
|
||||
}
|
||||
entryPointHandlers[entryPointName] = handler
|
||||
entryPointHandlers[entryPointName] = defaultHandler
|
||||
}
|
||||
|
||||
return entryPointHandlers
|
||||
|
@ -108,7 +108,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
|||
return nil, err
|
||||
}
|
||||
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "defaultHandler").Then(http.NotFoundHandler())
|
||||
defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
|||
continue
|
||||
}
|
||||
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service)
|
||||
observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability)
|
||||
handler, err = observabilityChain.Then(handler)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
|
@ -182,7 +182,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
|
|||
}
|
||||
|
||||
// Prevents from enabling observability for internal resources.
|
||||
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service)) {
|
||||
if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service), routerConfig.Observability) {
|
||||
m.routerHandlers[routerName] = handler
|
||||
return m.routerHandlers[routerName], nil
|
||||
}
|
||||
|
@ -221,12 +221,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
|||
chain := alice.New()
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() &&
|
||||
m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service)) {
|
||||
m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
|
||||
chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)))
|
||||
}
|
||||
|
||||
// Prevents from enabling tracing for internal resources.
|
||||
if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service)) {
|
||||
if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service), router.Observability) {
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
}
|
||||
|
||||
|
|
|
@ -356,7 +356,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
|
||||
qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName)
|
||||
|
||||
shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName)
|
||||
shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil)
|
||||
proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, shouldObserve, passHostHeader, server.PreservePath, flushInterval)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err)
|
||||
|
@ -364,14 +364,14 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
|
||||
// Prevents from enabling observability for internal resources.
|
||||
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName) {
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) {
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
|
||||
}
|
||||
|
||||
if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() &&
|
||||
m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName) {
|
||||
m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
|
||||
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName)
|
||||
|
||||
proxy, err = alice.New().
|
||||
|
@ -382,11 +382,11 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
}
|
||||
}
|
||||
|
||||
if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName) {
|
||||
if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) {
|
||||
proxy = observability.NewService(ctx, serviceName, proxy)
|
||||
}
|
||||
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName) {
|
||||
if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) {
|
||||
// Some piece of middleware, like the ErrorPage, are relying on this serviceBuilder to get the handler for a given service,
|
||||
// to re-target the request to it.
|
||||
// Those pieces of middleware can be configured on routes that expose a Traefik internal service.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue