otelhttp: get tracer from current context if not set in constructor (#873)
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
parent
188ebf0dcc
commit
e81f85aff9
|
@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The `Transport`, `Handler`, and HTTP client convenience wrappers in the `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` package now use the `TracerProvider` from the parent context if one exists and none was explicitly set when configuring the instrumentation. (#873)
|
||||||
|
|
||||||
## [1.1.0/0.26.0] - 2021-10-28
|
## [1.1.0/0.26.0] - 2021-10-28
|
||||||
|
|
||||||
Update dependency on the `go.opentelemetry.io/otel` project to `v1.1.0`.
|
Update dependency on the `go.opentelemetry.io/otel` project to `v1.1.0`.
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Attribute keys that can be added to a span.
|
// Attribute keys that can be added to a span.
|
||||||
|
@ -39,3 +40,7 @@ const (
|
||||||
// Filter is a predicate used to determine whether a given http.request should
|
// Filter is a predicate used to determine whether a given http.request should
|
||||||
// be traced. A Filter must return true if the request should be traced.
|
// be traced. A Filter must return true if the request should be traced.
|
||||||
type Filter func(*http.Request) bool
|
type Filter func(*http.Request) bool
|
||||||
|
|
||||||
|
func newTracer(tp trace.TracerProvider) trace.Tracer {
|
||||||
|
return tp.Tracer(instrumentationName, trace.WithInstrumentationVersion(SemVersion()))
|
||||||
|
}
|
||||||
|
|
|
@ -59,17 +59,17 @@ func (o optionFunc) apply(c *config) {
|
||||||
func newConfig(opts ...Option) *config {
|
func newConfig(opts ...Option) *config {
|
||||||
c := &config{
|
c := &config{
|
||||||
Propagators: otel.GetTextMapPropagator(),
|
Propagators: otel.GetTextMapPropagator(),
|
||||||
TracerProvider: otel.GetTracerProvider(),
|
|
||||||
MeterProvider: global.GetMeterProvider(),
|
MeterProvider: global.GetMeterProvider(),
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt.apply(c)
|
opt.apply(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Tracer = c.TracerProvider.Tracer(
|
// Tracer is only initialized if manually specified. Otherwise, can be passed with the tracing context.
|
||||||
instrumentationName,
|
if c.TracerProvider != nil {
|
||||||
trace.WithInstrumentationVersion(SemVersion()),
|
c.Tracer = newTracer(c.TracerProvider)
|
||||||
)
|
}
|
||||||
|
|
||||||
c.Meter = c.MeterProvider.Meter(
|
c.Meter = c.MeterProvider.Meter(
|
||||||
instrumentationName,
|
instrumentationName,
|
||||||
metric.WithInstrumentationVersion(SemVersion()),
|
metric.WithInstrumentationVersion(SemVersion()),
|
||||||
|
|
|
@ -127,8 +127,18 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
trace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
|
trace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
|
||||||
}, h.spanStartOptions...) // start with the configured options
|
}, h.spanStartOptions...) // start with the configured options
|
||||||
|
|
||||||
|
tracer := h.tracer
|
||||||
|
|
||||||
|
if tracer == nil {
|
||||||
|
if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
|
||||||
|
tracer = newTracer(span.TracerProvider())
|
||||||
|
} else {
|
||||||
|
tracer = newTracer(otel.GetTracerProvider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
|
ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
|
||||||
ctx, span := h.tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
|
ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
readRecordFunc := func(int64) {}
|
readRecordFunc := func(int64) {}
|
||||||
|
|
|
@ -85,3 +85,35 @@ func TestConvenienceWrappers(t *testing.T) {
|
||||||
assert.Equal(t, "HTTP POST", spans[2].Name())
|
assert.Equal(t, "HTTP POST", spans[2].Name())
|
||||||
assert.Equal(t, "HTTP POST", spans[3].Name())
|
assert.Equal(t, "HTTP POST", spans[3].Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientWithTraceContext(t *testing.T) {
|
||||||
|
sr := tracetest.NewSpanRecorder()
|
||||||
|
provider := trace.NewTracerProvider(trace.WithSpanProcessor(sr))
|
||||||
|
|
||||||
|
tracer := provider.Tracer("")
|
||||||
|
ctx, span := tracer.Start(context.Background(), "http requests")
|
||||||
|
|
||||||
|
content := []byte("Hello, world!")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if _, err := w.Write(content); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
res, err := otelhttp.Get(ctx, ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
|
||||||
|
spans := sr.Ended()
|
||||||
|
require.Equal(t, 2, len(spans))
|
||||||
|
assert.Equal(t, "HTTP GET", spans[0].Name())
|
||||||
|
assert.Equal(t, "http requests", spans[1].Name())
|
||||||
|
assert.NotEmpty(t, spans[0].Parent().SpanID())
|
||||||
|
assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID())
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -24,6 +25,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
@ -107,3 +109,37 @@ func TestHandlerBasics(t *testing.T) {
|
||||||
t.Fatalf("got %q, expected %q", got, expected)
|
t.Fatalf("got %q, expected %q", got, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandlerRequestWithTraceContext(t *testing.T) {
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h := otelhttp.NewHandler(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write([]byte("hello world"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}), "test_handler")
|
||||||
|
|
||||||
|
r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
spanRecorder := tracetest.NewSpanRecorder()
|
||||||
|
provider := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithSpanProcessor(spanRecorder),
|
||||||
|
)
|
||||||
|
tracer := provider.Tracer("")
|
||||||
|
ctx, span := tracer.Start(context.Background(), "test_request")
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
h.ServeHTTP(rr, r)
|
||||||
|
assert.Equal(t, 200, rr.Result().StatusCode)
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
|
||||||
|
spans := spanRecorder.Ended()
|
||||||
|
require.Len(t, spans, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, "test_handler", spans[0].Name())
|
||||||
|
assert.Equal(t, "test_request", spans[1].Name())
|
||||||
|
assert.NotEmpty(t, spans[0].Parent().SpanID())
|
||||||
|
assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID())
|
||||||
|
}
|
||||||
|
|
|
@ -15,11 +15,16 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
@ -116,3 +121,48 @@ func TestTransportErrorStatus(t *testing.T) {
|
||||||
t.Errorf("expected error status message on span; got: %q", got)
|
t.Errorf("expected error status message on span; got: %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransportRequestWithTraceContext(t *testing.T) {
|
||||||
|
spanRecorder := tracetest.NewSpanRecorder()
|
||||||
|
provider := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithSpanProcessor(spanRecorder),
|
||||||
|
)
|
||||||
|
content := []byte("Hello, world!")
|
||||||
|
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, err := w.Write(content)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
tracer := provider.Tracer("")
|
||||||
|
ctx, span := tracer.Start(context.Background(), "test_span")
|
||||||
|
|
||||||
|
r, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
tr := otelhttp.NewTransport(
|
||||||
|
http.DefaultTransport,
|
||||||
|
)
|
||||||
|
|
||||||
|
c := http.Client{Transport: tr}
|
||||||
|
res, err := c.Do(r)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, content, body)
|
||||||
|
|
||||||
|
spans := spanRecorder.Ended()
|
||||||
|
require.Len(t, spans, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, "test_span", spans[0].Name())
|
||||||
|
assert.Equal(t, "HTTP GET", spans[1].Name())
|
||||||
|
assert.NotEmpty(t, spans[1].Parent().SpanID())
|
||||||
|
assert.Equal(t, spans[0].SpanContext().SpanID(), spans[1].Parent().SpanID())
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/propagation"
|
"go.opentelemetry.io/otel/propagation"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
@ -87,9 +88,19 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracer := t.tracer
|
||||||
|
|
||||||
|
if tracer == nil {
|
||||||
|
if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
|
||||||
|
tracer = newTracer(span.TracerProvider())
|
||||||
|
} else {
|
||||||
|
tracer = newTracer(otel.GetTracerProvider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options
|
opts := append([]trace.SpanStartOption{}, t.spanStartOptions...) // start with the configured options
|
||||||
|
|
||||||
ctx, span := t.tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
ctx, span := tracer.Start(r.Context(), t.spanNameFormatter("", r), opts...)
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...)
|
span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(r)...)
|
||||||
|
|
Loading…
Reference in New Issue