1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-04-09 07:03:54 +02:00

Remove WithSpan method from Tracer interface (#1043)

* Remove WithSpan method from Tracer interface

Also remove implementation in SDK.

* Add panic event reporting to span End

* Update Changelog with changes

* Update CHANGELOG.md

* Update README.md

Fix code tabs

* Refactor span End

* Fix un-ended span from feedback.
This commit is contained in:
Tyler Yahn 2020-08-08 12:10:36 -07:00 committed by GitHub
parent f9ba15f2d1
commit 2dfa5e4fe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 113 additions and 355 deletions

View File

@ -20,11 +20,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- The `grpctrace` instrumentation was moved to the `go.opentelemetry.io/contrib` repository and out of this repository.
This move includes moving the `grpc` example to the `go.opentelemetry.io/contrib` as well. (#1027)
- The `WithSpan` method of the `Tracer` interface.
The functionality this method provided was limited compared to what a user can provide themselves.
It was removed with the understanding that if there is sufficient user need it can be added back based on actual user usage. (#1043)
### Fixed
- The `semconv.HTTPServerMetricAttributesFromHTTPRequest()` function no longer generates the high-cardinality `http.request.content.length` label. (#1031)
- Correct instrumentation version tag in Jaeger exporter. (#1037)
- The SDK span will now set an error event if the `End` method is called during a panic (i.e. it was deferred). (#1043)
## [0.10.0] - 2020-07-29

View File

@ -50,22 +50,9 @@ func main() {
defer pusher.Stop()
tracer := global.Tracer("ex.com/basic")
tracer.WithSpan(context.Background(), "foo",
func(ctx context.Context) error {
tracer.WithSpan(ctx, "bar",
func(ctx context.Context) error {
tracer.WithSpan(ctx, "baz",
func(ctx context.Context) error {
return nil
},
)
return nil
},
)
return nil
},
)
ctx, span := tracer.Start(context.Background(), "main")
defer span.End()
/* … */
}
```

View File

@ -114,15 +114,6 @@ func (t *tracer) setDelegate(provider trace.Provider) {
t.once.Do(func() { t.delegate = provider.Tracer(t.name, t.opts...) })
}
// WithSpan implements trace.Tracer by forwarding the call to t.delegate if
// set, otherwise it forwards the call to a NoopTracer.
func (t *tracer) WithSpan(ctx context.Context, name string, body func(context.Context) error, opts ...trace.StartOption) error {
if t.delegate != nil {
return t.delegate.WithSpan(ctx, name, body, opts...)
}
return trace.NoopTracer{}.WithSpan(ctx, name, body, opts...)
}
// Start implements trace.Tracer by forwarding the call to t.delegate if
// set, otherwise it forwards the call to a NoopTracer.
func (t *tracer) Start(ctx context.Context, name string, opts ...trace.StartOption) (context.Context, trace.Span) {

View File

@ -31,13 +31,9 @@ func TestTraceWithSDK(t *testing.T) {
ctx := context.Background()
gtp := global.TraceProvider()
tracer1 := gtp.Tracer("pre")
// This is started before an SDK was registered and should be dropped.
_, span1 := tracer1.Start(ctx, "span1")
// This should be dropped.
if err := tracer1.WithSpan(ctx, "withSpan1", func(context.Context) error { return nil }); err != nil {
t.Errorf("failed to wrap function with span prior to initialization: %v", err)
}
sr := new(testtrace.StandardSpanRecorder)
tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr))
global.SetTraceProvider(tp)
@ -48,17 +44,11 @@ func TestTraceWithSDK(t *testing.T) {
// The existing Tracer should have been configured to now use the configured SDK.
_, span2 := tracer1.Start(ctx, "span2")
span2.End()
if err := tracer1.WithSpan(ctx, "withSpan2", func(context.Context) error { return nil }); err != nil {
t.Errorf("failed to wrap function with span post initialization: %v", err)
}
// The global trace Provider should now create Tracers that also use the newly configured SDK.
tracer2 := gtp.Tracer("post")
_, span3 := tracer2.Start(ctx, "span3")
span3.End()
if err := tracer2.WithSpan(ctx, "withSpan3", func(context.Context) error { return nil }); err != nil {
t.Errorf("failed to wrap function with span post initialization with new tracer: %v", err)
}
filterNames := func(spans []*testtrace.Span) []string {
names := make([]string, len(spans))
@ -67,7 +57,7 @@ func TestTraceWithSDK(t *testing.T) {
}
return names
}
expected := []string{"span2", "withSpan2", "span3", "withSpan3"}
expected := []string{"span2", "span3"}
assert.ElementsMatch(t, expected, filterNames(sr.Started()))
assert.ElementsMatch(t, expected, filterNames(sr.Completed()))
}

View File

@ -16,7 +16,6 @@ package testharness
import (
"context"
"errors"
"sync"
"testing"
"time"
@ -177,138 +176,6 @@ func (h *Harness) TestTracer(subjectFactory func() trace.Tracer) {
})
})
h.t.Run("#WithSpan", func(t *testing.T) {
t.Run("returns nil if the body does not return an error", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
err := subject.WithSpan(context.Background(), "test", func(ctx context.Context) error {
return nil
})
e.Expect(err).ToBeNil()
})
t.Run("propagates the error from the body if the body errors", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
expectedErr := errors.New("test error")
err := subject.WithSpan(context.Background(), "test", func(ctx context.Context) error {
return expectedErr
})
e.Expect(err).ToMatchError(expectedErr)
})
t.Run("propagates the original context to the body", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
ctxKey := testCtxKey{}
ctxValue := "ctx value"
ctx := context.WithValue(context.Background(), ctxKey, ctxValue)
var actualCtx context.Context
err := subject.WithSpan(ctx, "test", func(ctx context.Context) error {
actualCtx = ctx
return nil
})
e.Expect(err).ToBeNil()
e.Expect(actualCtx.Value(ctxKey)).ToEqual(ctxValue)
})
t.Run("stores a span containing the expected properties on the context provided to the body function", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
var span trace.Span
err := subject.WithSpan(context.Background(), "test", func(ctx context.Context) error {
span = trace.SpanFromContext(ctx)
return nil
})
e.Expect(err).ToBeNil()
e.Expect(span).NotToBeNil()
e.Expect(span.Tracer()).ToEqual(subject)
e.Expect(span.SpanContext().IsValid()).ToBeTrue()
})
t.Run("starts spans with unique trace and span IDs", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
var span1 trace.Span
var span2 trace.Span
err := subject.WithSpan(context.Background(), "span1", func(ctx context.Context) error {
span1 = trace.SpanFromContext(ctx)
return nil
})
e.Expect(err).ToBeNil()
err = subject.WithSpan(context.Background(), "span2", func(ctx context.Context) error {
span2 = trace.SpanFromContext(ctx)
return nil
})
e.Expect(err).ToBeNil()
sc1 := span1.SpanContext()
sc2 := span2.SpanContext()
e.Expect(sc1.TraceID).NotToEqual(sc2.TraceID)
e.Expect(sc1.SpanID).NotToEqual(sc2.SpanID)
})
t.Run("propagates a parent's trace ID through the context", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
subject := subjectFactory()
ctx, parent := subject.Start(context.Background(), "parent")
var child trace.Span
err := subject.WithSpan(ctx, "child", func(ctx context.Context) error {
child = trace.SpanFromContext(ctx)
return nil
})
e.Expect(err).ToBeNil()
psc := parent.SpanContext()
csc := child.SpanContext()
e.Expect(csc.TraceID).ToEqual(psc.TraceID)
e.Expect(csc.SpanID).NotToEqual(psc.SpanID)
})
})
h.testSpan(subjectFactory)
}
@ -340,19 +207,6 @@ func (h *Harness) testSpan(tracerFactory func() trace.Tracer) {
return subject
},
"Span created via Tracer#WithSpan": func() trace.Span {
tracer := tracerFactory()
var actualCtx context.Context
_ = tracer.WithSpan(context.Background(), "test", func(ctx context.Context) error {
actualCtx = ctx
return nil
})
return trace.SpanFromContext(actualCtx)
},
}
for mechanismName, mechanism := range mechanisms {

View File

@ -54,16 +54,6 @@ func WithInstrumentationVersion(version string) TracerOption {
type Tracer interface {
// Start a span.
Start(ctx context.Context, spanName string, opts ...StartOption) (context.Context, Span)
// WithSpan wraps the execution of the fn function with a span.
// It starts a new span, sets it as an active span in the context,
// executes the fn function and closes the span before returning the result of fn.
WithSpan(
ctx context.Context,
spanName string,
fn func(ctx context.Context) error,
opts ...StartOption,
) error
}
// EndConfig provides options to set properties of span at the time of ending

View File

@ -22,11 +22,6 @@ type NoopTracer struct{}
var _ Tracer = NoopTracer{}
// WithSpan wraps around execution of func with noop span.
func (t NoopTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error, opts ...StartOption) error {
return body(ctx)
}
// Start starts a noop span.
func (NoopTracer) Start(ctx context.Context, name string, opts ...StartOption) (context.Context, Span) {
span := NoopSpan{}

View File

@ -86,10 +86,3 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...trace.StartOpti
}
return trace.ContextWithSpan(ctx, span), span
}
func (t *Tracer) WithSpan(ctx context.Context, name string, body func(ctx context.Context) error, opts ...trace.StartOption) error {
ctx, span := t.Start(ctx, name, opts...)
defer span.End()
return body(ctx)
}

View File

@ -256,46 +256,6 @@ func TestTracer(t *testing.T) {
e.Expect(links[link2.SpanContext]).ToEqual(link2.Attributes)
})
})
t.Run("#WithSpan", func(t *testing.T) {
testTracedSpan(t, func(tracer trace.Tracer, name string) (trace.Span, error) {
var span trace.Span
err := tracer.WithSpan(context.Background(), name, func(ctx context.Context) error {
span = trace.SpanFromContext(ctx)
return nil
})
return span, err
})
t.Run("honors StartOptions", func(t *testing.T) {
t.Parallel()
e := matchers.NewExpecter(t)
attr1 := kv.String("a", "1")
attr2 := kv.String("b", "2")
subject := tp.Tracer(t.Name())
var span trace.Span
err := subject.WithSpan(context.Background(), "test", func(ctx context.Context) error {
span = trace.SpanFromContext(ctx)
return nil
}, trace.WithAttributes(attr1, attr2))
e.Expect(err).ToBeNil()
testSpan, ok := span.(*testtrace.Span)
e.Expect(ok).ToBeTrue()
attributes := testSpan.Attributes()
e.Expect(attributes[attr1.Key]).ToEqual(attr1.Value)
e.Expect(attributes[attr2.Key]).ToEqual(attr2.Value)
})
})
}
func testTracedSpan(t *testing.T, fn func(tracer trace.Tracer, name string) (trace.Span, error)) {

View File

@ -71,12 +71,6 @@ func NewMockTracer() *MockTracer {
}
}
func (t *MockTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error, opts ...oteltrace.StartOption) error {
ctx, span := t.Start(ctx, name, opts...)
defer span.End()
return body(ctx)
}
func (t *MockTracer) Start(ctx context.Context, name string, opts ...oteltrace.StartOption) (context.Context, oteltrace.Span) {
spanOpts := oteltrace.StartConfig{}
for _, opt := range opts {

View File

@ -71,20 +71,6 @@ func (t *WrapperTracer) otelTracer() oteltrace.Tracer {
return t.tracer
}
// WithSpan forwards the call to the wrapped tracer with a modified
// body callback, which sets the active OpenTracing span before
// calling the original callback.
func (t *WrapperTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error, opts ...oteltrace.StartOption) error {
return t.otelTracer().WithSpan(ctx, name, func(ctx context.Context) error {
span := oteltrace.SpanFromContext(ctx)
if spanWithExtension, ok := span.(migration.OverrideTracerSpanExtension); ok {
spanWithExtension.OverrideTracer(t)
}
ctx = t.bridge.ContextWithBridgeSpan(ctx, span)
return body(ctx)
}, opts...)
}
// Start forwards the call to the wrapped tracer. It also tries to
// override the tracer of the returned span if the span implements the
// OverrideTracerSpanExtension interface.

View File

@ -67,11 +67,13 @@ func main() {
valuerecorder := valuerecorderTwo.Bind(commonLabels...)
defer valuerecorder.Unbind()
err = tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
err = func(ctx context.Context) error {
var span trace.Span
ctx, span = tracer.Start(ctx, "operation")
defer span.End()
trace.SpanFromContext(ctx).AddEvent(ctx, "Nice operation!", kv.Key("bogons").Int(100))
trace.SpanFromContext(ctx).SetAttributes(anotherKey.String("yes"))
span.AddEvent(ctx, "Nice operation!", kv.Key("bogons").Int(100))
span.SetAttributes(anotherKey.String("yes"))
meter.RecordBatch(
// Note: call-site variables added as context Entries:
@ -81,20 +83,18 @@ func main() {
valuerecorderTwo.Measurement(2.0),
)
return tracer.WithSpan(
ctx,
"Sub operation...",
func(ctx context.Context) error {
trace.SpanFromContext(ctx).SetAttributes(lemonsKey.String("five"))
return func(ctx context.Context) error {
var span trace.Span
ctx, span = tracer.Start(ctx, "Sub operation...")
defer span.End()
trace.SpanFromContext(ctx).AddEvent(ctx, "Sub span event")
span.SetAttributes(lemonsKey.String("five"))
span.AddEvent(ctx, "Sub span event")
valuerecorder.Record(ctx, 1.3)
valuerecorder.Record(ctx, 1.3)
return nil
},
)
})
return nil
}(ctx)
}(ctx)
if err != nil {
panic(err)
}

View File

@ -29,19 +29,15 @@ var (
// SubOperation is an example to demonstrate the use of named tracer.
// It creates a named tracer with its package path.
func SubOperation(ctx context.Context) error {
// Using global provider. Alternative is to have application provide a getter
// for its component to get the instance of the provider.
tr := global.Tracer("example/namedtracer/foo")
return tr.WithSpan(
ctx,
"Sub operation...",
func(ctx context.Context) error {
trace.SpanFromContext(ctx).SetAttributes(lemonsKey.String("five"))
trace.SpanFromContext(ctx).AddEvent(ctx, "Sub span event")
var span trace.Span
ctx, span = tr.Start(ctx, "Sub operation...")
defer span.End()
span.SetAttributes(lemonsKey.String("five"))
span.AddEvent(ctx, "Sub span event")
return nil
},
)
return nil
}

View File

@ -65,15 +65,12 @@ func main() {
barKey.String("bar1"),
)
err := tracer.WithSpan(ctx, "operation", func(ctx context.Context) error {
trace.SpanFromContext(ctx).AddEvent(ctx, "Nice operation!", kv.Key("bogons").Int(100))
trace.SpanFromContext(ctx).SetAttributes(anotherKey.String("yes"))
return foo.SubOperation(ctx)
})
if err != nil {
var span trace.Span
ctx, span = tracer.Start(ctx, "operation")
defer span.End()
span.AddEvent(ctx, "Nice operation!", kv.Key("bogons").Int(100))
span.SetAttributes(anotherKey.String("yes"))
if err := foo.SubOperation(ctx); err != nil {
panic(err)
}
}

View File

@ -42,20 +42,37 @@ var (
)
loopCounter = metric.Must(meter).NewInt64Counter("function.loops")
paramValue = metric.Must(meter).NewFloat64ValueRecorder("function.param")
paramValue = metric.Must(meter).NewInt64ValueRecorder("function.param")
nameKey = kv.Key("function.name")
)
func myFunction(ctx context.Context, values ...float64) error {
nameKV := nameKey.String("myFunction")
boundCount := loopCounter.Bind(nameKV)
boundValue := paramValue.Bind(nameKV)
for _, value := range values {
boundCount.Add(ctx, 1)
boundValue.Record(ctx, value)
}
return nil
func add(ctx context.Context, x, y int64) int64 {
nameKV := nameKey.String("add")
var span trace.Span
ctx, span = tracer.Start(ctx, "Addition")
defer span.End()
loopCounter.Add(ctx, 1, nameKV)
paramValue.Record(ctx, x, nameKV)
paramValue.Record(ctx, y, nameKV)
return x + y
}
func multiply(ctx context.Context, x, y int64) int64 {
nameKV := nameKey.String("multiply")
var span trace.Span
ctx, span = tracer.Start(ctx, "Multiplication")
defer span.End()
loopCounter.Add(ctx, 1, nameKV)
paramValue.Record(ctx, x, nameKV)
paramValue.Record(ctx, y, nameKV)
return x * y
}
func Example() {
@ -70,22 +87,6 @@ func Example() {
}
defer pusher.Stop()
err = tracer.WithSpan(
context.Background(),
"myFunction/call",
func(ctx context.Context) error {
err := tracer.WithSpan(
ctx,
"internal/call",
func(ctx context.Context) error { return myFunction(ctx, 200, 100, 5000, 600) },
)
if err != nil {
return err
}
return myFunction(ctx, 100, 200, 500, 800)
},
)
if err != nil {
log.Fatal("Failed to call myFunction", err)
}
ctx := context.Background()
log.Println("the answer is", add(ctx, multiply(ctx, multiply(ctx, 2, 2), 10), 2))
}

View File

@ -43,14 +43,6 @@ type MockTracer struct {
var _ apitrace.Tracer = (*MockTracer)(nil)
// WithSpan does nothing except executing the body.
func (mt *MockTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error, opts ...apitrace.StartOption) error {
ctx, span := mt.Start(ctx, name, opts...)
defer span.End()
return body(ctx)
}
// Start starts a MockSpan. It creates a new Span based on Parent SpanContext option.
// TracdID is used from Parent Span Context and SpanID is assigned.
// If Parent SpanContext option is not specified then random TraceID is used.

View File

@ -109,11 +109,23 @@ func (s *span) SetAttribute(k string, v interface{}) {
}
}
// End ends the span adding an error event if it was called while panicking.
func (s *span) End(options ...apitrace.EndOption) {
if s == nil {
return
}
if recovered := recover(); recovered != nil {
// Record but don't stop the panic.
defer panic(recovered)
s.addEventWithTimestamp(
time.Now(),
errorEventName,
errorTypeKey.String(typeStr(recovered)),
errorMessageKey.String(fmt.Sprint(recovered)),
)
}
if s.executionTracerTaskEnd != nil {
s.executionTracerTaskEnd()
}
@ -164,19 +176,21 @@ func (s *span) RecordError(ctx context.Context, err error, opts ...apitrace.Erro
s.SetStatus(cfg.StatusCode, "")
}
errType := reflect.TypeOf(err)
errTypeString := fmt.Sprintf("%s.%s", errType.PkgPath(), errType.Name())
if errTypeString == "." {
// PkgPath() and Name() may be empty for builtin Types
errTypeString = errType.String()
}
s.AddEventWithTimestamp(ctx, cfg.Timestamp, errorEventName,
errorTypeKey.String(errTypeString),
errorTypeKey.String(typeStr(err)),
errorMessageKey.String(err.Error()),
)
}
func typeStr(i interface{}) string {
t := reflect.TypeOf(i)
if t.PkgPath() == "" && t.Name() == "" {
// Likely a builtin type.
return t.String()
}
return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
}
func (s *span) Tracer() apitrace.Tracer {
return s.tracer
}

View File

@ -27,6 +27,8 @@ import (
"go.opentelemetry.io/otel/api/global"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"go.opentelemetry.io/otel/api/kv"
@ -1146,3 +1148,26 @@ func TestWithInstrumentationVersion(t *testing.T) {
t.Errorf("WithResource:\n -got +want %s", diff)
}
}
func TestSpanCapturesPanic(t *testing.T) {
var te testExporter
tp, _ := NewProvider(WithSyncer(&te))
_, span := tp.Tracer("CatchPanic").Start(
context.Background(),
"span",
apitrace.WithRecord(),
)
f := func() {
defer span.End()
panic(errors.New("error message"))
}
require.PanicsWithError(t, "error message", f)
require.Len(t, te.spans, 1)
require.Len(t, te.spans[0].MessageEvents, 1)
assert.Equal(t, te.spans[0].MessageEvents[0].Name, errorEventName)
assert.Equal(t, te.spans[0].MessageEvents[0].Attributes, []kv.KeyValue{
errorTypeKey.String("*errors.errorString"),
errorMessageKey.String("error message"),
})
}

View File

@ -66,14 +66,3 @@ func (tr *tracer) Start(ctx context.Context, name string, o ...apitrace.StartOpt
span.executionTracerTaskEnd = end
return apitrace.ContextWithSpan(ctx, span), span
}
func (tr *tracer) WithSpan(ctx context.Context, name string, body func(ctx context.Context) error, opts ...apitrace.StartOption) error {
ctx, span := tr.Start(ctx, name, opts...)
defer span.End()
if err := body(ctx); err != nil {
// TODO: set event with boolean attribute for error.
return err
}
return nil
}