Migrate to opentelemetry
This commit is contained in:
parent
45bb00be04
commit
4ddef9830b
89 changed files with 2113 additions and 3898 deletions
|
@ -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