1
0
Fork 0

Migrate to opentelemetry

This commit is contained in:
Jesse Haka 2024-01-08 10:10:06 +02:00 committed by GitHub
parent 45bb00be04
commit 4ddef9830b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
89 changed files with 2113 additions and 3898 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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