mirror of
https://github.com/hanzoai/krakend-otel.git
synced 2026-11-03 01:12:56 +00:00
200 lines
6.1 KiB
Go
200 lines
6.1 KiB
Go
package lura
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
|
|
|
"github.com/luraproject/lura/v2/config"
|
|
"github.com/luraproject/lura/v2/proxy"
|
|
|
|
kotelconfig "github.com/krakend/krakend-otel/config"
|
|
"github.com/krakend/krakend-otel/state"
|
|
)
|
|
|
|
func tracesMiddleware(next proxy.Proxy, mt *middlewareTracer) func(ctx context.Context, req *proxy.Request) (*proxy.Response, error) {
|
|
return func(ctx context.Context, req *proxy.Request) (*proxy.Response, error) {
|
|
ctx, span := mt.start(ctx, req)
|
|
resp, err := next(ctx, req)
|
|
mt.end(span, resp, err)
|
|
return resp, err
|
|
}
|
|
}
|
|
|
|
func metricsMiddleware(next proxy.Proxy, mm *middlewareMeter) func(ctx context.Context, req *proxy.Request) (*proxy.Response, error) {
|
|
return func(ctx context.Context, req *proxy.Request) (*proxy.Response, error) {
|
|
startedAt := time.Now()
|
|
resp, err := next(ctx, req)
|
|
durationInSecs := float64(time.Since(startedAt)) / float64(time.Second)
|
|
mm.report(ctx, durationInSecs, resp, err)
|
|
return resp, err
|
|
}
|
|
}
|
|
|
|
func metricsAndTracesMiddleware(next proxy.Proxy, mm *middlewareMeter, mt *middlewareTracer) func(ctx context.Context, req *proxy.Request) (*proxy.Response, error) {
|
|
return func(ctx context.Context, req *proxy.Request) (*proxy.Response, error) {
|
|
ctx, span := mt.start(ctx, req)
|
|
startedAt := time.Now()
|
|
resp, err := next(ctx, req)
|
|
durationInSecs := float64(time.Since(startedAt)) / float64(time.Second)
|
|
mm.report(ctx, durationInSecs, resp, err)
|
|
mt.end(span, resp, err)
|
|
return resp, err
|
|
}
|
|
}
|
|
|
|
// 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, metricsAttrs, tracesAttrs []attribute.KeyValue,
|
|
reportHeaders bool, skipHeaders []string,
|
|
) proxy.Middleware {
|
|
var mt *middlewareTracer
|
|
var mm *middlewareMeter
|
|
var err error
|
|
if metricsEnabled {
|
|
mm, err = newMiddlewareMeter(gs, stageName, metricsAttrs)
|
|
if err != nil {
|
|
// TODO: log the error
|
|
metricsEnabled = false
|
|
}
|
|
}
|
|
if tracesEnabled {
|
|
mt = newMiddlewareTracer(gs, urlPattern, stageName, reportHeaders, skipHeaders, tracesAttrs)
|
|
if mt == nil {
|
|
// TODO: log the error
|
|
tracesEnabled = false
|
|
}
|
|
}
|
|
|
|
return func(next ...proxy.Proxy) proxy.Proxy {
|
|
if len(next) > 1 {
|
|
panic(proxy.ErrTooManyProxies)
|
|
}
|
|
if len(next) < 1 {
|
|
panic(proxy.ErrNotEnoughProxies)
|
|
}
|
|
n := next[0]
|
|
|
|
if metricsEnabled {
|
|
if tracesEnabled {
|
|
return metricsAndTracesMiddleware(n, mm, mt)
|
|
}
|
|
return metricsMiddleware(n, mm)
|
|
} else if tracesEnabled {
|
|
return tracesMiddleware(n, mt)
|
|
}
|
|
return n
|
|
}
|
|
}
|
|
|
|
// ProxyFactory returns a proxy stage factory that wraps the provided proxy factory with the
|
|
// instrumentation [Middleware] based on the configuration options.
|
|
func ProxyFactory(pf proxy.Factory) proxy.FactoryFunc {
|
|
otelCfg := state.GlobalConfig()
|
|
if otelCfg == nil {
|
|
return pf.New
|
|
}
|
|
|
|
return func(cfg *config.EndpointConfig) (proxy.Proxy, error) {
|
|
next, err := pf.New(cfg)
|
|
if err != nil {
|
|
return next, err
|
|
}
|
|
|
|
if otelCfg.SkipEndpoint(cfg.Endpoint) {
|
|
return next, nil
|
|
}
|
|
|
|
pipeOpts := otelCfg.EndpointPipeOpts(cfg)
|
|
if pipeOpts.DisableMetrics && pipeOpts.DisableTraces {
|
|
return next, nil
|
|
}
|
|
|
|
gs := otelCfg.EndpointOTEL(cfg)
|
|
urlPattern := kotelconfig.NormalizeURLPattern(cfg.Endpoint)
|
|
attrs := []attribute.KeyValue{
|
|
semconv.HTTPRequestMethodKey.String(cfg.Method),
|
|
semconv.HTTPRoute(urlPattern),
|
|
}
|
|
|
|
// Add configured static attributes
|
|
metricsAttrs := attrs
|
|
tracesAttrs := attrs
|
|
for _, kv := range pipeOpts.MetricsStaticAttributes {
|
|
if kv.Key != "" && kv.Value != "" {
|
|
metricsAttrs = append(metricsAttrs, attribute.String(kv.Key, kv.Value))
|
|
}
|
|
}
|
|
|
|
for _, kv := range pipeOpts.TracesStaticAttributes {
|
|
if kv.Key != "" && kv.Value != "" {
|
|
tracesAttrs = append(tracesAttrs, attribute.String(kv.Key, kv.Value))
|
|
}
|
|
}
|
|
|
|
return middleware(gs, !pipeOpts.DisableMetrics, !pipeOpts.DisableTraces,
|
|
"proxy", urlPattern, metricsAttrs, tracesAttrs, pipeOpts.ReportHeaders,
|
|
pipeOpts.SkipHeaders)(next), nil
|
|
}
|
|
}
|
|
|
|
// BackendFactory returns a backend factory that wraps the provided backend factory with the
|
|
// instrumentation [Middleware] based on the configuration options.
|
|
func BackendFactory(bf proxy.BackendFactory) proxy.BackendFactory {
|
|
otelCfg := state.GlobalConfig()
|
|
if otelCfg == nil {
|
|
return bf
|
|
}
|
|
|
|
return func(cfg *config.Backend) proxy.Proxy {
|
|
next := bf(cfg)
|
|
if otelCfg.SkipEndpoint(cfg.ParentEndpoint) {
|
|
return next
|
|
}
|
|
backendOpts := otelCfg.BackendOpts(cfg)
|
|
metricsDisabled := backendOpts != nil && backendOpts.Metrics != nil && backendOpts.Metrics.DisableStage
|
|
tracesDisabled := backendOpts != nil && backendOpts.Traces != nil && backendOpts.Traces.DisableStage
|
|
if metricsDisabled && tracesDisabled {
|
|
return next
|
|
}
|
|
|
|
gs := otelCfg.BackendOTEL(cfg)
|
|
urlPattern := kotelconfig.NormalizeURLPattern(cfg.URLPattern)
|
|
parentEndpoint := kotelconfig.NormalizeURLPattern(cfg.ParentEndpoint)
|
|
attrs := []attribute.KeyValue{
|
|
semconv.HTTPRequestMethodKey.String(cfg.Method),
|
|
semconv.HTTPRoute(urlPattern), // <- for traces we can use URLFull to not have the matched path
|
|
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 kv.Key != "" && kv.Value != "" {
|
|
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 kv.Key != "" && kv.Value != "" {
|
|
tracesAttrs = append(tracesAttrs, attribute.String(kv.Key, kv.Value))
|
|
}
|
|
}
|
|
}
|
|
|
|
return middleware(gs, !metricsDisabled, !tracesDisabled,
|
|
"backend", urlPattern, metricsAttrs, tracesAttrs, reportHeaders,
|
|
backendOpts.Traces.SkipHeaders)(next)
|
|
}
|
|
}
|