API Server: Standalone observability (#84789)
Adds support for logs (specify level), metrics (enable metrics and Prometheus /metrics endpoint and traces (jaeger or otlp) for standalone API server. This will allow any grafana core service part of standalone apiserver to use logging, metrics and traces as normal.
This commit is contained in:
@@ -59,11 +59,11 @@ func (im *InternalMetricsService) Run(ctx context.Context) error {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func ProvideRegisterer(cfg *setting.Cfg) prometheus.Registerer {
|
||||
func ProvideRegisterer() prometheus.Registerer {
|
||||
return legacyregistry.Registerer()
|
||||
}
|
||||
|
||||
func ProvideGatherer(cfg *setting.Cfg) prometheus.Gatherer {
|
||||
func ProvideGatherer() prometheus.Gatherer {
|
||||
k8sGatherer := newAddPrefixWrapper(legacyregistry.DefaultGatherer)
|
||||
return newMultiRegistry(k8sGatherer, prometheus.DefaultGatherer)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func WithSpanProcessor(sp tracesdk.SpanProcessor) TracerForTestOption {
|
||||
|
||||
func InitializeTracerForTest(opts ...TracerForTestOption) Tracer {
|
||||
exp := tracetest.NewInMemoryExporter()
|
||||
tp, _ := initTracerProvider(exp, "testing", tracesdk.AlwaysSample())
|
||||
tp, _ := initTracerProvider(exp, "grafana", "testing", tracesdk.AlwaysSample())
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(tp)
|
||||
@@ -24,7 +24,9 @@ func InitializeTracerForTest(opts ...TracerForTestOption) Tracer {
|
||||
|
||||
otel.SetTracerProvider(tp)
|
||||
|
||||
ots := &TracingService{Propagation: "jaeger,w3c", tracerProvider: tp}
|
||||
cfg := NewEmptyTracingConfig()
|
||||
cfg.Propagation = "jaeger,w3c"
|
||||
ots := &TracingService{cfg: cfg, tracerProvider: tp}
|
||||
_ = ots.initOpentelemetryTracer()
|
||||
return ots
|
||||
}
|
||||
|
||||
+34
-129
@@ -6,7 +6,6 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
"github.com/go-kit/log/level"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -46,21 +44,11 @@ const (
|
||||
)
|
||||
|
||||
type TracingService struct {
|
||||
enabled string
|
||||
Address string
|
||||
Propagation string
|
||||
customAttribs []attribute.KeyValue
|
||||
|
||||
Sampler string
|
||||
SamplerParam float64
|
||||
SamplerRemoteURL string
|
||||
|
||||
cfg *TracingConfig
|
||||
log log.Logger
|
||||
|
||||
tracerProvider tracerProvider
|
||||
trace.Tracer
|
||||
|
||||
Cfg *setting.Cfg
|
||||
}
|
||||
|
||||
type tracerProvider interface {
|
||||
@@ -84,10 +72,9 @@ type Tracer interface {
|
||||
Inject(context.Context, http.Header, trace.Span)
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg) (*TracingService, error) {
|
||||
ots, err := ParseSettings(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func ProvideService(tracingCfg *TracingConfig) (*TracingService, error) {
|
||||
if tracingCfg == nil {
|
||||
return nil, fmt.Errorf("tracingCfg cannot be nil")
|
||||
}
|
||||
|
||||
log.RegisterContextualLogProvider(func(ctx context.Context) ([]any, bool) {
|
||||
@@ -97,21 +84,18 @@ func ProvideService(cfg *setting.Cfg) (*TracingService, error) {
|
||||
|
||||
return nil, false
|
||||
})
|
||||
|
||||
ots := &TracingService{
|
||||
cfg: tracingCfg,
|
||||
log: log.New("tracing"),
|
||||
}
|
||||
|
||||
if err := ots.initOpentelemetryTracer(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ots, nil
|
||||
}
|
||||
|
||||
func ParseSettings(cfg *setting.Cfg) (*TracingService, error) {
|
||||
ots := &TracingService{
|
||||
Cfg: cfg,
|
||||
log: log.New("tracing"),
|
||||
}
|
||||
err := ots.parseSettings()
|
||||
return ots, err
|
||||
}
|
||||
|
||||
func (ots *TracingService) GetTracerProvider() tracerProvider {
|
||||
return ots.tracerProvider
|
||||
}
|
||||
@@ -133,96 +117,17 @@ func (noopTracerProvider) Shutdown(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ots *TracingService) parseSettings() error {
|
||||
legacyAddress, legacyTags := "", ""
|
||||
if section, err := ots.Cfg.Raw.GetSection("tracing.jaeger"); err == nil {
|
||||
legacyAddress = section.Key("address").MustString("")
|
||||
if legacyAddress == "" {
|
||||
host, port := os.Getenv(envJaegerAgentHost), os.Getenv(envJaegerAgentPort)
|
||||
if host != "" || port != "" {
|
||||
legacyAddress = fmt.Sprintf("%s:%s", host, port)
|
||||
}
|
||||
}
|
||||
legacyTags = section.Key("always_included_tag").MustString("")
|
||||
ots.Sampler = section.Key("sampler_type").MustString("")
|
||||
ots.SamplerParam = section.Key("sampler_param").MustFloat64(1)
|
||||
ots.SamplerRemoteURL = section.Key("sampling_server_url").MustString("")
|
||||
}
|
||||
section := ots.Cfg.Raw.Section("tracing.opentelemetry")
|
||||
var err error
|
||||
// we default to legacy tag set (attributes) if the new config format is absent
|
||||
ots.customAttribs, err = splitCustomAttribs(section.Key("custom_attributes").MustString(legacyTags))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if sampler_type is set in tracing.opentelemetry, we ignore the config in tracing.jaeger
|
||||
sampler := section.Key("sampler_type").MustString("")
|
||||
if sampler != "" {
|
||||
ots.Sampler = sampler
|
||||
}
|
||||
|
||||
samplerParam := section.Key("sampler_param").MustFloat64(0)
|
||||
if samplerParam != 0 {
|
||||
ots.SamplerParam = samplerParam
|
||||
}
|
||||
|
||||
samplerRemoteURL := section.Key("sampling_server_url").MustString("")
|
||||
if samplerRemoteURL != "" {
|
||||
ots.SamplerRemoteURL = samplerRemoteURL
|
||||
}
|
||||
|
||||
section = ots.Cfg.Raw.Section("tracing.opentelemetry.jaeger")
|
||||
ots.enabled = noopExporter
|
||||
|
||||
// we default to legacy Jaeger agent address if the new config value is empty
|
||||
ots.Address = section.Key("address").MustString(legacyAddress)
|
||||
ots.Propagation = section.Key("propagation").MustString("")
|
||||
if ots.Address != "" {
|
||||
ots.enabled = jaegerExporter
|
||||
return nil
|
||||
}
|
||||
|
||||
section = ots.Cfg.Raw.Section("tracing.opentelemetry.otlp")
|
||||
ots.Address = section.Key("address").MustString("")
|
||||
if ots.Address != "" {
|
||||
ots.enabled = otlpExporter
|
||||
}
|
||||
ots.Propagation = section.Key("propagation").MustString("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ots *TracingService) OTelExporterEnabled() bool {
|
||||
return ots.enabled == otlpExporter
|
||||
}
|
||||
|
||||
func splitCustomAttribs(s string) ([]attribute.KeyValue, error) {
|
||||
res := []attribute.KeyValue{}
|
||||
|
||||
attribs := strings.Split(s, ",")
|
||||
for _, v := range attribs {
|
||||
parts := strings.SplitN(v, ":", 2)
|
||||
if len(parts) > 1 {
|
||||
res = append(res, attribute.String(parts[0], parts[1]))
|
||||
} else if v != "" {
|
||||
return nil, fmt.Errorf("custom attribute malformed - must be in 'key:value' form: %q", v)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ots *TracingService) initJaegerTracerProvider() (*tracesdk.TracerProvider, error) {
|
||||
var ep jaeger.EndpointOption
|
||||
// Create the Jaeger exporter: address can be either agent address (host:port) or collector URL
|
||||
if strings.HasPrefix(ots.Address, "http://") || strings.HasPrefix(ots.Address, "https://") {
|
||||
ots.log.Debug("using jaeger collector", "address", ots.Address)
|
||||
ep = jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(ots.Address))
|
||||
} else if host, port, err := net.SplitHostPort(ots.Address); err == nil {
|
||||
if strings.HasPrefix(ots.cfg.Address, "http://") || strings.HasPrefix(ots.cfg.Address, "https://") {
|
||||
ots.log.Debug("using jaeger collector", "address", ots.cfg.Address)
|
||||
ep = jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(ots.cfg.Address))
|
||||
} else if host, port, err := net.SplitHostPort(ots.cfg.Address); err == nil {
|
||||
ots.log.Debug("using jaeger agent", "host", host, "port", port)
|
||||
ep = jaeger.WithAgentEndpoint(jaeger.WithAgentHost(host), jaeger.WithAgentPort(port), jaeger.WithMaxPacketSize(64000))
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid tracer address: %s", ots.Address)
|
||||
return nil, fmt.Errorf("invalid tracer address: %s", ots.cfg.Address)
|
||||
}
|
||||
exp, err := jaeger.New(ep)
|
||||
if err != nil {
|
||||
@@ -234,10 +139,10 @@ func (ots *TracingService) initJaegerTracerProvider() (*tracesdk.TracerProvider,
|
||||
resource.WithAttributes(
|
||||
// TODO: why are these attributes different from ones added to the
|
||||
// OTLP provider?
|
||||
semconv.ServiceNameKey.String("grafana"),
|
||||
semconv.ServiceNameKey.String(ots.cfg.ServiceName),
|
||||
attribute.String("environment", "production"),
|
||||
),
|
||||
resource.WithAttributes(ots.customAttribs...),
|
||||
resource.WithAttributes(ots.cfg.CustomAttribs...),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -258,7 +163,7 @@ func (ots *TracingService) initJaegerTracerProvider() (*tracesdk.TracerProvider,
|
||||
}
|
||||
|
||||
func (ots *TracingService) initOTLPTracerProvider() (*tracesdk.TracerProvider, error) {
|
||||
client := otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(ots.Address), otlptracegrpc.WithInsecure())
|
||||
client := otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(ots.cfg.Address), otlptracegrpc.WithInsecure())
|
||||
exp, err := otlptrace.New(context.Background(), client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -269,39 +174,39 @@ func (ots *TracingService) initOTLPTracerProvider() (*tracesdk.TracerProvider, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return initTracerProvider(exp, ots.Cfg.BuildVersion, sampler, ots.customAttribs...)
|
||||
return initTracerProvider(exp, ots.cfg.ServiceName, ots.cfg.ServiceVersion, sampler, ots.cfg.CustomAttribs...)
|
||||
}
|
||||
|
||||
func (ots *TracingService) initSampler() (tracesdk.Sampler, error) {
|
||||
switch ots.Sampler {
|
||||
switch ots.cfg.Sampler {
|
||||
case "const", "":
|
||||
if ots.SamplerParam >= 1 {
|
||||
if ots.cfg.SamplerParam >= 1 {
|
||||
return tracesdk.AlwaysSample(), nil
|
||||
} else if ots.SamplerParam <= 0 {
|
||||
} else if ots.cfg.SamplerParam <= 0 {
|
||||
return tracesdk.NeverSample(), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid param for const sampler - must be 0 or 1: %f", ots.SamplerParam)
|
||||
return nil, fmt.Errorf("invalid param for const sampler - must be 0 or 1: %f", ots.cfg.SamplerParam)
|
||||
case "probabilistic":
|
||||
return tracesdk.TraceIDRatioBased(ots.SamplerParam), nil
|
||||
return tracesdk.TraceIDRatioBased(ots.cfg.SamplerParam), nil
|
||||
case "rateLimiting":
|
||||
return newRateLimiter(ots.SamplerParam), nil
|
||||
return newRateLimiter(ots.cfg.SamplerParam), nil
|
||||
case "remote":
|
||||
return jaegerremote.New("grafana",
|
||||
jaegerremote.WithSamplingServerURL(ots.SamplerRemoteURL),
|
||||
jaegerremote.WithInitialSampler(tracesdk.TraceIDRatioBased(ots.SamplerParam)),
|
||||
jaegerremote.WithSamplingServerURL(ots.cfg.SamplerRemoteURL),
|
||||
jaegerremote.WithInitialSampler(tracesdk.TraceIDRatioBased(ots.cfg.SamplerParam)),
|
||||
), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid sampler type: %s", ots.Sampler)
|
||||
return nil, fmt.Errorf("invalid sampler type: %s", ots.cfg.Sampler)
|
||||
}
|
||||
}
|
||||
|
||||
func initTracerProvider(exp tracesdk.SpanExporter, version string, sampler tracesdk.Sampler, customAttribs ...attribute.KeyValue) (*tracesdk.TracerProvider, error) {
|
||||
func initTracerProvider(exp tracesdk.SpanExporter, serviceName string, serviceVersion string, sampler tracesdk.Sampler, customAttribs ...attribute.KeyValue) (*tracesdk.TracerProvider, error) {
|
||||
res, err := resource.New(
|
||||
context.Background(),
|
||||
resource.WithAttributes(
|
||||
semconv.ServiceNameKey.String("grafana"),
|
||||
semconv.ServiceVersionKey.String(version),
|
||||
semconv.ServiceNameKey.String(serviceName),
|
||||
semconv.ServiceVersionKey.String(serviceVersion),
|
||||
),
|
||||
resource.WithAttributes(customAttribs...),
|
||||
resource.WithProcessRuntimeDescription(),
|
||||
@@ -326,7 +231,7 @@ func (ots *TracingService) initNoopTracerProvider() (tracerProvider, error) {
|
||||
func (ots *TracingService) initOpentelemetryTracer() error {
|
||||
var tp tracerProvider
|
||||
var err error
|
||||
switch ots.enabled {
|
||||
switch ots.cfg.enabled {
|
||||
case jaegerExporter:
|
||||
tp, err = ots.initJaegerTracerProvider()
|
||||
if err != nil {
|
||||
@@ -347,12 +252,12 @@ func (ots *TracingService) initOpentelemetryTracer() error {
|
||||
// Register our TracerProvider as the global so any imported
|
||||
// instrumentation in the future will default to using it
|
||||
// only if tracing is enabled
|
||||
if ots.enabled != "" {
|
||||
if ots.cfg.enabled != "" {
|
||||
otel.SetTracerProvider(tp)
|
||||
}
|
||||
|
||||
propagators := []propagation.TextMapPropagator{}
|
||||
for _, p := range strings.Split(ots.Propagation, ",") {
|
||||
for _, p := range strings.Split(ots.cfg.Propagation, ",") {
|
||||
switch p {
|
||||
case w3cPropagator:
|
||||
propagators = append(propagators, propagation.TraceContext{}, propagation.Baggage{})
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
type TracingConfig struct {
|
||||
enabled string
|
||||
Address string
|
||||
Propagation string
|
||||
CustomAttribs []attribute.KeyValue
|
||||
|
||||
Sampler string
|
||||
SamplerParam float64
|
||||
SamplerRemoteURL string
|
||||
|
||||
ServiceName string
|
||||
ServiceVersion string
|
||||
}
|
||||
|
||||
func ProvideTracingConfig(cfg *setting.Cfg) (*TracingConfig, error) {
|
||||
return ParseTracingConfig(cfg)
|
||||
}
|
||||
|
||||
func NewEmptyTracingConfig() *TracingConfig {
|
||||
return &TracingConfig{
|
||||
CustomAttribs: []attribute.KeyValue{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewJaegerTracingConfig(address string, propagation string) (*TracingConfig, error) {
|
||||
if address == "" {
|
||||
return nil, fmt.Errorf("address cannot be empty")
|
||||
}
|
||||
|
||||
cfg := NewEmptyTracingConfig()
|
||||
cfg.enabled = jaegerExporter
|
||||
cfg.Address = address
|
||||
cfg.Propagation = propagation
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func NewOTLPTracingConfig(address string, propagation string) (*TracingConfig, error) {
|
||||
if address == "" {
|
||||
return nil, fmt.Errorf("address cannot be empty")
|
||||
}
|
||||
|
||||
cfg := NewEmptyTracingConfig()
|
||||
cfg.enabled = otlpExporter
|
||||
cfg.Address = address
|
||||
cfg.Propagation = propagation
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func ParseTracingConfig(cfg *setting.Cfg) (*TracingConfig, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("cfg cannot be nil")
|
||||
}
|
||||
tc := NewEmptyTracingConfig()
|
||||
tc.ServiceName = "grafana"
|
||||
tc.ServiceVersion = cfg.BuildVersion
|
||||
|
||||
legacyAddress, legacyTags := "", ""
|
||||
if section, err := cfg.Raw.GetSection("tracing.jaeger"); err == nil {
|
||||
legacyAddress = section.Key("address").MustString("")
|
||||
if legacyAddress == "" {
|
||||
host, port := os.Getenv(envJaegerAgentHost), os.Getenv(envJaegerAgentPort)
|
||||
if host != "" || port != "" {
|
||||
legacyAddress = fmt.Sprintf("%s:%s", host, port)
|
||||
}
|
||||
}
|
||||
legacyTags = section.Key("always_included_tag").MustString("")
|
||||
tc.Sampler = section.Key("sampler_type").MustString("")
|
||||
tc.SamplerParam = section.Key("sampler_param").MustFloat64(1)
|
||||
tc.SamplerRemoteURL = section.Key("sampling_server_url").MustString("")
|
||||
}
|
||||
section := cfg.Raw.Section("tracing.opentelemetry")
|
||||
var err error
|
||||
// we default to legacy tag set (attributes) if the new config format is absent
|
||||
tc.CustomAttribs, err = splitCustomAttribs(section.Key("custom_attributes").MustString(legacyTags))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if sampler_type is set in tracing.opentelemetry, we ignore the config in tracing.jaeger
|
||||
sampler := section.Key("sampler_type").MustString("")
|
||||
if sampler != "" {
|
||||
tc.Sampler = sampler
|
||||
}
|
||||
|
||||
samplerParam := section.Key("sampler_param").MustFloat64(0)
|
||||
if samplerParam != 0 {
|
||||
tc.SamplerParam = samplerParam
|
||||
}
|
||||
|
||||
samplerRemoteURL := section.Key("sampling_server_url").MustString("")
|
||||
if samplerRemoteURL != "" {
|
||||
tc.SamplerRemoteURL = samplerRemoteURL
|
||||
}
|
||||
|
||||
section = cfg.Raw.Section("tracing.opentelemetry.jaeger")
|
||||
tc.enabled = noopExporter
|
||||
|
||||
// we default to legacy Jaeger agent address if the new config value is empty
|
||||
tc.Address = section.Key("address").MustString(legacyAddress)
|
||||
tc.Propagation = section.Key("propagation").MustString("")
|
||||
if tc.Address != "" {
|
||||
tc.enabled = jaegerExporter
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
section = cfg.Raw.Section("tracing.opentelemetry.otlp")
|
||||
tc.Address = section.Key("address").MustString("")
|
||||
if tc.Address != "" {
|
||||
tc.enabled = otlpExporter
|
||||
}
|
||||
tc.Propagation = section.Key("propagation").MustString("")
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func (tc TracingConfig) OTelExporterEnabled() bool {
|
||||
return tc.enabled == otlpExporter
|
||||
}
|
||||
|
||||
func splitCustomAttribs(s string) ([]attribute.KeyValue, error) {
|
||||
res := []attribute.KeyValue{}
|
||||
|
||||
attribs := strings.Split(s, ",")
|
||||
for _, v := range attribs {
|
||||
parts := strings.SplitN(v, ":", 2)
|
||||
if len(parts) > 1 {
|
||||
res = append(res, attribute.String(parts[0], parts[1]))
|
||||
} else if v != "" {
|
||||
return nil, fmt.Errorf("custom attribute malformed - must be in 'key:value' form: %q", v)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// TODO(zserge) Add proper tests for opentelemetry
|
||||
|
||||
func TestSplitCustomAttribs(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
input: "key1:value:1",
|
||||
expected: []attribute.KeyValue{attribute.String("key1", "value:1")},
|
||||
},
|
||||
{
|
||||
input: "key1:value1,key2:value2",
|
||||
expected: []attribute.KeyValue{
|
||||
attribute.String("key1", "value1"),
|
||||
attribute.String("key2", "value2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: []attribute.KeyValue{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
attribs, err := splitCustomAttribs(test.input)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, test.expected, attribs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitCustomAttribs_Malformed(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
}{
|
||||
{input: "key1=value1"},
|
||||
{input: "key1"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := splitCustomAttribs(test.input)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingConfig(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Cfg string
|
||||
Env map[string]string
|
||||
ExpectedExporter string
|
||||
ExpectedAddress string
|
||||
ExpectedPropagator string
|
||||
ExpectedAttrs []attribute.KeyValue
|
||||
|
||||
ExpectedSampler string
|
||||
ExpectedSamplerParam float64
|
||||
ExpectedSamplingServerURL string
|
||||
}{
|
||||
{
|
||||
Name: "default config uses noop exporter",
|
||||
Cfg: "",
|
||||
ExpectedExporter: noopExporter,
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "custom attributes are parsed",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry]
|
||||
custom_attributes = key1:value1,key2:value2
|
||||
`,
|
||||
ExpectedExporter: noopExporter,
|
||||
ExpectedAttrs: []attribute.KeyValue{attribute.String("key1", "value1"), attribute.String("key2", "value2")},
|
||||
},
|
||||
{
|
||||
Name: "jaeger address is parsed",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry.jaeger]
|
||||
address = jaeger.example.com:6831
|
||||
`,
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "jaeger.example.com:6831",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "OTLP address is parsed",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry.otlp]
|
||||
address = otlp.example.com:4317
|
||||
`,
|
||||
ExpectedExporter: otlpExporter,
|
||||
ExpectedAddress: "otlp.example.com:4317",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "legacy config format is supported",
|
||||
Cfg: `
|
||||
[tracing.jaeger]
|
||||
address = jaeger.example.com:6831
|
||||
`,
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "jaeger.example.com:6831",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "legacy env variables are supported",
|
||||
Cfg: `[tracing.jaeger]`,
|
||||
Env: map[string]string{
|
||||
"JAEGER_AGENT_HOST": "example.com",
|
||||
"JAEGER_AGENT_PORT": "12345",
|
||||
},
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "example.com:12345",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "opentelemetry config format is prioritised over legacy jaeger",
|
||||
Cfg: `
|
||||
[tracing.jaeger]
|
||||
address = foo.com:6831
|
||||
custom_tags = a:b
|
||||
sampler_param = 0
|
||||
[tracing.opentelemetry]
|
||||
custom_attributes = c:d
|
||||
sampler_param = 1
|
||||
[tracing.opentelemetry.jaeger]
|
||||
address = bar.com:6831
|
||||
`,
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "bar.com:6831",
|
||||
ExpectedAttrs: []attribute.KeyValue{attribute.String("c", "d")},
|
||||
ExpectedSamplerParam: 1.0,
|
||||
},
|
||||
{
|
||||
Name: "remote sampler config is parsed from otel config",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry]
|
||||
sampler_type = remote
|
||||
sampler_param = 0.5
|
||||
sampling_server_url = http://example.com:5778/sampling
|
||||
[tracing.opentelemetry.otlp]
|
||||
address = otlp.example.com:4317
|
||||
`,
|
||||
ExpectedExporter: otlpExporter,
|
||||
ExpectedAddress: "otlp.example.com:4317",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
ExpectedSampler: "remote",
|
||||
ExpectedSamplerParam: 0.5,
|
||||
ExpectedSamplingServerURL: "http://example.com:5778/sampling",
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
// export environment variables
|
||||
if test.Env != nil {
|
||||
for k, v := range test.Env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
// parse config sections
|
||||
cfg := setting.NewCfg()
|
||||
err := cfg.Raw.Append([]byte(test.Cfg))
|
||||
assert.NoError(t, err)
|
||||
// create tracingConfig
|
||||
tracingConfig, err := ProvideTracingConfig(cfg)
|
||||
assert.NoError(t, err)
|
||||
// make sure tracker is properly configured
|
||||
assert.Equal(t, test.ExpectedExporter, tracingConfig.enabled)
|
||||
assert.Equal(t, test.ExpectedAddress, tracingConfig.Address)
|
||||
assert.Equal(t, test.ExpectedPropagator, tracingConfig.Propagation)
|
||||
assert.Equal(t, test.ExpectedAttrs, tracingConfig.CustomAttribs)
|
||||
|
||||
if test.ExpectedSampler != "" {
|
||||
assert.Equal(t, test.ExpectedSampler, tracingConfig.Sampler)
|
||||
assert.Equal(t, test.ExpectedSamplerParam, tracingConfig.SamplerParam)
|
||||
assert.Equal(t, test.ExpectedSamplingServerURL, tracingConfig.SamplerRemoteURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,220 +5,38 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// TODO(zserge) Add proper tests for opentelemetry
|
||||
|
||||
func TestSplitCustomAttribs(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected []attribute.KeyValue
|
||||
}{
|
||||
{
|
||||
input: "key1:value:1",
|
||||
expected: []attribute.KeyValue{attribute.String("key1", "value:1")},
|
||||
},
|
||||
{
|
||||
input: "key1:value1,key2:value2",
|
||||
expected: []attribute.KeyValue{
|
||||
attribute.String("key1", "value1"),
|
||||
attribute.String("key2", "value2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "",
|
||||
expected: []attribute.KeyValue{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
attribs, err := splitCustomAttribs(test.input)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, test.expected, attribs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitCustomAttribs_Malformed(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
}{
|
||||
{input: "key1=value1"},
|
||||
{input: "key1"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err := splitCustomAttribs(test.input)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTracingConfig(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
Cfg string
|
||||
Env map[string]string
|
||||
ExpectedExporter string
|
||||
ExpectedAddress string
|
||||
ExpectedPropagator string
|
||||
ExpectedAttrs []attribute.KeyValue
|
||||
|
||||
ExpectedSampler string
|
||||
ExpectedSamplerParam float64
|
||||
ExpectedSamplingServerURL string
|
||||
}{
|
||||
{
|
||||
Name: "default config uses noop exporter",
|
||||
Cfg: "",
|
||||
ExpectedExporter: noopExporter,
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "custom attributes are parsed",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry]
|
||||
custom_attributes = key1:value1,key2:value2
|
||||
`,
|
||||
ExpectedExporter: noopExporter,
|
||||
ExpectedAttrs: []attribute.KeyValue{attribute.String("key1", "value1"), attribute.String("key2", "value2")},
|
||||
},
|
||||
{
|
||||
Name: "jaeger address is parsed",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry.jaeger]
|
||||
address = jaeger.example.com:6831
|
||||
`,
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "jaeger.example.com:6831",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "OTLP address is parsed",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry.otlp]
|
||||
address = otlp.example.com:4317
|
||||
`,
|
||||
ExpectedExporter: otlpExporter,
|
||||
ExpectedAddress: "otlp.example.com:4317",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "legacy config format is supported",
|
||||
Cfg: `
|
||||
[tracing.jaeger]
|
||||
address = jaeger.example.com:6831
|
||||
`,
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "jaeger.example.com:6831",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "legacy env variables are supported",
|
||||
Cfg: `[tracing.jaeger]`,
|
||||
Env: map[string]string{
|
||||
"JAEGER_AGENT_HOST": "example.com",
|
||||
"JAEGER_AGENT_PORT": "12345",
|
||||
},
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "example.com:12345",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
},
|
||||
{
|
||||
Name: "opentelemetry config format is prioritised over legacy jaeger",
|
||||
Cfg: `
|
||||
[tracing.jaeger]
|
||||
address = foo.com:6831
|
||||
custom_tags = a:b
|
||||
sampler_param = 0
|
||||
[tracing.opentelemetry]
|
||||
custom_attributes = c:d
|
||||
sampler_param = 1
|
||||
[tracing.opentelemetry.jaeger]
|
||||
address = bar.com:6831
|
||||
`,
|
||||
ExpectedExporter: jaegerExporter,
|
||||
ExpectedAddress: "bar.com:6831",
|
||||
ExpectedAttrs: []attribute.KeyValue{attribute.String("c", "d")},
|
||||
ExpectedSamplerParam: 1.0,
|
||||
},
|
||||
{
|
||||
Name: "remote sampler config is parsed from otel config",
|
||||
Cfg: `
|
||||
[tracing.opentelemetry]
|
||||
sampler_type = remote
|
||||
sampler_param = 0.5
|
||||
sampling_server_url = http://example.com:5778/sampling
|
||||
[tracing.opentelemetry.otlp]
|
||||
address = otlp.example.com:4317
|
||||
`,
|
||||
ExpectedExporter: otlpExporter,
|
||||
ExpectedAddress: "otlp.example.com:4317",
|
||||
ExpectedAttrs: []attribute.KeyValue{},
|
||||
ExpectedSampler: "remote",
|
||||
ExpectedSamplerParam: 0.5,
|
||||
ExpectedSamplingServerURL: "http://example.com:5778/sampling",
|
||||
},
|
||||
} {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
// export environment variables
|
||||
if test.Env != nil {
|
||||
for k, v := range test.Env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
// parse config sections
|
||||
cfg := setting.NewCfg()
|
||||
err := cfg.Raw.Append([]byte(test.Cfg))
|
||||
assert.NoError(t, err)
|
||||
// create tracer
|
||||
tracer, err := ProvideService(cfg)
|
||||
assert.NoError(t, err)
|
||||
// make sure tracker is properly configured
|
||||
assert.Equal(t, test.ExpectedExporter, tracer.enabled)
|
||||
assert.Equal(t, test.ExpectedAddress, tracer.Address)
|
||||
assert.Equal(t, test.ExpectedPropagator, tracer.Propagation)
|
||||
assert.Equal(t, test.ExpectedAttrs, tracer.customAttribs)
|
||||
|
||||
if test.ExpectedSampler != "" {
|
||||
assert.Equal(t, test.ExpectedSampler, tracer.Sampler)
|
||||
assert.Equal(t, test.ExpectedSamplerParam, tracer.SamplerParam)
|
||||
assert.Equal(t, test.ExpectedSamplingServerURL, tracer.SamplerRemoteURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitSampler(t *testing.T) {
|
||||
otel := &TracingService{}
|
||||
otel.cfg = NewEmptyTracingConfig()
|
||||
sampler, err := otel.initSampler()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "AlwaysOffSampler", sampler.Description())
|
||||
|
||||
otel.Sampler = "bogus"
|
||||
otel.cfg.Sampler = "bogus"
|
||||
_, err = otel.initSampler()
|
||||
require.Error(t, err)
|
||||
|
||||
otel.Sampler = "const"
|
||||
otel.SamplerParam = 0.5
|
||||
otel.cfg.Sampler = "const"
|
||||
otel.cfg.SamplerParam = 0.5
|
||||
_, err = otel.initSampler()
|
||||
require.Error(t, err)
|
||||
|
||||
otel.Sampler = "const"
|
||||
otel.SamplerParam = 1.0
|
||||
otel.cfg.Sampler = "const"
|
||||
otel.cfg.SamplerParam = 1.0
|
||||
sampler, err = otel.initSampler()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "AlwaysOnSampler", sampler.Description())
|
||||
|
||||
otel.Sampler = "probabilistic"
|
||||
otel.SamplerParam = 0.5
|
||||
otel.cfg.Sampler = "probabilistic"
|
||||
otel.cfg.SamplerParam = 0.5
|
||||
sampler, err = otel.initSampler()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "TraceIDRatioBased{0.5}", sampler.Description())
|
||||
|
||||
otel.Sampler = "rateLimiting"
|
||||
otel.SamplerParam = 100.25
|
||||
otel.cfg.Sampler = "rateLimiting"
|
||||
otel.cfg.SamplerParam = 100.25
|
||||
sampler, err = otel.initSampler()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "RateLimitingSampler{100.25}", sampler.Description())
|
||||
|
||||
Reference in New Issue
Block a user