Move code to pkg

This commit is contained in:
Ludovic Fernandez 2019-03-15 09:42:03 +01:00 committed by Traefiker Bot
parent bd4c822670
commit f1b085fa36
465 changed files with 656 additions and 680 deletions

25
pkg/tracing/carrier.go Normal file
View file

@ -0,0 +1,25 @@
package tracing
import "net/http"
// HTTPHeadersCarrier custom implementation to fix duplicated headers
// It has been fixed in https://github.com/opentracing/opentracing-go/pull/191
type HTTPHeadersCarrier http.Header
// Set conforms to the TextMapWriter interface.
func (c HTTPHeadersCarrier) Set(key, val string) {
h := http.Header(c)
h.Set(key, val)
}
// ForeachKey conforms to the TextMapReader interface.
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
for k, vals := range c {
for _, v := range vals {
if err := handler(k, v); err != nil {
return err
}
}
}
return nil
}

View file

@ -0,0 +1,50 @@
package datadog
import (
"io"
"strings"
"github.com/containous/traefik/pkg/log"
"github.com/opentracing/opentracing-go"
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:"Set datadog-agent's host:port that the reporter will used. Defaults to localhost:8126" export:"false"`
GlobalTag string `description:"Key:Value tag to be set on all the spans." export:"true"`
Debug bool `description:"Enable DataDog debug." export:"true"`
PrioritySampling bool `description:"Enable priority sampling. When using distributed tracing, this option must be enabled in order to get all the parts of a distributed trace sampled."`
}
// 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]
}
opts := []datadog.StartOption{
datadog.WithAgentAddr(c.LocalAgentHostPort),
datadog.WithServiceName(serviceName),
datadog.WithGlobalTag(tag[0], value),
datadog.WithDebugMode(c.Debug),
}
if c.PrioritySampling {
opts = append(opts, datadog.WithPrioritySampling())
}
tracer := ddtracer.New(opts...)
// Without this, child spans are getting the NOOP tracer
opentracing.SetGlobalTracer(tracer)
log.WithoutContext().Debug("DataDog tracer configured")
return tracer, nil, nil
}

View file

@ -0,0 +1,49 @@
package instana
import (
"io"
"github.com/containous/traefik/pkg/log"
instana "github.com/instana/go-sensor"
"github.com/opentracing/opentracing-go"
)
// Name sets the name of this tracer
const Name = "instana"
// Config provides configuration settings for a instana tracer
type Config struct {
LocalAgentHost string `description:"Set instana-agent's host that the reporter will used." export:"false"`
LocalAgentPort int `description:"Set instana-agent's port that the reporter will used." export:"false"`
LogLevel string `description:"Set instana-agent's log level. ('error','warn','info','debug')" export:"false"`
}
// 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
}
tracer := instana.NewTracerWithOptions(&instana.Options{
Service: serviceName,
LogLevel: logLevel,
AgentPort: c.LocalAgentPort,
AgentHost: c.LocalAgentHost,
})
// Without this, child spans are getting the NOOP tracer
opentracing.SetGlobalTracer(tracer)
log.WithoutContext().Debug("Instana tracer configured")
return tracer, nil, nil
}

View file

@ -0,0 +1,78 @@
package jaeger
import (
"fmt"
"io"
"github.com/containous/traefik/pkg/log"
"github.com/opentracing/opentracing-go"
jaeger "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:"set the sampling server url." export:"false"`
SamplingType string `description:"set the sampling type." export:"true"`
SamplingParam float64 `description:"set the sampling parameter." export:"true"`
LocalAgentHostPort string `description:"set jaeger-agent's host:port that the reporter will used." export:"false"`
Gen128Bit bool `description:"generate 128 bit span IDs." export:"true"`
Propagation string `description:"which propgation format to use (jaeger/b3)." export:"true"`
TraceContextHeaderName string `description:"set the header to use for the trace-id." export:"true"`
}
// Setup sets up the tracer
func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
jcfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
SamplingServerURL: c.SamplingServerURL,
Type: c.SamplingType,
Param: c.SamplingParam,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: c.LocalAgentHostPort,
},
Headers: &jaeger.HeadersConfig{
TraceContextHeaderName: c.TraceContextHeaderName,
},
}
jMetricsFactory := jaegermet.NullFactory
opts := []jaegercfg.Option{
jaegercfg.Logger(newJaegerLogger()),
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 {
log.WithoutContext().Warnf("Could not initialize jaeger tracer: %s", err.Error())
return nil, nil, err
}
log.WithoutContext().Debug("Jaeger tracer configured")
return opentracing.GlobalTracer(), closer, nil
}

View file

@ -0,0 +1,26 @@
package jaeger
import (
"github.com/containous/traefik/pkg/log"
"github.com/sirupsen/logrus"
)
// jaegerLogger is an implementation of the Logger interface that delegates to traefik log
type jaegerLogger struct {
logger logrus.FieldLogger
}
func newJaegerLogger() *jaegerLogger {
return &jaegerLogger{
logger: log.WithoutContext().WithField(log.TracingProviderName, "jaeger"),
}
}
func (l *jaegerLogger) Error(msg string) {
l.logger.Errorf("Tracing jaeger error: %s", msg)
}
// Infof logs a message at debug priority
func (l *jaegerLogger) Infof(msg string, args ...interface{}) {
l.logger.Debugf(msg, args...)
}

View file

@ -0,0 +1,65 @@
package tracing
import (
"crypto/sha256"
"fmt"
"strings"
"github.com/containous/traefik/pkg/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.WithoutContext().Warnf("SpanNameLimit cannot be lesser than %d: falling back on %d, maxLength, maxLength+3", maxLength)
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.WithoutContext().WithField("OperationName", name).Errorf("Failed to create Span name hash for %s: %v", name, err)
}
return fmt.Sprintf("%x", hash.Sum(nil))[:TraceNameHashLength]
}

View file

@ -0,0 +1,135 @@
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 pice of text",
expected: "0258ea1c",
},
{
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 pice 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.True(t, len(actual) <= test.limit)
})
}
}

188
pkg/tracing/tracing.go Normal file
View file

@ -0,0 +1,188 @@
package tracing
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"github.com/containous/traefik/pkg/log"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
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
}
// TrackingBackend is an abstraction for tracking backend (Jaeger, Zipkin, ...).
type TrackingBackend interface {
Setup(componentName string) (opentracing.Tracer, io.Closer, error)
}
// Tracing middleware.
type Tracing struct {
ServiceName string `description:"Set the name for this service" export:"true"`
SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)" export:"true"`
tracer opentracing.Tracer
closer io.Closer
}
// NewTracing Creates a Tracing.
func NewTracing(serviceName string, spanNameLimit int, trackingBackend TrackingBackend) (*Tracing, error) {
tracing := &Tracing{
ServiceName: serviceName,
SpanNameLimit: spanNameLimit,
}
var err error
tracing.tracer, tracing.closer, err = trackingBackend.Setup(serviceName)
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 interface{}, carrier interface{}) error {
return t.tracer.Inject(sm, format, carrier)
}
// Extract delegates to opentracing.Tracer.
func (t *Tracing) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
return t.tracer.Extract(format, carrier)
}
// IsEnabled determines if tracing was successfully activated.
func (t *Tracing) IsEnabled() bool {
if t == nil || t.tracer == nil {
return false
}
return true
}
// Close tracer
func (t *Tracing) Close() {
if t.closer != nil {
err := t.closer.Close()
if err != nil {
log.WithoutContext().Warn(err)
}
}
}
// 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)
}
}
// LogResponseCode used to log response code in span.
func LogResponseCode(span opentracing.Span, code int) {
if span != nil {
ext.HTTPStatusCode.Set(span, uint16(code))
if code >= 400 {
ext.Error.Set(span, true)
}
}
}
// 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,
HTTPHeadersCarrier(r.Header))
if err != nil {
log.FromContext(r.Context()).Error(err)
}
}
}
// 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...))
}
}
// 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
}
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)
}
}
// 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...)
}

View file

@ -0,0 +1,50 @@
package zipkin
import (
"io"
"time"
"github.com/containous/traefik/pkg/log"
"github.com/opentracing/opentracing-go"
zipkin "github.com/openzipkin/zipkin-go-opentracing"
)
// Name sets the name of this tracer.
const Name = "zipkin"
// Config provides configuration settings for a zipkin tracer.
type Config struct {
HTTPEndpoint string `description:"HTTP Endpoint to report traces to." export:"false"`
SameSpan bool `description:"Use Zipkin SameSpan RPC style traces." export:"true"`
ID128Bit bool `description:"Use Zipkin 128 bit root span IDs." export:"true"`
Debug bool `description:"Enable Zipkin debug." export:"true"`
SampleRate float64 `description:"The rate between 0.0 and 1.0 of requests to trace." export:"true"`
}
// Setup sets up the tracer
func (c *Config) Setup(serviceName string) (opentracing.Tracer, io.Closer, error) {
collector, err := zipkin.NewHTTPCollector(c.HTTPEndpoint)
if err != nil {
return nil, nil, err
}
recorder := zipkin.NewRecorder(collector, c.Debug, "0.0.0.0:0", serviceName)
tracer, err := zipkin.NewTracer(
recorder,
zipkin.ClientServerSameSpan(c.SameSpan),
zipkin.TraceID128Bit(c.ID128Bit),
zipkin.DebugMode(c.Debug),
zipkin.WithSampler(zipkin.NewBoundarySampler(c.SampleRate, time.Now().Unix())),
)
if err != nil {
return nil, nil, err
}
// Without this, child spans are getting the NOOP tracer
opentracing.SetGlobalTracer(tracer)
log.WithoutContext().Debug("Zipkin tracer configured")
return tracer, collector, nil
}