Skip to content

Commit

Permalink
Address remaining test implementation, add functional options
Browse files Browse the repository at this point in the history
Signed-off-by: Joseph Woodward <joseph.woodward@xeuse.com>
  • Loading branch information
josephwoodward committed Nov 1, 2022
1 parent bcec2d3 commit 4edfa2a
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 57 deletions.
157 changes: 112 additions & 45 deletions client/opentelemetry.go
Expand Up @@ -8,22 +8,97 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
"go.opentelemetry.io/otel/trace"
)

type config struct {
Tracer trace.Tracer
Propagators propagation.TextMapPropagator
SpanStartOptions []trace.SpanStartOption
SpanNameFormatter func(string, *http.Request) string
TracerProvider trace.TracerProvider
}

type OpenTelemetryOption interface {
apply(*config)
}

type optionFunc func(*config)

func (o optionFunc) apply(c *config) {
o(c)
}

// WithTracerProvider specifies a tracer provider to use for creating a tracer.
// If none is specified, the global provider is used.
func WithTracerProvider(provider trace.TracerProvider) OpenTelemetryOption {
return optionFunc(func(c *config) {
if provider != nil {
c.TracerProvider = provider
}
})
}

// WithPropagators configures specific propagators. If this
// option isn't specified, then the global TextMapPropagator is used.
func WithPropagators(ps propagation.TextMapPropagator) OpenTelemetryOption {
return optionFunc(func(c *config) {
if ps != nil {
c.Propagators = ps
}
})
}

// WithSpanOptions configures an additional set of
// trace.SpanOptions, which are applied to each new span.
func WithSpanOptions(opts ...trace.SpanStartOption) OpenTelemetryOption {
return optionFunc(func(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, opts...)
})
}

type openTelemetryTransport struct {
transport runtime.ClientTransport
host string
opts []trace.SpanStartOption
transport runtime.ClientTransport
host string
spanStartOptions []trace.SpanStartOption
propagator propagation.TextMapPropagator
provider trace.TracerProvider
tracer trace.Tracer
config *config
}

// newConfig creates a new config struct and applies opts to it.
func newConfig(opts ...OpenTelemetryOption) *config {
c := &config{
Propagators: otel.GetTextMapPropagator(),
}

for _, opt := range opts {
opt.apply(c)
}

// Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context.
if c.TracerProvider != nil {
c.Tracer = newTracer(c.TracerProvider)
}

return c
}

func newOpenTelemetryTransport(transport runtime.ClientTransport, host string, opts []trace.SpanStartOption) runtime.ClientTransport {
return &openTelemetryTransport{
transport: transport,
host: host,
opts: opts,
func newOpenTelemetryTransport(transport runtime.ClientTransport, host string, opts []OpenTelemetryOption) *openTelemetryTransport {
t := &openTelemetryTransport{
transport: transport,
host: host,
provider: otel.GetTracerProvider(),
propagator: otel.GetTextMapPropagator(),
}

c := newConfig(opts...)
t.config = c

return t
}

func (t *openTelemetryTransport) Submit(op *runtime.ClientOperation) (interface{}, error) {
Expand All @@ -42,7 +117,7 @@ func (t *openTelemetryTransport) Submit(op *runtime.ClientOperation) (interface{
}()

op.Params = runtime.ClientRequestWriterFunc(func(req runtime.ClientRequest, reg strfmt.Registry) error {
span = createOpenTelemetryClientSpan(op, req.GetHeaderParams(), t.host, t.opts)
span = t.newOpenTelemetrySpan(op, req.GetHeaderParams())
return params.WriteToRequest(req, reg)
})

Expand All @@ -65,50 +140,42 @@ func (t *openTelemetryTransport) Submit(op *runtime.ClientOperation) (interface{
return submit, err
}

func createOpenTelemetryClientSpan(op *runtime.ClientOperation, _ http.Header, host string, opts []trace.SpanStartOption) trace.Span {
func (t *openTelemetryTransport) newOpenTelemetrySpan(op *runtime.ClientOperation, header http.Header) trace.Span {
ctx := op.Context
if span := trace.SpanFromContext(ctx); span.IsRecording() {
// TODO: Can we get the version number for use with trace.WithInstrumentationVersion?
tracer := otel.GetTracerProvider().Tracer("")

ctx, span = tracer.Start(ctx, operationName(op), opts...)
op.Context = ctx

//TODO: There's got to be a better way to do this without the request, right?
var scheme string
if len(op.Schemes) == 1 {
scheme = op.Schemes[0]
tracer := t.tracer
if tracer == nil {
if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() {
tracer = newTracer(span.TracerProvider())
} else {
tracer = newTracer(otel.GetTracerProvider())
}
}

span.SetAttributes(
attribute.String("net.peer.name", host),
// attribute.String("net.peer.port", ""),
attribute.String(string(semconv.HTTPRouteKey), op.PathPattern),
attribute.String(string(semconv.HTTPMethodKey), op.Method),
attribute.String("span.kind", trace.SpanKindClient.String()),
attribute.String("http.scheme", scheme),
)
ctx, span := tracer.Start(ctx, operationName(op), t.spanStartOptions...)

return span
// TODO: Can we get the underlying request so we can wire these bits up easily?
// span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest()...)
var scheme string
if len(op.Schemes) == 1 {
scheme = op.Schemes[0]
}

// if span != nil {
// opts = append(opts, ext.SpanKindRPCClient)
// span, _ = opentracing.StartSpanFromContextWithTracer(
// ctx, span.Tracer(), operationName(op), opts...)
span.SetAttributes(
attribute.String("net.peer.name", t.host),
// attribute.String("net.peer.port", ""),
attribute.String(string(semconv.HTTPRouteKey), op.PathPattern),
attribute.String(string(semconv.HTTPMethodKey), op.Method),
attribute.String("span.kind", trace.SpanKindClient.String()),
attribute.String("http.scheme", scheme),
)

// ext.Component.Set(span, "go-openapi")
// ext.PeerHostname.Set(span, host)
// span.SetTag("http.path", op.PathPattern)
// ext.HTTPMethod.Set(span, op.Method)
carrier := propagation.HeaderCarrier(header)
t.propagator.Inject(ctx, carrier)

// _ = span.Tracer().Inject(
// span.Context(),
// opentracing.HTTPHeaders,
// opentracing.HTTPHeadersCarrier(header))

// return span
// }
return span
}

return nil
func newTracer(tp trace.TracerProvider) trace.Tracer {
return tp.Tracer("go-runtime", trace.WithInstrumentationVersion("1.0.0"))
}
35 changes: 25 additions & 10 deletions client/opentelemetry_test.go
Expand Up @@ -10,6 +10,7 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -71,22 +72,36 @@ func Test_OpenTelemetryRuntime_submit_nilContext(t *testing.T) {
assertOpenTelemetrySubmit(t, operation, exporter, 0) // just don't panic
}

// func Test_injectOpenTelemetrySpanContext(t *testing.T) {
// t.Parallel()
// tracer := mocktracer.New()
// _, ctx := opentracing.StartSpanFromContextWithTracer(context.Background(), tracer, "op")
// header := map[string][]string{}
// createOpenTelemetryClientSpan(testOperation(ctx), header, "", nil)
func Test_injectOpenTelemetrySpanContext(t *testing.T) {
t.Parallel()

exporter := tracetest.NewInMemoryExporter()

// // values are random - just check that something was injected
// assert.Len(t, header, 3)
// }
tp := tracesdk.NewTracerProvider(
tracesdk.WithSampler(tracesdk.AlwaysSample()),
tracesdk.WithSyncer(exporter),
)

otel.SetTracerProvider(tp)

tracer := tp.Tracer("go-runtime")
ctx, _ := tracer.Start(context.Background(), "op")
operation := testOperation(ctx)

header := map[string][]string{}
tr := newOpenTelemetryTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}}, "", nil)
tr.propagator = propagation.TraceContext{}
tr.Submit(operation)

// values are random - just check that something was injected
assert.Len(t, header, 1)
}

func assertOpenTelemetrySubmit(t *testing.T, operation *runtime.ClientOperation, exporter *tracetest.InMemoryExporter, expectedSpanCount int) {
header := map[string][]string{}
r := newOpenTelemetryTransport(&mockRuntime{runtime.TestClientRequest{Headers: header}},
"remote_host",
[]trace.SpanStartOption{})
nil)

_, err := r.Submit(operation)
require.NoError(t, err)
Expand Down
3 changes: 1 addition & 2 deletions client/runtime.go
Expand Up @@ -32,7 +32,6 @@ import (
"time"

"github.com/opentracing/opentracing-go"
"go.opentelemetry.io/otel/trace"

"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/logger"
Expand Down Expand Up @@ -306,7 +305,7 @@ func (r *Runtime) WithOpenTracing(opts ...opentracing.StartSpanOption) runtime.C
// A new client span is created for each request.
// If the context of the client operation does not contain an active span, no span is created.
// The provided opts are applied to each spans - for example to add global tags.
func (r *Runtime) WithOpenTelemetry(opts ...trace.SpanStartOption) runtime.ClientTransport {
func (r *Runtime) WithOpenTelemetry(opts ...OpenTelemetryOption) runtime.ClientTransport {
return newOpenTelemetryTransport(r, r.Host, opts)
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -16,6 +16,7 @@ require (
github.com/opentracing/opentracing-go v1.2.0
github.com/stretchr/testify v1.8.0
go.mongodb.org/mongo-driver v1.8.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.36.4
go.opentelemetry.io/otel v1.11.1 // indirect
go.opentelemetry.io/otel/sdk v1.11.1
go.opentelemetry.io/otel/trace v1.11.1
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Expand Up @@ -12,6 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down Expand Up @@ -141,8 +143,12 @@ go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R7
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.36.4 h1:aUEBEdCa6iamGzg6fuYxDA8ThxvOG240mAvWDU+XLio=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.36.4/go.mod h1:l2MdsbKTocpPS5nQZscqTR9jd8u96VYZdcpF8Sye7mA=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E=
go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI=
go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
Expand Down

0 comments on commit 4edfa2a

Please sign in to comment.