Add OpenTelemetry tracing and metrics support
This commit is contained in:
parent
db287c4d31
commit
0d81fac3fc
19 changed files with 2199 additions and 90 deletions
138
pkg/tracing/opentelemetry/opentelemetry.go
Normal file
138
pkg/tracing/opentelemetry/opentelemetry.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
package opentelemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
"github.com/traefik/traefik/v2/pkg/version"
|
||||
"go.opentelemetry.io/otel"
|
||||
oteltracer "go.opentelemetry.io/otel/bridge/opentracing"
|
||||
"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"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
)
|
||||
|
||||
// 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" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Config) SetDefaults() {
|
||||
c.Address = "localhost:4318"
|
||||
}
|
||||
|
||||
// Setup sets up the tracer.
|
||||
func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) {
|
||||
var (
|
||||
err error
|
||||
exporter *otlptrace.Exporter
|
||||
)
|
||||
if c.GRPC != nil {
|
||||
exporter, err = c.setupGRPCExporter()
|
||||
} else {
|
||||
exporter, err = c.setupHTTPExporter()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("setting up exporter: %w", err)
|
||||
}
|
||||
|
||||
bt := oteltracer.NewBridgeTracer()
|
||||
bt.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
||||
|
||||
bt.SetOpenTelemetryTracer(otel.Tracer(componentName, trace.WithInstrumentationVersion(version.Version)))
|
||||
opentracing.SetGlobalTracer(bt)
|
||||
|
||||
tracerProvider := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter))
|
||||
otel.SetTracerProvider(tracerProvider)
|
||||
|
||||
log.Debug().Msg("OpenTelemetry tracer configured")
|
||||
|
||||
return bt, tpCloser{provider: tracerProvider}, err
|
||||
}
|
||||
|
||||
func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) {
|
||||
host, port, err := net.SplitHostPort(c.Address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err)
|
||||
}
|
||||
|
||||
opts := []otlptracehttp.Option{
|
||||
otlptracehttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)),
|
||||
otlptracehttp.WithHeaders(c.Headers),
|
||||
otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
|
||||
}
|
||||
|
||||
if c.Insecure {
|
||||
opts = append(opts, otlptracehttp.WithInsecure())
|
||||
}
|
||||
|
||||
if c.Path != "" {
|
||||
opts = append(opts, otlptracehttp.WithURLPath(c.Path))
|
||||
}
|
||||
|
||||
if c.TLS != nil {
|
||||
tlsConfig, err := c.TLS.CreateTLSConfig(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating TLS client config: %w", err)
|
||||
}
|
||||
|
||||
opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig))
|
||||
}
|
||||
|
||||
return otlptrace.New(context.Background(), otlptracehttp.NewClient(opts...))
|
||||
}
|
||||
|
||||
func (c *Config) setupGRPCExporter() (*otlptrace.Exporter, error) {
|
||||
host, port, err := net.SplitHostPort(c.Address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err)
|
||||
}
|
||||
|
||||
opts := []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)),
|
||||
otlptracegrpc.WithHeaders(c.Headers),
|
||||
otlptracegrpc.WithCompressor(gzip.Name),
|
||||
}
|
||||
|
||||
if c.Insecure {
|
||||
opts = append(opts, otlptracegrpc.WithInsecure())
|
||||
}
|
||||
|
||||
if c.TLS != nil {
|
||||
tlsConfig, err := c.TLS.CreateTLSConfig(context.Background())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating TLS client config: %w", err)
|
||||
}
|
||||
|
||||
opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
|
||||
}
|
||||
|
||||
return otlptrace.New(context.Background(), otlptracegrpc.NewClient(opts...))
|
||||
}
|
||||
|
||||
// tpCloser converts a TraceProvider into an io.Closer.
|
||||
type tpCloser struct {
|
||||
provider *sdktrace.TracerProvider
|
||||
}
|
||||
|
||||
func (t tpCloser) Close() error {
|
||||
return t.provider.Shutdown(context.Background())
|
||||
}
|
67
pkg/tracing/opentelemetry/opentelemetry_test.go
Normal file
67
pkg/tracing/opentelemetry/opentelemetry_test.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package opentelemetry
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
mtracing "github.com/traefik/traefik/v2/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
|
||||
)
|
||||
|
||||
func TestTraceContextPropagation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gzr, err := gzip.NewReader(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(gzr)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := ptraceotlp.NewExportRequest()
|
||||
err = req.UnmarshalProto(body)
|
||||
require.NoError(t, err)
|
||||
|
||||
marshalledReq, err := json.Marshal(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
bodyStr := string(marshalledReq)
|
||||
assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, bodyStr)
|
||||
assert.Regexp(t, `("parentSpanId":"0000000000000001")`, bodyStr)
|
||||
assert.Regexp(t, `("traceState":"foo=bar")`, bodyStr)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfg := Config{
|
||||
Address: strings.TrimPrefix(ts.URL, "http://"),
|
||||
Insecure: true,
|
||||
}
|
||||
|
||||
newTracing, err := tracing.NewTracing("", 0, &cfg)
|
||||
require.NoError(t, err)
|
||||
defer newTracing.Close()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://www.test.com", nil)
|
||||
req.Header.Set("traceparent", "00-00000000000000000000000000000001-0000000000000001-00")
|
||||
req.Header.Set("tracestate", "foo=bar")
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
var forwarded bool
|
||||
next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
forwarded = true
|
||||
})
|
||||
|
||||
handler := mtracing.NewEntryPoint(context.Background(), newTracing, "test", next)
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
require.True(t, forwarded)
|
||||
}
|
|
@ -72,8 +72,8 @@ func TestComputeHash(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
desc: "hashing",
|
||||
text: "some very long pice of text",
|
||||
expected: "0258ea1c",
|
||||
text: "some very long piece of text",
|
||||
expected: "0c6e798b",
|
||||
},
|
||||
{
|
||||
desc: "short text less than limit 10",
|
||||
|
@ -109,7 +109,7 @@ func TestTruncateString(t *testing.T) {
|
|||
},
|
||||
{
|
||||
desc: "basic truncate with limit 10",
|
||||
text: "some very long pice of text",
|
||||
text: "some very long piece of text",
|
||||
limit: 10,
|
||||
expected: "some ve...",
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue