diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d81a9cca76..14aae8ad00e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm | 10 | Out of Range | | 14 | Unavailable | | 15 | Data Loss | +- The `Status` type was added to the `go.opentelemetry.io/otel/sdk/trace` package to represent the status of a span. (#1874) ### Changed @@ -31,6 +32,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - BatchSpanProcessor now report export failures when calling `ForceFlush()` method. (#1860) - `Set.Encoded(Encoder)` no longer caches the result of an encoding. (#1855) - Renamed `CloudZoneKey` to `CloudAvailabilityZoneKey` in Resource semantic conventions according to spec. (#1871) +- The `StatusCode` and `StatusMessage` methods of the `ReadOnlySpan` interface and the `Span` produced by the `go.opentelemetry.io/otel/sdk/trace` package have been replaced with a single `Status` method. + This method returns the status of a span using the new `Status` type. (#1874) ### Deprecated diff --git a/exporters/otlp/internal/otlptest/data.go b/exporters/otlp/internal/otlptest/data.go index 0a61f101cf7..adfdd6b5814 100644 --- a/exporters/otlp/internal/otlptest/data.go +++ b/exporters/otlp/internal/otlptest/data.go @@ -99,8 +99,7 @@ func SingleSpanSnapshot() []*tracesdk.SpanSnapshot { Attributes: []attribute.KeyValue{}, MessageEvents: []tracesdk.Event{}, Links: []trace.Link{}, - StatusCode: codes.Ok, - StatusMessage: "", + Status: tracesdk.Status{Code: codes.Ok}, DroppedAttributeCount: 0, DroppedMessageEventCount: 0, DroppedLinkCount: 0, diff --git a/exporters/otlp/internal/transform/span.go b/exporters/otlp/internal/transform/span.go index 342f349a24f..19801791abd 100644 --- a/exporters/otlp/internal/transform/span.go +++ b/exporters/otlp/internal/transform/span.go @@ -108,7 +108,7 @@ func span(sd *tracesdk.SpanSnapshot) *tracepb.Span { TraceId: tid[:], SpanId: sid[:], TraceState: sd.SpanContext.TraceState().String(), - Status: status(sd.StatusCode, sd.StatusMessage), + Status: status(sd.Status.Code, sd.Status.Description), StartTimeUnixNano: uint64(sd.StartTime.UnixNano()), EndTimeUnixNano: uint64(sd.EndTime.UnixNano()), Links: links(sd.Links), diff --git a/exporters/otlp/internal/transform/span_test.go b/exporters/otlp/internal/transform/span_test.go index 9d89a80b7bd..eef72645224 100644 --- a/exporters/otlp/internal/transform/span_test.go +++ b/exporters/otlp/internal/transform/span_test.go @@ -249,8 +249,10 @@ func TestSpanData(t *testing.T) { }, }, }, - StatusCode: codes.Error, - StatusMessage: "utterly unrecognized", + Status: tracesdk.Status{ + Code: codes.Error, + Description: "utterly unrecognized", + }, Attributes: []attribute.KeyValue{ attribute.Int64("timeout_ns", 12e9), }, @@ -276,7 +278,7 @@ func TestSpanData(t *testing.T) { Kind: tracepb.Span_SPAN_KIND_SERVER, StartTimeUnixNano: uint64(startTime.UnixNano()), EndTimeUnixNano: uint64(endTime.UnixNano()), - Status: status(spanData.StatusCode, spanData.StatusMessage), + Status: status(spanData.Status.Code, spanData.Status.Description), Events: spanEvents(spanData.MessageEvents), Links: links(spanData.Links), Attributes: Attributes(spanData.Attributes), diff --git a/exporters/otlp/otlp_span_test.go b/exporters/otlp/otlp_span_test.go index 26931ca6bb4..9764280e2e6 100644 --- a/exporters/otlp/otlp_span_test.go +++ b/exporters/otlp/otlp_span_test.go @@ -68,9 +68,11 @@ func TestExportSpans(t *testing.T) { attribute.String("user", "alice"), attribute.Bool("authenticated", true), }, - StatusCode: codes.Ok, - StatusMessage: "Ok", - Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")), + Status: tracesdk.Status{ + Code: codes.Ok, + Description: "Ok", + }, + Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")), InstrumentationLibrary: instrumentation.Library{ Name: "lib-a", Version: "v0.1.0", @@ -90,9 +92,11 @@ func TestExportSpans(t *testing.T) { attribute.String("user", "alice"), attribute.Bool("authenticated", true), }, - StatusCode: codes.Ok, - StatusMessage: "Ok", - Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")), + Status: tracesdk.Status{ + Code: codes.Ok, + Description: "Ok", + }, + Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")), InstrumentationLibrary: instrumentation.Library{ Name: "lib-b", Version: "v0.1.0", @@ -117,9 +121,11 @@ func TestExportSpans(t *testing.T) { attribute.String("user", "alice"), attribute.Bool("authenticated", true), }, - StatusCode: codes.Ok, - StatusMessage: "Ok", - Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")), + Status: tracesdk.Status{ + Code: codes.Ok, + Description: "Ok", + }, + Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")), InstrumentationLibrary: instrumentation.Library{ Name: "lib-a", Version: "v0.1.0", @@ -139,9 +145,11 @@ func TestExportSpans(t *testing.T) { attribute.String("user", "bob"), attribute.Bool("authenticated", false), }, - StatusCode: codes.Error, - StatusMessage: "Unauthenticated", - Resource: resource.NewWithAttributes(attribute.String("instance", "tester-b")), + Status: tracesdk.Status{ + Code: codes.Error, + Description: "Unauthenticated", + }, + Resource: resource.NewWithAttributes(attribute.String("instance", "tester-b")), InstrumentationLibrary: instrumentation.Library{ Name: "lib-a", Version: "v1.1.0", diff --git a/exporters/stdout/trace_test.go b/exporters/stdout/trace_test.go index 8fa64feff58..6c2974a5c57 100644 --- a/exporters/stdout/trace_test.go +++ b/exporters/stdout/trace_test.go @@ -64,10 +64,12 @@ func TestExporter_ExportSpan(t *testing.T) { {Name: "foo", Attributes: []attribute.KeyValue{attribute.String("key", keyValue)}, Time: now}, {Name: "bar", Attributes: []attribute.KeyValue{attribute.Float64("double", doubleValue)}, Time: now}, }, - SpanKind: trace.SpanKindInternal, - StatusCode: codes.Error, - StatusMessage: "interesting", - Resource: resource, + SpanKind: trace.SpanKindInternal, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "interesting", + }, + Resource: resource, } if err := ex.ExportSpans(context.Background(), []*tracesdk.SpanSnapshot{testSpan}); err != nil { t.Fatal(err) @@ -129,8 +131,7 @@ func TestExporter_ExportSpan(t *testing.T) { `}` + `],` + `"Links":null,` + - `"StatusCode":"Error",` + - `"StatusMessage":"interesting",` + + `"Status":{"Code":"Error","Description":"interesting"},` + `"DroppedAttributeCount":0,` + `"DroppedMessageEventCount":0,` + `"DroppedLinkCount":0,` + diff --git a/exporters/trace/jaeger/jaeger.go b/exporters/trace/jaeger/jaeger.go index d3f5f97fc2a..c2926b61f46 100644 --- a/exporters/trace/jaeger/jaeger.go +++ b/exporters/trace/jaeger/jaeger.go @@ -170,13 +170,13 @@ func spanSnapshotToThrift(ss *sdktrace.SpanSnapshot) *gen.Span { ) } - if ss.StatusCode != codes.Unset { - tags = append(tags, getInt64Tag(keyStatusCode, int64(ss.StatusCode))) - if ss.StatusMessage != "" { - tags = append(tags, getStringTag(keyStatusMessage, ss.StatusMessage)) + if ss.Status.Code != codes.Unset { + tags = append(tags, getInt64Tag(keyStatusCode, int64(ss.Status.Code))) + if ss.Status.Description != "" { + tags = append(tags, getStringTag(keyStatusMessage, ss.Status.Description)) } - if ss.StatusCode == codes.Error { + if ss.Status.Code == codes.Error { tags = append(tags, getBoolTag(keyError, true)) } } diff --git a/exporters/trace/jaeger/jaeger_test.go b/exporters/trace/jaeger/jaeger_test.go index cb9a4549593..a503f323b93 100644 --- a/exporters/trace/jaeger/jaeger_test.go +++ b/exporters/trace/jaeger/jaeger_test.go @@ -229,11 +229,11 @@ func Test_spanSnapshotToThrift(t *testing.T) { TraceID: traceID, SpanID: spanID, }), - Name: "/foo", - StartTime: now, - EndTime: now, - StatusCode: codes.Error, - SpanKind: trace.SpanKindClient, + Name: "/foo", + StartTime: now, + EndTime: now, + Status: sdktrace.Status{Code: codes.Error}, + SpanKind: trace.SpanKindClient, InstrumentationLibrary: instrumentation.Library{ Name: instrLibName, Version: instrLibVersion, @@ -287,9 +287,11 @@ func Test_spanSnapshotToThrift(t *testing.T) { Time: now, }, }, - StatusCode: codes.Error, - StatusMessage: statusMessage, - SpanKind: trace.SpanKindClient, + Status: sdktrace.Status{ + Code: codes.Error, + Description: statusMessage, + }, + SpanKind: trace.SpanKindClient, InstrumentationLibrary: instrumentation.Library{ Name: instrLibName, Version: instrLibVersion, @@ -370,9 +372,11 @@ func Test_spanSnapshotToThrift(t *testing.T) { Attributes: []attribute.KeyValue{ attribute.Array("arr", []int{0, 1, 2, 3}), }, - StatusCode: codes.Unset, - StatusMessage: statusMessage, - SpanKind: trace.SpanKindInternal, + Status: sdktrace.Status{ + Code: codes.Unset, + Description: statusMessage, + }, + SpanKind: trace.SpanKindInternal, InstrumentationLibrary: instrumentation.Library{ Name: instrLibName, Version: instrLibVersion, @@ -421,9 +425,11 @@ func Test_spanSnapshotToThrift(t *testing.T) { attribute.Int64("rk2", rv2), semconv.ServiceNameKey.String("service name"), ), - StatusCode: codes.Unset, - StatusMessage: statusMessage, - SpanKind: trace.SpanKindInternal, + Status: sdktrace.Status{ + Code: codes.Unset, + Description: statusMessage, + }, + SpanKind: trace.SpanKindInternal, InstrumentationLibrary: instrumentation.Library{ Name: instrLibName, Version: instrLibVersion, diff --git a/exporters/trace/zipkin/model.go b/exporters/trace/zipkin/model.go index 74a3ffc71a6..51a8bc75ec2 100644 --- a/exporters/trace/zipkin/model.go +++ b/exporters/trace/zipkin/model.go @@ -187,12 +187,12 @@ func toZipkinTags(data *tracesdk.SpanSnapshot) map[string]string { } } - if data.StatusCode != codes.Unset { - m["otel.status_code"] = data.StatusCode.String() + if data.Status.Code != codes.Unset { + m["otel.status_code"] = data.Status.Code.String() } - if data.StatusCode == codes.Error { - m["error"] = data.StatusMessage + if data.Status.Code == codes.Error { + m["error"] = data.Status.Description } else { delete(m, "error") } diff --git a/exporters/trace/zipkin/model_test.go b/exporters/trace/zipkin/model_test.go index ab0979a0554..999ae4f7859 100644 --- a/exporters/trace/zipkin/model_test.go +++ b/exporters/trace/zipkin/model_test.go @@ -74,9 +74,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data with no parent (same as typical, but has // invalid parent) @@ -107,9 +109,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data of unspecified kind { @@ -143,9 +147,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data of internal kind { @@ -179,9 +185,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data of client kind { @@ -218,9 +226,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data of producer kind { @@ -254,9 +264,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data of consumer kind { @@ -290,9 +302,11 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data with no events { @@ -313,9 +327,11 @@ func TestModelConversion(t *testing.T) { attribute.String("attr2", "bar"), }, MessageEvents: nil, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // span data with an "error" attribute set to "false" { @@ -348,8 +364,7 @@ func TestModelConversion(t *testing.T) { Attributes: nil, }, }, - StatusCode: codes.Unset, - Resource: resource, + Resource: resource, }, } @@ -759,8 +774,10 @@ func TestTagsTransformation(t *testing.T) { attribute.String("key", keyValue), attribute.Bool("error", true), }, - StatusCode: codes.Error, - StatusMessage: statusMessage, + Status: tracesdk.Status{ + Code: codes.Error, + Description: statusMessage, + }, }, want: map[string]string{ "error": statusMessage, diff --git a/exporters/trace/zipkin/zipkin_test.go b/exporters/trace/zipkin/zipkin_test.go index 95b0b3d625b..bd63f721e9a 100644 --- a/exporters/trace/zipkin/zipkin_test.go +++ b/exporters/trace/zipkin/zipkin_test.go @@ -246,9 +246,11 @@ func TestExportSpans(t *testing.T) { EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC), Attributes: nil, MessageEvents: nil, - StatusCode: codes.Error, - StatusMessage: "404, file not found", - Resource: resource, + Status: sdktrace.Status{ + Code: codes.Error, + Description: "404, file not found", + }, + Resource: resource, }, // child { @@ -266,9 +268,11 @@ func TestExportSpans(t *testing.T) { EndTime: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC), Attributes: nil, MessageEvents: nil, - StatusCode: codes.Error, - StatusMessage: "403, forbidden", - Resource: resource, + Status: sdktrace.Status{ + Code: codes.Error, + Description: "403, forbidden", + }, + Resource: resource, }, } models := []zkmodel.SpanModel{ diff --git a/sdk/trace/span.go b/sdk/trace/span.go index b7f5296cddf..c69964da0c1 100644 --- a/sdk/trace/span.go +++ b/sdk/trace/span.go @@ -46,8 +46,7 @@ type ReadOnlySpan interface { Attributes() []attribute.KeyValue Links() []trace.Link Events() []Event - StatusCode() codes.Code - StatusMessage() string + Status() Status Tracer() trace.Tracer IsRecording() bool InstrumentationLibrary() instrumentation.Library @@ -92,11 +91,8 @@ type span struct { // value of time.Time until the span is ended. endTime time.Time - // statusCode represents the status of this span as a codes.Code value. - statusCode codes.Code - - // statusMessage represents the status of this span as a string. - statusMessage string + // status is the status of this span. + status Status // childSpanCount holds the number of child spans created for this span. childSpanCount int @@ -154,19 +150,22 @@ func (s *span) IsRecording() bool { return !s.startTime.IsZero() && s.endTime.IsZero() } -// SetStatus sets the status of this span in the form of a code and a -// message. This overrides the existing value of this span's status if one -// exists. Message will be set only if status is error. If this span is not being -// recorded than this method does nothing. -func (s *span) SetStatus(code codes.Code, msg string) { +// SetStatus sets the status of the Span in the form of a code and a +// description, overriding previous values set. The description is only +// included in the set status when the code is for an error. If this span is +// not being recorded than this method does nothing. +func (s *span) SetStatus(code codes.Code, description string) { if !s.IsRecording() { return } - s.mu.Lock() - s.statusCode = code + + status := Status{Code: code} if code == codes.Error { - s.statusMessage = msg + status.Description = description } + + s.mu.Lock() + s.status = status s.mu.Unlock() } @@ -381,18 +380,11 @@ func (s *span) Events() []Event { return s.interfaceArrayToMessageEventArray() } -// StatusCode returns the status code of this span. -func (s *span) StatusCode() codes.Code { +// Status returns the status of this span. +func (s *span) Status() Status { s.mu.Lock() defer s.mu.Unlock() - return s.statusCode -} - -// StatusMessage returns the status message of this span. -func (s *span) StatusMessage() string { - s.mu.Lock() - defer s.mu.Unlock() - return s.statusMessage + return s.status } // InstrumentationLibrary returns the instrumentation.Library associated with @@ -443,8 +435,7 @@ func (s *span) Snapshot() *SpanSnapshot { sd.SpanContext = s.spanContext sd.SpanKind = s.spanKind sd.StartTime = s.startTime - sd.StatusCode = s.statusCode - sd.StatusMessage = s.statusMessage + sd.Status = s.status if s.attributes.evictList.Len() > 0 { sd.Attributes = s.attributes.toKeyValue() @@ -580,6 +571,15 @@ func isSampled(s SamplingResult) bool { return s.Decision == RecordAndSample } +// Status is the classified state of a Span. +type Status struct { + // Code is an identifier of a Span's state classification. + Code codes.Code + // Message is a user hint about why the status was set. It is only + // applicable when Code is Error. + Description string +} + // SpanSnapshot is a snapshot of a span which contains all the information // collected by the span. Its main purpose is exporting completed spans. // Although SpanSnapshot fields can be accessed and potentially modified, @@ -597,8 +597,7 @@ type SpanSnapshot struct { Attributes []attribute.KeyValue MessageEvents []Event Links []trace.Link - StatusCode codes.Code - StatusMessage string + Status Status // DroppedAttributeCount contains dropped attributes for the span itself. DroppedAttributeCount int diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index 6495215f116..4789c20d109 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -738,11 +738,13 @@ func TestSetSpanStatus(t *testing.T) { TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - StatusCode: codes.Error, - StatusMessage: "Error", + Parent: sc.WithRemote(true), + Name: "span0", + SpanKind: trace.SpanKindInternal, + Status: Status{ + Code: codes.Error, + Description: "Error", + }, InstrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, } if diff := cmpDiff(got, want); diff != "" { @@ -766,11 +768,13 @@ func TestSetSpanStatusWithoutMessageWhenStatusIsNotError(t *testing.T) { TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - StatusCode: codes.Ok, - StatusMessage: "", + Parent: sc.WithRemote(true), + Name: "span0", + SpanKind: trace.SpanKindInternal, + Status: Status{ + Code: codes.Ok, + Description: "", + }, InstrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, } if diff := cmpDiff(got, want); diff != "" { @@ -1115,10 +1119,10 @@ func TestRecordError(t *testing.T) { TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - StatusCode: codes.Unset, - SpanKind: trace.SpanKindInternal, + Parent: sc.WithRemote(true), + Name: "span0", + Status: Status{Code: codes.Unset}, + SpanKind: trace.SpanKindInternal, MessageEvents: []Event{ { Name: semconv.ExceptionEventName, @@ -1154,11 +1158,13 @@ func TestRecordErrorNil(t *testing.T) { TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - StatusCode: codes.Unset, - StatusMessage: "", + Parent: sc.WithRemote(true), + Name: "span0", + SpanKind: trace.SpanKindInternal, + Status: Status{ + Code: codes.Unset, + Description: "", + }, InstrumentationLibrary: instrumentation.Library{Name: "RecordErrorNil"}, } if diff := cmpDiff(got, want); diff != "" { @@ -1380,8 +1386,8 @@ func TestReadOnlySpan(t *testing.T) { assert.Equal(t, linked, ro.Links()[0].SpanContext) assert.Equal(t, kv.Key, ro.Events()[0].Attributes[0].Key) assert.Equal(t, kv.Value, ro.Events()[0].Attributes[0].Value) - assert.Equal(t, codes.Ok, ro.StatusCode()) - assert.Equal(t, "", ro.StatusMessage()) + assert.Equal(t, codes.Ok, ro.Status().Code) + assert.Equal(t, "", ro.Status().Description) assert.Equal(t, "ReadOnlySpan", ro.InstrumentationLibrary().Name) assert.Equal(t, "3", ro.InstrumentationLibrary().Version) assert.Equal(t, kv.Key, ro.Resource().Attributes()[0].Key) diff --git a/trace/trace.go b/trace/trace.go index af1d89e5c5e..d1494100609 100644 --- a/trace/trace.go +++ b/trace/trace.go @@ -525,9 +525,9 @@ type Span interface { SpanContext() SpanContext // SetStatus sets the status of the Span in the form of a code and a - // message. SetStatus overrides the value of previous calls to SetStatus - // on the Span. - SetStatus(code codes.Code, msg string) + // description, overriding previous values set. The description is only + // included in a status when the code is for an error. + SetStatus(code codes.Code, description string) // SetName sets the Span name. SetName(name string)