diff --git a/config/config.go b/config/config.go index 27b8423..edf0ded 100644 --- a/config/config.go +++ b/config/config.go @@ -138,18 +138,22 @@ type LayersOpts struct { // We can select if we want to disable the metrics, // the traces, and / or the trace propagation. type GlobalOpts struct { - DisableMetrics bool `json:"disable_metrics"` - DisableTraces bool `json:"disable_traces"` - DisablePropagation bool `json:"disable_propagation"` - ReportHeaders bool `json:"report_headers"` + DisableMetrics bool `json:"disable_metrics"` + DisableTraces bool `json:"disable_traces"` + DisablePropagation bool `json:"disable_propagation"` + ReportHeaders bool `json:"report_headers"` + MetricsStaticAttributes Attributes `json:"metrics_static_attributes"` + TracesStaticAttributes Attributes `json:"traces_static_attributes"` } // PipeOpts has the options for the KrakenD pipe stage // to disable metrics and traces. type PipeOpts struct { - DisableMetrics bool `json:"disable_metrics"` - DisableTraces bool `json:"disable_traces"` - ReportHeaders bool `json:"report_headers"` + DisableMetrics bool `json:"disable_metrics"` + DisableTraces bool `json:"disable_traces"` + ReportHeaders bool `json:"report_headers"` + MetricsStaticAttributes Attributes `json:"metrics_static_attributes"` + TracesStaticAttributes Attributes `json:"traces_static_attributes"` } // Enabled returns if either metrics or traces are enabled diff --git a/config/lura.go b/config/lura.go index 2de0e26..9e8c32c 100644 --- a/config/lura.go +++ b/config/lura.go @@ -24,14 +24,8 @@ var ErrNoConfig = errors.New("no config found for opentelemetry") // In case no "Layers" config is provided, a set of defaults with // everything enabled will be used. func FromLura(srvCfg luraconfig.ServiceConfig) (*ConfigData, error) { - cfg := new(ConfigData) - tmp, ok := srvCfg.ExtraConfig[Namespace] - if !ok { - return nil, ErrNoConfig - } - buf := new(bytes.Buffer) - json.NewEncoder(buf).Encode(tmp) - if err := json.NewDecoder(buf).Decode(cfg); err != nil { + cfg, err := LuraExtraCfg(srvCfg.ExtraConfig) + if err != nil { return nil, err } @@ -46,3 +40,24 @@ func FromLura(srvCfg luraconfig.ServiceConfig) (*ConfigData, error) { cfg.UnsetFieldsToDefaults() return cfg, nil } + +// LuraExtraCfg extracts the extra config field for the namespace if +// provided +func LuraExtraCfg(extraCfg luraconfig.ExtraConfig) (*ConfigData, error) { + tmp, ok := extraCfg[Namespace] + if !ok { + return nil, ErrNoConfig + } + + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(tmp); err != nil { + return nil, err + } + + cfg := new(ConfigData) + if err := json.NewDecoder(buf).Decode(cfg); err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/example/Makefile b/example/Makefile index 4df2068..b60abf7 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,8 +1,8 @@ KRAKEND_LOCAL_IP ?= 192.168.1.12 -build: +srv: go build -o srv ./server -.PHONY: build +.PHONY: srv image: docker build -f ./server/Dockerfile -t krakend-otel-example:latest .. diff --git a/example/docker_compose/conf/krakend_back/configuration.json b/example/docker_compose/conf/krakend_back/configuration.json index e8c42ad..4a9db0e 100644 --- a/example/docker_compose/conf/krakend_back/configuration.json +++ b/example/docker_compose/conf/krakend_back/configuration.json @@ -28,6 +28,40 @@ }, { "endpoint": "/back_combination/{id}", + "extra_config": { + "telemetry/opentelemetry": { + "layers": { + "global": { + "metrics_static_attributes": [ + { + "key": "my_metric_global_override_attr", + "value": "my_metric_global_override_val" + } + ], + "traces_static_attributes": [ + { + "key": "my_trace_global_override_attr", + "value": "my_trace_global_override_val" + } + ] + }, + "proxy": { + "metrics_static_attributes": [ + { + "key": "my_metric_proxy_override_attr", + "value": "my_metric_proxy_override_val" + } + ], + , + "traces_static_attributes": [ + { + "key": "my_trace_proxy_override_attr", + "value": "my_trace_proxy_override_val" + } + ] + } + } + }, "backend": [ { "host": [ @@ -37,6 +71,30 @@ "is_collection": true, "mapping": { "collection": "posts" + }, + "extra_config": { + "telemetry/opentelemetry": { + "layers": { + "backend": { + "metrics": { + "static_attributes": [ + { + "key": "my_metric_backend_override_attr", + "value": "my_metric_backend_override_val" + } + ] + }, + "traces": { + "static_attributes": [ + { + "key": "my_trace_backend_override_attr", + "value": "my_trace_backend_override_val" + } + ] + } + } + } + } } }, { @@ -60,12 +118,36 @@ "global": { "disable_metrics": false, "disable_traces": false, - "disable_propagation": false + "disable_propagation": false, + "metrics_static_attributes": [ + { + "key": "my_metric_global_attr", + "value": "my_metric_global_val" + } + ], + "traces_static_attributes": [ + { + "key": "my_trace_global_attr", + "value": "my_trace_global_val" + } + ] }, "proxy": { "disable_metrics": false, - "disable_traces": false - }, + "disable_traces": false, + "metrics_static_attributes": [ + { + "key": "my_metric_proxy_attr", + "value": "my_metric_proxy_val" + } + ], + "traces_static_attributes": [ + { + "key": "my_trace_proxy_attr", + "value": "my_trace_proxy_val" + } + ] + }, "backend": { "metrics": { "disable_stage": false, @@ -74,8 +156,8 @@ "detailed_connection": true, "static_attributes": [ { - "key": "my_metric_attr", - "value": "my_metric_val" + "key": "my_metric_backend_attr", + "value": "my_metric_backend_val" } ] }, @@ -86,8 +168,8 @@ "detailed_connection": true, "static_attributes": [ { - "key": "my_trace_attr", - "value": "my_trace_val" + "key": "my_trace_backend_attr", + "value": "my_trace_backend_val" } ] } diff --git a/http/server/metrics.go b/http/server/metrics.go index b5ddd87..350e0db 100644 --- a/http/server/metrics.go +++ b/http/server/metrics.go @@ -5,7 +5,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/semconv/v1.21.0" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" kotelconfig "github.com/krakend/krakend-otel/config" ) @@ -36,11 +36,11 @@ func (m *metricsHTTP) report(t *tracking, r *http.Request) { if m == nil || m.latency == nil { return } - dynAttrsOpts := metric.WithAttributes( - semconv.HTTPRoute(t.EndpointPattern()), - semconv.HTTPRequestMethodKey.String(r.Method), - semconv.HTTPResponseStatusCode(t.responseStatus), - ) + dynAttrs := t.metricsStaticAttrs + dynAttrs = append(dynAttrs, semconv.HTTPRoute(t.EndpointPattern())) + dynAttrs = append(dynAttrs, semconv.HTTPRequestMethodKey.String(r.Method)) + dynAttrs = append(dynAttrs, semconv.HTTPResponseStatusCode(t.responseStatus)) + dynAttrsOpts := metric.WithAttributes(dynAttrs...) m.latency.Record(t.ctx, t.latencyInSecs, m.fixedAttrsOpts, dynAttrsOpts) m.size.Record(t.ctx, int64(t.responseSize), m.fixedAttrsOpts, dynAttrsOpts) } diff --git a/http/server/server.go b/http/server/server.go index 767557d..ba56235 100644 --- a/http/server/server.go +++ b/http/server/server.go @@ -74,14 +74,26 @@ func NewTrackingHandler(next http.Handler) http.Handler { var m *metricsHTTP if !gCfg.DisableMetrics { - m = newMetricsHTTP(s.Meter(), []attribute.KeyValue{}) + var metricsAttrs []attribute.KeyValue + for _, kv := range gCfg.MetricsStaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + metricsAttrs = append(metricsAttrs, attribute.String(kv.Key, kv.Value)) + } + } + + m = newMetricsHTTP(s.Meter(), metricsAttrs) } var t *tracesHTTP if !gCfg.DisableTraces { - t = newTracesHTTP(s.Tracer(), []attribute.KeyValue{ - attribute.String("krakend.stage", "global"), - }, gCfg.ReportHeaders) + tracesAttrs := []attribute.KeyValue{attribute.String("krakend.stage", "global")} + for _, kv := range gCfg.TracesStaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + tracesAttrs = append(tracesAttrs, attribute.String(kv.Key, kv.Value)) + } + } + + t = newTracesHTTP(s.Tracer(), tracesAttrs, gCfg.ReportHeaders) } return &trackingHandler{ diff --git a/http/server/traces.go b/http/server/traces.go index 9283be2..10a8991 100644 --- a/http/server/traces.go +++ b/http/server/traces.go @@ -6,7 +6,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/semconv/v1.21.0" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" otelhttp "github.com/krakend/krakend-otel/http" @@ -71,6 +71,7 @@ func (t *tracesHTTP) end(tr *tracking) { semconv.HTTPRoute(tr.EndpointPattern()), semconv.HTTPResponseStatusCode(tr.responseStatus), semconv.HTTPResponseBodySize(tr.responseSize)) + tr.span.SetAttributes(tr.tracesStaticAttrs...) if tr.responseHeaders != nil { // report all incoming headers diff --git a/http/server/tracking.go b/http/server/tracking.go index b640333..4807738 100644 --- a/http/server/tracking.go +++ b/http/server/tracking.go @@ -4,6 +4,7 @@ import ( "context" "time" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) @@ -21,14 +22,16 @@ type tracking struct { ctx context.Context span trace.Span - latencyInSecs float64 - responseSize int - responseStatus int - responseHeaders map[string][]string - writeErrs []error - endpointPattern string - isHijacked bool - hijackedErr error + latencyInSecs float64 + responseSize int + responseStatus int + responseHeaders map[string][]string + writeErrs []error + endpointPattern string + isHijacked bool + metricsStaticAttrs []attribute.KeyValue + tracesStaticAttrs []attribute.KeyValue + hijackedErr error } func (t *tracking) EndpointPattern() string { @@ -41,6 +44,14 @@ func (t *tracking) EndpointPattern() string { return t.endpointPattern } +func (t *tracking) MetricsStaticAttributes() []attribute.KeyValue { + return t.metricsStaticAttrs +} + +func (t *tracking) TracesStaticAttributes() []attribute.KeyValue { + return t.tracesStaticAttrs +} + func newTracking() *tracking { return &tracking{ responseStatus: 200, @@ -64,6 +75,15 @@ func SetEndpointPattern(ctx context.Context, endpointPattern string) { } } +// SetStaticAttributtes allows to set metrics and traces static attributes in +// the request context +func SetStaticAttributtes(ctx context.Context, metricAttrs, tracesAttrs []attribute.KeyValue) { + if t := fromContext(ctx); t != nil { + t.metricsStaticAttrs = metricAttrs + t.tracesStaticAttrs = tracesAttrs + } +} + func (t *tracking) Start() { t.startTime = time.Now() } diff --git a/lura/proxy.go b/lura/proxy.go index 8f2f790..e050812 100644 --- a/lura/proxy.go +++ b/lura/proxy.go @@ -5,7 +5,7 @@ import ( "time" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/semconv/v1.21.0" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "github.com/luraproject/lura/v2/config" "github.com/luraproject/lura/v2/proxy" @@ -48,20 +48,25 @@ func metricsAndTracesMiddleware(next proxy.Proxy, mm *middlewareMeter, mt *middl // middleware creates a proxy that instruments the proxy it wraps by creating an span if enabled, // and report the duration of this stage in metrics if enabled. func middleware(gs state.OTEL, metricsEnabled bool, tracesEnabled bool, - stageName string, urlPattern string, attrs []attribute.KeyValue, reportHeaders bool, + stageName string, urlPattern string, metricsAttrs, tracesAttrs []attribute.KeyValue, + reportHeaders bool, ) proxy.Middleware { var mt *middlewareTracer var mm *middlewareMeter var err error if metricsEnabled { - mm, err = newMiddlewareMeter(gs, stageName, attrs) + mm, err = newMiddlewareMeter(gs, stageName, metricsAttrs) if err != nil { // TODO: log the error metricsEnabled = false } } if tracesEnabled { - mt = newMiddlewareTracer(gs, urlPattern, stageName, reportHeaders, attrs) + mt = newMiddlewareTracer(gs, urlPattern, stageName, reportHeaders, tracesAttrs) + if mt == nil { + // TODO: log the error + tracesEnabled = false + } } return func(next ...proxy.Proxy) proxy.Proxy { @@ -114,8 +119,24 @@ func ProxyFactory(pf proxy.Factory) proxy.FactoryFunc { semconv.HTTPRequestMethodKey.String(cfg.Method), semconv.HTTPRoute(urlPattern), } + + // Add configured static attributes + metricsAttrs := attrs + tracesAttrs := attrs + for _, kv := range pipeOpts.MetricsStaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + metricsAttrs = append(metricsAttrs, attribute.String(kv.Key, kv.Value)) + } + } + + for _, kv := range pipeOpts.TracesStaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + tracesAttrs = append(tracesAttrs, attribute.String(kv.Key, kv.Value)) + } + } + return middleware(gs, !pipeOpts.DisableMetrics, !pipeOpts.DisableTraces, - "proxy", urlPattern, attrs, pipeOpts.ReportHeaders)(next), nil + "proxy", urlPattern, metricsAttrs, tracesAttrs, pipeOpts.ReportHeaders)(next), nil } } @@ -138,10 +159,6 @@ func BackendFactory(bf proxy.BackendFactory) proxy.BackendFactory { if metricsDisabled && tracesDisabled { return next } - reportHeaders := false - if backendOpts.Traces != nil && backendOpts.Traces.ReportHeaders { - reportHeaders = true - } gs := otelCfg.BackendOTEL(cfg) urlPattern := kotelconfig.NormalizeURLPattern(cfg.URLPattern) @@ -152,7 +169,30 @@ func BackendFactory(bf proxy.BackendFactory) proxy.BackendFactory { attribute.String("krakend.endpoint.route", parentEndpoint), attribute.String("krakend.endpoint.method", cfg.ParentEndpointMethod), } + + // Add configured static attributes + metricsAttrs := attrs + tracesAttrs := attrs + if backendOpts.Metrics != nil { + for _, kv := range backendOpts.Metrics.StaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + metricsAttrs = append(metricsAttrs, attribute.String(kv.Key, kv.Value)) + } + } + } + + reportHeaders := false + if backendOpts.Traces != nil { + reportHeaders = backendOpts.Traces.ReportHeaders + for _, kv := range backendOpts.Traces.StaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + tracesAttrs = append(tracesAttrs, attribute.String(kv.Key, kv.Value)) + } + } + } + return middleware(gs, !metricsDisabled, !tracesDisabled, - "backend", urlPattern, attrs, reportHeaders)(next) + "backend", urlPattern, metricsAttrs, tracesAttrs, reportHeaders)(next) + } } diff --git a/lura/proxy_meter.go b/lura/proxy_meter.go index 22e4a87..5fe3160 100644 --- a/lura/proxy_meter.go +++ b/lura/proxy_meter.go @@ -19,10 +19,16 @@ type middlewareMeter struct { } func newMiddlewareMeter(s state.OTEL, stageName string, attrs []attribute.KeyValue) (*middlewareMeter, error) { + if s == nil { + return nil, errors.New("no OTEL state provided") + } mAttrs := make([]attribute.KeyValue, 0, len(attrs)) mAttrs = append(mAttrs, attrs...) meter := s.Meter() + if meter == nil { + return nil, errors.New("OTEL state returned nil meter") + } var err error durationName := "krakend." + stageName + ".duration" duration, err := meter.Float64Histogram(durationName, kotelconfig.TimeBucketsOpt) diff --git a/lura/proxy_tracer.go b/lura/proxy_tracer.go index 790f31b..ca55e22 100644 --- a/lura/proxy_tracer.go +++ b/lura/proxy_tracer.go @@ -23,6 +23,9 @@ type middlewareTracer struct { func newMiddlewareTracer(s state.OTEL, name string, stageName string, reportHeaders bool, attrs []attribute.KeyValue) *middlewareTracer { tracer := s.Tracer() + if tracer == nil { + return nil + } tAttrs := make([]attribute.KeyValue, 0, len(attrs)+1) tAttrs = append(tAttrs, attrs...) tAttrs = append(tAttrs, attribute.String("krakend.stage", stageName)) diff --git a/router/gin/endpoint.go b/router/gin/endpoint.go index e95a721..382864d 100644 --- a/router/gin/endpoint.go +++ b/router/gin/endpoint.go @@ -5,6 +5,7 @@ import ( luraconfig "github.com/luraproject/lura/v2/config" "github.com/luraproject/lura/v2/proxy" krakendgin "github.com/luraproject/lura/v2/router/gin" + "go.opentelemetry.io/otel/attribute" kotelconfig "github.com/krakend/krakend-otel/config" kotelserver "github.com/krakend/krakend-otel/http/server" @@ -23,11 +24,30 @@ func New(hf krakendgin.HandlerFactory) krakendgin.HandlerFactory { } urlPattern := kotelconfig.NormalizeURLPattern(cfg.Endpoint) next := hf(cfg, p) + var metricsAttrs []attribute.KeyValue + var tracesAttrs []attribute.KeyValue + + cfgExtra, err := kotelconfig.LuraExtraCfg(cfg.ExtraConfig) + if err == nil && cfgExtra.Layers != nil && cfgExtra.Layers.Global != nil { + for _, kv := range cfgExtra.Layers.Global.MetricsStaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + metricsAttrs = append(metricsAttrs, attribute.String(kv.Key, kv.Value)) + } + } + + for _, kv := range cfgExtra.Layers.Global.TracesStaticAttributes { + if len(kv.Key) > 0 && len(kv.Value) > 0 { + tracesAttrs = append(tracesAttrs, attribute.String(kv.Key, kv.Value)) + } + } + } + return func(c *gin.Context) { // we set the matched route to a data struct stored in the // context by the outer http layer, so it can be reported // in metrics and traces. kotelserver.SetEndpointPattern(c.Request.Context(), urlPattern) + kotelserver.SetStaticAttributtes(c.Request.Context(), metricsAttrs, tracesAttrs) next(c) } } diff --git a/state/config.go b/state/config.go index 6ffa116..d652157 100644 --- a/state/config.go +++ b/state/config.go @@ -1,9 +1,8 @@ package state import ( - luraconfig "github.com/luraproject/lura/v2/config" - "github.com/krakend/krakend-otel/config" + luraconfig "github.com/luraproject/lura/v2/config" ) type Config interface { @@ -40,20 +39,96 @@ func (*StateConfig) EndpointOTEL(_ *luraconfig.EndpointConfig) OTEL { return GlobalState() } -func (s *StateConfig) EndpointPipeOpts(_ *luraconfig.EndpointConfig) *config.PipeOpts { - return s.cfgData.Layers.Pipe +func (s *StateConfig) EndpointPipeOpts(cfg *luraconfig.EndpointConfig) *config.PipeOpts { + var sOpts *config.PipeOpts + var extraPOpts *config.PipeOpts + + if s != nil && s.cfgData.Layers != nil { + sOpts = s.cfgData.Layers.Pipe + } + + cfgExtra, err := config.LuraExtraCfg(cfg.ExtraConfig) + if err == nil && cfgExtra != nil && cfgExtra.Layers != nil { + extraPOpts = cfgExtra.Layers.Pipe + } + + if extraPOpts == nil { + if sOpts == nil { + return new(config.PipeOpts) + } + return sOpts + } else if sOpts == nil { + return extraPOpts + } + + pOpts := new(config.PipeOpts) + *pOpts = *sOpts + + pOpts.MetricsStaticAttributes = append( + pOpts.MetricsStaticAttributes, + cfgExtra.Layers.Pipe.MetricsStaticAttributes..., + ) + + pOpts.TracesStaticAttributes = append( + pOpts.TracesStaticAttributes, + cfgExtra.Layers.Pipe.TracesStaticAttributes..., + ) + + return pOpts } -func (s *StateConfig) EndpointBackendOpts(_ *luraconfig.Backend) *config.BackendOpts { - return s.cfgData.Layers.Backend +func (s *StateConfig) EndpointBackendOpts(cfg *luraconfig.Backend) *config.BackendOpts { + return s.mergedBackendOpts(cfg) } func (*StateConfig) BackendOTEL(_ *luraconfig.Backend) OTEL { return GlobalState() } -func (s *StateConfig) BackendOpts(_ *luraconfig.Backend) *config.BackendOpts { - return s.cfgData.Layers.Backend +func (s *StateConfig) BackendOpts(cfg *luraconfig.Backend) *config.BackendOpts { + return s.mergedBackendOpts(cfg) +} + +func (s *StateConfig) mergedBackendOpts(cfg *luraconfig.Backend) *config.BackendOpts { + var extraBOpts *config.BackendOpts + var sOpts *config.BackendOpts + + if s != nil && s.cfgData.Layers != nil { + sOpts = s.cfgData.Layers.Backend + } + + cfgExtra, err := config.LuraExtraCfg(cfg.ExtraConfig) + if err == nil && cfgExtra != nil && cfgExtra.Layers != nil { + extraBOpts = cfgExtra.Layers.Backend + } + + if extraBOpts == nil { + if sOpts == nil { + return new(config.BackendOpts) + } + return sOpts + } else if sOpts == nil { + return extraBOpts + } + + bOpts := new(config.BackendOpts) + *bOpts = *sOpts + + if extraBOpts.Metrics != nil { + bOpts.Metrics.StaticAttributes = append( + bOpts.Metrics.StaticAttributes, + cfgExtra.Layers.Backend.Metrics.StaticAttributes..., + ) + } + + if extraBOpts.Traces != nil { + bOpts.Traces.StaticAttributes = append( + bOpts.Traces.StaticAttributes, + cfgExtra.Layers.Backend.Traces.StaticAttributes..., + ) + } + + return bOpts } func (s *StateConfig) SkipEndpoint(endpoint string) bool { diff --git a/state/config_test.go b/state/config_test.go new file mode 100644 index 0000000..3fc84f9 --- /dev/null +++ b/state/config_test.go @@ -0,0 +1,225 @@ +package state + +import ( + "testing" + + "github.com/krakend/krakend-otel/config" + luraconfig "github.com/luraproject/lura/v2/config" +) + +func TestEndpointPipeConfigOverride(t *testing.T) { + globalMetricAttrs := makeGlobalMetricAttr() + overrideMetricAttrs := makeOverrideMetricAttr() + expectedMetricAttrs := append(globalMetricAttrs, overrideMetricAttrs...) // skipcq: CRT-D0001 + + globalTraceAttrs := makeGlobalTraceAttr() + overrideTraceAttrs := makeOverrideTraceAttr() + expectedTraceAttrs := append(globalTraceAttrs, overrideTraceAttrs...) // skipcq: CRT-D0001 + + stateCfg := &StateConfig{ + cfgData: makePipeConf(globalMetricAttrs, globalTraceAttrs), + } + + pipeCfg := &luraconfig.EndpointConfig{ + ExtraConfig: map[string]interface{}{ + "telemetry/opentelemetry": makePipeConf(overrideMetricAttrs, overrideTraceAttrs), + }, + } + + pipeOpts := stateCfg.EndpointPipeOpts(pipeCfg) + + if len(pipeOpts.MetricsStaticAttributes) != len(expectedMetricAttrs) { + t.Errorf( + "Incorrect number of attributes for metrics. returned: %+v - expected: %+v", + pipeOpts.MetricsStaticAttributes, + expectedMetricAttrs, + ) + } + + if len(pipeOpts.TracesStaticAttributes) != len(expectedTraceAttrs) { + t.Errorf( + "Incorrect number of attributes for traces. returned: %+v - expected: %+v", + pipeOpts.TracesStaticAttributes, + expectedTraceAttrs, + ) + } +} + +func TestEndpointPipeNoOverride(t *testing.T) { + stateCfg := &StateConfig{ + cfgData: makePipeConf(makeGlobalMetricAttr(), makeGlobalTraceAttr()), + } + + // Empty config + pipeCfg := &luraconfig.EndpointConfig{ + ExtraConfig: map[string]interface{}{}, + } + + pipeOpts := stateCfg.EndpointPipeOpts(pipeCfg) + + if len(pipeOpts.MetricsStaticAttributes) > 1 { + t.Errorf( + "Incorrect number of attributes for metrics. returned: %+v", + pipeOpts.MetricsStaticAttributes, + ) + } +} + +func TestEndpointPipeConfigOnlyOverride(t *testing.T) { + stateCfg := &StateConfig{ + cfgData: makePipeConf([]config.KeyValue{}, []config.KeyValue{}), + } + + pipeCfg := &luraconfig.EndpointConfig{ + ExtraConfig: map[string]interface{}{ + "telemetry/opentelemetry": makePipeConf(makeOverrideMetricAttr(), makeOverrideTraceAttr()), + }, + } + + pipeOpts := stateCfg.EndpointPipeOpts(pipeCfg) + + if len(pipeOpts.MetricsStaticAttributes) > 1 { + t.Errorf( + "Incorrect number of attributes for metrics. returned: %+v", + pipeOpts.MetricsStaticAttributes, + ) + } +} + +func TestBackendConfigOverride(t *testing.T) { + globalMetricAttrs := makeGlobalMetricAttr() + overrideMetricAttrs := makeOverrideMetricAttr() + expectedMetricAttrs := append(globalMetricAttrs, overrideMetricAttrs...) // skipcq: CRT-D0001 + + globalTraceAttrs := makeGlobalTraceAttr() + overrideTraceAttrs := makeOverrideTraceAttr() + expectedTraceAttrs := append(globalTraceAttrs, overrideTraceAttrs...) // skipcq: CRT-D0001 + + stateCfg := &StateConfig{ + cfgData: makeBackendConf(globalMetricAttrs, globalTraceAttrs), + } + + backendCfg := &luraconfig.Backend{ + ExtraConfig: map[string]interface{}{ + "telemetry/opentelemetry": makeBackendConf(overrideMetricAttrs, overrideTraceAttrs), + }, + } + + backendOpts := stateCfg.BackendOpts(backendCfg) + + if len(backendOpts.Metrics.StaticAttributes) != len(expectedMetricAttrs) { + t.Errorf( + "Incorrect number of attributes for metrics. returned: %+v - expected: %+v", + backendOpts.Metrics.StaticAttributes, + expectedMetricAttrs, + ) + } + + if len(backendOpts.Traces.StaticAttributes) != len(expectedTraceAttrs) { + t.Errorf( + "Incorrect number of attributes for traces. returned: %+v - expected: %+v", + backendOpts.Traces.StaticAttributes, + expectedTraceAttrs, + ) + } +} + +func TestBackendConfigNoOverride(t *testing.T) { + stateCfg := &StateConfig{ + cfgData: makeBackendConf(makeGlobalMetricAttr(), makeGlobalTraceAttr()), + } + + // Empty config + backendCfg := &luraconfig.Backend{ + ExtraConfig: map[string]interface{}{}, + } + + backendOpts := stateCfg.BackendOpts(backendCfg) + + if len(backendOpts.Metrics.StaticAttributes) > 1 { + t.Errorf( + "Incorrect number of attributes for metrics. returned: %+v", + backendOpts.Traces.StaticAttributes, + ) + } +} + +func TestBackendConfigOnlyOverride(t *testing.T) { + stateCfg := &StateConfig{ + cfgData: makeBackendConf([]config.KeyValue{}, []config.KeyValue{}), + } + + backendCfg := &luraconfig.Backend{ + ExtraConfig: map[string]interface{}{ + "telemetry/opentelemetry": makeBackendConf(makeOverrideMetricAttr(), makeOverrideTraceAttr()), + }, + } + + backendOpts := stateCfg.BackendOpts(backendCfg) + + if len(backendOpts.Metrics.StaticAttributes) > 1 { + t.Errorf( + "Incorrect number of attributes for metrics. returned: %+v", + backendOpts.Traces.StaticAttributes, + ) + } +} + +func makePipeConf(metricAttrs, traceAttrs []config.KeyValue) config.ConfigData { + return config.ConfigData{ + Layers: &config.LayersOpts{ + Pipe: makePipeOpts(metricAttrs, traceAttrs), + }, + } +} + +func makePipeOpts(metricAttrs, traceAttrs []config.KeyValue) *config.PipeOpts { + return &config.PipeOpts{ + MetricsStaticAttributes: metricAttrs, + TracesStaticAttributes: traceAttrs, + } +} + +func makeBackendConf(metricAttrs, traceAttrs []config.KeyValue) config.ConfigData { + return config.ConfigData{ + Layers: &config.LayersOpts{ + Backend: makeBackendOpts(metricAttrs, traceAttrs), + }, + } +} + +func makeBackendOpts(metricAttrs, traceAttrs []config.KeyValue) *config.BackendOpts { + return &config.BackendOpts{ + Metrics: &config.BackendMetricOpts{ + StaticAttributes: metricAttrs, + }, + Traces: &config.BackendTraceOpts{ + StaticAttributes: traceAttrs, + }, + } +} + +func makeGlobalMetricAttr() []config.KeyValue { + return makeStaticAttr("my_metric_key", "my_metric_value") +} + +func makeOverrideMetricAttr() []config.KeyValue { + return makeStaticAttr("my_metric_override_key", "my_metric_override_value") +} + +func makeGlobalTraceAttr() []config.KeyValue { + return makeStaticAttr("my_trace_key", "my_trace_value") +} + +func makeOverrideTraceAttr() []config.KeyValue { + return makeStaticAttr("my_trace_override_key", "my_trace_override_value") +} + +func makeStaticAttr(key, value string) []config.KeyValue { + return []config.KeyValue{ + { + Key: key, + Value: value, + }, + } +} diff --git a/state/state.go b/state/state.go index cd0dd22..c11dd21 100644 --- a/state/state.go +++ b/state/state.go @@ -126,6 +126,9 @@ func NewWithVersion(serviceName string, cfg *OTELStateConfig, version string, // Tracer returns a tracer to start a span. func (s *OTELState) Tracer() trace.Tracer { + if s == nil { + return nil + } return s.tracer }