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:
parent
367635b740
commit
6489b07bf5
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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) {}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user