1
0
Fork 0

Dynamic Configuration Refactoring

This commit is contained in:
Ludovic Fernandez 2018-11-14 10:18:03 +01:00 committed by Traefiker Bot
parent d3ae88f108
commit a09dfa3ce1
452 changed files with 21023 additions and 9419 deletions

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,45 @@
package datadog
import (
"io"
"strings"
"github.com/containous/traefik/old/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"`
}
// 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]
}
tracer := ddtracer.New(
datadog.WithAgentAddr(c.LocalAgentHostPort),
datadog.WithServiceName(serviceName),
datadog.WithGlobalTag(tag[0], value),
datadog.WithDebugMode(c.Debug),
)
// Without this, child spans are getting the NOOP tracer
opentracing.SetGlobalTracer(tracer)
log.Debug("DataDog tracer configured")
return tracer, nil, nil
}

View file

@ -0,0 +1,57 @@
package tracing
import (
"fmt"
"net/http"
"github.com/containous/traefik/old/log"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
"github.com/urfave/negroni"
)
type entryPointMiddleware struct {
entryPoint string
*Tracing
}
// NewEntryPoint creates a new middleware that the incoming request
func (t *Tracing) NewEntryPoint(name string) negroni.Handler {
log.Debug("Added entrypoint tracing middleware")
return &entryPointMiddleware{Tracing: t, entryPoint: name}
}
func (e *entryPointMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
opNameFunc := generateEntryPointSpanName
ctx, _ := e.Extract(opentracing.HTTPHeaders, HTTPHeadersCarrier(r.Header))
span := e.StartSpan(opNameFunc(r, e.entryPoint, e.SpanNameLimit), ext.RPCServerOption(ctx))
ext.Component.Set(span, e.ServiceName)
LogRequest(span, r)
ext.SpanKindRPCServer.Set(span)
r = r.WithContext(opentracing.ContextWithSpan(r.Context(), span))
recorder := newStatusCodeRecoder(w, 200)
next(recorder, r)
LogResponseCode(span, recorder.Status())
span.Finish()
}
// generateEntryPointSpanName will return a Span name of an appropriate lenth based on the 'spanLimit' argument. If needed, it will be truncated, but will not be less than 24 characters.
func generateEntryPointSpanName(r *http.Request, entryPoint string, spanLimit int) string {
name := fmt.Sprintf("Entrypoint %s %s", entryPoint, r.Host)
if spanLimit > 0 && len(name) > spanLimit {
if spanLimit < EntryPointMaxLengthNumber {
log.Warnf("SpanNameLimit is set to be less than required static number of characters, defaulting to %d + 3", EntryPointMaxLengthNumber)
spanLimit = EntryPointMaxLengthNumber + 3
}
hash := computeHash(name)
limit := (spanLimit - EntryPointMaxLengthNumber) / 2
name = fmt.Sprintf("Entrypoint %s %s %s", truncateString(entryPoint, limit), truncateString(r.Host, limit), hash)
}
return name
}

View file

@ -0,0 +1,70 @@
package tracing
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/opentracing/opentracing-go/ext"
"github.com/stretchr/testify/assert"
)
func TestEntryPointMiddlewareServeHTTP(t *testing.T) {
expectedTags := map[string]interface{}{
"span.kind": ext.SpanKindRPCServerEnum,
"http.method": "GET",
"component": "",
"http.url": "http://www.test.com",
"http.host": "www.test.com",
}
testCases := []struct {
desc string
entryPoint string
tracing *Tracing
expectedTags map[string]interface{}
expectedName string
}{
{
desc: "no truncation test",
entryPoint: "test",
tracing: &Tracing{
SpanNameLimit: 0,
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
},
expectedTags: expectedTags,
expectedName: "Entrypoint test www.test.com",
}, {
desc: "basic test",
entryPoint: "test",
tracing: &Tracing{
SpanNameLimit: 25,
tracer: &MockTracer{Span: &MockSpan{Tags: make(map[string]interface{})}},
},
expectedTags: expectedTags,
expectedName: "Entrypoint te... ww... 39b97e58",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
e := &entryPointMiddleware{
entryPoint: test.entryPoint,
Tracing: test.tracing,
}
next := func(http.ResponseWriter, *http.Request) {
span := test.tracing.tracer.(*MockTracer).Span
actual := span.Tags
assert.Equal(t, test.expectedTags, actual)
assert.Equal(t, test.expectedName, span.OpName)
}
e.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "http://www.test.com", nil), next)
})
}
}

View file

@ -0,0 +1,63 @@
package tracing
import (
"fmt"
"net/http"
"github.com/containous/traefik/old/log"
"github.com/opentracing/opentracing-go/ext"
"github.com/urfave/negroni"
)
type forwarderMiddleware struct {
frontend string
backend string
opName string
*Tracing
}
// NewForwarderMiddleware creates a new forwarder middleware that traces the outgoing request
func (t *Tracing) NewForwarderMiddleware(frontend, backend string) negroni.Handler {
log.Debugf("Added outgoing tracing middleware %s", frontend)
return &forwarderMiddleware{
Tracing: t,
frontend: frontend,
backend: backend,
opName: generateForwardSpanName(frontend, backend, t.SpanNameLimit),
}
}
func (f *forwarderMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
span, r, finish := StartSpan(r, f.opName, true)
defer finish()
span.SetTag("frontend.name", f.frontend)
span.SetTag("backend.name", f.backend)
ext.HTTPMethod.Set(span, r.Method)
ext.HTTPUrl.Set(span, fmt.Sprintf("%s%s", r.URL.String(), r.RequestURI))
span.SetTag("http.host", r.Host)
InjectRequestHeaders(r)
recorder := newStatusCodeRecoder(w, 200)
next(recorder, r)
LogResponseCode(span, recorder.Status())
}
// generateForwardSpanName will return a Span name of an appropriate lenth based on the 'spanLimit' argument. If needed, it will be truncated, but will not be less than 21 characters
func generateForwardSpanName(frontend, backend string, spanLimit int) string {
name := fmt.Sprintf("forward %s/%s", frontend, backend)
if spanLimit > 0 && len(name) > spanLimit {
if spanLimit < ForwardMaxLengthNumber {
log.Warnf("SpanNameLimit is set to be less than required static number of characters, defaulting to %d + 3", ForwardMaxLengthNumber)
spanLimit = ForwardMaxLengthNumber + 3
}
hash := computeHash(name)
limit := (spanLimit - ForwardMaxLengthNumber) / 2
name = fmt.Sprintf("forward %s/%s/%s", truncateString(frontend, limit), truncateString(backend, limit), hash)
}
return name
}

View file

@ -0,0 +1,93 @@
package tracing
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTracingNewForwarderMiddleware(t *testing.T) {
testCases := []struct {
desc string
tracer *Tracing
frontend string
backend string
expected *forwarderMiddleware
}{
{
desc: "Simple Forward Tracer without truncation and hashing",
tracer: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service.domain.tld",
backend: "some-service.domain.tld",
expected: &forwarderMiddleware{
Tracing: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service.domain.tld",
backend: "some-service.domain.tld",
opName: "forward some-service.domain.tld/some-service.domain.tld",
},
}, {
desc: "Simple Forward Tracer with truncation and hashing",
tracer: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service-100.slug.namespace.environment.domain.tld",
backend: "some-service-100.slug.namespace.environment.domain.tld",
expected: &forwarderMiddleware{
Tracing: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service-100.slug.namespace.environment.domain.tld",
backend: "some-service-100.slug.namespace.environment.domain.tld",
opName: "forward some-service-100.slug.namespace.enviro.../some-service-100.slug.namespace.enviro.../bc4a0d48",
},
},
{
desc: "Exactly 101 chars",
tracer: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service1.namespace.environment.domain.tld",
backend: "some-service1.namespace.environment.domain.tld",
expected: &forwarderMiddleware{
Tracing: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service1.namespace.environment.domain.tld",
backend: "some-service1.namespace.environment.domain.tld",
opName: "forward some-service1.namespace.environment.domain.tld/some-service1.namespace.environment.domain.tld",
},
},
{
desc: "More than 101 chars",
tracer: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service1.frontend.namespace.environment.domain.tld",
backend: "some-service1.backend.namespace.environment.domain.tld",
expected: &forwarderMiddleware{
Tracing: &Tracing{
SpanNameLimit: 101,
},
frontend: "some-service1.frontend.namespace.environment.domain.tld",
backend: "some-service1.backend.namespace.environment.domain.tld",
opName: "forward some-service1.frontend.namespace.envir.../some-service1.backend.namespace.enviro.../fa49dd23",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.tracer.NewForwarderMiddleware(test.frontend, test.backend)
assert.Equal(t, test.expected, actual)
assert.True(t, len(test.expected.opName) <= test.tracer.SpanNameLimit)
})
}
}

View file

@ -0,0 +1,73 @@
package jaeger
import (
"fmt"
"io"
"github.com/containous/traefik/old/log"
"github.com/opentracing/opentracing-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"`
}
// 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,
},
}
jMetricsFactory := jaegermet.NullFactory
opts := []jaegercfg.Option{
jaegercfg.Logger(&jaegerLogger{}),
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.Warnf("Could not initialize jaeger tracer: %s", err.Error())
return nil, nil, err
}
log.Debug("Jaeger tracer configured")
return opentracing.GlobalTracer(), closer, nil
}

View file

@ -0,0 +1,15 @@
package jaeger
import "github.com/containous/traefik/old/log"
// jaegerLogger is an implementation of the Logger interface that delegates to traefik log
type jaegerLogger struct{}
func (l *jaegerLogger) Error(msg string) {
log.Errorf("Tracing jaeger error: %s", msg)
}
// Infof logs a message at debug priority
func (l *jaegerLogger) Infof(msg string, args ...interface{}) {
log.Debugf(msg, args...)
}

View file

@ -0,0 +1,57 @@
package tracing
import (
"bufio"
"net"
"net/http"
)
type statusCodeRecoder interface {
http.ResponseWriter
Status() int
}
type statusCodeWithoutCloseNotify struct {
http.ResponseWriter
status int
}
// WriteHeader captures the status code for later retrieval.
func (s *statusCodeWithoutCloseNotify) WriteHeader(status int) {
s.status = status
s.ResponseWriter.WriteHeader(status)
}
// Status get response status
func (s *statusCodeWithoutCloseNotify) Status() int {
return s.status
}
// Hijack hijacks the connection
func (s *statusCodeWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return s.ResponseWriter.(http.Hijacker).Hijack()
}
// Flush sends any buffered data to the client.
func (s *statusCodeWithoutCloseNotify) Flush() {
if flusher, ok := s.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
}
type statusCodeWithCloseNotify struct {
*statusCodeWithoutCloseNotify
}
func (s *statusCodeWithCloseNotify) CloseNotify() <-chan bool {
return s.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder {
recorder := &statusCodeWithoutCloseNotify{rw, status}
if _, ok := rw.(http.CloseNotifier); ok {
return &statusCodeWithCloseNotify{recorder}
}
return recorder
}

View file

@ -0,0 +1,197 @@
package tracing
import (
"crypto/sha256"
"fmt"
"io"
"net/http"
"github.com/containous/traefik/old/log"
"github.com/containous/traefik/old/middlewares/tracing/datadog"
"github.com/containous/traefik/old/middlewares/tracing/jaeger"
"github.com/containous/traefik/old/middlewares/tracing/zipkin"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
// ForwardMaxLengthNumber defines the number of static characters in the Forwarding Span Trace name : 8 chars for 'forward ' + 8 chars for hash + 2 chars for '_'.
const ForwardMaxLengthNumber = 18
// EntryPointMaxLengthNumber defines the number of static characters in the Entrypoint Span Trace name : 11 chars for 'Entrypoint ' + 8 chars for hash + 2 chars for '_'.
const EntryPointMaxLengthNumber = 21
// TraceNameHashLength defines the number of characters to use from the head of the generated hash.
const TraceNameHashLength = 8
// Tracing middleware
type Tracing struct {
Backend string `description:"Selects the tracking backend ('jaeger','zipkin', 'datadog')." export:"true"`
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"`
Jaeger *jaeger.Config `description:"Settings for jaeger"`
Zipkin *zipkin.Config `description:"Settings for zipkin"`
DataDog *datadog.Config `description:"Settings for DataDog"`
tracer opentracing.Tracer
closer io.Closer
}
// StartSpan delegates to opentracing.Tracer
func (t *Tracing) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
return t.tracer.StartSpan(operationName, 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)
}
// Backend describes things we can use to setup tracing
type Backend interface {
Setup(serviceName string) (opentracing.Tracer, io.Closer, error)
}
// Setup Tracing middleware
func (t *Tracing) Setup() {
var err error
switch t.Backend {
case jaeger.Name:
t.tracer, t.closer, err = t.Jaeger.Setup(t.ServiceName)
case zipkin.Name:
t.tracer, t.closer, err = t.Zipkin.Setup(t.ServiceName)
case datadog.Name:
t.tracer, t.closer, err = t.DataDog.Setup(t.ServiceName)
default:
log.Warnf("Unknown tracer %q", t.Backend)
return
}
if err != nil {
log.Warnf("Could not initialize %s tracing: %v", t.Backend, err)
}
}
// 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.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.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, spanKinClient bool, opts ...opentracing.StartSpanOption) (opentracing.Span, *http.Request, func()) {
span, ctx := opentracing.StartSpanFromContext(r.Context(), operationName, opts...)
if spanKinClient {
ext.SpanKindRPCClient.Set(span)
}
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)
}
}
// SetErrorAndDebugLog flags the span associated with this request as in error and create a debug log.
func SetErrorAndDebugLog(r *http.Request, format string, args ...interface{}) {
SetError(r)
log.Debugf(format, args...)
LogEventf(r, format, args...)
}
// SetErrorAndWarnLog flags the span associated with this request as in error and create a debug log.
func SetErrorAndWarnLog(r *http.Request, format string, args ...interface{}) {
SetError(r)
log.Warnf(format, args...)
LogEventf(r, format, args...)
}
// 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.Errorf("Fail to create Span name hash for %s: %v", name, err)
}
return fmt.Sprintf("%x", hash.Sum(nil))[:TraceNameHashLength]
}

View file

@ -0,0 +1,133 @@
package tracing
import (
"testing"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/log"
"github.com/stretchr/testify/assert"
)
type MockTracer struct {
Span *MockSpan
}
type MockSpan struct {
OpName string
Tags map[string]interface{}
}
type MockSpanContext struct {
}
// MockSpanContext:
func (n MockSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
// MockSpan:
func (n MockSpan) Context() opentracing.SpanContext { return MockSpanContext{} }
func (n MockSpan) SetBaggageItem(key, val string) opentracing.Span {
return MockSpan{Tags: make(map[string]interface{})}
}
func (n MockSpan) BaggageItem(key string) string { return "" }
func (n MockSpan) SetTag(key string, value interface{}) opentracing.Span {
n.Tags[key] = value
return n
}
func (n MockSpan) LogFields(fields ...log.Field) {}
func (n MockSpan) LogKV(keyVals ...interface{}) {}
func (n MockSpan) Finish() {}
func (n MockSpan) FinishWithOptions(opts opentracing.FinishOptions) {}
func (n MockSpan) SetOperationName(operationName string) opentracing.Span { return n }
func (n MockSpan) Tracer() opentracing.Tracer { return MockTracer{} }
func (n MockSpan) LogEvent(event string) {}
func (n MockSpan) LogEventWithPayload(event string, payload interface{}) {}
func (n MockSpan) Log(data opentracing.LogData) {}
func (n MockSpan) Reset() {
n.Tags = make(map[string]interface{})
}
// StartSpan belongs to the Tracer interface.
func (n MockTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
n.Span.OpName = operationName
return n.Span
}
// Inject belongs to the Tracer interface.
func (n MockTracer) Inject(sp opentracing.SpanContext, format interface{}, carrier interface{}) error {
return nil
}
// Extract belongs to the Tracer interface.
func (n MockTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
return nil, opentracing.ErrSpanContextNotFound
}
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)
})
}
}
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)
})
}
}

View file

@ -0,0 +1,66 @@
package tracing
import (
"net/http"
"github.com/urfave/negroni"
)
// NewNegroniHandlerWrapper return a negroni.Handler struct
func (t *Tracing) NewNegroniHandlerWrapper(name string, handler negroni.Handler, clientSpanKind bool) negroni.Handler {
if t.IsEnabled() && handler != nil {
return &NegroniHandlerWrapper{
name: name,
next: handler,
clientSpanKind: clientSpanKind,
}
}
return handler
}
// NewHTTPHandlerWrapper return a http.Handler struct
func (t *Tracing) NewHTTPHandlerWrapper(name string, handler http.Handler, clientSpanKind bool) http.Handler {
if t.IsEnabled() && handler != nil {
return &HTTPHandlerWrapper{
name: name,
handler: handler,
clientSpanKind: clientSpanKind,
}
}
return handler
}
// NegroniHandlerWrapper is used to wrap negroni handler middleware
type NegroniHandlerWrapper struct {
name string
next negroni.Handler
clientSpanKind bool
}
func (t *NegroniHandlerWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
var finish func()
_, r, finish = StartSpan(r, t.name, t.clientSpanKind)
defer finish()
if t.next != nil {
t.next.ServeHTTP(rw, r, next)
}
}
// HTTPHandlerWrapper is used to wrap http handler middleware
type HTTPHandlerWrapper struct {
name string
handler http.Handler
clientSpanKind bool
}
func (t *HTTPHandlerWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
var finish func()
_, r, finish = StartSpan(r, t.name, t.clientSpanKind)
defer finish()
if t.handler != nil {
t.handler.ServeHTTP(rw, r)
}
}

View file

@ -0,0 +1,49 @@
package zipkin
import (
"io"
"time"
"github.com/containous/traefik/old/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.Debug("Zipkin tracer configured")
return tracer, collector, nil
}