diff --git a/go.mod b/go.mod index dca2cfeb24..5a9d182967 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,14 @@ require ( github.com/uber/jaeger-lib v2.4.1+incompatible github.com/yookoala/gofast v0.8.0 github.com/yuin/gopher-lua v1.1.1 + go.opentelemetry.io/contrib/propagators/b3 v1.28.0 + go.opentelemetry.io/contrib/propagators/ot v1.33.0 + go.opentelemetry.io/otel v1.33.0 + go.opentelemetry.io/otel/bridge/opentracing v1.33.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 + go.opentelemetry.io/otel/sdk v1.28.0 go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 @@ -160,16 +168,14 @@ require ( github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect @@ -183,4 +189,6 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -go 1.22 +go 1.22.0 + +toolchain go1.22.4 diff --git a/go.sum b/go.sum index 64968e8bbb..c79ce833f2 100644 --- a/go.sum +++ b/go.sum @@ -406,8 +406,8 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sarslanhan/cronmask v0.0.0-20230801193303-54e29300a091 h1:L644WnBAUw4546Wrt52yzuSPoV24t0ArlMwc5iRr8U0= github.com/sarslanhan/cronmask v0.0.0-20230801193303-54e29300a091/go.mod h1:qZKxttzn8iyVLtc7edFrmQper3FUBJsc/rHCONN2wIQ= @@ -502,22 +502,30 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/contrib/propagators/b3 v1.28.0 h1:XR6CFQrQ/ttAYmTBX2loUEFGdk1h17pxYI8828dk/1Y= +go.opentelemetry.io/contrib/propagators/b3 v1.28.0/go.mod h1:DWRkzJONLquRz7OJPh2rRbZ7MugQj62rk7g6HRnEqh0= +go.opentelemetry.io/contrib/propagators/ot v1.33.0 h1:xj/pQFKo4ROsx0v129KpLgFwaYMgFTu3dAMEEih97cY= +go.opentelemetry.io/contrib/propagators/ot v1.33.0/go.mod h1:/xxHCLhTmaypEFwMViRGROj2qgrGiFrkxIlATt0rddc= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/bridge/opentracing v1.33.0 h1:eH88qvKdY7ns7Xu6WlJBQNOzZ3MVvBR6tEl2euaYS9w= +go.opentelemetry.io/otel/bridge/opentracing v1.33.0/go.mod h1:FNai/nhRSn/kHyv+V1zaf/30BU8hO/DXo0MvV0PaUS8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -526,6 +534,8 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab h1:+yW1yrZ09EYNu1spCUOHBBNRbrLnfmutwyhbhCv3b6Q= go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/tracing/tracers/lightstepotelbridge/README.md b/tracing/tracers/lightstepotelbridge/README.md new file mode 100644 index 0000000000..95fe7bf75b --- /dev/null +++ b/tracing/tracers/lightstepotelbridge/README.md @@ -0,0 +1,28 @@ +# lightstep-otel-bridge tracer + +As with [other tracers](https://pkg.go.dev/github.com/zalando/skipper/tracing), the lightstep-otel-bridge tracer is configured by setting +`-opentracing="lightstep-otel-bridge OPTIONS"`. Valid options are: + +* `component-name` - set component name instead of `skipper` +* `access-token` - Access token for the lightstep satellites (REQUIRED) +* `protocol` - sets `UseGRPC` option to true if set to `"grpc"`, defaults to `"grpc"`, but can be set to `"http"` +* `tag` - key-value pairs (`key=value`) separated by commas (`,`) to set as tags + in every span +* `environment` - set the environment tag, defaults to `dev` +* `service-name` - set the service name tag, defaults to `skipper` +* `service-version` - set the service version tag, defaults to `unknown` +* `batch-size` - maximum number of spans to send in a batch +* `batch-timeout` - maximum time ms to wait before sending spans to the satellites +* `processor-queue-size` - maximum number of spans to queue before sending to the satellites +* `export-timeout` - maximum time to wait in ms for a batch to be sent +* `collector` - hostname (+port) - (e.g. `lightstep-satellites.example.org:4443`) to send the + spans to, i.e. your lightstep satellites +* `insecure-connection` (boolean) - force plaintext communication with satellites +* `propagators` - set propagators to use (i.e. format of http headers used for tracing). This can be used + to pick up traces from / to applications which only understand the B3 format (e.g. grafana where the + jaeger instrumentation can be switched to use the B3 format). This can be combined, e.g. `ottrace,b3` + should be used to pick up both formats (attempted in that order). + * `ottrace` - use the standard lightstep headers (default) + * `b3` - use the B3 propagation format + * `baggage` - use the baggage propagation format + * `tracecontext` - use the tracecontext propagation format diff --git a/tracing/tracers/lightstepotelbridge/bridge.go b/tracing/tracers/lightstepotelbridge/bridge.go new file mode 100644 index 0000000000..2d62b50ea3 --- /dev/null +++ b/tracing/tracers/lightstepotelbridge/bridge.go @@ -0,0 +1,385 @@ +package lightstepotelbridge + +import ( + "context" + "errors" + "fmt" + "github.com/opentracing/opentracing-go" + log "github.com/sirupsen/logrus" + "go.opentelemetry.io/contrib/propagators/b3" + ottrace "go.opentelemetry.io/contrib/propagators/ot" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + otelBridge "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" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "net" + "os" + "strconv" + "strings" + "time" +) + +const ( + defaultEndpoint = "ingest.lightstep.com:443" + defaultServiceName = "skipper" + defaultServiceVersion = "0.1.0" + defaultEnvironment = "dev" + defaultTracerName = "lightstep-otel-bridge" + defaultComponentName = "skipper" + urlPath = "traces/otlp/v0.9" + defaultPropagators = "ottrace,b3" + hostNameKey = "hostname" + lsEnvironmentKey = "environment" + defaultBatchTimeout = 2500 * time.Millisecond + defaultProcessorQueueSize = 10000 + defaultBatchSize = 512 + defaultExportTimeout = 5000 * time.Millisecond +) + +type Options struct { + Collector string + AccessToken string + Environment string + UseHttp bool + UseGrpc bool + UsePlainText bool + ComponentName string + ServiceName string + ServiceVersion string + BatchTimeout time.Duration + ProcessorQueueSize int + BatchSize int + ExportTimeout time.Duration + Hostname string + Propagators []propagation.TextMapPropagator + GlobalAttributes []attribute.KeyValue +} + +func parseOptions(opts []string) (Options, error) { + + var ( + serviceName string + serviceVersion string + endpoint string + lsToken string + lsEnvironment string + componentName string + propagators string + useHttp, useGrpc, usePlainText bool + err error + globalTags []attribute.KeyValue + hostname, _ = os.Hostname() + batchTimeout = defaultBatchTimeout + processorQueueSize = defaultProcessorQueueSize + batchSize = defaultBatchSize + exportTimeout = defaultExportTimeout + ) + + for _, o := range opts { + key, val, _ := strings.Cut(o, "=") + switch key { + case "collector": + var sport string + + _, sport, err = net.SplitHostPort(val) + if err != nil { + return Options{}, err + } + + _, err = strconv.Atoi(sport) + if err != nil { + return Options{}, fmt.Errorf("failed to parse %s as int: %w", sport, err) + } + endpoint = val + + case "access-token": + lsToken = val + case "environment": + lsEnvironment = val + case "protocol": + if strings.ToLower(val) == "http" { + useHttp = true + } else if strings.ToLower(val) == "grpc" { + useGrpc = true + } else { + return Options{}, fmt.Errorf("unsupported protocol %s", val) + } + case "insecure-connection": + usePlainText, err = strconv.ParseBool(val) + if err != nil { + return Options{}, fmt.Errorf("failed to parse %s as bool: %w", val, err) + } + case "component-name": + componentName = val + case "service-name": + serviceName = val + case "service-version": + serviceVersion = val + case "batch-timeout": + intVal, err := strconv.Atoi(val) + if err != nil { + return Options{}, errors.New("failed to parse batch-timeout as int") + } + batchTimeout = time.Duration(intVal) * time.Millisecond + case "processor-queue-size": + intVal, err := strconv.Atoi(val) + if err != nil { + return Options{}, errors.New("failed to parse processor-queue-size as int") + } + processorQueueSize = intVal + case "batch-size": + intVal, err := strconv.Atoi(val) + if err != nil { + return Options{}, errors.New("failed to parse batch-size as int") + } + batchSize = intVal + case "export-timeout": + intVal, err := strconv.Atoi(val) + if err != nil { + return Options{}, errors.New("failed to parse export-timeout as int") + } + exportTimeout = time.Duration(intVal) * time.Millisecond + case "propagators": + for _, p := range strings.Split(val, ",") { + switch strings.ToLower(p) { + case "tracecontext": + // no-op + case "baggage": + // no-op + case "ottrace": + // no-op + case "b3": + // no-op + default: + return Options{}, fmt.Errorf("unsupported propagator %s", p) + } + } + propagators = val + case "tag": + if val != "" { + tag, tagVal, found := strings.Cut(val, "=") + if !found { + return Options{}, fmt.Errorf("missing value for tag %s", val) + } + globalTags = append(globalTags, attribute.String(tag, tagVal)) + } + } + } + + if endpoint == "" { + endpoint = defaultEndpoint + } + + if lsToken == "" { + return Options{}, errors.New("missing Lightstep access token") + } + + if lsEnvironment == "" { + lsEnvironment = defaultEnvironment + } + + if !useHttp { + useGrpc = true + } + + if componentName == "" { + componentName = defaultComponentName + } + + if serviceName == "" { + serviceName = defaultServiceName + } + + if serviceVersion == "" { + serviceVersion = defaultServiceVersion + } + + if propagators == "" { + propagators = defaultPropagators + } + + var textMapPropagator []propagation.TextMapPropagator + + for _, prop := range strings.Split(propagators, ",") { + switch strings.ToLower(prop) { + case "tracecontext": + textMapPropagator = append(textMapPropagator, propagation.TraceContext{}) + case "baggage": + textMapPropagator = append(textMapPropagator, propagation.Baggage{}) + case "ottrace": + textMapPropagator = append(textMapPropagator, ottrace.OT{}) + case "b3": + textMapPropagator = append(textMapPropagator, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader|b3.B3SingleHeader))) + default: + return Options{}, fmt.Errorf("unsupported propagator %s", prop) + } + } + log.Infof("serviceName: %s, serviceVersion: %s, endpoint: %s, lsEnvironment: %s, componentName: %s, useHttp: %t, useGrpc: %t, usePlainText: %t, batchTimeout: %s, processorQueueSize: %d, batchSize: %d, exportTimeout: %s, hostname: %s, propagators: %s, globalTags: %v", serviceName, serviceVersion, endpoint, lsEnvironment, componentName, useHttp, useGrpc, usePlainText, batchTimeout, processorQueueSize, batchSize, exportTimeout, hostname, propagators, globalTags) + return Options{ + Collector: endpoint, + AccessToken: lsToken, + Environment: lsEnvironment, + UseHttp: useHttp, + UseGrpc: useGrpc, + UsePlainText: usePlainText, + ComponentName: componentName, + ServiceName: serviceName, + ServiceVersion: serviceVersion, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + Propagators: textMapPropagator, + GlobalAttributes: globalTags, + }, nil +} + +// setupOTelSDK bootstraps the OpenTelemetry pipeline. +// If it does not return an error, make sure to call shutdown for proper cleanup. +func setupOTelSDK(ctx context.Context, schemaUrl string, opts Options) (shutdown func(context.Context) error, err error) { + var shutdownFuncs []func(context.Context) error + + // shutdown calls cleanup functions registered via shutdownFuncs. + // The errors from the calls are joined. + // Each registered cleanup will be invoked once. + shutdown = func(ctx context.Context) error { + var err error + for _, fn := range shutdownFuncs { + err = errors.Join(err, fn(ctx)) + } + shutdownFuncs = nil + return err + } + + // handleErr calls shutdown for cleanup and makes sure that all errors are returned. + handleErr := func(inErr error) { + err = errors.Join(inErr, shutdown(ctx)) + } + + // Set up propagator. + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + opts.Propagators..., + ), + ) + + // Set up trace provider. + exp, _, err := newExporter(ctx, opts) + if err != nil { + handleErr(err) + return + } + + tracerProvider, err := newTraceProvider(exp, schemaUrl, resource.Default(), opts) + if err != nil { + handleErr(err) + return + } + + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + otel.SetTracerProvider(tracerProvider) + + return +} + +func newExporter(ctx context.Context, opt Options) (*otlptrace.Exporter, bool, error) { + + var headers = map[string]string{ + "lightstep-access-token": opt.AccessToken, + } + + var client otlptrace.Client + var isSecure = false + + if opt.UseHttp { + var tOpt []otlptracehttp.Option + tOpt = append(tOpt, otlptracehttp.WithHeaders(headers)) + tOpt = append(tOpt, otlptracehttp.WithEndpoint(opt.Collector)) + tOpt = append(tOpt, otlptracehttp.WithURLPath(urlPath)) + if opt.UsePlainText { + tOpt = append(tOpt, otlptracehttp.WithInsecure()) + isSecure = true + } + client = otlptracehttp.NewClient(tOpt...) + } else { + var tOpt []otlptracegrpc.Option + tOpt = append(tOpt, otlptracegrpc.WithHeaders(headers)) + tOpt = append(tOpt, otlptracegrpc.WithEndpoint(opt.Collector)) + if opt.UsePlainText { + tOpt = append(tOpt, otlptracegrpc.WithInsecure()) + isSecure = true + } + client = otlptracegrpc.NewClient(tOpt...) + } + + exp, err := otlptrace.New(ctx, client) + return exp, isSecure, err +} + +func newTraceProvider(exp *otlptrace.Exporter, schemaUrl string, r *resource.Resource, opt Options) (*sdktrace.TracerProvider, error) { + + opts := opt.GlobalAttributes + + opts = append( + opts, + semconv.ServiceName(opt.ServiceName), + semconv.HostName(opt.Hostname), + semconv.ServiceVersionKey.String(opt.ServiceVersion), + attribute.String(lsEnvironmentKey, opt.Environment), + attribute.String(hostNameKey, opt.Hostname), + ) + + r, err := resource.Merge( + r, + resource.NewWithAttributes( + schemaUrl, + opts..., + ), + ) + + if err != nil { + return nil, err + } + + return sdktrace.NewTracerProvider( + sdktrace.WithBatcher( + exp, + sdktrace.WithBatchTimeout(opt.BatchTimeout), + sdktrace.WithMaxExportBatchSize(opt.BatchSize), + sdktrace.WithMaxQueueSize(opt.ProcessorQueueSize), + sdktrace.WithExportTimeout(opt.ExportTimeout), + ), + sdktrace.WithResource(r), + ), nil +} + +func InitTracer(opts []string) opentracing.Tracer { + + options, err := parseOptions(opts) + if err != nil { + log.WithError(err).Error("failed to parse options") + return &opentracing.NoopTracer{} + } + + _, err = setupOTelSDK(context.Background(), semconv.SchemaURL, options) + if err != nil { + log.WithError(err).Error("failed to set up OpenTelemetry SDK") + return &opentracing.NoopTracer{} + } + + provider := otel.GetTracerProvider() + otelTracer := provider.Tracer(defaultTracerName) + bridgeTracer, wrapperTracerProvider := otelBridge.NewTracerPair(otelTracer) + otel.SetTracerProvider(wrapperTracerProvider) + + log.Infof("OpenTelemetry Lightstep bridge tracer initialized") + + return bridgeTracer +} diff --git a/tracing/tracers/lightstepotelbridge/bridge_test.go b/tracing/tracers/lightstepotelbridge/bridge_test.go new file mode 100644 index 0000000000..61e1a04321 --- /dev/null +++ b/tracing/tracers/lightstepotelbridge/bridge_test.go @@ -0,0 +1,818 @@ +package lightstepotelbridge + +import ( + "context" + "github.com/google/go-cmp/cmp" + "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/contrib/propagators/b3" + "go.opentelemetry.io/contrib/propagators/ot" + "go.opentelemetry.io/otel/attribute" + opentracing2 "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" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "os" + "reflect" + "testing" + "time" + "unsafe" +) + +func Test_parseOptions(t *testing.T) { + + var ( + batchTimeout = defaultBatchTimeout + processorQueueSize = defaultProcessorQueueSize + batchSize = defaultBatchSize + exportTimeout = defaultExportTimeout + hostname, _ = os.Hostname() + ) + + token := "mytoken" + + tests := []struct { + name string + opts []string + want Options + wantErr bool + }{ + { + name: "test without token should fail", + opts: []string{}, + want: Options{}, + wantErr: true, + }, + { + name: "test with token works", + opts: []string{"access-token=" + token}, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token works and environment set", + opts: []string{"access-token=" + token, "environment=production"}, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: "production", + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token works and set service name", + opts: []string{"access-token=" + token, "service-name=myservice"}, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: "myservice", + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token works and set service version", + opts: []string{"access-token=" + token, "service-version=1.3.4"}, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: "1.3.4", + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token works and component set", + opts: []string{"access-token=" + token, "component-name=mycomponent"}, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: "mycomponent", + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token set collector", + opts: []string{ + "access-token=" + token, + "collector=collector.example.com:8888", + }, + want: Options{ + AccessToken: token, + Collector: "collector.example.com:8888", + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token set collector wrong format", + opts: []string{ + "access-token=" + token, + "collector=collector.example.com=8888", + }, + want: Options{}, + wantErr: true, + }, + { + name: "test with token set collector wrong port", + opts: []string{ + "access-token=" + token, + "collector=collector.example.com:abc", + }, + want: Options{}, + wantErr: true, + }, + { + name: "test with token set component name", + opts: []string{ + "access-token=" + token, + "component-name=skipper-ingress", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: "skipper-ingress", + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + wantErr: false, + }, + { + name: "test with token set protocol to use grpc", + opts: []string{ + "access-token=" + token, + "protocol=grpc", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set protocol to use http", + opts: []string{ + "access-token=" + token, + "protocol=http", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseHttp: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set and wrong protocol", + opts: []string{ + "access-token=" + token, + "protocol=wrong", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token set protocol to use grpc and insecure", + opts: []string{ + "access-token=" + token, + "protocol=grpc", + "insecure-connection=true", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + UsePlainText: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set protocol to use grpc and insecure incorrect value", + opts: []string{ + "access-token=" + token, + "protocol=grpc", + "insecure-connection=wrong", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token set protocol to use http and insecure", + opts: []string{ + "access-token=" + token, + "protocol=http", + "insecure-connection=true", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseHttp: true, + UsePlainText: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set and max queue size set", + opts: []string{ + "access-token=" + token, + "processor-queue-size=100", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + ProcessorQueueSize: 100, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set and max queue size set to non numeric", + opts: []string{ + "access-token=" + token, + "processor-queue-size=wrong", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token set and batch size set", + opts: []string{ + "access-token=" + token, + "batch-size=100", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + BatchSize: 100, + ExportTimeout: exportTimeout, + Hostname: hostname, + ProcessorQueueSize: processorQueueSize, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set and batch size set to non numeric", + opts: []string{ + "access-token=" + token, + "batch-size=wrong", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token set and export timeout set", + opts: []string{ + "access-token=" + token, + "export-timeout=100", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + BatchSize: batchSize, + ExportTimeout: 100 * time.Millisecond, + Hostname: hostname, + ProcessorQueueSize: processorQueueSize, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set and export timeout set to non numeric", + opts: []string{ + "access-token=" + token, + "export-timeout=wrong", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token set and batch timeout set", + opts: []string{ + "access-token=" + token, + "batch-timeout=100", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: 100 * time.Millisecond, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + ProcessorQueueSize: processorQueueSize, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + }, + }, + { + name: "test with token set and batch timeout set to non numeric", + opts: []string{ + "access-token=" + token, + "batch-timeout=wrong", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token set and propagators set", + opts: []string{ + "access-token=" + token, + "propagators=ottrace,baggage,b3,tracecontext", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + ProcessorQueueSize: processorQueueSize, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, propagation.Baggage{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader)), propagation.TraceContext{}}, + }, + }, + { + name: "test with token set and propagators set and b3 removed", + opts: []string{ + "access-token=" + token, + "propagators=ottrace,baggage,tracecontext", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + ProcessorQueueSize: processorQueueSize, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, propagation.Baggage{}, propagation.TraceContext{}}, + }, + }, + { + name: "test with token set and propagators set and b3 removed", + opts: []string{ + "access-token=" + token, + "propagators=wro,ng", + }, + wantErr: true, + want: Options{}, + }, + { + name: "test with token works with global tag", + opts: []string{ + "access-token=" + token, + "tag=foo=bar", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + GlobalAttributes: []attribute.KeyValue{attribute.String("foo", "bar")}, + }, + wantErr: false, + }, + { + name: "test with token works with multiple global tag", + opts: []string{ + "access-token=" + token, + "tag=foo=bar", + "tag=bar=foo", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + GlobalAttributes: []attribute.KeyValue{attribute.String("foo", "bar"), attribute.String("bar", "foo")}, + }, + wantErr: false, + }, + { + name: "test with token works with global tag empty", + opts: []string{ + "access-token=" + token, + "tag=", + }, + want: Options{ + AccessToken: token, + Collector: defaultEndpoint, + Environment: defaultEnvironment, + ServiceName: defaultServiceName, + ServiceVersion: defaultServiceVersion, + ComponentName: defaultComponentName, + BatchTimeout: batchTimeout, + ProcessorQueueSize: processorQueueSize, + BatchSize: batchSize, + ExportTimeout: exportTimeout, + Hostname: hostname, + UseGrpc: true, + Propagators: []propagation.TextMapPropagator{ot.OT{}, b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader))}, + //GlobalAttributes: []attribute.KeyValue{attribute.String("foo", "bar")}, + }, + wantErr: false, + }, + { + name: "test with token works with global tag wrong format", + opts: []string{ + "access-token=" + token, + "tag=wrong", + }, + wantErr: true, + want: Options{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseOptions(tt.opts) + if (err != nil) != tt.wantErr { + t.Errorf("parseOptions() error = %v, wantErr %v", err, tt.wantErr) + return + } + //if !reflect.DeepEqual(got.Propagators, tt.propagators) { + // t.Logf("diff: %v", cmp.Diff(tt.propagators, got.Propagators)) + // t.Errorf("propagators = %v, want %v", got.Propagators, tt.propagators) + //} + //got.Propagators = nil + + if !reflect.DeepEqual(got, tt.want) { + t.Logf("diff: %v", cmp.Diff(tt.want, got)) + t.Errorf("parseOptions() = %v, want %v", got, tt.want) + } + }) + } + +} + +func Test_newExporter(t *testing.T) { + type args struct { + ctx context.Context + opt Options + } + tests := []struct { + name string + args args + wantErr bool + want interface{} + wantPlainText bool + }{ + { + name: "test with grpc", + args: args{ + ctx: context.Background(), + opt: Options{}, + }, + wantErr: false, + want: otlptracegrpc.NewClient(), + }, + { + name: "test with grpc and plain text", + args: args{ + ctx: context.Background(), + opt: Options{ + UsePlainText: true, + }, + }, + wantErr: false, + want: otlptracegrpc.NewClient(), + wantPlainText: true, + }, + { + name: "test with http", + args: args{ + ctx: context.Background(), + opt: Options{ + UseHttp: true, + }, + }, + wantErr: false, + want: otlptracehttp.NewClient(), + }, + { + name: "test with http and plain text", + args: args{ + ctx: context.Background(), + opt: Options{ + UseHttp: true, + UsePlainText: true, + }, + }, + wantErr: false, + want: otlptracehttp.NewClient(), + wantPlainText: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, plainText, err := newExporter(tt.args.ctx, tt.args.opt) + if (err != nil) != tt.wantErr { + t.Errorf("newExporter() error = %v, wantErr %v", err, tt.wantErr) + return + } + + gotClient := reflect.ValueOf(got).Elem().FieldByName("client") + // Use unsafe to get the underlying value of the unexported field + ptr := unsafe.Pointer(gotClient.UnsafeAddr()) + concreteValue := reflect.NewAt(gotClient.Type(), ptr).Elem().Interface() + concreteType := reflect.TypeOf(concreteValue) + + if plainText != tt.wantPlainText { + t.Errorf("newExporter() = %v, want %v", plainText, tt.wantPlainText) + } + + if !reflect.DeepEqual(concreteType, reflect.TypeOf(tt.want)) { + t.Errorf("newExporter() = %v, want %v", concreteType, reflect.TypeOf(tt.want)) + } + }) + } +} + +func Test_newTraceProvider(t *testing.T) { + + exp := &otlptrace.Exporter{} + opts := Options{} + r := resource.Default() + tp := sdktrace.NewTracerProvider() + + type args struct { + exp *otlptrace.Exporter + schemaUrl string + r *resource.Resource + opt Options + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + { + name: "test newTraceProvider fail resource.Merge", + args: args{ + exp: exp, + schemaUrl: "wrongschema", + opt: opts, + r: r, + }, + wantErr: true, + }, + { + name: "test newTraceProvider success", + args: args{ + exp: exp, + schemaUrl: semconv.SchemaURL, + opt: opts, + r: r, + }, + wantErr: false, + want: tp, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newTraceProvider(tt.args.exp, tt.args.schemaUrl, tt.args.r, tt.args.opt) + if err != nil { + if (err != nil) != tt.wantErr { + t.Errorf("newTraceProvider() error = %v, wantErr %v", err, tt.wantErr) + } + return + } + + if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { + t.Errorf("newTraceProvider() got = %v, want %v", reflect.TypeOf(got), reflect.TypeOf(tt.want)) + } + }) + } +} + +func Test_setupOTelSDK(t *testing.T) { + type args struct { + ctx context.Context + schemaUrl string + opts Options + } + tests := []struct { + name string + args args + wantShutdown func(context.Context) error + wantErr bool + }{ + { + name: "test setupOTelSDK success", + }, + { + name: "test setupOTelSDK fail", + args: args{ + ctx: context.Background(), + opts: Options{}, + schemaUrl: "wrong", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotShutdown, err := setupOTelSDK(tt.args.ctx, tt.args.schemaUrl, tt.args.opts) + if (err != nil) != tt.wantErr { + t.Errorf("setupOTelSDK() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if reflect.TypeOf(gotShutdown) != reflect.TypeOf(tt.wantShutdown) { + t.Errorf("setupOTelSDK() gotShutdown = %v, want %v", reflect.TypeOf(gotShutdown), reflect.TypeOf(tt.wantShutdown)) + } + }) + } +} + +func TestInitTracer(t *testing.T) { + + type args struct { + opts []string + } + tests := []struct { + name string + args args + want interface{} + }{ + { + name: "test InitTracer successful", + want: &opentracing2.BridgeTracer{}, + args: args{ + opts: []string{"access-token=mytoken", "collector=example.com:8443"}, + }, + }, + { + name: "test InitTracer fail parse options and return no op tracer", + want: &opentracing.NoopTracer{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := InitTracer(tt.args.opts) + if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { + t.Errorf("InitTracer() got = %v, want %v", reflect.TypeOf(got), reflect.TypeOf(tt.want)) + } + }) + } +} diff --git a/tracing/tracing.go b/tracing/tracing.go index be66d20963..4c13628c94 100644 --- a/tracing/tracing.go +++ b/tracing/tracing.go @@ -53,6 +53,7 @@ import ( "context" "errors" "fmt" + "github.com/zalando/skipper/tracing/tracers/lightstepotelbridge" "path/filepath" "plugin" @@ -86,6 +87,8 @@ func InitTracer(opts []string) (tracer ot.Tracer, err error) { return instana.InitTracer(opts) case "jaeger": return jaeger.InitTracer(opts) + case "lightstep-otel-bridge": + return lightstepotelbridge.InitTracer(opts), nil case "lightstep": return lightstep.InitTracer(opts) default: