Migrate to opentelemetry
This commit is contained in:
parent
45bb00be04
commit
4ddef9830b
89 changed files with 2113 additions and 3898 deletions
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/rest"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/jaeger"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -259,9 +258,7 @@ func TestHandler_Overview(t *testing.T) {
|
|||
Metrics: &types.Metrics{
|
||||
Prometheus: &types.Prometheus{},
|
||||
},
|
||||
Tracing: &static.Tracing{
|
||||
Jaeger: &jaeger.Config{},
|
||||
},
|
||||
Tracing: &static.Tracing{},
|
||||
},
|
||||
confDyn: runtime.Configuration{},
|
||||
expected: expected{
|
||||
|
|
2
pkg/api/testdata/overview-features.json
vendored
2
pkg/api/testdata/overview-features.json
vendored
|
@ -2,7 +2,7 @@
|
|||
"features": {
|
||||
"accessLog": false,
|
||||
"metrics": "Prometheus",
|
||||
"tracing": "Jaeger"
|
||||
"tracing": ""
|
||||
},
|
||||
"http": {
|
||||
"middlewares": {
|
||||
|
|
|
@ -122,41 +122,29 @@
|
|||
|
||||
[tracing]
|
||||
serviceName = "foobar"
|
||||
spanNameLimit = 42
|
||||
[tracing.jaeger]
|
||||
samplingServerURL = "foobar"
|
||||
samplingType = "foobar"
|
||||
samplingParam = 42.0
|
||||
localAgentHostPort = "foobar"
|
||||
gen128Bit = true
|
||||
propagation = "foobar"
|
||||
traceContextHeaderName = "foobar"
|
||||
[tracing.zipkin]
|
||||
httpEndpoint = "foobar"
|
||||
sameSpan = true
|
||||
id128Bit = true
|
||||
debug = true
|
||||
sampleRate = 42.0
|
||||
[tracing.datadog]
|
||||
localAgentHostPort = "foobar"
|
||||
localAgentSocket = "foobar"
|
||||
debug = true
|
||||
prioritySampling = true
|
||||
traceIDHeaderName = "foobar"
|
||||
parentIDHeaderName = "foobar"
|
||||
samplingPriorityHeaderName = "foobar"
|
||||
bagagePrefixHeaderName = "foobar"
|
||||
[tracing.instana]
|
||||
localAgentHost = "foobar"
|
||||
localAgentPort = 42
|
||||
logLevel = "foobar"
|
||||
[tracing.haystack]
|
||||
localAgentHost = "foobar"
|
||||
localAgentPort = 42
|
||||
globalTag = "foobar"
|
||||
traceIDHeaderName = "foobar"
|
||||
parentIDHeaderName = "foobar"
|
||||
spanIDHeaderName = "foobar"
|
||||
sampleRate = 42
|
||||
[tracing.headers]
|
||||
header1 = "foobar"
|
||||
header2 = "foobar"
|
||||
[tracing.globalAttributes]
|
||||
attr1 = "foobar"
|
||||
attr2 = "foobar"
|
||||
[tracing.otlp.grpc]
|
||||
endpoint = "foobar"
|
||||
insecure = true
|
||||
[tracing.otlp.grpc.tls]
|
||||
ca = "foobar"
|
||||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
[tracing.otlp.http]
|
||||
endpoint = "foobar"
|
||||
insecure = true
|
||||
[tracing.otlp.http.tls]
|
||||
ca = "foobar"
|
||||
cert = "foobar"
|
||||
key = "foobar"
|
||||
insecureSkipVerify = true
|
||||
|
||||
[hostResolver]
|
||||
cnameFlattening = true
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -27,13 +28,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/provider/nomad"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/rest"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/datadog"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/elastic"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/haystack"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/instana"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/jaeger"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/opentelemetry"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/zipkin"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -187,21 +182,18 @@ 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"`
|
||||
SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty" export:"true"`
|
||||
Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
OpenTelemetry *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"openTelemetry,omitempty" toml:"openTelemetry,omitempty" yaml:"openTelemetry,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
|
||||
Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,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"`
|
||||
|
||||
OTLP *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (t *Tracing) SetDefaults() {
|
||||
t.ServiceName = "traefik"
|
||||
t.SpanNameLimit = 0
|
||||
t.SampleRate = 1.0
|
||||
}
|
||||
|
||||
// Providers contains providers configuration.
|
||||
|
@ -326,6 +318,20 @@ func (c *Configuration) ValidateConfiguration() error {
|
|||
acmeEmail = resolver.ACME.Email
|
||||
}
|
||||
|
||||
if c.Tracing != nil && c.Tracing.OTLP != nil {
|
||||
if c.Tracing.OTLP.HTTP == nil && c.Tracing.OTLP.GRPC == nil {
|
||||
return errors.New("tracing OTLP: at least one of HTTP and gRPC options should be defined")
|
||||
}
|
||||
|
||||
if c.Tracing.OTLP.HTTP != nil && c.Tracing.OTLP.GRPC != nil {
|
||||
return errors.New("tracing OTLP: HTTP and gRPC options are mutually exclusive")
|
||||
}
|
||||
|
||||
if c.Tracing.OTLP.GRPC != nil && c.Tracing.OTLP.GRPC.TLS != nil && c.Tracing.OTLP.GRPC.Insecure {
|
||||
return errors.New("tracing OTLP GRPC: TLS and Insecure options are mutually exclusive")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package logs
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type HaystackLogger struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func NewHaystackLogger(logger zerolog.Logger) *HaystackLogger {
|
||||
return &HaystackLogger{logger: logger}
|
||||
}
|
||||
|
||||
// Error prints the error message.
|
||||
func (l HaystackLogger) Error(format string, v ...interface{}) {
|
||||
l.logger.Error().CallerSkipFrame(1).Msgf(format, v...)
|
||||
}
|
||||
|
||||
// Info prints the info message.
|
||||
func (l HaystackLogger) Info(format string, v ...interface{}) {
|
||||
l.logger.Info().CallerSkipFrame(1).Msgf(format, v...)
|
||||
}
|
||||
|
||||
// Debug prints the info message.
|
||||
func (l HaystackLogger) Debug(format string, v ...interface{}) {
|
||||
l.logger.Debug().CallerSkipFrame(1).Msgf(format, v...)
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewHaystackLogger(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
cwb := zerolog.ConsoleWriter{Out: buf, TimeFormat: time.RFC3339, NoColor: true}
|
||||
|
||||
out := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}, cwb)
|
||||
|
||||
logger := NewHaystackLogger(zerolog.New(out).With().Caller().Logger())
|
||||
|
||||
logger.Info("foo")
|
||||
|
||||
assert.Equal(t, "<nil> INF haystack_test.go:21 > foo\n", buf.String())
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package logs
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// JaegerLogger is an implementation of the Logger interface that delegates to traefik log.
|
||||
type JaegerLogger struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func NewJaegerLogger(logger zerolog.Logger) *JaegerLogger {
|
||||
return &JaegerLogger{logger: logger}
|
||||
}
|
||||
|
||||
func (l *JaegerLogger) Error(msg string) {
|
||||
l.logger.Error().CallerSkipFrame(1).Msg(msg)
|
||||
}
|
||||
|
||||
// Infof logs a message at debug priority.
|
||||
func (l *JaegerLogger) Infof(msg string, args ...interface{}) {
|
||||
l.logger.Debug().CallerSkipFrame(1).Msgf(msg, args...)
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewJaegerLogger(t *testing.T) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
cwb := zerolog.ConsoleWriter{Out: buf, TimeFormat: time.RFC3339, NoColor: true}
|
||||
|
||||
out := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}, cwb)
|
||||
|
||||
logger := NewJaegerLogger(zerolog.New(out).With().Caller().Logger())
|
||||
|
||||
logger.Infof("foo")
|
||||
|
||||
assert.Equal(t, "<nil> DBG jaeger_test.go:21 > foo\n", buf.String())
|
||||
}
|
|
@ -19,7 +19,7 @@ import (
|
|||
"go.opentelemetry.io/otel/metric"
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
)
|
||||
|
|
|
@ -5,10 +5,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -40,8 +39,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (a *addPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return a.name, tracing.SpanKindNoneEnum
|
||||
func (a *addPrefix) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return a.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -8,15 +8,15 @@ import (
|
|||
"strings"
|
||||
|
||||
goauth "github.com/abbot/go-http-auth"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
basicTypeName = "BasicAuth"
|
||||
typeNameBasic = "BasicAuth"
|
||||
)
|
||||
|
||||
type basicAuth struct {
|
||||
|
@ -30,7 +30,7 @@ type basicAuth struct {
|
|||
|
||||
// NewBasic creates a basicAuth middleware.
|
||||
func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAuth, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, basicTypeName).Debug().Msg("Creating middleware")
|
||||
middlewares.GetLogger(ctx, name, typeNameBasic).Debug().Msg("Creating middleware")
|
||||
|
||||
users, err := getUsers(authConfig.UsersFile, authConfig.Users, basicUserParser)
|
||||
if err != nil {
|
||||
|
@ -55,12 +55,12 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
|
|||
return ba, nil
|
||||
}
|
||||
|
||||
func (b *basicAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return b.name, tracing.SpanKindNoneEnum
|
||||
func (b *basicAuth) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return b.name, typeNameBasic, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
logger := middlewares.GetLogger(req.Context(), b.name, basicTypeName)
|
||||
logger := middlewares.GetLogger(req.Context(), b.name, typeNameBasic)
|
||||
|
||||
user, password, ok := req.BasicAuth()
|
||||
if ok {
|
||||
|
@ -77,7 +77,7 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
if !ok {
|
||||
logger.Debug().Msg("Authentication failed")
|
||||
tracing.SetErrorWithEvent(req, "Authentication failed")
|
||||
tracing.SetStatusErrorf(req.Context(), "Authentication failed")
|
||||
|
||||
b.auth.RequireAuth(rw, req)
|
||||
return
|
||||
|
|
|
@ -8,15 +8,15 @@ import (
|
|||
"strings"
|
||||
|
||||
goauth "github.com/abbot/go-http-auth"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
digestTypeName = "digestAuth"
|
||||
typeNameDigest = "digestAuth"
|
||||
)
|
||||
|
||||
type digestAuth struct {
|
||||
|
@ -30,7 +30,7 @@ type digestAuth struct {
|
|||
|
||||
// NewDigest creates a digest auth middleware.
|
||||
func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.DigestAuth, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, digestTypeName).Debug().Msg("Creating middleware")
|
||||
middlewares.GetLogger(ctx, name, typeNameDigest).Debug().Msg("Creating middleware")
|
||||
|
||||
users, err := getUsers(authConfig.UsersFile, authConfig.Users, digestUserParser)
|
||||
if err != nil {
|
||||
|
@ -54,12 +54,12 @@ func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.Digest
|
|||
return da, nil
|
||||
}
|
||||
|
||||
func (d *digestAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return d.name, tracing.SpanKindNoneEnum
|
||||
func (d *digestAuth) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return d.name, typeNameDigest, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
logger := middlewares.GetLogger(req.Context(), d.name, digestTypeName)
|
||||
logger := middlewares.GetLogger(req.Context(), d.name, typeNameDigest)
|
||||
|
||||
username, authinfo := d.auth.CheckAuth(req)
|
||||
if username == "" {
|
||||
|
@ -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.SetErrorWithEvent(req, "Digest authentication failed, possibly because out of order requests")
|
||||
tracing.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.SetErrorWithEvent(req, "Digest authentication failed")
|
||||
tracing.SetStatusErrorf(req.Context(), "Digest authentication failed")
|
||||
d.auth.RequireAuth(rw, req)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -11,21 +11,22 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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/tracing"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
xForwardedURI = "X-Forwarded-Uri"
|
||||
xForwardedMethod = "X-Forwarded-Method"
|
||||
forwardedTypeName = "ForwardedAuthType"
|
||||
xForwardedURI = "X-Forwarded-Uri"
|
||||
xForwardedMethod = "X-Forwarded-Method"
|
||||
)
|
||||
|
||||
const typeNameForward = "ForwardAuth"
|
||||
|
||||
// hopHeaders Hop-by-hop headers to be removed in the authentication request.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||||
// Proxy-Authorization header is forwarded to the authentication server (see https://tools.ietf.org/html/rfc7235#section-4.4).
|
||||
|
@ -51,7 +52,7 @@ type forwardAuth struct {
|
|||
|
||||
// NewForward creates a forward auth middleware.
|
||||
func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAuth, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, forwardedTypeName).Debug().Msg("Creating middleware")
|
||||
middlewares.GetLogger(ctx, name, typeNameForward).Debug().Msg("Creating middleware")
|
||||
|
||||
fa := &forwardAuth{
|
||||
address: config.Address,
|
||||
|
@ -89,30 +90,38 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
|||
fa.authResponseHeadersRegex = re
|
||||
}
|
||||
|
||||
return connectionheader.Remover(fa), nil
|
||||
return fa, nil
|
||||
}
|
||||
|
||||
func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return fa.name, ext.SpanKindRPCClientEnum
|
||||
func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return fa.name, typeNameForward, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
logger := middlewares.GetLogger(req.Context(), fa.name, forwardedTypeName)
|
||||
logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward)
|
||||
|
||||
forwardReq, err := http.NewRequest(http.MethodGet, fa.address, nil)
|
||||
tracing.LogRequest(tracing.GetSpan(req), forwardReq)
|
||||
req = connectionheader.Remove(req)
|
||||
|
||||
forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil)
|
||||
if err != nil {
|
||||
logMessage := fmt.Sprintf("Error calling %s. Cause %s", fa.address, err)
|
||||
logger.Debug().Msg(logMessage)
|
||||
tracing.SetErrorWithEvent(req, logMessage)
|
||||
|
||||
tracing.SetStatusErrorf(req.Context(), logMessage)
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure tracing headers are in the request before we copy the headers to the
|
||||
// forwardReq.
|
||||
tracing.InjectRequestHeaders(req)
|
||||
var forwardSpan trace.Span
|
||||
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()
|
||||
|
||||
forwardReq = forwardReq.WithContext(tracingCtx)
|
||||
|
||||
tracing.InjectContextIntoCarrier(forwardReq)
|
||||
tracing.LogClientRequest(forwardSpan, forwardReq)
|
||||
}
|
||||
|
||||
writeHeader(req, forwardReq, fa.trustForwardHeader, fa.authRequestHeaders)
|
||||
|
||||
|
@ -120,7 +129,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
if forwardErr != nil {
|
||||
logMessage := fmt.Sprintf("Error calling %s. Cause: %s", fa.address, forwardErr)
|
||||
logger.Debug().Msg(logMessage)
|
||||
tracing.SetErrorWithEvent(req, logMessage)
|
||||
tracing.SetStatusErrorf(forwardReq.Context(), logMessage)
|
||||
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -131,12 +140,18 @@ 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.SetErrorWithEvent(req, logMessage)
|
||||
tracing.SetStatusErrorf(forwardReq.Context(), logMessage)
|
||||
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Ending the forward request span as soon as the response is handled.
|
||||
// If any errors happen earlier, this span will be close by the defer instruction.
|
||||
if forwardSpan != nil {
|
||||
forwardSpan.End()
|
||||
}
|
||||
|
||||
// Pass the forward response's body and selected headers if it
|
||||
// didn't return a response within the range of [200, 300).
|
||||
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
|
||||
|
@ -152,7 +167,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.SetErrorWithEvent(req, logMessage)
|
||||
tracing.SetStatusErrorf(forwardReq.Context(), logMessage)
|
||||
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -162,7 +177,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
rw.Header().Set("Location", redirectURL.String())
|
||||
}
|
||||
|
||||
tracing.LogResponseCode(tracing.GetSpan(req), forwardResponse.StatusCode)
|
||||
tracing.LogResponseCode(forwardSpan, forwardResponse.StatusCode, trace.SpanKindClient)
|
||||
rw.WriteHeader(forwardResponse.StatusCode)
|
||||
|
||||
if _, err = rw.Write(body); err != nil {
|
||||
|
@ -193,6 +208,8 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
tracing.LogResponseCode(forwardSpan, forwardResponse.StatusCode, trace.SpanKindClient)
|
||||
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
fa.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
|
|
@ -8,15 +8,22 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"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/version"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestForwardAuthFail(t *testing.T) {
|
||||
|
@ -452,8 +459,8 @@ func Test_writeHeader(t *testing.T) {
|
|||
|
||||
func TestForwardAuthUsesTracing(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Mockpfx-Ids-Traceid") == "" {
|
||||
t.Errorf("expected Mockpfx-Ids-Traceid header to be present in request")
|
||||
if r.Header.Get("Traceparent") == "" {
|
||||
t.Errorf("expected Traceparent header to be present in request")
|
||||
}
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
@ -464,15 +471,44 @@ func TestForwardAuthUsesTracing(t *testing.T) {
|
|||
Address: server.URL,
|
||||
}
|
||||
|
||||
tracer := mocktracer.New()
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
exporter := tracetest.NewInMemoryExporter()
|
||||
|
||||
tr, _ := tracing.NewTracing("testApp", 100, &mockBackend{tracer})
|
||||
|
||||
next, err := NewForward(context.Background(), next, auth, "authTest")
|
||||
tres, err := resource.New(context.Background(),
|
||||
resource.WithAttributes(semconv.ServiceNameKey.String("traefik")),
|
||||
resource.WithAttributes(semconv.ServiceVersionKey.String(version.Version)),
|
||||
resource.WithFromEnv(),
|
||||
resource.WithTelemetrySDK(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
next = tracingMiddleware.NewEntryPoint(context.Background(), tr, "tracingTest", next)
|
||||
tracerProvider := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||
sdktrace.WithResource(tres),
|
||||
sdktrace.WithBatcher(exporter),
|
||||
)
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
|
||||
config := &static.Tracing{
|
||||
ServiceName: "testApp",
|
||||
SampleRate: 1,
|
||||
OTLP: &opentelemetry.Config{
|
||||
HTTP: &opentelemetry.HTTP{
|
||||
Endpoint: "http://127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}
|
||||
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)
|
||||
|
||||
chain := alice.New(tracingMiddleware.WrapEntryPointHandler(context.Background(), tr, "tracingTest"))
|
||||
next, err = chain.Then(next)
|
||||
require.NoError(t, err)
|
||||
|
||||
ts := httptest.NewServer(next)
|
||||
t.Cleanup(ts.Close)
|
||||
|
@ -482,11 +518,3 @@ func TestForwardAuthUsesTracing(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode)
|
||||
}
|
||||
|
||||
type mockBackend struct {
|
||||
opentracing.Tracer
|
||||
}
|
||||
|
||||
func (b *mockBackend) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||
return b.Tracer, io.NopCloser(nil), nil
|
||||
}
|
||||
|
|
|
@ -4,13 +4,12 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/rs/zerolog"
|
||||
"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"
|
||||
oxybuffer "github.com/vulcand/oxy/v2/buffer"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -49,8 +48,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (b *buffer) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return b.name, tracing.SpanKindNoneEnum
|
||||
func (b *buffer) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return b.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (b *buffer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
|
@ -13,6 +12,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/vulcand/oxy/v2/cbreaker"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const typeName = "CircuitBreaker"
|
||||
|
@ -32,7 +32,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.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression)
|
||||
tracing.SetStatusErrorf(req.Context(), "blocked by circuit-breaker (%q)", expression)
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
||||
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
|
||||
|
@ -66,8 +66,8 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *circuitBreaker) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return c.name, tracing.SpanKindNoneEnum
|
||||
func (c *circuitBreaker) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return c.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (c *circuitBreaker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -9,11 +9,10 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/compress/brotli"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const typeName = "Compress"
|
||||
|
@ -114,8 +113,8 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
c.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (c *compress) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return c.name, tracing.SpanKindNoneEnum
|
||||
func (c *compress) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return c.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (c *compress) newGzipHandler() (http.Handler, error) {
|
||||
|
|
|
@ -17,24 +17,29 @@ const (
|
|||
// See RFC 7230, section 6.1.
|
||||
func Remover(next http.Handler) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
var reqUpType string
|
||||
if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) {
|
||||
reqUpType = req.Header.Get(upgradeHeader)
|
||||
}
|
||||
|
||||
removeConnectionHeaders(req.Header)
|
||||
|
||||
if reqUpType != "" {
|
||||
req.Header.Set(connectionHeader, upgradeHeader)
|
||||
req.Header.Set(upgradeHeader, reqUpType)
|
||||
} else {
|
||||
req.Header.Del(connectionHeader)
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
next.ServeHTTP(rw, Remove(req))
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes hop-by-hop header on the request.
|
||||
func Remove(req *http.Request) *http.Request {
|
||||
var reqUpType string
|
||||
if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) {
|
||||
reqUpType = req.Header.Get(upgradeHeader)
|
||||
}
|
||||
|
||||
removeConnectionHeaders(req.Header)
|
||||
|
||||
if reqUpType != "" {
|
||||
req.Header.Set(connectionHeader, upgradeHeader)
|
||||
req.Header.Set(upgradeHeader, reqUpType)
|
||||
} else {
|
||||
req.Header.Del(connectionHeader)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func removeConnectionHeaders(h http.Header) {
|
||||
for _, f := range h[connectionHeader] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
|
|
|
@ -10,12 +10,12 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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/types"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Compile time validation that the response recorder implements http interfaces correctly.
|
||||
|
@ -24,7 +24,7 @@ var (
|
|||
_ middlewares.Stateful = &codeCatcher{}
|
||||
)
|
||||
|
||||
const typeName = "customError"
|
||||
const typeName = "CustomError"
|
||||
|
||||
type serviceBuilder interface {
|
||||
BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error)
|
||||
|
@ -62,8 +62,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *customErrors) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return c.name, tracing.SpanKindNoneEnum
|
||||
func (c *customErrors) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return c.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -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.SetErrorWithEvent(req, "Error pages: no backend handler.")
|
||||
tracing.SetStatusErrorf(req.Context(), "Error pages: no backend handler.")
|
||||
c.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
@ -96,12 +96,12 @@ 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())
|
||||
http.Error(rw, http.StatusText(code), code)
|
||||
return
|
||||
}
|
||||
|
||||
utils.CopyHeaders(pageReq.Header, req.Header)
|
||||
|
||||
c.backendHandler.ServeHTTP(newCodeModifier(rw, code),
|
||||
pageReq.WithContext(req.Context()))
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
)
|
||||
|
||||
const typeName = "grpc-web"
|
||||
const typeName = "GRPCWeb"
|
||||
|
||||
// New builds a new gRPC web request converter.
|
||||
func New(ctx context.Context, next http.Handler, config dynamic.GrpcWeb, name string) http.Handler {
|
||||
|
|
|
@ -6,11 +6,10 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -61,8 +60,8 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (h *headers) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return h.name, tracing.SpanKindNoneEnum
|
||||
func (h *headers) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return h.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func TestNew_withoutOptions(t *testing.T) {
|
||||
|
@ -109,10 +109,11 @@ func Test_headers_getTracingInformation(t *testing.T) {
|
|||
name: "testing",
|
||||
}
|
||||
|
||||
name, trace := mid.GetTracingInformation()
|
||||
name, typeName, spanKind := mid.GetTracingInformation()
|
||||
|
||||
assert.Equal(t, "testing", name)
|
||||
assert.Equal(t, tracing.SpanKindNoneEnum, trace)
|
||||
assert.Equal(t, "Headers", typeName)
|
||||
assert.Equal(t, trace.SpanKindInternal, spanKind)
|
||||
}
|
||||
|
||||
// This test is an adapted version of net/http/httputil.Test1xxResponses test.
|
||||
|
|
|
@ -5,13 +5,12 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/rs/zerolog"
|
||||
"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/vulcand/oxy/v2/connlimit"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -54,8 +53,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, nam
|
|||
return &inFlightReq{handler: handler, name: name}, nil
|
||||
}
|
||||
|
||||
func (i *inFlightReq) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return i.name, tracing.SpanKindNoneEnum
|
||||
func (i *inFlightReq) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return i.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -6,12 +6,12 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/rs/zerolog/log"
|
||||
"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"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -55,8 +55,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (al *ipAllowLister) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return al.name, tracing.SpanKindNoneEnum
|
||||
func (al *ipAllowLister) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return al.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -68,7 +68,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.SetErrorWithEvent(req, msg)
|
||||
tracing.SetStatusErrorf(req.Context(), msg)
|
||||
reject(ctx, rw)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -39,6 +41,7 @@ type metricsMiddleware struct {
|
|||
reqsBytesCounter gokitmetrics.Counter
|
||||
respsBytesCounter gokitmetrics.Counter
|
||||
baseLabels []string
|
||||
name string
|
||||
}
|
||||
|
||||
// NewEntryPointMiddleware creates a new metrics middleware for an Entrypoint.
|
||||
|
@ -53,6 +56,7 @@ func NewEntryPointMiddleware(ctx context.Context, next http.Handler, registry me
|
|||
reqsBytesCounter: registry.EntryPointReqsBytesCounter(),
|
||||
respsBytesCounter: registry.EntryPointRespsBytesCounter(),
|
||||
baseLabels: []string{"entrypoint", entryPointName},
|
||||
name: nameEntrypoint,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,6 +72,7 @@ func NewRouterMiddleware(ctx context.Context, next http.Handler, registry metric
|
|||
reqsBytesCounter: registry.RouterReqsBytesCounter(),
|
||||
respsBytesCounter: registry.RouterRespsBytesCounter(),
|
||||
baseLabels: []string{"router", routerName, "service", serviceName},
|
||||
name: nameRouter,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,6 +88,7 @@ func NewServiceMiddleware(ctx context.Context, next http.Handler, registry metri
|
|||
reqsBytesCounter: registry.ServiceReqsBytesCounter(),
|
||||
respsBytesCounter: registry.ServiceRespsBytesCounter(),
|
||||
baseLabels: []string{"service", serviceName},
|
||||
name: nameService,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +106,17 @@ func WrapRouterHandler(ctx context.Context, registry metrics.Registry, routerNam
|
|||
}
|
||||
}
|
||||
|
||||
// WrapServiceHandler Wraps metrics service to alice.Constructor.
|
||||
func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
return NewServiceMiddleware(ctx, next, registry, serviceName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return m.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
proto := getRequestProtocol(req)
|
||||
|
||||
|
@ -127,6 +144,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")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,10 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const typeName = "PassClientTLSCert"
|
||||
|
@ -140,8 +139,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (p *passTLSClientCert) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return p.name, tracing.SpanKindNoneEnum
|
||||
func (p *passTLSClientCert) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return p.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -9,17 +9,17 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/mailgun/ttlmap"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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/vulcand/oxy/v2/utils"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "RateLimiterType"
|
||||
typeName = "RateLimiter"
|
||||
maxSources = 65536
|
||||
)
|
||||
|
||||
|
@ -122,8 +122,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (rl *rateLimiter) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return rl.name, tracing.SpanKindNoneEnum
|
||||
func (rl *rateLimiter) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return rl.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -153,12 +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")
|
||||
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")
|
||||
http.Error(rw, "No bursty traffic allowed", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ import (
|
|||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -15,6 +14,8 @@ const (
|
|||
schemeHTTPS = "https"
|
||||
)
|
||||
|
||||
const typeName = "Redirect"
|
||||
|
||||
var uriRegexp = regexp.MustCompile(`^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`)
|
||||
|
||||
type redirect struct {
|
||||
|
@ -45,8 +46,8 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, r
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return r.name, tracing.SpanKindNoneEnum
|
||||
func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return r.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -35,8 +35,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePath, nam
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (r *replacePath) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return r.name, tracing.SpanKindNoneEnum
|
||||
func (r *replacePath) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return r.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -52,6 +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())
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,11 +8,11 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/replacepath"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const typeName = "ReplacePathRegex"
|
||||
|
@ -42,8 +42,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePathRegex
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (rp *replacePathRegex) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return rp.name, tracing.SpanKindNoneEnum
|
||||
func (rp *replacePathRegex) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return rp.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -63,6 +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())
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Compile time validation that the response writer implements http interfaces correctly.
|
||||
|
@ -60,10 +62,6 @@ func New(ctx context.Context, next http.Handler, config dynamic.Retry, listener
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (r *retry) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return r.name, tracing.SpanKindNoneEnum
|
||||
}
|
||||
|
||||
func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if r.attempts == 1 {
|
||||
r.next.ServeHTTP(rw, req)
|
||||
|
@ -79,12 +77,35 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
attempts := 1
|
||||
|
||||
initialCtx := req.Context()
|
||||
tracer := tracing.TracerFromContext(initialCtx)
|
||||
|
||||
var currentSpan trace.Span
|
||||
operation := func() error {
|
||||
if tracer != nil {
|
||||
if currentSpan != nil {
|
||||
currentSpan.End()
|
||||
}
|
||||
// Because multiple tracing spans may need to be created,
|
||||
// the Retry middleware does not implement trace.Traceable,
|
||||
// and creates directly a new span for each retry operation.
|
||||
var tracingCtx context.Context
|
||||
tracingCtx, currentSpan = tracer.Start(initialCtx, typeName, trace.WithSpanKind(trace.SpanKindInternal))
|
||||
|
||||
currentSpan.SetAttributes(attribute.String("traefik.middleware.name", r.name))
|
||||
// Only add the attribute "http.resend_count" defined by semantic conventions starting from second attempt.
|
||||
if attempts > 1 {
|
||||
currentSpan.SetAttributes(semconv.HTTPResendCount(attempts - 1))
|
||||
}
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
}
|
||||
|
||||
shouldRetry := attempts < r.attempts
|
||||
retryResponseWriter := newResponseWriter(rw, shouldRetry)
|
||||
|
||||
// Disable retries when the backend already received request data
|
||||
trace := &httptrace.ClientTrace{
|
||||
clientTrace := &httptrace.ClientTrace{
|
||||
WroteHeaders: func() {
|
||||
retryResponseWriter.DisableRetries()
|
||||
},
|
||||
|
@ -92,7 +113,7 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
retryResponseWriter.DisableRetries()
|
||||
},
|
||||
}
|
||||
newCtx := httptrace.WithClientTrace(req.Context(), trace)
|
||||
newCtx := httptrace.WithClientTrace(req.Context(), clientTrace)
|
||||
|
||||
r.next.ServeHTTP(retryResponseWriter, req.Clone(newCtx))
|
||||
|
||||
|
@ -119,6 +140,10 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
if err != nil {
|
||||
logger.Debug().Err(err).Msg("Final retry attempt failed")
|
||||
}
|
||||
|
||||
if currentSpan != nil {
|
||||
currentSpan.End()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *retry) newBackOff() backoff.BackOff {
|
||||
|
|
|
@ -5,10 +5,9 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,8 +33,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, nam
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *stripPrefix) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return s.name, tracing.SpanKindNoneEnum
|
||||
func (s *stripPrefix) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return s.name, typeName, trace.SpanKindUnspecified
|
||||
}
|
||||
|
||||
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -6,11 +6,10 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/stripprefix"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -44,8 +43,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefixRegex
|
|||
return &stripPrefix, nil
|
||||
}
|
||||
|
||||
func (s *stripPrefixRegex) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return s.name, tracing.SpanKindNoneEnum
|
||||
func (s *stripPrefixRegex) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return s.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
||||
func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -5,57 +5,53 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"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"
|
||||
)
|
||||
|
||||
// NewEntryPoint creates a new middleware that the incoming request.
|
||||
func NewEntryPoint(ctx context.Context, t *tracing.Tracing, entryPointName string, next http.Handler) http.Handler {
|
||||
middlewares.GetLogger(ctx, "tracing", entryPointTypeName).Debug().Msg("Creating middleware")
|
||||
|
||||
return &entryPointMiddleware{
|
||||
entryPoint: entryPointName,
|
||||
Tracing: t,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
type entryPointMiddleware struct {
|
||||
*tracing.Tracing
|
||||
type entryPointTracing struct {
|
||||
tracer trace.Tracer
|
||||
entryPoint string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
func (e *entryPointMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
spanCtx, err := e.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
|
||||
if err != nil {
|
||||
middlewares.GetLogger(req.Context(), "tracing", entryPointTypeName).
|
||||
Debug().Err(err).Msg("Failed to extract the context")
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
span, req, finish := e.StartSpanf(req, ext.SpanKindRPCServerEnum, "EntryPoint", []string{e.entryPoint, req.Host}, " ", ext.RPCServerOption(spanCtx))
|
||||
defer finish()
|
||||
// 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")
|
||||
|
||||
ext.Component.Set(span, e.ServiceName)
|
||||
tracing.LogRequest(span, req)
|
||||
return &entryPointTracing{
|
||||
entryPoint: entryPointName,
|
||||
tracer: tracer,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
req = req.WithContext(tracing.WithTracing(req.Context(), e.Tracing))
|
||||
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())
|
||||
}
|
||||
|
||||
// WrapEntryPointHandler Wraps tracing to alice.Constructor.
|
||||
func WrapEntryPointHandler(ctx context.Context, tracer *tracing.Tracing, entryPointName string) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
return NewEntryPoint(ctx, tracer, entryPointName, next), nil
|
||||
}
|
||||
tracing.LogResponseCode(span, recorder.Status(), trace.SpanKindServer)
|
||||
}
|
||||
|
|
|
@ -6,81 +6,69 @@ import (
|
|||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestEntryPointMiddleware(t *testing.T) {
|
||||
type expected struct {
|
||||
Tags map[string]interface{}
|
||||
OperationName string
|
||||
name string
|
||||
attributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
entryPoint string
|
||||
spanNameLimit int
|
||||
tracing *trackingBackenMock
|
||||
expected expected
|
||||
desc string
|
||||
entryPoint string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "no truncation test",
|
||||
entryPoint: "test",
|
||||
spanNameLimit: 0,
|
||||
tracing: &trackingBackenMock{
|
||||
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||
},
|
||||
desc: "basic test",
|
||||
entryPoint: "test",
|
||||
expected: expected{
|
||||
Tags: map[string]interface{}{
|
||||
"span.kind": ext.SpanKindRPCServerEnum,
|
||||
"http.method": http.MethodGet,
|
||||
"component": "",
|
||||
"http.url": "http://www.test.com",
|
||||
"http.host": "www.test.com",
|
||||
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)),
|
||||
},
|
||||
OperationName: "EntryPoint test www.test.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "basic test",
|
||||
entryPoint: "test",
|
||||
spanNameLimit: 25,
|
||||
tracing: &trackingBackenMock{
|
||||
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||
},
|
||||
expected: expected{
|
||||
Tags: map[string]interface{}{
|
||||
"span.kind": ext.SpanKindRPCServerEnum,
|
||||
"http.method": http.MethodGet,
|
||||
"component": "",
|
||||
"http.url": "http://www.test.com",
|
||||
"http.host": "www.test.com",
|
||||
},
|
||||
OperationName: "EntryPoint te... ww... 0c15301b",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
newTracing, err := tracing.NewTracing("", test.spanNameLimit, test.tracing)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com", nil)
|
||||
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(http.ResponseWriter, *http.Request) {
|
||||
span := test.tracing.tracer.(*MockTracer).Span
|
||||
|
||||
tags := span.Tags
|
||||
assert.Equal(t, test.expected.Tags, tags)
|
||||
assert.Equal(t, test.expected.OperationName, span.OpName)
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
handler := NewEntryPoint(context.Background(), newTracing, test.entryPoint, next)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
)
|
||||
|
||||
const (
|
||||
forwarderTypeName = "TracingForwarder"
|
||||
)
|
||||
|
||||
type forwarderMiddleware struct {
|
||||
router string
|
||||
service string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// NewForwarder creates a new forwarder middleware that traces the outgoing request.
|
||||
func NewForwarder(ctx context.Context, router, service string, next http.Handler) http.Handler {
|
||||
middlewares.GetLogger(ctx, "tracing", forwarderTypeName).
|
||||
Debug().Str(logs.ServiceName, service).Msg("Added outgoing tracing middleware")
|
||||
|
||||
return &forwarderMiddleware{
|
||||
router: router,
|
||||
service: service,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *forwarderMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
tr, err := tracing.FromContext(req.Context())
|
||||
if err != nil {
|
||||
f.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
opParts := []string{f.service, f.router}
|
||||
span, req, finish := tr.StartSpanf(req, ext.SpanKindRPCClientEnum, "forward", opParts, "/")
|
||||
defer finish()
|
||||
|
||||
span.SetTag("traefik.service.name", f.service)
|
||||
span.SetTag("traefik.router.name", f.router)
|
||||
ext.HTTPMethod.Set(span, req.Method)
|
||||
ext.HTTPUrl.Set(span, req.URL.String())
|
||||
span.SetTag("http.host", req.Host)
|
||||
|
||||
tracing.InjectRequestHeaders(req)
|
||||
|
||||
recorder := newStatusCodeRecorder(rw, 200)
|
||||
|
||||
f.next.ServeHTTP(recorder, req)
|
||||
|
||||
tracing.LogResponseCode(span, recorder.Status())
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
)
|
||||
|
||||
func TestNewForwarder(t *testing.T) {
|
||||
type expected struct {
|
||||
Tags map[string]interface{}
|
||||
OperationName string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
spanNameLimit int
|
||||
tracing *trackingBackenMock
|
||||
service string
|
||||
router string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "Simple Forward Tracer without truncation and hashing",
|
||||
spanNameLimit: 101,
|
||||
tracing: &trackingBackenMock{
|
||||
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||
},
|
||||
service: "some-service.domain.tld",
|
||||
router: "some-service.domain.tld",
|
||||
expected: expected{
|
||||
Tags: map[string]interface{}{
|
||||
"http.host": "www.test.com",
|
||||
"http.method": "GET",
|
||||
"http.url": "http://www.test.com/toto",
|
||||
"traefik.service.name": "some-service.domain.tld",
|
||||
"traefik.router.name": "some-service.domain.tld",
|
||||
"span.kind": ext.SpanKindRPCClientEnum,
|
||||
},
|
||||
OperationName: "forward some-service.domain.tld/some-service.domain.tld",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Simple Forward Tracer with truncation and hashing",
|
||||
spanNameLimit: 101,
|
||||
tracing: &trackingBackenMock{
|
||||
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||
},
|
||||
service: "some-service-100.slug.namespace.environment.domain.tld",
|
||||
router: "some-service-100.slug.namespace.environment.domain.tld",
|
||||
expected: expected{
|
||||
Tags: map[string]interface{}{
|
||||
"http.host": "www.test.com",
|
||||
"http.method": "GET",
|
||||
"http.url": "http://www.test.com/toto",
|
||||
"traefik.service.name": "some-service-100.slug.namespace.environment.domain.tld",
|
||||
"traefik.router.name": "some-service-100.slug.namespace.environment.domain.tld",
|
||||
"span.kind": ext.SpanKindRPCClientEnum,
|
||||
},
|
||||
OperationName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Exactly 101 chars",
|
||||
spanNameLimit: 101,
|
||||
tracing: &trackingBackenMock{
|
||||
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||
},
|
||||
service: "some-service1.namespace.environment.domain.tld",
|
||||
router: "some-service1.namespace.environment.domain.tld",
|
||||
expected: expected{
|
||||
Tags: map[string]interface{}{
|
||||
"http.host": "www.test.com",
|
||||
"http.method": "GET",
|
||||
"http.url": "http://www.test.com/toto",
|
||||
"traefik.service.name": "some-service1.namespace.environment.domain.tld",
|
||||
"traefik.router.name": "some-service1.namespace.environment.domain.tld",
|
||||
"span.kind": ext.SpanKindRPCClientEnum,
|
||||
},
|
||||
OperationName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "More than 101 chars",
|
||||
spanNameLimit: 101,
|
||||
tracing: &trackingBackenMock{
|
||||
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
|
||||
},
|
||||
service: "some-service1.frontend.namespace.environment.domain.tld",
|
||||
router: "some-service1.backend.namespace.environment.domain.tld",
|
||||
expected: expected{
|
||||
Tags: map[string]interface{}{
|
||||
"http.host": "www.test.com",
|
||||
"http.method": "GET",
|
||||
"http.url": "http://www.test.com/toto",
|
||||
"traefik.service.name": "some-service1.frontend.namespace.environment.domain.tld",
|
||||
"traefik.router.name": "some-service1.backend.namespace.environment.domain.tld",
|
||||
"span.kind": ext.SpanKindRPCClientEnum,
|
||||
},
|
||||
OperationName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
newTracing, err := tracing.NewTracing("", test.spanNameLimit, test.tracing)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/toto", nil)
|
||||
req = req.WithContext(tracing.WithTracing(req.Context(), newTracing))
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
span := test.tracing.tracer.(*MockTracer).Span
|
||||
|
||||
tags := span.Tags
|
||||
assert.Equal(t, test.expected.Tags, tags)
|
||||
assert.LessOrEqual(t, len(test.expected.OperationName), test.spanNameLimit,
|
||||
"the len of the operation name %q [len: %d] doesn't respect limit %d",
|
||||
test.expected.OperationName, len(test.expected.OperationName), test.spanNameLimit)
|
||||
assert.Equal(t, test.expected.OperationName, span.OpName)
|
||||
})
|
||||
|
||||
handler := NewForwarder(context.Background(), test.router, test.service, next)
|
||||
handler.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
}
|
71
pkg/middlewares/tracing/middleware.go
Normal file
71
pkg/middlewares/tracing/middleware.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Traceable embeds tracing information.
|
||||
type Traceable interface {
|
||||
GetTracingInformation() (name string, typeName string, spanKind trace.SpanKind)
|
||||
}
|
||||
|
||||
// WrapMiddleware adds traceability to an alice.Constructor.
|
||||
func WrapMiddleware(ctx context.Context, constructor alice.Constructor) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
if constructor == nil {
|
||||
return nil, nil
|
||||
}
|
||||
handler, err := constructor(next)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if traceableHandler, ok := handler.(Traceable); ok {
|
||||
name, typeName, spanKind := traceableHandler.GetTracingInformation()
|
||||
log.Ctx(ctx).Debug().Str(logs.MiddlewareName, name).Msg("Adding tracing to middleware")
|
||||
return NewMiddleware(handler, name, typeName, spanKind), nil
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewMiddleware returns a http.Handler struct.
|
||||
func NewMiddleware(next http.Handler, name string, typeName string, spanKind trace.SpanKind) http.Handler {
|
||||
return &middlewareTracing{
|
||||
next: next,
|
||||
name: name,
|
||||
typeName: typeName,
|
||||
spanKind: spanKind,
|
||||
}
|
||||
}
|
||||
|
||||
// middlewareTracing is used to wrap http handler middleware.
|
||||
type middlewareTracing struct {
|
||||
next http.Handler
|
||||
name string
|
||||
typeName string
|
||||
spanKind trace.SpanKind
|
||||
}
|
||||
|
||||
func (w *middlewareTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
|
||||
tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(w.spanKind))
|
||||
defer span.End()
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
|
||||
span.SetAttributes(attribute.String("traefik.middleware.name", w.name))
|
||||
}
|
||||
|
||||
if w.next != nil {
|
||||
w.next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
|
@ -1,70 +1,62 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"io"
|
||||
"context"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/embedded"
|
||||
)
|
||||
|
||||
type MockTracer struct {
|
||||
Span *MockSpan
|
||||
type mockTracerProvider struct {
|
||||
embedded.TracerProvider
|
||||
}
|
||||
|
||||
// StartSpan belongs to the Tracer interface.
|
||||
func (n MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
|
||||
n.Span.OpName = operationName
|
||||
return n.Span
|
||||
var _ trace.TracerProvider = mockTracerProvider{}
|
||||
|
||||
func (p mockTracerProvider) Tracer(string, ...trace.TracerOption) trace.Tracer {
|
||||
return &mockTracer{}
|
||||
}
|
||||
|
||||
// Inject belongs to the Tracer interface.
|
||||
func (n MockTracer) Inject(sp opentracing.SpanContext, format, carrier interface{}) error {
|
||||
return nil
|
||||
type mockTracer struct {
|
||||
embedded.Tracer
|
||||
|
||||
spans []*mockSpan
|
||||
}
|
||||
|
||||
// Extract belongs to the Tracer interface.
|
||||
func (n MockTracer) Extract(format, carrier interface{}) (opentracing.SpanContext, error) {
|
||||
return nil, opentracing.ErrSpanContextNotFound
|
||||
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
|
||||
}
|
||||
|
||||
// MockSpanContext a span context mock.
|
||||
type MockSpanContext struct{}
|
||||
// mockSpan is an implementation of Span that preforms no operations.
|
||||
type mockSpan struct {
|
||||
embedded.Span
|
||||
|
||||
func (n MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
|
||||
|
||||
// MockSpan a span mock.
|
||||
type MockSpan struct {
|
||||
OpName string
|
||||
Tags map[string]interface{}
|
||||
name string
|
||||
attributes []attribute.KeyValue
|
||||
}
|
||||
|
||||
func (n MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} }
|
||||
func (n MockSpan) SetBaggageItem(key, val string) opentracing.Span {
|
||||
return MockSpan{Tags: make(map[string]interface{})}
|
||||
}
|
||||
func (n MockSpan) BaggageItem(key string) string { return "" }
|
||||
func (n MockSpan) SetTag(key string, value interface{}) opentracing.Span {
|
||||
n.Tags[key] = value
|
||||
return n
|
||||
}
|
||||
func (n MockSpan) LogFields(fields ...log.Field) {}
|
||||
func (n MockSpan) LogKV(keyVals ...interface{}) {}
|
||||
func (n MockSpan) Finish() {}
|
||||
func (n MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {}
|
||||
func (n MockSpan) SetOperationName(operationName string) opentracing.Span { return n }
|
||||
func (n MockSpan) Tracer() opentracing.Tracer { return MockTracer{} }
|
||||
func (n MockSpan) LogEvent(event string) {}
|
||||
func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {}
|
||||
func (n MockSpan) Log(data opentracing.LogData) {}
|
||||
func (n *MockSpan) Reset() {
|
||||
n.Tags = make(map[string]interface{})
|
||||
}
|
||||
var _ trace.Span = &mockSpan{}
|
||||
|
||||
type trackingBackenMock struct {
|
||||
tracer opentracing.Tracer
|
||||
func (*mockSpan) SpanContext() trace.SpanContext { return trace.SpanContext{} }
|
||||
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 (t *trackingBackenMock) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||
opentracing.SetGlobalTracer(t.tracer)
|
||||
return t.tracer, nil, nil
|
||||
}
|
||||
func (s *mockSpan) SetName(name string) { s.name = name }
|
||||
|
||||
func (*mockSpan) TracerProvider() trace.TracerProvider { return mockTracerProvider{} }
|
||||
|
|
60
pkg/middlewares/tracing/router.go
Normal file
60
pkg/middlewares/tracing/router.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
routerTypeName = "TracingRouter"
|
||||
)
|
||||
|
||||
type routerTracing struct {
|
||||
router string
|
||||
routerRule string
|
||||
service string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// WrapRouterHandler Wraps tracing to alice.Constructor.
|
||||
func WrapRouterHandler(ctx context.Context, router, routerRule, service string) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
return newRouter(ctx, router, routerRule, service, next), nil
|
||||
}
|
||||
}
|
||||
|
||||
// newRouter creates a new tracing middleware that traces the internal requests.
|
||||
func newRouter(ctx context.Context, router, routerRule, service string, next http.Handler) http.Handler {
|
||||
middlewares.GetLogger(ctx, "tracing", routerTypeName).
|
||||
Debug().Str(logs.RouterName, router).Str(logs.ServiceName, service).Msg("Added outgoing tracing middleware")
|
||||
|
||||
return &routerTracing{
|
||||
router: router,
|
||||
routerRule: routerRule,
|
||||
service: service,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *routerTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
|
||||
tracingCtx, span := tracer.Start(req.Context(), "Router", trace.WithSpanKind(trace.SpanKindInternal))
|
||||
defer span.End()
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
|
||||
span.SetAttributes(attribute.String("traefik.service.name", f.service))
|
||||
span.SetAttributes(attribute.String("traefik.router.name", f.router))
|
||||
span.SetAttributes(semconv.HTTPRoute(f.routerRule))
|
||||
}
|
||||
|
||||
f.next.ServeHTTP(rw, req)
|
||||
}
|
86
pkg/middlewares/tracing/router_test.go
Normal file
86
pkg/middlewares/tracing/router_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func TestNewRouter(t *testing.T) {
|
||||
type expected struct {
|
||||
attributes []attribute.KeyValue
|
||||
name string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
service string
|
||||
router string
|
||||
routerRule string
|
||||
expected []expected
|
||||
}{
|
||||
{
|
||||
desc: "base",
|
||||
service: "myService",
|
||||
router: "myRouter",
|
||||
routerRule: "Path(`/`)",
|
||||
expected: []expected{
|
||||
{
|
||||
name: "EntryPoint",
|
||||
attributes: []attribute.KeyValue{
|
||||
attribute.String("span.kind", "server"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Router",
|
||||
attributes: []attribute.KeyValue{
|
||||
attribute.String("span.kind", "internal"),
|
||||
attribute.String("http.request.method", "GET"),
|
||||
attribute.Int64("http.response.status_code", int64(404)),
|
||||
attribute.String("network.protocol.version", "1.1"),
|
||||
attribute.String("server.address", "www.test.com"),
|
||||
attribute.Int64("server.port", int64(80)),
|
||||
attribute.String("url.full", "http://www.test.com/traces?p=OpenTelemetry"),
|
||||
attribute.String("url.scheme", "http"),
|
||||
attribute.String("traefik.service.name", "myService"),
|
||||
attribute.String("traefik.router.name", "myRouter"),
|
||||
attribute.String("http.route", "Path(`/`)"),
|
||||
attribute.String("user_agent.original", "router-test"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/traces?p=OpenTelemetry", nil)
|
||||
req.RemoteAddr = "10.0.0.1:1234"
|
||||
req.Header.Set("User-Agent", "router-test")
|
||||
|
||||
tracer := &mockTracer{}
|
||||
tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer))
|
||||
defer entryPointSpan.End()
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
handler := newRouter(context.Background(), test.router, test.routerRule, test.service, next)
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
for i, span := range tracer.spans {
|
||||
assert.Equal(t, test.expected[i].name, span.name)
|
||||
assert.Equal(t, test.expected[i].attributes, span.attributes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
45
pkg/middlewares/tracing/service.go
Normal file
45
pkg/middlewares/tracing/service.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"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 (
|
||||
serviceTypeName = "TracingService"
|
||||
)
|
||||
|
||||
type serviceTracing struct {
|
||||
service string
|
||||
next http.Handler
|
||||
}
|
||||
|
||||
// NewService creates a new tracing middleware that traces the outgoing requests.
|
||||
func NewService(ctx context.Context, service string, next http.Handler) http.Handler {
|
||||
middlewares.GetLogger(ctx, "tracing", serviceTypeName).
|
||||
Debug().Str(logs.ServiceName, service).Msg("Added outgoing tracing middleware")
|
||||
|
||||
return &serviceTracing{
|
||||
service: service,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *serviceTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
|
||||
tracingCtx, span := tracer.Start(req.Context(), "Service", trace.WithSpanKind(trace.SpanKindInternal))
|
||||
defer span.End()
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
|
||||
span.SetAttributes(attribute.String("traefik.service.name", t.service))
|
||||
}
|
||||
|
||||
t.next.ServeHTTP(rw, req)
|
||||
}
|
80
pkg/middlewares/tracing/service_test.go
Normal file
80
pkg/middlewares/tracing/service_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func TestNewService(t *testing.T) {
|
||||
type expected struct {
|
||||
attributes []attribute.KeyValue
|
||||
name string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
service string
|
||||
expected []expected
|
||||
}{
|
||||
{
|
||||
desc: "base",
|
||||
service: "myService",
|
||||
expected: []expected{
|
||||
{
|
||||
name: "EntryPoint",
|
||||
attributes: []attribute.KeyValue{
|
||||
attribute.String("span.kind", "server"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Service",
|
||||
attributes: []attribute.KeyValue{
|
||||
attribute.String("span.kind", "internal"),
|
||||
attribute.String("http.request.method", "GET"),
|
||||
attribute.Int64("http.response.status_code", int64(404)),
|
||||
attribute.String("network.protocol.version", "1.1"),
|
||||
attribute.String("server.address", "www.test.com"),
|
||||
attribute.Int64("server.port", int64(80)),
|
||||
attribute.String("url.full", "http://www.test.com/traces?p=OpenTelemetry"),
|
||||
attribute.String("url.scheme", "http"),
|
||||
attribute.String("traefik.service.name", "myService"),
|
||||
attribute.String("user_agent.original", "service-test"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com/traces?p=OpenTelemetry", nil)
|
||||
req.RemoteAddr = "10.0.0.1:1234"
|
||||
req.Header.Set("User-Agent", "service-test")
|
||||
|
||||
tracer := &mockTracer{}
|
||||
tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer))
|
||||
defer entryPointSpan.End()
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
|
||||
handler := NewService(context.Background(), test.service, next)
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
for i, span := range tracer.spans {
|
||||
assert.Equal(t, test.expected[i].name, span.name)
|
||||
assert.Equal(t, test.expected[i].attributes, span.attributes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
)
|
||||
|
||||
// Traceable embeds tracing information.
|
||||
type Traceable interface {
|
||||
GetTracingInformation() (name string, spanKind ext.SpanKindEnum)
|
||||
}
|
||||
|
||||
// Wrap adds traceability to an alice.Constructor.
|
||||
func Wrap(ctx context.Context, constructor alice.Constructor) alice.Constructor {
|
||||
return func(next http.Handler) (http.Handler, error) {
|
||||
if constructor == nil {
|
||||
return nil, nil
|
||||
}
|
||||
handler, err := constructor(next)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if traceableHandler, ok := handler.(Traceable); ok {
|
||||
name, spanKind := traceableHandler.GetTracingInformation()
|
||||
log.Ctx(ctx).Debug().Str(logs.MiddlewareName, name).Msg("Adding tracing to middleware")
|
||||
return NewWrapper(handler, name, spanKind), nil
|
||||
}
|
||||
return handler, nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewWrapper returns a http.Handler struct.
|
||||
func NewWrapper(next http.Handler, name string, spanKind ext.SpanKindEnum) http.Handler {
|
||||
return &Wrapper{
|
||||
next: next,
|
||||
name: name,
|
||||
spanKind: spanKind,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper is used to wrap http handler middleware.
|
||||
type Wrapper struct {
|
||||
next http.Handler
|
||||
name string
|
||||
spanKind ext.SpanKindEnum
|
||||
}
|
||||
|
||||
func (w *Wrapper) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
_, err := tracing.FromContext(req.Context())
|
||||
if err != nil {
|
||||
w.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
||||
var finish func()
|
||||
_, req, finish = tracing.StartSpan(req, w.name, w.spanKind)
|
||||
defer finish()
|
||||
|
||||
if w.next != nil {
|
||||
w.next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
|
@ -30,12 +30,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/provider/kv/zk"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/rest"
|
||||
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/datadog"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/elastic"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/haystack"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/instana"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/jaeger"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/zipkin"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/opentelemetry"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -850,58 +845,19 @@ func TestDo_staticConfiguration(t *testing.T) {
|
|||
}
|
||||
|
||||
config.Tracing = &static.Tracing{
|
||||
ServiceName: "myServiceName",
|
||||
SpanNameLimit: 42,
|
||||
Jaeger: &jaeger.Config{
|
||||
SamplingServerURL: "foobar",
|
||||
SamplingType: "foobar",
|
||||
SamplingParam: 42,
|
||||
LocalAgentHostPort: "foobar",
|
||||
Gen128Bit: true,
|
||||
Propagation: "foobar",
|
||||
TraceContextHeaderName: "foobar",
|
||||
Collector: &jaeger.Collector{
|
||||
ServiceName: "myServiceName",
|
||||
Headers: map[string]string{
|
||||
"foobar": "foobar",
|
||||
},
|
||||
GlobalAttributes: map[string]string{
|
||||
"foobar": "foobar",
|
||||
},
|
||||
SampleRate: 42,
|
||||
OTLP: &opentelemetry.Config{
|
||||
HTTP: &opentelemetry.HTTP{
|
||||
Endpoint: "foobar",
|
||||
User: "foobar",
|
||||
Password: "foobar",
|
||||
TLS: nil,
|
||||
},
|
||||
DisableAttemptReconnecting: true,
|
||||
},
|
||||
Zipkin: &zipkin.Config{
|
||||
HTTPEndpoint: "foobar",
|
||||
SameSpan: true,
|
||||
ID128Bit: true,
|
||||
SampleRate: 42,
|
||||
},
|
||||
Datadog: &datadog.Config{
|
||||
LocalAgentHostPort: "foobar",
|
||||
LocalAgentSocket: "foobar",
|
||||
GlobalTags: map[string]string{"foobar": "foobar"},
|
||||
Debug: true,
|
||||
PrioritySampling: true,
|
||||
TraceIDHeaderName: "foobar",
|
||||
ParentIDHeaderName: "foobar",
|
||||
SamplingPriorityHeaderName: "foobar",
|
||||
BagagePrefixHeaderName: "foobar",
|
||||
},
|
||||
Instana: &instana.Config{
|
||||
LocalAgentHost: "foobar",
|
||||
LocalAgentPort: 4242,
|
||||
LogLevel: "foobar",
|
||||
},
|
||||
Haystack: &haystack.Config{
|
||||
LocalAgentHost: "foobar",
|
||||
LocalAgentPort: 42,
|
||||
GlobalTag: "foobar",
|
||||
TraceIDHeaderName: "foobar",
|
||||
ParentIDHeaderName: "foobar",
|
||||
SpanIDHeaderName: "foobar",
|
||||
BaggagePrefixHeaderName: "foobar",
|
||||
},
|
||||
Elastic: &elastic.Config{
|
||||
ServerURL: "foobar",
|
||||
SecretToken: "foobar",
|
||||
ServiceEnvironment: "foobar",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -344,57 +344,17 @@
|
|||
},
|
||||
"tracing": {
|
||||
"serviceName": "myServiceName",
|
||||
"spanNameLimit": 42,
|
||||
"jaeger": {
|
||||
"samplingServerURL": "xxxx",
|
||||
"samplingType": "foobar",
|
||||
"samplingParam": 42,
|
||||
"localAgentHostPort": "xxxx",
|
||||
"gen128Bit": true,
|
||||
"propagation": "foobar",
|
||||
"traceContextHeaderName": "foobar",
|
||||
"collector": {
|
||||
"endpoint": "xxxx",
|
||||
"user": "xxxx",
|
||||
"password": "xxxx"
|
||||
},
|
||||
"disableAttemptReconnecting": true
|
||||
"headers": {
|
||||
"foobar": "foobar"
|
||||
},
|
||||
"zipkin": {
|
||||
"httpEndpoint": "xxxx",
|
||||
"sameSpan": true,
|
||||
"id128Bit": true,
|
||||
"sampleRate": 42
|
||||
"globalAttributes": {
|
||||
"foobar": "foobar"
|
||||
},
|
||||
"datadog": {
|
||||
"localAgentHostPort": "xxxx",
|
||||
"localAgentSocket": "xxxx",
|
||||
"globalTags": {
|
||||
"foobar": "foobar"
|
||||
},
|
||||
"debug": true,
|
||||
"prioritySampling": true,
|
||||
"traceIDHeaderName": "foobar",
|
||||
"parentIDHeaderName": "foobar",
|
||||
"samplingPriorityHeaderName": "foobar",
|
||||
"bagagePrefixHeaderName": "foobar"
|
||||
},
|
||||
"instana": {
|
||||
"localAgentHost": "xxxx",
|
||||
"logLevel": "foobar"
|
||||
},
|
||||
"haystack": {
|
||||
"localAgentHost": "xxxx",
|
||||
"globalTag": "foobar",
|
||||
"traceIDHeaderName": "foobar",
|
||||
"parentIDHeaderName": "foobar",
|
||||
"spanIDHeaderName": "foobar",
|
||||
"baggagePrefixHeaderName": "foobar"
|
||||
},
|
||||
"elastic": {
|
||||
"serverURL": "xxxx",
|
||||
"secretToken": "xxxx",
|
||||
"serviceEnvironment": "foobar"
|
||||
"sampleRate": 42,
|
||||
"otlp": {
|
||||
"http": {
|
||||
"endpoint": "xxxx"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hostResolver": {
|
||||
|
@ -447,4 +407,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,20 +8,20 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
metricsmiddleware "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
mTracing "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// ChainBuilder Creates a middleware chain by entry point. It is used for middlewares that are created almost systematically and that need to be created before all others.
|
||||
type ChainBuilder struct {
|
||||
metricsRegistry metrics.Registry
|
||||
accessLoggerMiddleware *accesslog.Handler
|
||||
tracer *tracing.Tracing
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
// NewChainBuilder Creates a new ChainBuilder.
|
||||
func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer *tracing.Tracing) *ChainBuilder {
|
||||
func NewChainBuilder(metricsRegistry metrics.Registry, accessLoggerMiddleware *accesslog.Handler, tracer trace.Tracer) *ChainBuilder {
|
||||
return &ChainBuilder{
|
||||
metricsRegistry: metricsRegistry,
|
||||
accessLoggerMiddleware: accessLoggerMiddleware,
|
||||
|
@ -42,11 +42,12 @@ func (c *ChainBuilder) Build(ctx context.Context, entryPointName string) alice.C
|
|||
}
|
||||
|
||||
if c.tracer != nil {
|
||||
chain = chain.Append(mTracing.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
|
||||
chain = chain.Append(tracingMiddle.WrapEntryPointHandler(ctx, c.tracer, entryPointName))
|
||||
}
|
||||
|
||||
if c.metricsRegistry != nil && c.metricsRegistry.IsEpEnabled() {
|
||||
chain = chain.Append(metricsmiddleware.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName))
|
||||
metricsHandler := metricsMiddle.WrapEntryPointHandler(ctx, c.metricsRegistry, entryPointName)
|
||||
chain = chain.Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler))
|
||||
}
|
||||
|
||||
return chain
|
||||
|
@ -59,8 +60,4 @@ func (c *ChainBuilder) Close() {
|
|||
log.Error().Err(err).Msg("Could not close the access log file")
|
||||
}
|
||||
}
|
||||
|
||||
if c.tracer != nil {
|
||||
c.tracer.Close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -372,7 +372,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
|||
return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName)
|
||||
}
|
||||
|
||||
return tracing.Wrap(ctx, middleware), nil
|
||||
return tracing.WrapMiddleware(ctx, middleware), nil
|
||||
}
|
||||
|
||||
func inSlice(element string, stack []string) bool {
|
||||
|
|
|
@ -5,12 +5,13 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/plugins"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
const typeName = "Plugin"
|
||||
|
||||
// PluginsBuilder the plugin's builder interface.
|
||||
type PluginsBuilder interface {
|
||||
Build(pName string, config map[string]interface{}, middlewareName string) (plugins.Constructor, error)
|
||||
|
@ -54,6 +55,6 @@ func (s *traceablePlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
s.h.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (s *traceablePlugin) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return s.name, tracing.SpanKindNoneEnum
|
||||
func (s *traceablePlugin) GetTracingInformation() (string, string, trace.SpanKind) {
|
||||
return s.name, typeName, trace.SpanKindInternal
|
||||
}
|
||||
|
|
|
@ -198,21 +198,20 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
|||
|
||||
mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares)
|
||||
|
||||
tHandler := func(next http.Handler) (http.Handler, error) {
|
||||
return tracing.NewForwarder(ctx, routerName, router.Service, next), nil
|
||||
}
|
||||
|
||||
chain := alice.New()
|
||||
|
||||
chain = chain.Append(tracing.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service)))
|
||||
|
||||
if m.metricsRegistry != nil && m.metricsRegistry.IsRouterEnabled() {
|
||||
chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service)))
|
||||
metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.metricsRegistry, routerName, provider.GetQualifiedName(ctx, router.Service))
|
||||
chain = chain.Append(tracing.WrapMiddleware(ctx, metricsHandler))
|
||||
}
|
||||
|
||||
if router.DefaultRule {
|
||||
chain = chain.Append(denyrouterrecursion.WrapHandler(routerName))
|
||||
}
|
||||
|
||||
return chain.Extend(*mHandler).Append(tHandler).Then(sHandler)
|
||||
return chain.Extend(*mHandler).Then(sHandler)
|
||||
}
|
||||
|
||||
// BuildDefaultHTTPRouter creates a default HTTP router.
|
||||
|
|
|
@ -3,6 +3,7 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
@ -27,12 +28,12 @@ type Server struct {
|
|||
stopChan chan bool
|
||||
|
||||
routinesPool *safe.Pool
|
||||
|
||||
tracerCloser io.Closer
|
||||
}
|
||||
|
||||
// NewServer returns an initialized Server.
|
||||
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher,
|
||||
chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler,
|
||||
) *Server {
|
||||
func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsUDP UDPEntryPoints, watcher *ConfigurationWatcher, chainBuilder *middleware.ChainBuilder, accessLoggerMiddleware *accesslog.Handler, tracerCloser io.Closer) *Server {
|
||||
srv := &Server{
|
||||
watcher: watcher,
|
||||
tcpEntryPoints: entryPoints,
|
||||
|
@ -42,6 +43,7 @@ func NewServer(routinesPool *safe.Pool, entryPoints TCPEntryPoints, entryPointsU
|
|||
stopChan: make(chan bool, 1),
|
||||
routinesPool: routinesPool,
|
||||
udpEntryPoints: entryPointsUDP,
|
||||
tracerCloser: tracerCloser,
|
||||
}
|
||||
|
||||
srv.configureSignals()
|
||||
|
@ -105,6 +107,12 @@ func (s *Server) Close() {
|
|||
|
||||
s.chainBuilder.Close()
|
||||
|
||||
if s.tracerCloser != nil {
|
||||
if err := s.tracerCloser.Close(); err != nil {
|
||||
log.Error().Err(err).Msg("Could not close the tracer")
|
||||
}
|
||||
}
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,13 @@ const StatusClientClosedRequest = 499
|
|||
const StatusClientClosedRequestText = "Client Closed Request"
|
||||
|
||||
func buildSingleHostProxy(target *url.URL, passHostHeader bool, flushInterval time.Duration, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) http.Handler {
|
||||
// Wrapping the roundTripper with the Tracing roundTripper,
|
||||
// to handle the reverseProxy client span creation.
|
||||
tracingRoundTripper := newTracingRoundTripper(roundTripper)
|
||||
|
||||
return &httputil.ReverseProxy{
|
||||
Director: directorBuilder(target, passHostHeader),
|
||||
Transport: roundTripper,
|
||||
Transport: tracingRoundTripper,
|
||||
FlushInterval: flushInterval,
|
||||
BufferPool: bufferPool,
|
||||
ErrorHandler: errorHandler,
|
||||
|
@ -94,23 +98,7 @@ func isWebSocketUpgrade(req *http.Request) bool {
|
|||
}
|
||||
|
||||
func errorHandler(w http.ResponseWriter, req *http.Request, err error) {
|
||||
statusCode := http.StatusInternalServerError
|
||||
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
statusCode = http.StatusBadGateway
|
||||
case errors.Is(err, context.Canceled):
|
||||
statusCode = StatusClientClosedRequest
|
||||
default:
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
if netErr.Timeout() {
|
||||
statusCode = http.StatusGatewayTimeout
|
||||
} else {
|
||||
statusCode = http.StatusBadGateway
|
||||
}
|
||||
}
|
||||
}
|
||||
statusCode := computeStatusCode(err)
|
||||
|
||||
logger := log.Ctx(req.Context())
|
||||
logger.Debug().Err(err).Msgf("%d %s", statusCode, statusText(statusCode))
|
||||
|
@ -121,6 +109,26 @@ func errorHandler(w http.ResponseWriter, req *http.Request, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func computeStatusCode(err error) int {
|
||||
switch {
|
||||
case errors.Is(err, io.EOF):
|
||||
return http.StatusBadGateway
|
||||
case errors.Is(err, context.Canceled):
|
||||
return StatusClientClosedRequest
|
||||
default:
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
if netErr.Timeout() {
|
||||
return http.StatusGatewayTimeout
|
||||
}
|
||||
|
||||
return http.StatusBadGateway
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
func statusText(statusCode int) string {
|
||||
if statusCode == StatusClientClosedRequest {
|
||||
return StatusClientClosedRequestText
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
|
@ -22,6 +23,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/safe"
|
||||
"github.com/traefik/traefik/v3/pkg/server/cookie"
|
||||
"github.com/traefik/traefik/v3/pkg/server/provider"
|
||||
|
@ -305,9 +307,18 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
|
||||
|
||||
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
|
||||
proxy = metricsMiddle.NewServiceMiddleware(ctx, proxy, m.metricsRegistry, serviceName)
|
||||
metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.metricsRegistry, serviceName)
|
||||
|
||||
proxy, err = alice.New().
|
||||
Append(tracingMiddle.WrapMiddleware(ctx, metricsHandler)).
|
||||
Then(proxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error wrapping metrics handler: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
proxy = tracingMiddle.NewService(ctx, serviceName, proxy)
|
||||
|
||||
lb.Add(proxyName, proxy, nil)
|
||||
|
||||
// servers are considered UP by default.
|
||||
|
|
42
pkg/server/service/tracing_roundtripper.go
Normal file
42
pkg/server/service/tracing_roundtripper.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type wrapper struct {
|
||||
rt http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
var span trace.Span
|
||||
if tracer := tracing.TracerFromContext(req.Context()); tracer != nil {
|
||||
var tracingCtx context.Context
|
||||
tracingCtx, span = tracer.Start(req.Context(), "ReverseProxy", trace.WithSpanKind(trace.SpanKindClient))
|
||||
defer span.End()
|
||||
|
||||
req = req.WithContext(tracingCtx)
|
||||
|
||||
tracing.LogClientRequest(span, req)
|
||||
tracing.InjectContextIntoCarrier(req)
|
||||
}
|
||||
|
||||
response, err := t.rt.RoundTrip(req)
|
||||
if err != nil {
|
||||
statusCode := computeStatusCode(err)
|
||||
tracing.LogResponseCode(span, statusCode, trace.SpanKindClient)
|
||||
return response, err
|
||||
}
|
||||
|
||||
tracing.LogResponseCode(span, response.StatusCode, trace.SpanKindClient)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func newTracingRoundTripper(rt http.RoundTripper) http.RoundTripper {
|
||||
return &wrapper{rt: rt}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package datadog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
ddtracer "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer"
|
||||
datadog "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
|
||||
)
|
||||
|
||||
// Name sets the name of this tracer.
|
||||
const Name = "datadog"
|
||||
|
||||
// Config provides configuration settings for a datadog tracer.
|
||||
type Config struct {
|
||||
LocalAgentHostPort string `description:"Sets the Datadog Agent host:port." json:"localAgentHostPort,omitempty" toml:"localAgentHostPort,omitempty" yaml:"localAgentHostPort,omitempty"`
|
||||
LocalAgentSocket string `description:"Sets the socket for the Datadog Agent." json:"localAgentSocket,omitempty" toml:"localAgentSocket,omitempty" yaml:"localAgentSocket,omitempty"`
|
||||
GlobalTags map[string]string `description:"Sets a list of key:value tags on all spans." json:"globalTags,omitempty" toml:"globalTags,omitempty" yaml:"globalTags,omitempty" export:"true"`
|
||||
Debug bool `description:"Enables Datadog debug." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"`
|
||||
PrioritySampling bool `description:"Enables priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled." json:"prioritySampling,omitempty" toml:"prioritySampling,omitempty" yaml:"prioritySampling,omitempty" export:"true"`
|
||||
TraceIDHeaderName string `description:"Sets the header name used to store the trace ID." json:"traceIDHeaderName,omitempty" toml:"traceIDHeaderName,omitempty" yaml:"traceIDHeaderName,omitempty" export:"true"`
|
||||
ParentIDHeaderName string `description:"Sets the header name used to store the parent ID." json:"parentIDHeaderName,omitempty" toml:"parentIDHeaderName,omitempty" yaml:"parentIDHeaderName,omitempty" export:"true"`
|
||||
SamplingPriorityHeaderName string `description:"Sets the header name used to store the sampling priority." json:"samplingPriorityHeaderName,omitempty" toml:"samplingPriorityHeaderName,omitempty" yaml:"samplingPriorityHeaderName,omitempty" export:"true"`
|
||||
BagagePrefixHeaderName string `description:"Sets the header name prefix used to store baggage items in a map." json:"bagagePrefixHeaderName,omitempty" toml:"bagagePrefixHeaderName,omitempty" yaml:"bagagePrefixHeaderName,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
host, ok := os.LookupEnv("DD_AGENT_HOST")
|
||||
if !ok {
|
||||
host = "localhost"
|
||||
}
|
||||
|
||||
port, ok := os.LookupEnv("DD_TRACE_AGENT_PORT")
|
||||
if !ok {
|
||||
port = "8126"
|
||||
}
|
||||
|
||||
c.LocalAgentHostPort = net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
logger := log.With().Str(logs.TracingProviderName, Name).Logger()
|
||||
|
||||
opts := []datadog.StartOption{
|
||||
datadog.WithService(serviceName),
|
||||
datadog.WithDebugMode(c.Debug),
|
||||
datadog.WithPropagator(datadog.NewPropagator(&datadog.PropagatorConfig{
|
||||
TraceHeader: c.TraceIDHeaderName,
|
||||
ParentHeader: c.ParentIDHeaderName,
|
||||
PriorityHeader: c.SamplingPriorityHeaderName,
|
||||
BaggagePrefix: c.BagagePrefixHeaderName,
|
||||
})),
|
||||
datadog.WithLogger(logs.NewDatadogLogger(logger)),
|
||||
}
|
||||
|
||||
if c.LocalAgentSocket != "" {
|
||||
opts = append(opts, datadog.WithUDS(c.LocalAgentSocket))
|
||||
} else {
|
||||
opts = append(opts, datadog.WithAgentAddr(c.LocalAgentHostPort))
|
||||
}
|
||||
|
||||
for k, v := range c.GlobalTags {
|
||||
opts = append(opts, datadog.WithGlobalTag(k, v))
|
||||
}
|
||||
|
||||
if c.PrioritySampling {
|
||||
opts = append(opts, datadog.WithPrioritySampling())
|
||||
}
|
||||
|
||||
tracer := ddtracer.New(opts...)
|
||||
|
||||
// Without this, child spans are getting the NOOP tracer
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
|
||||
logger.Debug().Msg("Datadog tracer configured")
|
||||
|
||||
return tracer, nil, nil
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package elastic
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/version"
|
||||
"go.elastic.co/apm"
|
||||
"go.elastic.co/apm/module/apmot"
|
||||
"go.elastic.co/apm/transport"
|
||||
)
|
||||
|
||||
// Name sets the name of this tracer.
|
||||
const Name = "elastic"
|
||||
|
||||
func init() {
|
||||
// The APM lib uses the init() function to create a default tracer.
|
||||
// So this default tracer must be disabled.
|
||||
// https://github.com/elastic/apm-agent-go/blob/8dd383d0d21776faad8841fe110f35633d199a03/tracer.go#L61-L65
|
||||
apm.DefaultTracer.Close()
|
||||
}
|
||||
|
||||
// Config provides configuration settings for a elastic.co tracer.
|
||||
type Config struct {
|
||||
ServerURL string `description:"Sets the URL of the Elastic APM server." json:"serverURL,omitempty" toml:"serverURL,omitempty" yaml:"serverURL,omitempty"`
|
||||
SecretToken string `description:"Sets the token used to connect to Elastic APM Server." json:"secretToken,omitempty" toml:"secretToken,omitempty" yaml:"secretToken,omitempty" loggable:"false"`
|
||||
ServiceEnvironment string `description:"Sets the name of the environment Traefik is deployed in, e.g. 'production' or 'staging'." json:"serviceEnvironment,omitempty" toml:"serviceEnvironment,omitempty" yaml:"serviceEnvironment,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
// Create default transport.
|
||||
tr, err := transport.NewHTTPTransport()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if c.ServerURL != "" {
|
||||
serverURL, err := url.Parse(c.ServerURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tr.SetServerURL(serverURL)
|
||||
}
|
||||
|
||||
if c.SecretToken != "" {
|
||||
tr.SetSecretToken(c.SecretToken)
|
||||
}
|
||||
|
||||
tracer, err := apm.NewTracerOptions(apm.TracerOptions{
|
||||
ServiceName: serviceName,
|
||||
ServiceVersion: version.Version,
|
||||
ServiceEnvironment: c.ServiceEnvironment,
|
||||
Transport: tr,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
logger := log.With().Str(logs.TracingProviderName, Name).Logger()
|
||||
tracer.SetLogger(logs.NewElasticLogger(logger))
|
||||
|
||||
otTracer := apmot.New(apmot.WithTracer(tracer))
|
||||
|
||||
// Without this, child spans are getting the NOOP tracer
|
||||
opentracing.SetGlobalTracer(otTracer)
|
||||
|
||||
log.Debug().Msg("Elastic tracer configured")
|
||||
|
||||
return otTracer, nil, nil
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
package haystack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ExpediaDotCom/haystack-client-go"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
)
|
||||
|
||||
// Name sets the name of this tracer.
|
||||
const Name = "haystack"
|
||||
|
||||
// Config provides configuration settings for a haystack tracer.
|
||||
type Config struct {
|
||||
LocalAgentHost string `description:"Sets the Haystack Agent host." json:"localAgentHost,omitempty" toml:"localAgentHost,omitempty" yaml:"localAgentHost,omitempty"`
|
||||
LocalAgentPort int `description:"Sets the Haystack Agent port." json:"localAgentPort,omitempty" toml:"localAgentPort,omitempty" yaml:"localAgentPort,omitempty"`
|
||||
GlobalTag string `description:"Sets a key:value tag on all spans." json:"globalTag,omitempty" toml:"globalTag,omitempty" yaml:"globalTag,omitempty" export:"true"`
|
||||
TraceIDHeaderName string `description:"Sets the header name used to store the trace ID." json:"traceIDHeaderName,omitempty" toml:"traceIDHeaderName,omitempty" yaml:"traceIDHeaderName,omitempty" export:"true"`
|
||||
ParentIDHeaderName string `description:"Sets the header name used to store the parent ID." json:"parentIDHeaderName,omitempty" toml:"parentIDHeaderName,omitempty" yaml:"parentIDHeaderName,omitempty" export:"true"`
|
||||
SpanIDHeaderName string `description:"Sets the header name used to store the span ID." json:"spanIDHeaderName,omitempty" toml:"spanIDHeaderName,omitempty" yaml:"spanIDHeaderName,omitempty" export:"true"`
|
||||
BaggagePrefixHeaderName string `description:"Sets the header name prefix used to store baggage items in a map." json:"baggagePrefixHeaderName,omitempty" toml:"baggagePrefixHeaderName,omitempty" yaml:"baggagePrefixHeaderName,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.LocalAgentHost = "127.0.0.1"
|
||||
c.LocalAgentPort = 35000
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
tag := strings.SplitN(c.GlobalTag, ":", 2)
|
||||
|
||||
value := ""
|
||||
if len(tag) == 2 {
|
||||
value = tag[1]
|
||||
}
|
||||
|
||||
host := "localhost"
|
||||
port := 35000
|
||||
if len(c.LocalAgentHost) > 0 {
|
||||
host = c.LocalAgentHost
|
||||
}
|
||||
if c.LocalAgentPort > 0 {
|
||||
port = c.LocalAgentPort
|
||||
}
|
||||
|
||||
logger := log.With().Str(logs.TracingProviderName, Name).Logger()
|
||||
|
||||
tracer, closer := haystack.NewTracer(serviceName, haystack.NewAgentDispatcher(host, port, 3*time.Second, 1000),
|
||||
haystack.TracerOptionsFactory.Tag(tag[0], value),
|
||||
haystack.TracerOptionsFactory.Propagator(opentracing.HTTPHeaders,
|
||||
haystack.NewTextMapPropagator(haystack.PropagatorOpts{
|
||||
TraceIDKEYName: c.TraceIDHeaderName,
|
||||
ParentSpanIDKEYName: c.ParentIDHeaderName,
|
||||
SpanIDKEYName: c.SpanIDHeaderName,
|
||||
BaggagePrefixKEYName: c.BaggagePrefixHeaderName,
|
||||
}, haystack.DefaultCodex{})),
|
||||
haystack.TracerOptionsFactory.Logger(logs.NewHaystackLogger(logger)),
|
||||
)
|
||||
|
||||
// Without this, child spans are getting the NOOP tracer
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
|
||||
log.Debug().Msg("haystack tracer configured")
|
||||
|
||||
return tracer, closer, nil
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package instana
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
instana "github.com/instana/go-sensor"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
)
|
||||
|
||||
// Name sets the name of this tracer.
|
||||
const Name = "instana"
|
||||
|
||||
// Config provides configuration settings for an instana tracer.
|
||||
type Config struct {
|
||||
LocalAgentHost string `description:"Sets the Instana Agent host." json:"localAgentHost,omitempty" toml:"localAgentHost,omitempty" yaml:"localAgentHost,omitempty"`
|
||||
LocalAgentPort int `description:"Sets the Instana Agent port." json:"localAgentPort,omitempty" toml:"localAgentPort,omitempty" yaml:"localAgentPort,omitempty"`
|
||||
LogLevel string `description:"Sets the log level for the Instana tracer. ('error','warn','info','debug')" json:"logLevel,omitempty" toml:"logLevel,omitempty" yaml:"logLevel,omitempty" export:"true"`
|
||||
EnableAutoProfile bool `description:"Enables automatic profiling for the Traefik process." json:"enableAutoProfile,omitempty" toml:"enableAutoProfile,omitempty" yaml:"enableAutoProfile,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.LocalAgentPort = 42699
|
||||
c.LogLevel = "info"
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
// set default logLevel
|
||||
logLevel := instana.Info
|
||||
|
||||
// check/set logLevel overrides
|
||||
switch c.LogLevel {
|
||||
case "error":
|
||||
logLevel = instana.Error
|
||||
case "warn":
|
||||
logLevel = instana.Warn
|
||||
case "debug":
|
||||
logLevel = instana.Debug
|
||||
}
|
||||
|
||||
logger := log.With().Str(logs.TracingProviderName, Name).Logger()
|
||||
instana.SetLogger(logs.NewInstanaLogger(logger))
|
||||
|
||||
tracer := instana.NewTracerWithOptions(&instana.Options{
|
||||
Service: serviceName,
|
||||
LogLevel: logLevel,
|
||||
AgentPort: c.LocalAgentPort,
|
||||
AgentHost: c.LocalAgentHost,
|
||||
EnableAutoProfile: c.EnableAutoProfile,
|
||||
})
|
||||
|
||||
// Without this, child spans are getting the NOOP tracer
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
|
||||
logger.Debug().Msg("Instana tracer configured")
|
||||
|
||||
return tracer, nil, nil
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package jaeger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/logs"
|
||||
jaegercli "github.com/uber/jaeger-client-go"
|
||||
jaegercfg "github.com/uber/jaeger-client-go/config"
|
||||
"github.com/uber/jaeger-client-go/zipkin"
|
||||
jaegermet "github.com/uber/jaeger-lib/metrics"
|
||||
)
|
||||
|
||||
// Name sets the name of this tracer.
|
||||
const Name = "jaeger"
|
||||
|
||||
// Config provides configuration settings for a jaeger tracer.
|
||||
type Config struct {
|
||||
SamplingServerURL string `description:"Sets the sampling server URL." json:"samplingServerURL,omitempty" toml:"samplingServerURL,omitempty" yaml:"samplingServerURL,omitempty"`
|
||||
SamplingType string `description:"Sets the sampling type." json:"samplingType,omitempty" toml:"samplingType,omitempty" yaml:"samplingType,omitempty" export:"true"`
|
||||
SamplingParam float64 `description:"Sets the sampling parameter." json:"samplingParam,omitempty" toml:"samplingParam,omitempty" yaml:"samplingParam,omitempty" export:"true"`
|
||||
LocalAgentHostPort string `description:"Sets the Jaeger Agent host:port." json:"localAgentHostPort,omitempty" toml:"localAgentHostPort,omitempty" yaml:"localAgentHostPort,omitempty"`
|
||||
Gen128Bit bool `description:"Generates 128 bits span IDs." json:"gen128Bit,omitempty" toml:"gen128Bit,omitempty" yaml:"gen128Bit,omitempty" export:"true"`
|
||||
Propagation string `description:"Sets the propagation format (jaeger/b3)." json:"propagation,omitempty" toml:"propagation,omitempty" yaml:"propagation,omitempty" export:"true"`
|
||||
TraceContextHeaderName string `description:"Sets the header name used to store the trace ID." json:"traceContextHeaderName,omitempty" toml:"traceContextHeaderName,omitempty" yaml:"traceContextHeaderName,omitempty" export:"true"`
|
||||
Collector *Collector `description:"Defines the collector information." json:"collector,omitempty" toml:"collector,omitempty" yaml:"collector,omitempty" export:"true"`
|
||||
DisableAttemptReconnecting bool `description:"Disables the periodic re-resolution of the agent's hostname and reconnection if there was a change." json:"disableAttemptReconnecting,omitempty" toml:"disableAttemptReconnecting,omitempty" yaml:"disableAttemptReconnecting,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.SamplingServerURL = "http://localhost:5778/sampling"
|
||||
c.SamplingType = "const"
|
||||
c.SamplingParam = 1.0
|
||||
c.LocalAgentHostPort = "127.0.0.1:6831"
|
||||
c.Propagation = "jaeger"
|
||||
c.Gen128Bit = false
|
||||
c.TraceContextHeaderName = jaegercli.TraceContextHeaderName
|
||||
c.DisableAttemptReconnecting = true
|
||||
}
|
||||
|
||||
// Collector provides configuration settings for jaeger collector.
|
||||
type Collector struct {
|
||||
Endpoint string `description:"Instructs reporter to send spans to jaeger-collector at this URL." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
User string `description:"User for basic http authentication when sending spans to jaeger-collector." json:"user,omitempty" toml:"user,omitempty" yaml:"user,omitempty" loggable:"false"`
|
||||
Password string `description:"Password for basic http authentication when sending spans to jaeger-collector." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Collector) SetDefaults() {
|
||||
c.Endpoint = ""
|
||||
c.User = ""
|
||||
c.Password = ""
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||
reporter := &jaegercfg.ReporterConfig{
|
||||
LogSpans: true,
|
||||
LocalAgentHostPort: c.LocalAgentHostPort,
|
||||
DisableAttemptReconnecting: c.DisableAttemptReconnecting,
|
||||
}
|
||||
|
||||
if c.Collector != nil {
|
||||
reporter.CollectorEndpoint = c.Collector.Endpoint
|
||||
reporter.User = c.Collector.User
|
||||
reporter.Password = c.Collector.Password
|
||||
}
|
||||
|
||||
jcfg := &jaegercfg.Configuration{
|
||||
Sampler: &jaegercfg.SamplerConfig{
|
||||
SamplingServerURL: c.SamplingServerURL,
|
||||
Type: c.SamplingType,
|
||||
Param: c.SamplingParam,
|
||||
},
|
||||
Reporter: reporter,
|
||||
Headers: &jaegercli.HeadersConfig{
|
||||
TraceContextHeaderName: c.TraceContextHeaderName,
|
||||
},
|
||||
}
|
||||
|
||||
// Overrides existing tracer's Configuration with environment variables.
|
||||
_, err := jcfg.FromEnv()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
jMetricsFactory := jaegermet.NullFactory
|
||||
|
||||
logger := log.With().Str(logs.TracingProviderName, Name).Logger()
|
||||
|
||||
opts := []jaegercfg.Option{
|
||||
jaegercfg.Logger(logs.NewJaegerLogger(logger)),
|
||||
jaegercfg.Metrics(jMetricsFactory),
|
||||
jaegercfg.Gen128Bit(c.Gen128Bit),
|
||||
}
|
||||
|
||||
switch c.Propagation {
|
||||
case "b3":
|
||||
p := zipkin.NewZipkinB3HTTPHeaderPropagator()
|
||||
opts = append(opts,
|
||||
jaegercfg.Injector(opentracing.HTTPHeaders, p),
|
||||
jaegercfg.Extractor(opentracing.HTTPHeaders, p),
|
||||
)
|
||||
case "jaeger", "":
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unknown propagation format: %s", c.Propagation)
|
||||
}
|
||||
|
||||
// Initialize tracer with a logger and a metrics factory
|
||||
closer, err := jcfg.InitGlobalTracer(
|
||||
componentName,
|
||||
opts...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("Could not initialize jaeger tracer")
|
||||
return nil, nil, err
|
||||
}
|
||||
logger.Debug().Msg("Jaeger tracer configured")
|
||||
|
||||
return opentracing.GlobalTracer(), closer, nil
|
||||
}
|
|
@ -5,20 +5,21 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
"github.com/traefik/traefik/v3/pkg/version"
|
||||
"go.opentelemetry.io/otel"
|
||||
oteltracer "go.opentelemetry.io/otel/bridge/opentracing"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
@ -26,85 +27,108 @@ import (
|
|||
|
||||
// Config provides configuration settings for the open-telemetry tracer.
|
||||
type Config struct {
|
||||
// NOTE: as no gRPC option is implemented yet, the type is empty and is used as a boolean for upward compatibility purposes.
|
||||
GRPC *struct{} `description:"gRPC specific configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
GRPC *GRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true"`
|
||||
HTTP *HTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
Address string `description:"Sets the address (host:port) of the collector endpoint." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
||||
Path string `description:"Sets the URL path of the collector endpoint." json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"`
|
||||
Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
||||
Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"`
|
||||
TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
// GRPC provides configuration settings for the gRPC open-telemetry tracer.
|
||||
type GRPC struct {
|
||||
Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
|
||||
Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
||||
TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.Address = "localhost:4318"
|
||||
func (c *GRPC) SetDefaults() {
|
||||
c.Endpoint = "localhost:4317"
|
||||
}
|
||||
|
||||
// HTTP provides configuration settings for the HTTP open-telemetry tracer.
|
||||
type HTTP struct {
|
||||
Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/v1/traces) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *HTTP) SetDefaults() {
|
||||
c.Endpoint = "localhost:4318"
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||
func (c *Config) Setup(serviceName string, sampleRate float64, globalAttributes map[string]string, headers map[string]string) (trace.Tracer, io.Closer, error) {
|
||||
var (
|
||||
err error
|
||||
exporter *otlptrace.Exporter
|
||||
)
|
||||
if c.GRPC != nil {
|
||||
exporter, err = c.setupGRPCExporter()
|
||||
exporter, err = c.setupGRPCExporter(headers)
|
||||
} else {
|
||||
exporter, err = c.setupHTTPExporter()
|
||||
exporter, err = c.setupHTTPExporter(headers)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("setting up exporter: %w", err)
|
||||
}
|
||||
|
||||
bt := oteltracer.NewBridgeTracer()
|
||||
bt.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
attr := []attribute.KeyValue{
|
||||
semconv.ServiceNameKey.String(serviceName),
|
||||
semconv.ServiceVersionKey.String(version.Version),
|
||||
}
|
||||
|
||||
bt.SetOpenTelemetryTracer(otel.Tracer(componentName, trace.WithInstrumentationVersion(version.Version)))
|
||||
opentracing.SetGlobalTracer(bt)
|
||||
for k, v := range globalAttributes {
|
||||
attr = append(attr, attribute.String(k, v))
|
||||
}
|
||||
|
||||
res, err := resource.New(context.Background(),
|
||||
resource.WithAttributes(semconv.ServiceNameKey.String("traefik")),
|
||||
resource.WithAttributes(semconv.ServiceVersionKey.String(version.Version)),
|
||||
resource.WithAttributes(attr...),
|
||||
resource.WithFromEnv(),
|
||||
resource.WithTelemetrySDK(),
|
||||
resource.WithOSType(),
|
||||
resource.WithProcessCommandArgs(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("building resource: %w", err)
|
||||
}
|
||||
|
||||
// Register the trace exporter with a TracerProvider, using a batch
|
||||
// span processor to aggregate spans before export.
|
||||
bsp := sdktrace.NewBatchSpanProcessor(exporter)
|
||||
tracerProvider := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(sampleRate)),
|
||||
sdktrace.WithResource(res),
|
||||
sdktrace.WithBatcher(exporter),
|
||||
sdktrace.WithSpanProcessor(bsp),
|
||||
)
|
||||
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
otel.SetTextMapPropagator(propagation.TraceContext{})
|
||||
|
||||
log.Debug().Msg("OpenTelemetry tracer configured")
|
||||
|
||||
return bt, tpCloser{provider: tracerProvider}, err
|
||||
return tracerProvider.Tracer("github.com/traefik/traefik"), &tpCloser{provider: tracerProvider}, err
|
||||
}
|
||||
|
||||
func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) {
|
||||
host, port, err := net.SplitHostPort(c.Address)
|
||||
func (c *Config) setupHTTPExporter(headers map[string]string) (*otlptrace.Exporter, error) {
|
||||
endpoint, err := url.Parse(c.HTTP.Endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err)
|
||||
return nil, fmt.Errorf("invalid collector endpoint %q: %w", c.HTTP.Endpoint, err)
|
||||
}
|
||||
|
||||
opts := []otlptracehttp.Option{
|
||||
otlptracehttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)),
|
||||
otlptracehttp.WithHeaders(c.Headers),
|
||||
otlptracehttp.WithEndpoint(endpoint.Host),
|
||||
otlptracehttp.WithHeaders(headers),
|
||||
otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
|
||||
}
|
||||
|
||||
if c.Insecure {
|
||||
if endpoint.Scheme == "http" {
|
||||
opts = append(opts, otlptracehttp.WithInsecure())
|
||||
}
|
||||
|
||||
if c.Path != "" {
|
||||
opts = append(opts, otlptracehttp.WithURLPath(c.Path))
|
||||
if endpoint.Path != "" {
|
||||
opts = append(opts, otlptracehttp.WithURLPath(endpoint.Path))
|
||||
}
|
||||
|
||||
if c.TLS != nil {
|
||||
tlsConfig, err := c.TLS.CreateTLSConfig(context.Background())
|
||||
if c.HTTP.TLS != nil {
|
||||
tlsConfig, err := c.HTTP.TLS.CreateTLSConfig(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating TLS client config: %w", err)
|
||||
}
|
||||
|
@ -115,24 +139,24 @@ func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) {
|
|||
return otlptrace.New(context.Background(), otlptracehttp.NewClient(opts...))
|
||||
}
|
||||
|
||||
func (c *Config) setupGRPCExporter() (*otlptrace.Exporter, error) {
|
||||
host, port, err := net.SplitHostPort(c.Address)
|
||||
func (c *Config) setupGRPCExporter(headers map[string]string) (*otlptrace.Exporter, error) {
|
||||
host, port, err := net.SplitHostPort(c.GRPC.Endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err)
|
||||
return nil, fmt.Errorf("invalid collector address %q: %w", c.GRPC.Endpoint, err)
|
||||
}
|
||||
|
||||
opts := []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)),
|
||||
otlptracegrpc.WithHeaders(c.Headers),
|
||||
otlptracegrpc.WithHeaders(headers),
|
||||
otlptracegrpc.WithCompressor(gzip.Name),
|
||||
}
|
||||
|
||||
if c.Insecure {
|
||||
if c.GRPC.Insecure {
|
||||
opts = append(opts, otlptracegrpc.WithInsecure())
|
||||
}
|
||||
|
||||
if c.TLS != nil {
|
||||
tlsConfig, err := c.TLS.CreateTLSConfig(context.Background())
|
||||
if c.GRPC.TLS != nil {
|
||||
tlsConfig, err := c.GRPC.TLS.CreateTLSConfig(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating TLS client config: %w", err)
|
||||
}
|
||||
|
@ -148,6 +172,13 @@ type tpCloser struct {
|
|||
provider *sdktrace.TracerProvider
|
||||
}
|
||||
|
||||
func (t tpCloser) Close() error {
|
||||
return t.provider.Shutdown(context.Background())
|
||||
func (t *tpCloser) Close() error {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
|
||||
defer cancel()
|
||||
|
||||
return t.provider.Shutdown(ctx)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package opentelemetry
|
||||
package opentelemetry_test
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
|
@ -7,14 +7,16 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/alice"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
mtracing "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
tracingMiddle "github.com/traefik/traefik/v3/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/opentelemetry"
|
||||
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
|
||||
)
|
||||
|
||||
|
@ -68,14 +70,25 @@ func TestTracing(t *testing.T) {
|
|||
}))
|
||||
t.Cleanup(collector.Close)
|
||||
|
||||
newTracing, err := tracing.NewTracing("", 0, &Config{
|
||||
Insecure: true,
|
||||
Address: strings.TrimPrefix(collector.URL, "http://"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(newTracing.Close)
|
||||
tracingConfig := &static.Tracing{
|
||||
ServiceName: "traefik",
|
||||
SampleRate: 1.0,
|
||||
OTLP: &opentelemetry.Config{
|
||||
HTTP: &opentelemetry.HTTP{
|
||||
Endpoint: collector.URL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
epHandler := mtracing.NewEntryPoint(context.Background(), newTracing, "test", http.NotFoundHandler())
|
||||
newTracing, closer, err := tracing.NewTracing(tracingConfig)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = closer.Close()
|
||||
})
|
||||
|
||||
chain := alice.New(tracingMiddle.WrapEntryPointHandler(context.Background(), newTracing, "test"))
|
||||
epHandler, err := chain.Then(http.NotFoundHandler())
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// TraceNameHashLength defines the number of characters to use from the head of the generated hash.
|
||||
const TraceNameHashLength = 8
|
||||
|
||||
// OperationNameMaxLengthNumber defines the number of static characters in a Span Trace name:
|
||||
// 8 chars for hash + 2 chars for '_'.
|
||||
const OperationNameMaxLengthNumber = 10
|
||||
|
||||
func generateOperationName(prefix string, parts []string, sep string, spanLimit int) string {
|
||||
name := prefix + " " + strings.Join(parts, sep)
|
||||
|
||||
maxLength := OperationNameMaxLengthNumber + len(prefix) + 1
|
||||
|
||||
if spanLimit > 0 && len(name) > spanLimit {
|
||||
if spanLimit < maxLength {
|
||||
log.Warn().Msgf("SpanNameLimit cannot be lesser than %d: falling back on %d", maxLength, maxLength+3)
|
||||
spanLimit = maxLength + 3
|
||||
}
|
||||
|
||||
limit := (spanLimit - maxLength) / 2
|
||||
|
||||
var fragments []string
|
||||
for _, value := range parts {
|
||||
fragments = append(fragments, truncateString(value, limit))
|
||||
}
|
||||
fragments = append(fragments, computeHash(name))
|
||||
|
||||
name = prefix + " " + strings.Join(fragments, sep)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// truncateString reduces the length of the 'str' argument to 'num' - 3 and adds a '...' suffix to the tail.
|
||||
func truncateString(str string, num int) string {
|
||||
text := str
|
||||
if len(str) > num {
|
||||
if num > 3 {
|
||||
num -= 3
|
||||
}
|
||||
text = str[0:num] + "..."
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// computeHash returns the first TraceNameHashLength character of the sha256 hash for 'name' argument.
|
||||
func computeHash(name string) string {
|
||||
data := []byte(name)
|
||||
hash := sha256.New()
|
||||
if _, err := hash.Write(data); err != nil {
|
||||
// Impossible case
|
||||
log.Error().Str("OperationName", name).Err(err).Msgf("Failed to create Span name hash for %s", name)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum(nil))[:TraceNameHashLength]
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package tracing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_generateOperationName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
prefix string
|
||||
parts []string
|
||||
sep string
|
||||
spanLimit int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "empty",
|
||||
expected: " ",
|
||||
},
|
||||
{
|
||||
desc: "with prefix, without parts",
|
||||
prefix: "foo",
|
||||
parts: []string{},
|
||||
sep: "-",
|
||||
spanLimit: 0,
|
||||
expected: "foo ",
|
||||
},
|
||||
{
|
||||
desc: "with prefix, without parts, too small span limit",
|
||||
prefix: "foo",
|
||||
parts: []string{},
|
||||
sep: "-",
|
||||
spanLimit: 1,
|
||||
expected: "foo 6c2d2c76",
|
||||
},
|
||||
{
|
||||
desc: "with prefix, with parts",
|
||||
prefix: "foo",
|
||||
parts: []string{"fii", "fuu", "fee", "faa"},
|
||||
sep: "-",
|
||||
spanLimit: 0,
|
||||
expected: "foo fii-fuu-fee-faa",
|
||||
},
|
||||
{
|
||||
desc: "with prefix, with parts, with span limit",
|
||||
prefix: "foo",
|
||||
parts: []string{"fff", "ooo", "ooo", "bbb", "aaa", "rrr"},
|
||||
sep: "-",
|
||||
spanLimit: 20,
|
||||
expected: "foo fff-ooo-ooo-bbb-aaa-rrr-1a8e8ac1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
opName := generateOperationName(test.prefix, test.parts, test.sep, test.spanLimit)
|
||||
assert.Equal(t, test.expected, opName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeHash(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
text string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "hashing",
|
||||
text: "some very long piece of text",
|
||||
expected: "0c6e798b",
|
||||
},
|
||||
{
|
||||
desc: "short text less than limit 10",
|
||||
text: "short",
|
||||
expected: "f9b0078b",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := computeHash(test.text)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
text string
|
||||
limit int
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
desc: "short text less than limit 10",
|
||||
text: "short",
|
||||
limit: 10,
|
||||
expected: "short",
|
||||
},
|
||||
{
|
||||
desc: "basic truncate with limit 10",
|
||||
text: "some very long piece of text",
|
||||
limit: 10,
|
||||
expected: "some ve...",
|
||||
},
|
||||
{
|
||||
desc: "truncate long FQDN to 39 chars",
|
||||
text: "some-service-100.slug.namespace.environment.domain.tld",
|
||||
limit: 39,
|
||||
expected: "some-service-100.slug.namespace.envi...",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
actual := truncateString(test.text, test.limit)
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
assert.LessOrEqual(t, len(actual), test.limit)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2,182 +2,213 @@ package tracing
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing/opentelemetry"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
const (
|
||||
// SpanKindNoneEnum Span kind enum none.
|
||||
SpanKindNoneEnum ext.SpanKindEnum = "none"
|
||||
tracingKey contextKey = iota
|
||||
)
|
||||
|
||||
// WithTracing Adds Tracing into the context.
|
||||
func WithTracing(ctx context.Context, tracing *Tracing) context.Context {
|
||||
return context.WithValue(ctx, tracingKey, tracing)
|
||||
}
|
||||
|
||||
// FromContext Gets Tracing from context.
|
||||
func FromContext(ctx context.Context) (*Tracing, error) {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
|
||||
tracer, ok := ctx.Value(tracingKey).(*Tracing)
|
||||
if !ok {
|
||||
return nil, errors.New("unable to find tracing in the context")
|
||||
}
|
||||
return tracer, nil
|
||||
}
|
||||
|
||||
// Backend is an abstraction for tracking backend (Jaeger, Zipkin, ...).
|
||||
// Backend is an abstraction for tracking backend (OpenTelemetry, ...).
|
||||
type Backend interface {
|
||||
Setup(componentName string) (opentracing.Tracer, io.Closer, error)
|
||||
}
|
||||
|
||||
// Tracing middleware.
|
||||
type Tracing struct {
|
||||
ServiceName string `description:"Sets the name for this service" export:"true"`
|
||||
SpanNameLimit int `description:"Sets the maximum character limit for span names (default 0 = no limit)" export:"true"`
|
||||
|
||||
tracer opentracing.Tracer
|
||||
closer io.Closer
|
||||
Setup(serviceName string, sampleRate float64, globalAttributes map[string]string, headers map[string]string) (trace.Tracer, io.Closer, error)
|
||||
}
|
||||
|
||||
// NewTracing Creates a Tracing.
|
||||
func NewTracing(serviceName string, spanNameLimit int, tracingBackend Backend) (*Tracing, error) {
|
||||
tracing := &Tracing{
|
||||
ServiceName: serviceName,
|
||||
SpanNameLimit: spanNameLimit,
|
||||
func NewTracing(conf *static.Tracing) (trace.Tracer, io.Closer, error) {
|
||||
var backend Backend
|
||||
|
||||
if conf.OTLP != nil {
|
||||
backend = conf.OTLP
|
||||
}
|
||||
|
||||
var err error
|
||||
tracing.tracer, tracing.closer, err = tracingBackend.Setup(serviceName)
|
||||
if backend == nil {
|
||||
log.Debug().Msg("Could not initialize tracing, using OpenTelemetry by default")
|
||||
defaultBackend := &opentelemetry.Config{}
|
||||
backend = defaultBackend
|
||||
}
|
||||
|
||||
return backend.Setup(conf.ServiceName, conf.SampleRate, conf.GlobalAttributes, conf.Headers)
|
||||
}
|
||||
|
||||
// TracerFromContext extracts the trace.Tracer from the given context.
|
||||
func TracerFromContext(ctx context.Context) trace.Tracer {
|
||||
span := trace.SpanFromContext(ctx)
|
||||
if span != nil && span.TracerProvider() != nil {
|
||||
return span.TracerProvider().Tracer("github.com/traefik/traefik")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractCarrierIntoContext reads cross-cutting concerns from the carrier into a Context.
|
||||
func ExtractCarrierIntoContext(ctx context.Context, headers http.Header) context.Context {
|
||||
propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
|
||||
return propagator.Extract(ctx, propagation.HeaderCarrier(headers))
|
||||
}
|
||||
|
||||
// InjectContextIntoCarrier sets cross-cutting concerns from the request context into the request headers.
|
||||
func InjectContextIntoCarrier(req *http.Request) {
|
||||
propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
|
||||
propagator.Inject(req.Context(), propagation.HeaderCarrier(req.Header))
|
||||
}
|
||||
|
||||
// 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...))
|
||||
}
|
||||
}
|
||||
|
||||
// LogClientRequest used to add span attributes from the request as a Client.
|
||||
// TODO: the semconv does not implement Semantic Convention v1.23.0.
|
||||
func LogClientRequest(span trace.Span, r *http.Request) {
|
||||
if r == nil || span == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Common attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
|
||||
span.SetAttributes(semconv.HTTPRequestMethodKey.String(r.Method))
|
||||
span.SetAttributes(semconv.NetworkProtocolVersion(proto(r.Proto)))
|
||||
|
||||
// Client attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#http-client
|
||||
span.SetAttributes(semconv.URLFull(r.URL.String()))
|
||||
span.SetAttributes(semconv.URLScheme(r.URL.Scheme))
|
||||
span.SetAttributes(semconv.UserAgentOriginal(r.UserAgent()))
|
||||
|
||||
host, port, err := net.SplitHostPort(r.URL.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tracing, nil
|
||||
}
|
||||
|
||||
// StartSpan delegates to opentracing.Tracer.
|
||||
func (t *Tracing) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
|
||||
return t.tracer.StartSpan(operationName, opts...)
|
||||
}
|
||||
|
||||
// StartSpanf delegates to StartSpan.
|
||||
func (t *Tracing) StartSpanf(r *http.Request, spanKind ext.SpanKindEnum, opPrefix string, opParts []string, separator string, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) {
|
||||
operationName := generateOperationName(opPrefix, opParts, separator, t.SpanNameLimit)
|
||||
|
||||
return StartSpan(r, operationName, spanKind, opts...)
|
||||
}
|
||||
|
||||
// Inject delegates to opentracing.Tracer.
|
||||
func (t *Tracing) Inject(sm opentracing.SpanContext, format, carrier interface{}) error {
|
||||
return t.tracer.Inject(sm, format, carrier)
|
||||
}
|
||||
|
||||
// Extract delegates to opentracing.Tracer.
|
||||
func (t *Tracing) Extract(format, carrier interface{}) (opentracing.SpanContext, error) {
|
||||
return t.tracer.Extract(format, carrier)
|
||||
}
|
||||
|
||||
// IsEnabled determines if tracing was successfully activated.
|
||||
func (t *Tracing) IsEnabled() bool {
|
||||
return t != nil && t.tracer != nil
|
||||
}
|
||||
|
||||
// Close tracer.
|
||||
func (t *Tracing) Close() {
|
||||
if t.closer != nil {
|
||||
err := t.closer.Close()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Send()
|
||||
span.SetAttributes(attribute.String("network.peer.address", host))
|
||||
span.SetAttributes(semconv.ServerAddress(r.URL.Host))
|
||||
switch r.URL.Scheme {
|
||||
case "http":
|
||||
span.SetAttributes(attribute.String("network.peer.port", "80"))
|
||||
span.SetAttributes(semconv.ServerPort(80))
|
||||
case "https":
|
||||
span.SetAttributes(attribute.String("network.peer.port", "443"))
|
||||
span.SetAttributes(semconv.ServerPort(443))
|
||||
}
|
||||
} else {
|
||||
span.SetAttributes(attribute.String("network.peer.address", host))
|
||||
span.SetAttributes(attribute.String("network.peer.port", port))
|
||||
intPort, _ := strconv.Atoi(port)
|
||||
span.SetAttributes(semconv.ServerAddress(host))
|
||||
span.SetAttributes(semconv.ServerPort(intPort))
|
||||
}
|
||||
}
|
||||
|
||||
// LogRequest used to create span tags from the request.
|
||||
func LogRequest(span opentracing.Span, r *http.Request) {
|
||||
if span != nil && r != nil {
|
||||
ext.HTTPMethod.Set(span, r.Method)
|
||||
ext.HTTPUrl.Set(span, r.URL.String())
|
||||
span.SetTag("http.host", r.Host)
|
||||
// LogServerRequest used to add span attributes from the request as a Server.
|
||||
// TODO: the semconv does not implement Semantic Convention v1.23.0.
|
||||
func LogServerRequest(span trace.Span, r *http.Request) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Common attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes
|
||||
span.SetAttributes(semconv.HTTPRequestMethodKey.String(r.Method))
|
||||
span.SetAttributes(semconv.NetworkProtocolVersion(proto(r.Proto)))
|
||||
|
||||
// Server attributes https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#http-server-semantic-conventions
|
||||
span.SetAttributes(semconv.HTTPRequestBodySize(int(r.ContentLength)))
|
||||
span.SetAttributes(semconv.URLPath(r.URL.Path))
|
||||
span.SetAttributes(semconv.URLQuery(r.URL.RawQuery))
|
||||
span.SetAttributes(semconv.URLScheme(r.Header.Get("X-Forwarded-Proto")))
|
||||
span.SetAttributes(semconv.UserAgentOriginal(r.UserAgent()))
|
||||
span.SetAttributes(semconv.ServerAddress(r.Host))
|
||||
|
||||
host, port, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
span.SetAttributes(semconv.ClientAddress(r.RemoteAddr))
|
||||
span.SetAttributes(attribute.String("network.peer.address", r.RemoteAddr))
|
||||
} else {
|
||||
span.SetAttributes(attribute.String("network.peer.address", host))
|
||||
span.SetAttributes(attribute.String("network.peer.port", port))
|
||||
span.SetAttributes(semconv.ClientAddress(host))
|
||||
intPort, _ := strconv.Atoi(port)
|
||||
span.SetAttributes(semconv.ClientPort(intPort))
|
||||
}
|
||||
|
||||
span.SetAttributes(semconv.ClientSocketAddress(r.Header.Get("X-Forwarded-For")))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// LogResponseCode used to log response code in span.
|
||||
func LogResponseCode(span opentracing.Span, code int) {
|
||||
func LogResponseCode(span trace.Span, code int, spanKind trace.SpanKind) {
|
||||
if span != nil {
|
||||
ext.HTTPStatusCode.Set(span, uint16(code))
|
||||
if code >= http.StatusInternalServerError {
|
||||
ext.Error.Set(span, true)
|
||||
var status codes.Code
|
||||
var desc string
|
||||
switch spanKind {
|
||||
case trace.SpanKindServer:
|
||||
status, desc = ServerStatus(code)
|
||||
case trace.SpanKindClient:
|
||||
status, desc = ClientStatus(code)
|
||||
default:
|
||||
status, desc = DefaultStatus(code)
|
||||
}
|
||||
span.SetStatus(status, desc)
|
||||
if code > 0 {
|
||||
span.SetAttributes(semconv.HTTPResponseStatusCode(code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetSpan used to retrieve span from request context.
|
||||
func GetSpan(r *http.Request) opentracing.Span {
|
||||
return opentracing.SpanFromContext(r.Context())
|
||||
}
|
||||
|
||||
// InjectRequestHeaders used to inject OpenTracing headers into the request.
|
||||
func InjectRequestHeaders(r *http.Request) {
|
||||
if span := GetSpan(r); span != nil {
|
||||
err := opentracing.GlobalTracer().Inject(
|
||||
span.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(r.Header))
|
||||
if err != nil {
|
||||
log.Ctx(r.Context()).Error().Err(err).Send()
|
||||
}
|
||||
// ServerStatus returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func ServerStatus(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
// LogEventf logs an event to the span in the request context.
|
||||
func LogEventf(r *http.Request, format string, args ...interface{}) {
|
||||
if span := GetSpan(r); span != nil {
|
||||
span.LogKV("event", fmt.Sprintf(format, args...))
|
||||
if code >= 500 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
// StartSpan starts a new span from the one in the request context.
|
||||
func StartSpan(r *http.Request, operationName string, spanKind ext.SpanKindEnum, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) {
|
||||
span, ctx := opentracing.StartSpanFromContext(r.Context(), operationName, opts...)
|
||||
|
||||
switch spanKind {
|
||||
case ext.SpanKindRPCClientEnum:
|
||||
ext.SpanKindRPCClient.Set(span)
|
||||
case ext.SpanKindRPCServerEnum:
|
||||
ext.SpanKindRPCServer.Set(span)
|
||||
case ext.SpanKindProducerEnum:
|
||||
ext.SpanKindProducer.Set(span)
|
||||
case ext.SpanKindConsumerEnum:
|
||||
ext.SpanKindConsumer.Set(span)
|
||||
default:
|
||||
// noop
|
||||
// ClientStatus returns a span status code and message for an HTTP status code
|
||||
// value returned by a server. Status codes in the 400-499 range are not
|
||||
// returned as errors.
|
||||
func ClientStatus(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
return span, r, func() { span.Finish() }
|
||||
}
|
||||
|
||||
// SetError flags the span associated with this request as in error.
|
||||
func SetError(r *http.Request) {
|
||||
if span := GetSpan(r); span != nil {
|
||||
ext.Error.Set(span, true)
|
||||
if code >= 400 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
||||
// SetErrorWithEvent flags the span associated with this request as in error and log an event.
|
||||
func SetErrorWithEvent(r *http.Request, format string, args ...interface{}) {
|
||||
SetError(r)
|
||||
LogEventf(r, format, args...)
|
||||
// DefaultStatus returns a span status code and message for an HTTP status code
|
||||
// value generated internally.
|
||||
func DefaultStatus(code int) (codes.Code, string) {
|
||||
if code < 100 || code >= 600 {
|
||||
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||
}
|
||||
if code >= 500 {
|
||||
return codes.Error, ""
|
||||
}
|
||||
return codes.Unset, ""
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
package zipkin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing"
|
||||
"github.com/openzipkin/zipkin-go"
|
||||
"github.com/openzipkin/zipkin-go/reporter/http"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Name sets the name of this tracer.
|
||||
const Name = "zipkin"
|
||||
|
||||
// Config provides configuration settings for a zipkin tracer.
|
||||
type Config struct {
|
||||
HTTPEndpoint string `description:"Sets the HTTP Endpoint to report traces to." json:"httpEndpoint,omitempty" toml:"httpEndpoint,omitempty" yaml:"httpEndpoint,omitempty"`
|
||||
SameSpan bool `description:"Uses SameSpan RPC style traces." json:"sameSpan,omitempty" toml:"sameSpan,omitempty" yaml:"sameSpan,omitempty" export:"true"`
|
||||
ID128Bit bool `description:"Uses 128 bits root span IDs." json:"id128Bit,omitempty" toml:"id128Bit,omitempty" yaml:"id128Bit,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"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.HTTPEndpoint = "http://localhost:9411/api/v2/spans"
|
||||
c.SameSpan = false
|
||||
c.ID128Bit = true
|
||||
c.SampleRate = 1.0
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
|
||||
// create our local endpoint
|
||||
endpoint, err := zipkin.NewEndpoint(serviceName, "0.0.0.0:0")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// create our sampler
|
||||
sampler, err := zipkin.NewBoundarySampler(c.SampleRate, time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// create the span reporter
|
||||
reporter := http.NewReporter(c.HTTPEndpoint)
|
||||
|
||||
// create the native Zipkin tracer
|
||||
nativeTracer, err := zipkin.NewTracer(
|
||||
reporter,
|
||||
zipkin.WithLocalEndpoint(endpoint),
|
||||
zipkin.WithSharedSpans(c.SameSpan),
|
||||
zipkin.WithTraceID128Bit(c.ID128Bit),
|
||||
zipkin.WithSampler(sampler),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// wrap the Zipkin native tracer with the OpenTracing Bridge
|
||||
tracer := zipkinot.Wrap(nativeTracer)
|
||||
|
||||
// Without this, child spans are getting the NOOP tracer
|
||||
opentracing.SetGlobalTracer(tracer)
|
||||
|
||||
log.Debug().Msg("Zipkin tracer configured")
|
||||
|
||||
return tracer, reporter, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue