Ver código fonte

Fixed TraceIdRatioBased sampler; IMGPROXY_OPEN_TELEMETRY_TRACE_ID_GENERATOR config

DarthSim 2 anos atrás
pai
commit
f08cc76806
6 arquivos alterados com 145 adições e 5 exclusões
  1. 2 0
      CHANGELOG.md
  2. 3 0
      config/config.go
  3. 1 0
      docs/configuration.md
  4. 3 0
      docs/open_telemetry.md
  5. 18 5
      metrics/otel/otel.go
  6. 118 0
      metrics/otel/sampler.go

+ 2 - 0
CHANGELOG.md

@@ -3,11 +3,13 @@
 ## [Unreleased]
 ### Add
 - Add `IMGPROXY_OPEN_TELEMETRY_GRPC_INSECURE` config.
+- Add `IMGPROXY_OPEN_TELEMETRY_TRACE_ID_GENERATOR` config.
 - (pro) Add XMP data to the `/info` response.
 
 ### Change
 - Better XMP data stripping.
 - Use parent-based OpenTelemetry sampler by default.
+- Use fixed TraceIdRatioBased sampler with OpenTelemetry.
 
 ## [3.10.0] - 2022-11-04
 ### Add

+ 3 - 0
config/config.go

@@ -152,6 +152,7 @@ var (
 	OpenTelemetryClientKey         string
 	OpenTelemetryGRPCInsecure      bool
 	OpenTelemetryPropagators       []string
+	OpenTelemetryTraceIDGenerator  string
 	OpenTelemetryConnectionTimeout int
 
 	BugsnagKey   string
@@ -331,6 +332,7 @@ func Reset() {
 	OpenTelemetryClientKey = ""
 	OpenTelemetryGRPCInsecure = true
 	OpenTelemetryPropagators = make([]string, 0)
+	OpenTelemetryTraceIDGenerator = "xray"
 	OpenTelemetryConnectionTimeout = 5
 
 	BugsnagKey = ""
@@ -522,6 +524,7 @@ func Configure() error {
 	configurators.String(&OpenTelemetryClientKey, "IMGPROXY_OPEN_TELEMETRY_CLIENT_KEY")
 	configurators.Bool(&OpenTelemetryGRPCInsecure, "IMGPROXY_OPEN_TELEMETRY_GRPC_INSECURE")
 	configurators.StringSlice(&OpenTelemetryPropagators, "IMGPROXY_OPEN_TELEMETRY_PROPAGATORS")
+	configurators.String(&OpenTelemetryTraceIDGenerator, "IMGPROXY_OPEN_TELEMETRY_TRACE_ID_GENERATOR")
 	configurators.Int(&OpenTelemetryConnectionTimeout, "IMGPROXY_OPEN_TELEMETRY_CONNECTION_TIMEOUT")
 
 	configurators.String(&BugsnagKey, "IMGPROXY_BUGSNAG_KEY")

+ 1 - 0
docs/configuration.md

@@ -409,6 +409,7 @@ imgproxy can send request traces to an OpenTelemetry collector:
 * `IMGPROXY_OPEN_TELEMETRY_CLIENT_KEY`: OpenTelemetry client TLS key, PEM-encoded. Default: blank
 * `IMGPROXY_OPEN_TELEMETRY_GRPC_INSECURE`: when `true`, imgproxy will use an insecure GRPC connection unless the collector TLS certificate is not provided. Default: `true`
 * `IMGPROXY_OPEN_TELEMETRY_PROPAGATORS`: a list of OpenTelemetry text map propagators, comma divided. Supported propagators are `tracecontext`, `baggage`, `b3`, `b3multi`, `jaeger`, `xray`, and `ottrace`. Default: blank
+* `IMGPROXY_OPEN_TELEMETRY_TRACE_ID_GENERATOR`: OpenTelemetry trace ID generator. Supported generators are `xray` and `random`. Default: `xray`
 * `IMGPROXY_OPEN_TELEMETRY_CONNECTION_TIMEOUT`: the maximum duration (in seconds) for establishing a connection to the OpenTelemetry collector. Default: `5`
 
 Check out the [OpenTelemetry](open_telemetry.md) guide to learn more.

+ 3 - 0
docs/open_telemetry.md

@@ -18,6 +18,9 @@ imgproxy can send request traces to an OpenTelemetry collector. To use this feat
     * `ottrace`: [OT Trace](https://github.com/opentracing?q=basic&type=&language=)
 5. _(optional)_ [Set up TLS certificates](#tls-configuration) or set `IMGPROXY_OPEN_TELEMETRY_GRPC_INSECURE` to `false` to use secure connection without TLS certificates set.
 6. _(optional)_ Set `IMGPROXY_OPEN_TELEMETRY_ENABLE_METRICS` to `true` to enable sending metrics via OpenTelemetry Metrics API.
+7. _(optional)_ Set `IMGPROXY_OPEN_TELEMETRY_TRACE_ID_GENERATOR` to environment variable to be the desired trace ID generator. Supported values are:
+    * `xray`: _(default)_ Amazon X-Ray compatible trace ID generator
+    * `random`: random trace ID generator
 
 imgproxy will send the following info to the collector:
 

+ 18 - 5
metrics/otel/otel.go

@@ -101,13 +101,26 @@ func Init() error {
 		res, _ = resource.Merge(res, awsRes)
 	}
 
-	idg := xray.NewIDGenerator()
-
-	tracerProvider = sdktrace.NewTracerProvider(
+	opts := []sdktrace.TracerProviderOption{
 		sdktrace.WithResource(res),
 		sdktrace.WithBatcher(traceExporter),
-		sdktrace.WithIDGenerator(idg),
-	)
+	}
+
+	if opts, err = addTraceIDRatioSampler(opts); err != nil {
+		return err
+	}
+
+	switch g := config.OpenTelemetryTraceIDGenerator; g {
+	case "xray":
+		idg := xray.NewIDGenerator()
+		opts = append(opts, sdktrace.WithIDGenerator(idg))
+	case "random":
+		// Do nothing. OTel uses random generator by default
+	default:
+		return fmt.Errorf("Unknown Trace ID generator: %s", g)
+	}
+
+	tracerProvider = sdktrace.NewTracerProvider(opts...)
 
 	tracer = tracerProvider.Tracer("imgproxy")
 

+ 118 - 0
metrics/otel/sampler.go

@@ -0,0 +1,118 @@
+package otel
+
+import (
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	sdktrace "go.opentelemetry.io/otel/sdk/trace"
+	"go.opentelemetry.io/otel/trace"
+)
+
+const (
+	tracesSamplerKey    = "OTEL_TRACES_SAMPLER"
+	tracesSamplerArgKey = "OTEL_TRACES_SAMPLER_ARG"
+
+	samplerTraceIDRatio = "traceidratio"
+)
+
+var (
+	errNegativeTraceIDRatio       = errors.New("invalid trace ID ratio: less than 0.0")
+	errGreaterThanOneTraceIDRatio = errors.New("invalid trace ID ratio: greater than 1.0")
+)
+
+type samplerArgParseError struct {
+	parseErr error
+}
+
+func (e samplerArgParseError) Error() string {
+	return fmt.Sprintf("parsing sampler argument: %s", e.parseErr.Error())
+}
+
+func (e samplerArgParseError) Unwrap() error {
+	return e.parseErr
+}
+
+type traceIDRatioSampler struct {
+	traceIDUpperBound uint64
+	description       string
+}
+
+func (ts traceIDRatioSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
+	psc := trace.SpanContextFromContext(p.ParentContext)
+	x := binary.BigEndian.Uint64(p.TraceID[len(p.TraceID)-8:]) >> 1
+	if x < ts.traceIDUpperBound {
+		return sdktrace.SamplingResult{
+			Decision:   sdktrace.RecordAndSample,
+			Tracestate: psc.TraceState(),
+		}
+	}
+	return sdktrace.SamplingResult{
+		Decision:   sdktrace.Drop,
+		Tracestate: psc.TraceState(),
+	}
+}
+
+func (ts traceIDRatioSampler) Description() string {
+	return ts.description
+}
+
+func traceIDRatioBased(fraction float64) sdktrace.Sampler {
+	if fraction >= 1 {
+		return sdktrace.AlwaysSample()
+	}
+
+	if fraction <= 0 {
+		fraction = 0
+	}
+
+	return &traceIDRatioSampler{
+		traceIDUpperBound: uint64(fraction * (1 << 63)),
+		description:       fmt.Sprintf("traceIDRatioBased{%g}", fraction),
+	}
+}
+
+func parseTraceIDRatio(arg string) (sdktrace.Sampler, error) {
+	v, err := strconv.ParseFloat(arg, 64)
+	if err != nil {
+		return traceIDRatioBased(1.0), samplerArgParseError{err}
+	}
+	if v < 0.0 {
+		return traceIDRatioBased(0.0), errNegativeTraceIDRatio
+	}
+	if v > 1.0 {
+		return traceIDRatioBased(1.0), errGreaterThanOneTraceIDRatio
+	}
+
+	return traceIDRatioBased(v), nil
+}
+
+func addTraceIDRatioSampler(opts []sdktrace.TracerProviderOption) ([]sdktrace.TracerProviderOption, error) {
+	samplerName, ok := os.LookupEnv(tracesSamplerKey)
+	if !ok {
+		return opts, nil
+	}
+
+	if strings.ToLower(strings.TrimSpace(samplerName)) != samplerTraceIDRatio {
+		return opts, nil
+	}
+
+	samplerArg, hasSamplerArg := os.LookupEnv(tracesSamplerArgKey)
+	samplerArg = strings.TrimSpace(samplerArg)
+
+	var (
+		sampler sdktrace.Sampler
+		err     error
+	)
+
+	if !hasSamplerArg {
+		sampler = traceIDRatioBased(1.0)
+	} else {
+		sampler, err = parseTraceIDRatio(samplerArg)
+	}
+
+	return append(opts, sdktrace.WithSampler(sampler)), err
+}