1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-03-05 15:05:51 +02:00

othttp: add WithSpanFormatter option (#617)

* othttp: add WithSpanFormatter option

* plugin/othttp: add span formatter test

* remove typo

* preserve operation && change option name

* nil check

* fix comment typo

Co-Authored-By: Rahul Patel <rghetia@yahoo.com>

* add default formatter test case

Co-authored-by: Rahul Patel <rghetia@yahoo.com>
This commit is contained in:
Marwan Sulaiman 2020-04-06 11:37:25 -04:00 committed by GitHub
parent 367635b740
commit 6489b07bf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 9 deletions

View File

@ -28,6 +28,7 @@ import (
type MockSpan struct { type MockSpan struct {
sc core.SpanContext sc core.SpanContext
tracer apitrace.Tracer tracer apitrace.Tracer
Name string
} }
var _ apitrace.Span = (*MockSpan)(nil) var _ apitrace.Span = (*MockSpan)(nil)
@ -66,8 +67,9 @@ func (ms *MockSpan) End(options ...apitrace.EndOption) {
func (ms *MockSpan) RecordError(ctx context.Context, err error, opts ...apitrace.ErrorOption) { func (ms *MockSpan) RecordError(ctx context.Context, err error, opts ...apitrace.ErrorOption) {
} }
// SetName does nothing. // SetName sets the span name.
func (ms *MockSpan) SetName(name string) { func (ms *MockSpan) SetName(name string) {
ms.Name = name
} }
// Tracer returns MockTracer implementation of Tracer. // Tracer returns MockTracer implementation of Tracer.

View File

@ -37,6 +37,9 @@ type MockTracer struct {
// Sampled specifies if the new span should be sampled or not. // Sampled specifies if the new span should be sampled or not.
Sampled bool Sampled bool
// OnSpanStarted is called every time a new trace span is started
OnSpanStarted func(span *MockSpan)
} }
var _ apitrace.Tracer = (*MockTracer)(nil) var _ apitrace.Tracer = (*MockTracer)(nil)
@ -77,6 +80,10 @@ func (mt *MockTracer) Start(ctx context.Context, name string, o ...apitrace.Star
span = &MockSpan{ span = &MockSpan{
sc: sc, sc: sc,
tracer: mt, tracer: mt,
Name: name,
}
if mt.OnSpanStarted != nil {
mt.OnSpanStarted(span)
} }
return apitrace.ContextWithSpan(ctx, span), span return apitrace.ContextWithSpan(ctx, span), span

View File

@ -53,12 +53,13 @@ type Handler struct {
operation string operation string
handler http.Handler handler http.Handler
tracer trace.Tracer tracer trace.Tracer
props propagation.Propagators props propagation.Propagators
spanStartOptions []trace.StartOption spanStartOptions []trace.StartOption
readEvent bool readEvent bool
writeEvent bool writeEvent bool
filters []Filter filters []Filter
spanNameFormatter func(string, *http.Request) string
} }
// Option function used for setting *optional* Handler properties // Option function used for setting *optional* Handler properties
@ -140,10 +141,27 @@ func WithMessageEvents(events ...event) Option {
} }
} }
// WithSpanNameFormatter takes a function that will be called on every
// incoming request and the returned string will become the Span Name
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option {
return func(h *Handler) {
h.spanNameFormatter = f
}
}
func defaultFormatter(operation string, _ *http.Request) string {
return operation
}
// NewHandler wraps the passed handler, functioning like middleware, in a span // NewHandler wraps the passed handler, functioning like middleware, in a span
// named after the operation and with any provided HandlerOptions. // named after the operation and with any provided HandlerOptions.
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler { func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
h := Handler{handler: handler, operation: operation} h := Handler{
handler: handler,
operation: operation,
spanNameFormatter: defaultFormatter,
}
defaultOpts := []Option{ defaultOpts := []Option{
WithTracer(global.Tracer("go.opentelemetry.io/plugin/othttp")), WithTracer(global.Tracer("go.opentelemetry.io/plugin/othttp")),
WithPropagators(global.Propagators()), WithPropagators(global.Propagators()),
@ -169,7 +187,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
opts := append([]trace.StartOption{}, h.spanStartOptions...) // start with the configured options opts := append([]trace.StartOption{}, h.spanStartOptions...) // start with the configured options
ctx := propagation.ExtractHTTP(r.Context(), h.props, r.Header) ctx := propagation.ExtractHTTP(r.Context(), h.props, r.Header)
ctx, span := h.tracer.Start(ctx, h.operation, opts...) ctx, span := h.tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
defer span.End() defer span.End()
readRecordFunc := func(int64) {} readRecordFunc := func(int64) {}

View File

@ -100,3 +100,63 @@ func TestBasicFilter(t *testing.T) {
t.Fatalf("got %q, expected %q", got, expected) t.Fatalf("got %q, expected %q", got, expected)
} }
} }
func TestSpanNameFormatter(t *testing.T) {
var testCases = []struct {
name string
formatter func(s string, r *http.Request) string
operation string
expected string
}{
{
name: "default formatter",
formatter: defaultFormatter,
operation: "test_operation",
expected: "test_operation",
},
{
name: "custom formatter",
formatter: func(s string, r *http.Request) string {
return r.URL.Path
},
operation: "",
expected: "/hello",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rr := httptest.NewRecorder()
var id uint64
var spanName string
tracer := mocktrace.MockTracer{
StartSpanID: &id,
OnSpanStarted: func(span *mocktrace.MockSpan) {
spanName = span.Name
},
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, err := io.WriteString(w, "hello world"); err != nil {
t.Fatal(err)
}
})
h := NewHandler(
handler,
tc.operation,
WithTracer(&tracer),
WithSpanNameFormatter(tc.formatter),
)
r, err := http.NewRequest(http.MethodGet, "http://localhost/hello", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(rr, r)
if got, expected := rr.Result().StatusCode, http.StatusOK; got != expected {
t.Fatalf("got %d, expected %d", got, expected)
}
if got, expected := spanName, tc.expected; got != expected {
t.Fatalf("got %q, expected %q", got, expected)
}
})
}
}