Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Otelmux span options #5251

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108)
- Add `WithSpanStartOption` and `WithSpanEndOption` to the `go.opentelemetry.io/contrib/github.com/gorilla/mux/otelmux` package to provide options on span start and end. (#5250)

### Removed

Expand Down
18 changes: 18 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/config.go
Expand Up @@ -18,6 +18,8 @@ type config struct {
PublicEndpoint bool
PublicEndpointFn func(*http.Request) bool
Filters []Filter
SpanStartOptions []oteltrace.SpanStartOption
SpanEndOptions []oteltrace.SpanEndOption
}

// Option specifies instrumentation configuration options.
Expand Down Expand Up @@ -86,6 +88,22 @@ func WithSpanNameFormatter(fn func(routeName string, r *http.Request) string) Op
})
}

// WithSpanStartOption applies options to all the HTTP span created by the
// instrumentation.
func WithSpanStartOption(o ...oteltrace.SpanStartOption) Option {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In otelhttp and otelgrpc this option is called WithSpanOptions. Can we name it in the same way for sake of consistency?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely. I'll leave the code as is for now, since this may not be relevant, if it is replaced with the option to add stacktraces instead - see this comment.
I'll return to this, if we decide to keep the function.

return optionFunc(func(c *config) {
c.SpanStartOptions = append(c.SpanStartOptions, o...)
})
}

// WithSpanEndOption applies options when ending the HTTP span created by the
// instrumentation.
func WithSpanEndOption(o ...oteltrace.SpanEndOption) Option {
return optionFunc(func(c *config) {
c.SpanEndOptions = append(c.SpanEndOptions, o...)
})
}

// WithFilter adds a filter to the list of filters used by the handler.
// If any filter indicates to exclude a request then the request will not be
// traced. All filters must allow a request to be traced for a Span to be created.
Expand Down
10 changes: 7 additions & 3 deletions instrumentation/github.com/gorilla/mux/otelmux/mux.go
Expand Up @@ -55,6 +55,8 @@ func Middleware(service string, opts ...Option) mux.MiddlewareFunc {
publicEndpoint: cfg.PublicEndpoint,
publicEndpointFn: cfg.PublicEndpointFn,
filters: cfg.Filters,
SpanStartOptions: cfg.SpanStartOptions,
SpanEndOptions: cfg.SpanEndOptions,
}
}
}
Expand All @@ -68,6 +70,8 @@ type traceware struct {
publicEndpoint bool
publicEndpointFn func(*http.Request) bool
filters []Filter
SpanStartOptions []trace.SpanStartOption
SpanEndOptions []trace.SpanEndOption
}

type recordingResponseWriter struct {
Expand Down Expand Up @@ -141,10 +145,10 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}

opts := []trace.SpanStartOption{
opts := append(tw.SpanStartOptions, []trace.SpanStartOption{
trace.WithAttributes(semconvutil.HTTPServerRequest(tw.service, r)...),
trace.WithSpanKind(trace.SpanKindServer),
}
}...)

if tw.publicEndpoint || (tw.publicEndpointFn != nil && tw.publicEndpointFn(r.WithContext(ctx))) {
opts = append(opts, trace.WithNewRoot())
Expand All @@ -162,7 +166,7 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
spanName := tw.spanNameFormatter(routeStr, r)
ctx, span := tw.tracer.Start(ctx, spanName, opts...)
defer span.End()
defer span.End(tw.SpanEndOptions...)
r2 := r.WithContext(ctx)
rrw := getRRW(w)
defer putRRW(rrw)
Expand Down
65 changes: 65 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/test/mux_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -282,3 +283,67 @@ func TestWithPublicEndpointFn(t *testing.T) {
})
}
}

func TestWithSpanStartOptions(t *testing.T) {
sr := tracetest.NewSpanRecorder()
provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))

// Setup
router := mux.NewRouter()
router.Use(otelmux.Middleware("foobar",
otelmux.WithTracerProvider(provider),
otelmux.WithSpanStartOption(
trace.WithAttributes(attribute.String("spanStart", "true")),
),
))

// Configure an empty handler
router.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
})
r := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
response := w.Result()
assert.Equal(t, http.StatusOK, response.StatusCode)

// Verify that the attribute is set as expected
spans := sr.Ended()
require.Len(t, spans, 1)
span := spans[0]
assert.Equal(t, "/", span.Name())
attr := span.Attributes()
assert.Contains(t, attr, attribute.String("spanStart", "true"))
}

func TestWithSpanEndOptions(t *testing.T) {
sr := tracetest.NewSpanRecorder()
provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))

// Setup
router := mux.NewRouter()
endTime := time.Now()
router.Use(otelmux.Middleware("foobar",
otelmux.WithTracerProvider(provider),
otelmux.WithSpanEndOption(
trace.WithTimestamp(endTime),
),
))

// Configure an empty handler
router.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
})
r := httptest.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)
response := w.Result()
assert.Equal(t, http.StatusOK, response.StatusCode)

// Verify that the attribute is set as expected
spans := sr.Ended()
require.Len(t, spans, 1)
span := spans[0]
assert.Equal(t, "/", span.Name())

// Assert that the time set in the SpanEndOptions above matches the EndTime of the span
assert.Equal(t, span.EndTime(), endTime)
}