1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-07-13 01:00:22 +02:00

Update Span API event methods (#1254)

* Update Span API event methods

Remove the context argument from the event methods. It is unused and can
be added back in as a passed option if needed in the future.

Update AddEvent to accept a required name and a set of options. These
options are the new EventOption type that can be used to configure a
SpanConfig Timestamp and Attributes.

Remove the AddEventWithTimestamp method as it is redundant to calling
AddEvent with a WithTimestamp option.

Update RecordError to also accept the EventOptions.

* Add changes to CHANGELOG

* Add LifeCycleOption

Use the LifeCycleOption to encapsulate the options passed to a span for
life cycle events.
This commit is contained in:
Tyler Yahn
2020-10-16 08:09:27 -07:00
committed by GitHub
parent 786a78ea36
commit ec300b28ad
16 changed files with 162 additions and 283 deletions

View File

@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added ### Added
- `EventOption` and the related `NewEventConfig` function are added to the `go.opentelemetry.io/otel` package to configure Span events. (#1254)
- A `TextMapPropagator` and associated `TextMapCarrier` are added to the `go.opentelemetry.io/otel/oteltest` package to test TextMap type propagators and their use. (#1259) - A `TextMapPropagator` and associated `TextMapCarrier` are added to the `go.opentelemetry.io/otel/oteltest` package to test TextMap type propagators and their use. (#1259)
### Changed ### Changed
@ -27,10 +28,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
This matches the returned type and fixes misuse of the term metric. (#1240) This matches the returned type and fixes misuse of the term metric. (#1240)
- Move test harness from the `go.opentelemetry.io/otel/api/apitest` package into `go.opentelemetry.io/otel/oteltest`. (#1241) - Move test harness from the `go.opentelemetry.io/otel/api/apitest` package into `go.opentelemetry.io/otel/oteltest`. (#1241)
- Rename `MergeItererator` to `MergeIterator` in the `go.opentelemetry.io/otel/label` package. (#1244) - Rename `MergeItererator` to `MergeIterator` in the `go.opentelemetry.io/otel/label` package. (#1244)
- The function signature of the Span `AddEvent` method in `go.opentelemetry.io/otel` is updated to no longer take an unused context and instead take a required name and a variable number of `EventOption`s. (#1254)
- The function signature of the Span `RecordError` method in `go.opentelemetry.io/otel` is updated to no longer take an unused context and instead take a required error value and a variable number of `EventOption`s. (#1254)
### Removed ### Removed
- The `ErrInvalidHexID`, `ErrInvalidTraceIDLength`, `ErrInvalidSpanIDLength`, `ErrInvalidSpanIDLength`, or `ErrNilSpanID` from the `go.opentelemetry.io/otel` package are unexported now. (#1243) - The `ErrInvalidHexID`, `ErrInvalidTraceIDLength`, `ErrInvalidSpanIDLength`, `ErrInvalidSpanIDLength`, or `ErrNilSpanID` from the `go.opentelemetry.io/otel` package are unexported now. (#1243)
- The `AddEventWithTimestamp` method on the `Span` interface in `go.opentelemetry.io/otel` is removed due to its redundancy.
It is replaced by using the `AddEvent` method with a `WithTimestamp` option. (#1254)
### Fixed ### Fixed

View File

@ -114,7 +114,11 @@ func (s *bridgeSpan) FinishWithOptions(opts ot.FinishOptions) {
} }
func (s *bridgeSpan) logRecord(record ot.LogRecord) { func (s *bridgeSpan) logRecord(record ot.LogRecord) {
s.otelSpan.AddEventWithTimestamp(context.Background(), record.Timestamp, "", otLogFieldsToOTelLabels(record.Fields)...) s.otelSpan.AddEvent(
"",
otel.WithTimestamp(record.Timestamp),
otel.WithAttributes(otLogFieldsToOTelLabels(record.Fields)...),
)
} }
func (s *bridgeSpan) Context() ot.SpanContext { func (s *bridgeSpan) Context() ot.SpanContext {
@ -141,7 +145,10 @@ func (s *bridgeSpan) SetTag(key string, value interface{}) ot.Span {
} }
func (s *bridgeSpan) LogFields(fields ...otlog.Field) { func (s *bridgeSpan) LogFields(fields ...otlog.Field) {
s.otelSpan.AddEvent(context.Background(), "", otLogFieldsToOTelLabels(fields)...) s.otelSpan.AddEvent(
"",
otel.WithAttributes(otLogFieldsToOTelLabels(fields)...),
)
} }
type bridgeFieldEncoder struct { type bridgeFieldEncoder struct {

View File

@ -179,10 +179,9 @@ func (t *MockTracer) DeferredContextSetupHook(ctx context.Context, span otel.Spa
} }
type MockEvent struct { type MockEvent struct {
CtxAttributes baggage.Map Timestamp time.Time
Timestamp time.Time Name string
Name string Attributes baggage.Map
Attributes baggage.Map
} }
type MockSpan struct { type MockSpan struct {
@ -245,7 +244,7 @@ func (s *MockSpan) End(options ...otel.SpanOption) {
s.mockTracer.FinishedSpans = append(s.mockTracer.FinishedSpans, s) s.mockTracer.FinishedSpans = append(s.mockTracer.FinishedSpans, s)
} }
func (s *MockSpan) RecordError(ctx context.Context, err error, opts ...otel.ErrorOption) { func (s *MockSpan) RecordError(err error, opts ...otel.EventOption) {
if err == nil { if err == nil {
return // no-op on nil error return // no-op on nil error
} }
@ -254,37 +253,25 @@ func (s *MockSpan) RecordError(ctx context.Context, err error, opts ...otel.Erro
return // already finished return // already finished
} }
cfg := otel.NewErrorConfig(opts...) s.SetStatus(codes.Error, "")
opts = append(opts, otel.WithAttributes(
if cfg.Timestamp.IsZero() {
cfg.Timestamp = time.Now()
}
if cfg.StatusCode != codes.Ok {
s.SetStatus(cfg.StatusCode, "")
}
s.AddEventWithTimestamp(ctx, cfg.Timestamp, "error",
label.String("error.type", reflect.TypeOf(err).String()), label.String("error.type", reflect.TypeOf(err).String()),
label.String("error.message", err.Error()), label.String("error.message", err.Error()),
) ))
s.AddEvent("error", opts...)
} }
func (s *MockSpan) Tracer() otel.Tracer { func (s *MockSpan) Tracer() otel.Tracer {
return s.officialTracer return s.officialTracer
} }
func (s *MockSpan) AddEvent(ctx context.Context, name string, attrs ...label.KeyValue) { func (s *MockSpan) AddEvent(name string, o ...otel.EventOption) {
s.AddEventWithTimestamp(ctx, time.Now(), name, attrs...) c := otel.NewEventConfig(o...)
}
func (s *MockSpan) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue) {
s.Events = append(s.Events, MockEvent{ s.Events = append(s.Events, MockEvent{
CtxAttributes: baggage.MapFromContext(ctx), Timestamp: c.Timestamp,
Timestamp: timestamp, Name: name,
Name: name,
Attributes: baggage.NewMap(baggage.MapUpdate{ Attributes: baggage.NewMap(baggage.MapUpdate{
MultiKV: attrs, MultiKV: c.Attributes,
}), }),
}) })
} }

View File

@ -17,7 +17,6 @@ package otel
import ( import (
"time" "time"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
) )
@ -51,44 +50,6 @@ func WithInstrumentationVersion(version string) TracerOption {
return instVersionTracerOption(version) return instVersionTracerOption(version)
} }
// ErrorConfig is a group of options for an error event.
type ErrorConfig struct {
Timestamp time.Time
StatusCode codes.Code
}
// NewErrorConfig applies all the options to a returned ErrorConfig.
func NewErrorConfig(options ...ErrorOption) *ErrorConfig {
c := new(ErrorConfig)
for _, option := range options {
option.ApplyError(c)
}
return c
}
// ErrorOption applies an option to a ErrorConfig.
type ErrorOption interface {
ApplyError(*ErrorConfig)
}
type errorTimeOption time.Time
func (o errorTimeOption) ApplyError(c *ErrorConfig) { c.Timestamp = time.Time(o) }
// WithErrorTime sets the time at which the error event should be recorded.
func WithErrorTime(t time.Time) ErrorOption {
return errorTimeOption(t)
}
type errorStatusOption struct{ c codes.Code }
func (o errorStatusOption) ApplyError(c *ErrorConfig) { c.StatusCode = o.c }
// WithErrorStatus indicates the span status that should be set when recording an error event.
func WithErrorStatus(c codes.Code) ErrorOption {
return errorStatusOption{c}
}
// SpanConfig is a group of options for a Span. // SpanConfig is a group of options for a Span.
type SpanConfig struct { type SpanConfig struct {
// Attributes describe the associated qualities of a Span. // Attributes describe the associated qualities of a Span.
@ -124,27 +85,64 @@ type SpanOption interface {
ApplySpan(*SpanConfig) ApplySpan(*SpanConfig)
} }
// NewEventConfig applies all the EventOptions to a returned SpanConfig. If no
// timestamp option is passed, the returned SpanConfig will have a Timestamp
// set to the call time, otherwise no validation is performed on the returned
// SpanConfig.
func NewEventConfig(options ...EventOption) *SpanConfig {
c := new(SpanConfig)
for _, option := range options {
option.ApplyEvent(c)
}
if c.Timestamp.IsZero() {
c.Timestamp = time.Now()
}
return c
}
// EventOption applies span event options to a SpanConfig.
type EventOption interface {
ApplyEvent(*SpanConfig)
}
// LifeCycleOption applies span life-cycle options to a SpanConfig. These
// options set values releated to events in a spans life-cycle like starting,
// ending, experiencing an error and other user defined notable events.
type LifeCycleOption interface {
SpanOption
EventOption
}
type attributeSpanOption []label.KeyValue type attributeSpanOption []label.KeyValue
func (o attributeSpanOption) ApplySpan(c *SpanConfig) { func (o attributeSpanOption) ApplySpan(c *SpanConfig) { o.apply(c) }
func (o attributeSpanOption) ApplyEvent(c *SpanConfig) { o.apply(c) }
func (o attributeSpanOption) apply(c *SpanConfig) {
c.Attributes = append(c.Attributes, []label.KeyValue(o)...) c.Attributes = append(c.Attributes, []label.KeyValue(o)...)
} }
// WithAttributes adds the attributes to a span. These attributes are meant to // WithAttributes adds the attributes related to a span life-cycle event.
// provide additional information about the work the Span represents. The // These attributes are used to describe the work a Span represents when this
// attributes are added to the existing Span attributes, i.e. this does not // option is provided to a Span's start or end events. Otherwise, these
// overwrite. // attributes provide additional information about the event being recorded
func WithAttributes(attributes ...label.KeyValue) SpanOption { // (e.g. error, state change, processing progress, system event).
//
// If multiple of these options are passed the attributes of each successive
// option will extend the attributes instead of overwriting. There is no
// guarantee of uniqueness in the resulting attributes.
func WithAttributes(attributes ...label.KeyValue) LifeCycleOption {
return attributeSpanOption(attributes) return attributeSpanOption(attributes)
} }
type timestampSpanOption time.Time type timestampSpanOption time.Time
func (o timestampSpanOption) ApplySpan(c *SpanConfig) { c.Timestamp = time.Time(o) } func (o timestampSpanOption) ApplySpan(c *SpanConfig) { o.apply(c) }
func (o timestampSpanOption) ApplyEvent(c *SpanConfig) { o.apply(c) }
func (o timestampSpanOption) apply(c *SpanConfig) { c.Timestamp = time.Time(o) }
// WithTimestamp sets the time of a Span life-cycle moment (e.g. started or // WithTimestamp sets the time of a Span life-cycle moment (e.g. started,
// stopped). // stopped, errored).
func WithTimestamp(t time.Time) SpanOption { func WithTimestamp(t time.Time) LifeCycleOption {
return timestampSpanOption(t) return timestampSpanOption(t)
} }

View File

@ -88,7 +88,7 @@ func main() {
ctx, span = tracer.Start(ctx, "operation") ctx, span = tracer.Start(ctx, "operation")
defer span.End() defer span.End()
span.AddEvent(ctx, "Nice operation!", label.Int("bogons", 100)) span.AddEvent("Nice operation!", otel.WithAttributes(label.Int("bogons", 100)))
span.SetAttributes(anotherKey.String("yes")) span.SetAttributes(anotherKey.String("yes"))
meter.RecordBatch( meter.RecordBatch(
@ -105,7 +105,7 @@ func main() {
defer span.End() defer span.End()
span.SetAttributes(lemonsKey.String("five")) span.SetAttributes(lemonsKey.String("five"))
span.AddEvent(ctx, "Sub span event") span.AddEvent("Sub span event")
valuerecorder.Record(ctx, 1.3) valuerecorder.Record(ctx, 1.3)
return nil return nil

View File

@ -34,10 +34,10 @@ func SubOperation(ctx context.Context) error {
tr := global.Tracer("example/namedtracer/foo") tr := global.Tracer("example/namedtracer/foo")
var span otel.Span var span otel.Span
ctx, span = tr.Start(ctx, "Sub operation...") _, span = tr.Start(ctx, "Sub operation...")
defer span.End() defer span.End()
span.SetAttributes(lemonsKey.String("five")) span.SetAttributes(lemonsKey.String("five"))
span.AddEvent(ctx, "Sub span event") span.AddEvent("Sub span event")
return nil return nil
} }

View File

@ -68,7 +68,7 @@ func main() {
var span otel.Span var span otel.Span
ctx, span = tracer.Start(ctx, "operation") ctx, span = tracer.Start(ctx, "operation")
defer span.End() defer span.End()
span.AddEvent(ctx, "Nice operation!", label.Int("bogons", 100)) span.AddEvent("Nice operation!", otel.WithAttributes(label.Int("bogons", 100)))
span.SetAttributes(anotherKey.String("yes")) span.SetAttributes(anotherKey.String("yes"))
if err := foo.SubOperation(ctx); err != nil { if err := foo.SubOperation(ctx); err != nil {
panic(err) panic(err)

View File

@ -20,7 +20,7 @@ import (
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
) )
// Event encapsulates the properties of calls to AddEvent or AddEventWithTimestamp. // Event encapsulates the properties of calls to AddEvent.
type Event struct { type Event struct {
Timestamp time.Time Timestamp time.Time
Name string Name string

View File

@ -188,10 +188,10 @@ func (h *Harness) testSpan(tracerFactory func() otel.Tracer) {
span.End() span.End()
}, },
"#AddEvent": func(span otel.Span) { "#AddEvent": func(span otel.Span) {
span.AddEvent(context.Background(), "test event") span.AddEvent("test event")
}, },
"#AddEventWithTimestamp": func(span otel.Span) { "#AddEventWithTimestamp": func(span otel.Span) {
span.AddEventWithTimestamp(context.Background(), time.Now(), "test event") span.AddEvent("test event", otel.WithTimestamp(time.Now().Add(1*time.Second)))
}, },
"#SetStatus": func(span otel.Span) { "#SetStatus": func(span otel.Span) {
span.SetStatus(codes.Error, "internal") span.SetStatus(codes.Error, "internal")

View File

@ -15,9 +15,6 @@
package oteltest package oteltest
import ( import (
"context"
"time"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
@ -44,9 +41,7 @@ func (ms *MockSpan) SpanContext() otel.SpanContext {
} }
// IsRecording always returns false for MockSpan. // IsRecording always returns false for MockSpan.
func (ms *MockSpan) IsRecording() bool { func (ms *MockSpan) IsRecording() bool { return false }
return false
}
// SetStatus does nothing. // SetStatus does nothing.
func (ms *MockSpan) SetStatus(status codes.Code, msg string) { func (ms *MockSpan) SetStatus(status codes.Code, msg string) {
@ -55,35 +50,22 @@ func (ms *MockSpan) SetStatus(status codes.Code, msg string) {
} }
// SetError does nothing. // SetError does nothing.
func (ms *MockSpan) SetError(v bool) { func (ms *MockSpan) SetError(v bool) {}
}
// SetAttributes does nothing. // SetAttributes does nothing.
func (ms *MockSpan) SetAttributes(attributes ...label.KeyValue) { func (ms *MockSpan) SetAttributes(attributes ...label.KeyValue) {}
}
// End does nothing. // End does nothing.
func (ms *MockSpan) End(options ...otel.SpanOption) { func (ms *MockSpan) End(options ...otel.SpanOption) {}
}
// RecordError does nothing. // RecordError does nothing.
func (ms *MockSpan) RecordError(ctx context.Context, err error, opts ...otel.ErrorOption) { func (ms *MockSpan) RecordError(err error, opts ...otel.EventOption) {}
}
// SetName sets the span name. // SetName sets the span name.
func (ms *MockSpan) SetName(name string) { func (ms *MockSpan) SetName(name string) { ms.Name = name }
ms.Name = name
}
// Tracer returns MockTracer implementation of Tracer. // Tracer returns MockTracer implementation of Tracer.
func (ms *MockSpan) Tracer() otel.Tracer { func (ms *MockSpan) Tracer() otel.Tracer { return ms.tracer }
return ms.tracer
}
// AddEvent does nothing. // AddEvent does nothing.
func (ms *MockSpan) AddEvent(ctx context.Context, name string, attrs ...label.KeyValue) { func (ms *MockSpan) AddEvent(string, ...otel.EventOption) {}
}
// AddEventWithTimestamp does nothing.
func (ms *MockSpan) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue) {
}

View File

@ -15,7 +15,6 @@
package oteltest package oteltest
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"sync" "sync"
@ -81,40 +80,28 @@ func (s *Span) End(opts ...otel.SpanOption) {
} }
// RecordError records an error as a Span event. // RecordError records an error as a Span event.
func (s *Span) RecordError(ctx context.Context, err error, opts ...otel.ErrorOption) { func (s *Span) RecordError(err error, opts ...otel.EventOption) {
if err == nil || s.ended { if err == nil || s.ended {
return return
} }
cfg := otel.NewErrorConfig(opts...)
if cfg.Timestamp.IsZero() {
cfg.Timestamp = time.Now()
}
if cfg.StatusCode != codes.Unset {
s.SetStatus(cfg.StatusCode, "")
}
errType := reflect.TypeOf(err) errType := reflect.TypeOf(err)
errTypeString := fmt.Sprintf("%s.%s", errType.PkgPath(), errType.Name()) errTypeString := fmt.Sprintf("%s.%s", errType.PkgPath(), errType.Name())
if errTypeString == "." { if errTypeString == "." {
errTypeString = errType.String() errTypeString = errType.String()
} }
s.AddEventWithTimestamp(ctx, cfg.Timestamp, errorEventName, s.SetStatus(codes.Error, "")
opts = append(opts, otel.WithAttributes(
errorTypeKey.String(errTypeString), errorTypeKey.String(errTypeString),
errorMessageKey.String(err.Error()), errorMessageKey.String(err.Error()),
) ))
s.AddEvent(errorEventName, opts...)
} }
// AddEvent adds an event to s. // AddEvent adds an event to s.
func (s *Span) AddEvent(ctx context.Context, name string, attrs ...label.KeyValue) { func (s *Span) AddEvent(name string, o ...otel.EventOption) {
s.AddEventWithTimestamp(ctx, time.Now(), name, attrs...)
}
// AddEventWithTimestamp adds an event that occurred at timestamp to s.
func (s *Span) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
@ -122,14 +109,18 @@ func (s *Span) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, n
return return
} }
attributes := make(map[label.Key]label.Value) c := otel.NewEventConfig(o...)
for _, attr := range attrs { var attributes map[label.Key]label.Value
attributes[attr.Key] = attr.Value if l := len(c.Attributes); l > 0 {
attributes = make(map[label.Key]label.Value, l)
for _, attr := range c.Attributes {
attributes[attr.Key] = attr.Value
}
} }
s.events = append(s.events, Event{ s.events = append(s.events, Event{
Timestamp: timestamp, Timestamp: c.Timestamp,
Name: name, Name: name,
Attributes: attributes, Attributes: attributes,
}) })
@ -186,16 +177,12 @@ func (s *Span) SetAttributes(attrs ...label.KeyValue) {
// Name returns the name most recently set on s, either at or after creation // Name returns the name most recently set on s, either at or after creation
// time. It cannot be change after End has been called on s. // time. It cannot be change after End has been called on s.
func (s *Span) Name() string { func (s *Span) Name() string { return s.name }
return s.name
}
// ParentSpanID returns the SpanID of the parent Span. If s is a root Span, // ParentSpanID returns the SpanID of the parent Span. If s is a root Span,
// and therefore does not have a parent, the returned SpanID will be invalid // and therefore does not have a parent, the returned SpanID will be invalid
// (i.e., it will contain all zeroes). // (i.e., it will contain all zeroes).
func (s *Span) ParentSpanID() otel.SpanID { func (s *Span) ParentSpanID() otel.SpanID { return s.parentSpanID }
return s.parentSpanID
}
// Attributes returns the attributes set on s, either at or after creation // Attributes returns the attributes set on s, either at or after creation
// time. If the same attribute key was set multiple times, the last call will // time. If the same attribute key was set multiple times, the last call will
@ -215,9 +202,7 @@ func (s *Span) Attributes() map[label.Key]label.Value {
// Events returns the events set on s. Events cannot be changed after End has // Events returns the events set on s. Events cannot be changed after End has
// been called on s. // been called on s.
func (s *Span) Events() []Event { func (s *Span) Events() []Event { return s.events }
return s.events
}
// Links returns the links set on s at creation time. If multiple links for // Links returns the links set on s at creation time. If multiple links for
// the same SpanContext were set, the last link will be used. // the same SpanContext were set, the last link will be used.
@ -233,37 +218,25 @@ func (s *Span) Links() map[otel.SpanContext][]label.KeyValue {
// StartTime returns the time at which s was started. This will be the // StartTime returns the time at which s was started. This will be the
// wall-clock time unless a specific start time was provided. // wall-clock time unless a specific start time was provided.
func (s *Span) StartTime() time.Time { func (s *Span) StartTime() time.Time { return s.startTime }
return s.startTime
}
// EndTime returns the time at which s was ended if at has been ended, or // EndTime returns the time at which s was ended if at has been ended, or
// false otherwise. If the span has been ended, the returned time will be the // false otherwise. If the span has been ended, the returned time will be the
// wall-clock time unless a specific end time was provided. // wall-clock time unless a specific end time was provided.
func (s *Span) EndTime() (time.Time, bool) { func (s *Span) EndTime() (time.Time, bool) { return s.endTime, s.ended }
return s.endTime, s.ended
}
// Ended returns whether s has been ended, i.e. whether End has been called at // Ended returns whether s has been ended, i.e. whether End has been called at
// least once on s. // least once on s.
func (s *Span) Ended() bool { func (s *Span) Ended() bool { return s.ended }
return s.ended
}
// StatusCode returns the code of the status most recently set on s, or // StatusCode returns the code of the status most recently set on s, or
// codes.OK if no status has been explicitly set. It cannot be changed after // codes.OK if no status has been explicitly set. It cannot be changed after
// End has been called on s. // End has been called on s.
func (s *Span) StatusCode() codes.Code { func (s *Span) StatusCode() codes.Code { return s.statusCode }
return s.statusCode
}
// StatusMessage returns the status message most recently set on s or the // StatusMessage returns the status message most recently set on s or the
// empty string if no status message was set. // empty string if no status message was set.
func (s *Span) StatusMessage() string { func (s *Span) StatusMessage() string { return s.statusMessage }
return s.statusMessage
}
// SpanKind returns the span kind of s. // SpanKind returns the span kind of s.
func (s *Span) SpanKind() otel.SpanKind { func (s *Span) SpanKind() otel.SpanKind { return s.spanKind }
return s.spanKind
}

View File

@ -150,13 +150,13 @@ func TestSpan(t *testing.T) {
e := matchers.NewExpecter(t) e := matchers.NewExpecter(t)
tracer := tp.Tracer(t.Name()) tracer := tp.Tracer(t.Name())
ctx, span := tracer.Start(context.Background(), "test") _, span := tracer.Start(context.Background(), "test")
subject, ok := span.(*oteltest.Span) subject, ok := span.(*oteltest.Span)
e.Expect(ok).ToBeTrue() e.Expect(ok).ToBeTrue()
testTime := time.Now() testTime := time.Now()
subject.RecordError(ctx, s.err, otel.WithErrorTime(testTime)) subject.RecordError(s.err, otel.WithTimestamp(testTime))
expectedEvents := []oteltest.Event{{ expectedEvents := []oteltest.Event{{
Timestamp: testTime, Timestamp: testTime,
@ -167,7 +167,7 @@ func TestSpan(t *testing.T) {
}, },
}} }}
e.Expect(subject.Events()).ToEqual(expectedEvents) e.Expect(subject.Events()).ToEqual(expectedEvents)
e.Expect(subject.StatusCode()).ToEqual(codes.Unset) e.Expect(subject.StatusCode()).ToEqual(codes.Error)
e.Expect(subject.StatusMessage()).ToEqual("") e.Expect(subject.StatusMessage()).ToEqual("")
} }
}) })
@ -178,7 +178,7 @@ func TestSpan(t *testing.T) {
e := matchers.NewExpecter(t) e := matchers.NewExpecter(t)
tracer := tp.Tracer(t.Name()) tracer := tp.Tracer(t.Name())
ctx, span := tracer.Start(context.Background(), "test") _, span := tracer.Start(context.Background(), "test")
subject, ok := span.(*oteltest.Span) subject, ok := span.(*oteltest.Span)
e.Expect(ok).ToBeTrue() e.Expect(ok).ToBeTrue()
@ -186,8 +186,7 @@ func TestSpan(t *testing.T) {
errMsg := "test error message" errMsg := "test error message"
testErr := ottest.NewTestError(errMsg) testErr := ottest.NewTestError(errMsg)
testTime := time.Now() testTime := time.Now()
expStatusCode := codes.Error subject.RecordError(testErr, otel.WithTimestamp(testTime))
subject.RecordError(ctx, testErr, otel.WithErrorTime(testTime), otel.WithErrorStatus(expStatusCode))
expectedEvents := []oteltest.Event{{ expectedEvents := []oteltest.Event{{
Timestamp: testTime, Timestamp: testTime,
@ -198,7 +197,7 @@ func TestSpan(t *testing.T) {
}, },
}} }}
e.Expect(subject.Events()).ToEqual(expectedEvents) e.Expect(subject.Events()).ToEqual(expectedEvents)
e.Expect(subject.StatusCode()).ToEqual(expStatusCode) e.Expect(subject.StatusCode()).ToEqual(codes.Error)
}) })
t.Run("cannot be set after the span has ended", func(t *testing.T) { t.Run("cannot be set after the span has ended", func(t *testing.T) {
@ -207,13 +206,13 @@ func TestSpan(t *testing.T) {
e := matchers.NewExpecter(t) e := matchers.NewExpecter(t)
tracer := tp.Tracer(t.Name()) tracer := tp.Tracer(t.Name())
ctx, span := tracer.Start(context.Background(), "test") _, span := tracer.Start(context.Background(), "test")
subject, ok := span.(*oteltest.Span) subject, ok := span.(*oteltest.Span)
e.Expect(ok).ToBeTrue() e.Expect(ok).ToBeTrue()
subject.End() subject.End()
subject.RecordError(ctx, errors.New("ignored error")) subject.RecordError(errors.New("ignored error"))
e.Expect(len(subject.Events())).ToEqual(0) e.Expect(len(subject.Events())).ToEqual(0)
}) })
@ -224,12 +223,12 @@ func TestSpan(t *testing.T) {
e := matchers.NewExpecter(t) e := matchers.NewExpecter(t)
tracer := tp.Tracer(t.Name()) tracer := tp.Tracer(t.Name())
ctx, span := tracer.Start(context.Background(), "test") _, span := tracer.Start(context.Background(), "test")
subject, ok := span.(*oteltest.Span) subject, ok := span.(*oteltest.Span)
e.Expect(ok).ToBeTrue() e.Expect(ok).ToBeTrue()
subject.RecordError(ctx, nil) subject.RecordError(nil)
e.Expect(len(subject.Events())).ToEqual(0) e.Expect(len(subject.Events())).ToEqual(0)
}) })
@ -465,7 +464,7 @@ func TestSpan(t *testing.T) {
} }
event1Start := time.Now() event1Start := time.Now()
subject.AddEvent(context.Background(), event1Name, event1Attributes...) subject.AddEvent(event1Name, otel.WithAttributes(event1Attributes...))
event1End := time.Now() event1End := time.Now()
event2Timestamp := time.Now().AddDate(5, 0, 0) event2Timestamp := time.Now().AddDate(5, 0, 0)
@ -474,7 +473,7 @@ func TestSpan(t *testing.T) {
label.String("event2Attr", "abc"), label.String("event2Attr", "abc"),
} }
subject.AddEventWithTimestamp(context.Background(), event2Timestamp, event2Name, event2Attributes...) subject.AddEvent(event2Name, otel.WithTimestamp(event2Timestamp), otel.WithAttributes(event2Attributes...))
events := subject.Events() events := subject.Events()
@ -511,14 +510,14 @@ func TestSpan(t *testing.T) {
subject, ok := span.(*oteltest.Span) subject, ok := span.(*oteltest.Span)
e.Expect(ok).ToBeTrue() e.Expect(ok).ToBeTrue()
subject.AddEvent(context.Background(), "test") subject.AddEvent("test")
e.Expect(len(subject.Events())).ToEqual(1) e.Expect(len(subject.Events())).ToEqual(1)
expectedEvent := subject.Events()[0] expectedEvent := subject.Events()[0]
subject.End() subject.End()
subject.AddEvent(context.Background(), "should not occur") subject.AddEvent("should not occur")
e.Expect(len(subject.Events())).ToEqual(1) e.Expect(len(subject.Events())).ToEqual(1)
e.Expect(subject.Events()[0]).ToEqual(expectedEvent) e.Expect(subject.Events()[0]).ToEqual(expectedEvent)

View File

@ -15,7 +15,6 @@
package trace package trace
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
@ -119,11 +118,12 @@ func (s *span) End(options ...otel.SpanOption) {
if recovered := recover(); recovered != nil { if recovered := recover(); recovered != nil {
// Record but don't stop the panic. // Record but don't stop the panic.
defer panic(recovered) defer panic(recovered)
s.addEventWithTimestamp( s.addEvent(
time.Now(),
errorEventName, errorEventName,
errorTypeKey.String(typeStr(recovered)), otel.WithAttributes(
errorMessageKey.String(fmt.Sprint(recovered)), errorTypeKey.String(typeStr(recovered)),
errorMessageKey.String(fmt.Sprint(recovered)),
),
) )
} }
@ -151,29 +151,17 @@ func (s *span) End(options ...otel.SpanOption) {
}) })
} }
func (s *span) RecordError(ctx context.Context, err error, opts ...otel.ErrorOption) { func (s *span) RecordError(err error, opts ...otel.EventOption) {
if s == nil || err == nil { if s == nil || err == nil || !s.IsRecording() {
return return
} }
if !s.IsRecording() { s.SetStatus(codes.Error, "")
return opts = append(opts, otel.WithAttributes(
}
cfg := otel.NewErrorConfig(opts...)
if cfg.Timestamp.IsZero() {
cfg.Timestamp = time.Now()
}
if cfg.StatusCode != codes.Unset {
s.SetStatus(cfg.StatusCode, "")
}
s.AddEventWithTimestamp(ctx, cfg.Timestamp, errorEventName,
errorTypeKey.String(typeStr(err)), errorTypeKey.String(typeStr(err)),
errorMessageKey.String(err.Error()), errorMessageKey.String(err.Error()),
) ))
s.addEvent(errorEventName, opts...)
} }
func typeStr(i interface{}) string { func typeStr(i interface{}) string {
@ -189,27 +177,22 @@ func (s *span) Tracer() otel.Tracer {
return s.tracer return s.tracer
} }
func (s *span) AddEvent(ctx context.Context, name string, attrs ...label.KeyValue) { func (s *span) AddEvent(name string, o ...otel.EventOption) {
if !s.IsRecording() { if !s.IsRecording() {
return return
} }
s.addEventWithTimestamp(time.Now(), name, attrs...) s.addEvent(name, o...)
} }
func (s *span) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue) { func (s *span) addEvent(name string, o ...otel.EventOption) {
if !s.IsRecording() { c := otel.NewEventConfig(o...)
return
}
s.addEventWithTimestamp(timestamp, name, attrs...)
}
func (s *span) addEventWithTimestamp(timestamp time.Time, name string, attrs ...label.KeyValue) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
s.messageEvents.add(export.Event{ s.messageEvents.add(export.Event{
Name: name, Name: name,
Attributes: attrs, Attributes: c.Attributes,
Time: timestamp, Time: c.Timestamp,
}) })
} }

View File

@ -512,11 +512,11 @@ func TestEvents(t *testing.T) {
k2v2 := label.Bool("key2", true) k2v2 := label.Bool("key2", true)
k3v3 := label.Int64("key3", 3) k3v3 := label.Int64("key3", 3)
span.AddEvent(context.Background(), "foo", label.String("key1", "value1")) span.AddEvent("foo", otel.WithAttributes(label.String("key1", "value1")))
span.AddEvent(context.Background(), "bar", span.AddEvent("bar", otel.WithAttributes(
label.Bool("key2", true), label.Bool("key2", true),
label.Int64("key3", 3), label.Int64("key3", 3),
) ))
got, err := endSpan(te, span) got, err := endSpan(te, span)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -558,16 +558,16 @@ func TestEventsOverLimit(t *testing.T) {
k2v2 := label.Bool("key2", false) k2v2 := label.Bool("key2", false)
k3v3 := label.String("key3", "value3") k3v3 := label.String("key3", "value3")
span.AddEvent(context.Background(), "fooDrop", label.String("key1", "value1")) span.AddEvent("fooDrop", otel.WithAttributes(label.String("key1", "value1")))
span.AddEvent(context.Background(), "barDrop", span.AddEvent("barDrop", otel.WithAttributes(
label.Bool("key2", true), label.Bool("key2", true),
label.String("key3", "value3"), label.String("key3", "value3"),
) ))
span.AddEvent(context.Background(), "foo", label.String("key1", "value1")) span.AddEvent("foo", otel.WithAttributes(label.String("key1", "value1")))
span.AddEvent(context.Background(), "bar", span.AddEvent("bar", otel.WithAttributes(
label.Bool("key2", false), label.Bool("key2", false),
label.String("key3", "value3"), label.String("key3", "value3"),
) ))
got, err := endSpan(te, span) got, err := endSpan(te, span)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1062,9 +1062,7 @@ func TestRecordError(t *testing.T) {
span := startSpan(tp, "RecordError") span := startSpan(tp, "RecordError")
errTime := time.Now() errTime := time.Now()
span.RecordError(context.Background(), s.err, span.RecordError(s.err, otel.WithTimestamp(errTime))
otel.WithErrorTime(errTime),
)
got, err := endSpan(te, span) got, err := endSpan(te, span)
if err != nil { if err != nil {
@ -1078,6 +1076,7 @@ func TestRecordError(t *testing.T) {
}, },
ParentSpanID: sid, ParentSpanID: sid,
Name: "span0", Name: "span0",
StatusCode: codes.Error,
SpanKind: otel.SpanKindInternal, SpanKind: otel.SpanKindInternal,
HasRemoteParent: true, HasRemoteParent: true,
MessageEvents: []export.Event{ MessageEvents: []export.Event{
@ -1098,58 +1097,12 @@ func TestRecordError(t *testing.T) {
} }
} }
func TestRecordErrorWithStatus(t *testing.T) {
te := NewTestExporter()
tp := NewTracerProvider(WithSyncer(te))
span := startSpan(tp, "RecordErrorWithStatus")
testErr := ottest.NewTestError("test error")
errTime := time.Now()
testStatus := codes.Error
span.RecordError(context.Background(), testErr,
otel.WithErrorTime(errTime),
otel.WithErrorStatus(testStatus),
)
got, err := endSpan(te, span)
if err != nil {
t.Fatal(err)
}
want := &export.SpanData{
SpanContext: otel.SpanContext{
TraceID: tid,
TraceFlags: 0x1,
},
ParentSpanID: sid,
Name: "span0",
SpanKind: otel.SpanKindInternal,
StatusCode: codes.Error,
StatusMessage: "",
HasRemoteParent: true,
MessageEvents: []export.Event{
{
Name: errorEventName,
Time: errTime,
Attributes: []label.KeyValue{
errorTypeKey.String("go.opentelemetry.io/otel/internal/testing.TestError"),
errorMessageKey.String("test error"),
},
},
},
InstrumentationLibrary: instrumentation.Library{Name: "RecordErrorWithStatus"},
}
if diff := cmpDiff(got, want); diff != "" {
t.Errorf("SpanErrorOptions: -got +want %s", diff)
}
}
func TestRecordErrorNil(t *testing.T) { func TestRecordErrorNil(t *testing.T) {
te := NewTestExporter() te := NewTestExporter()
tp := NewTracerProvider(WithSyncer(te)) tp := NewTracerProvider(WithSyncer(te))
span := startSpan(tp, "RecordErrorNil") span := startSpan(tp, "RecordErrorNil")
span.RecordError(context.Background(), nil) span.RecordError(nil)
got, err := endSpan(te, span) got, err := endSpan(te, span)
if err != nil { if err != nil {

View File

@ -19,7 +19,6 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"time"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
@ -241,18 +240,15 @@ type Span interface {
// called other than setting the status. // called other than setting the status.
End(options ...SpanOption) End(options ...SpanOption)
// AddEvent adds an event to the span. // AddEvent adds an event with the provided name and options.
AddEvent(ctx context.Context, name string, attrs ...label.KeyValue) AddEvent(name string, options ...EventOption)
// AddEventWithTimestamp adds an event that occurred at timestamp to the
// Span.
AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...label.KeyValue)
// IsRecording returns the recording state of the Span. It will return // IsRecording returns the recording state of the Span. It will return
// true if the Span is active and events can be recorded. // true if the Span is active and events can be recorded.
IsRecording() bool IsRecording() bool
// RecordError records an error as a Span event. // RecordError records an error as a Span event.
RecordError(ctx context.Context, err error, opts ...ErrorOption) RecordError(err error, options ...EventOption)
// SpanContext returns the SpanContext of the Span. The returned // SpanContext returns the SpanContext of the Span. The returned
// SpanContext is usable even after the End has been called for the Span. // SpanContext is usable even after the End has been called for the Span.

View File

@ -16,7 +16,6 @@ package otel
import ( import (
"context" "context"
"time"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
@ -73,16 +72,13 @@ func (noopSpan) SetAttributes(...label.KeyValue) {}
func (noopSpan) End(...SpanOption) {} func (noopSpan) End(...SpanOption) {}
// RecordError does nothing. // RecordError does nothing.
func (noopSpan) RecordError(context.Context, error, ...ErrorOption) {} func (noopSpan) RecordError(error, ...EventOption) {}
// Tracer returns the Tracer that created this Span. // Tracer returns the Tracer that created this Span.
func (noopSpan) Tracer() Tracer { return noopTracer{} } func (noopSpan) Tracer() Tracer { return noopTracer{} }
// AddEvent does nothing. // AddEvent does nothing.
func (noopSpan) AddEvent(context.Context, string, ...label.KeyValue) {} func (noopSpan) AddEvent(string, ...EventOption) {}
// AddEventWithTimestamp does nothing.
func (noopSpan) AddEventWithTimestamp(context.Context, time.Time, string, ...label.KeyValue) {}
// SetName does nothing. // SetName does nothing.
func (noopSpan) SetName(string) {} func (noopSpan) SetName(string) {}