diff --git a/CHANGELOG.md b/CHANGELOG.md index 94f5ca008ee..8078e631b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm | 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) +- The `SpanStub` type and its associated functions were added to the `go.opentelemetry.io/otel/sdk/trace/tracetest` package. + This type can be used as a testing replacement for the `SpanSnapshot` that was removed from the `go.opentelemetry.io/otel/sdk/trace` package. (#1873) ### Changed @@ -34,6 +36,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - 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) +- The `ExportSpans` method of the`SpanExporter` interface type was updated to accept `ReadOnlySpan`s instead of the removed `SpanSnapshot`. + This brings the export interface into compliance with the specification in that it now accepts an explicitly immutable type instead of just an implied one. (#1873) - Unembed `SpanContext` in `Link`. (#1877) ### Deprecated @@ -42,6 +46,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Remove `resource.WithoutBuiltin()`. Use `resource.New()`. (#1810) - Unexported types `resource.FromEnv`, `resource.Host`, and `resource.TelemetrySDK`, Use the corresponding `With*()` to use individually. (#1810) +- Removed the `Tracer` and `IsRecording` method from the `ReadOnlySpan` in the `go.opentelemetry.io/otel/sdk/trace`. + The `Tracer` method is not a required to be included in this interface and given the mutable nature of the tracer that is associated with a span, this method is not appropriate. + The `IsRecording` method returns if the span is recording or not. + A read-only span value does not need to know if updates to it will be recorded or not. + By definition, it cannot be updated so there is no point in communicating if an update is recorded. (#1873) +- Removed the `SpanSnapshot` type from the `go.opentelemetry.io/otel/sdk/trace` package. + The use of this type has been replaced with the use of the explicitly immutable `ReadOnlySpan` type. + When a concrete representation of a read-only span is needed for testing, the newly added `SpanStub` in the `go.opentelemetry.io/otel/sdk/trace/tracetest` package should be used. (#1873) ### Fixed diff --git a/exporters/otlp/internal/otlptest/data.go b/exporters/otlp/internal/otlptest/data.go index adfdd6b5814..08f5dc38d00 100644 --- a/exporters/otlp/internal/otlptest/data.go +++ b/exporters/otlp/internal/otlptest/data.go @@ -28,6 +28,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric/aggregator/sum" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) @@ -78,39 +79,40 @@ func (OneRecordCheckpointSet) ForEach(kindSelector exportmetric.ExportKindSelect return recordFunc(rec) } -// SingleSpanSnapshot returns a one-element slice with a snapshot. It +// SingleReadOnlySpan returns a one-element slice with a read-only span. It // may be useful for testing driver's trace export. -func SingleSpanSnapshot() []*tracesdk.SpanSnapshot { - sd := &tracesdk.SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, - SpanID: trace.SpanID{3, 4, 5, 6, 7, 8, 9, 0}, - TraceFlags: trace.FlagsSampled, - }), - Parent: trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, - SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, - TraceFlags: trace.FlagsSampled, - }), - SpanKind: trace.SpanKindInternal, - Name: "foo", - StartTime: time.Date(2020, time.December, 8, 20, 23, 0, 0, time.UTC), - EndTime: time.Date(2020, time.December, 0, 20, 24, 0, 0, time.UTC), - Attributes: []attribute.KeyValue{}, - MessageEvents: []tracesdk.Event{}, - Links: []trace.Link{}, - Status: tracesdk.Status{Code: codes.Ok}, - DroppedAttributeCount: 0, - DroppedMessageEventCount: 0, - DroppedLinkCount: 0, - ChildSpanCount: 0, - Resource: resource.NewWithAttributes(attribute.String("a", "b")), - InstrumentationLibrary: instrumentation.Library{ - Name: "bar", - Version: "0.0.0", +func SingleReadOnlySpan() []tracesdk.ReadOnlySpan { + return tracetest.SpanStubs{ + { + SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, + SpanID: trace.SpanID{3, 4, 5, 6, 7, 8, 9, 0}, + TraceFlags: trace.FlagsSampled, + }), + Parent: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9}, + SpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8}, + TraceFlags: trace.FlagsSampled, + }), + SpanKind: trace.SpanKindInternal, + Name: "foo", + StartTime: time.Date(2020, time.December, 8, 20, 23, 0, 0, time.UTC), + EndTime: time.Date(2020, time.December, 0, 20, 24, 0, 0, time.UTC), + Attributes: []attribute.KeyValue{}, + Events: []tracesdk.Event{}, + Links: []trace.Link{}, + Status: tracesdk.Status{Code: codes.Ok}, + DroppedAttributes: 0, + DroppedEvents: 0, + DroppedLinks: 0, + ChildSpanCount: 0, + Resource: resource.NewWithAttributes(attribute.String("a", "b")), + InstrumentationLibrary: instrumentation.Library{ + Name: "bar", + Version: "0.0.0", + }, }, - } - return []*tracesdk.SpanSnapshot{sd} + }.Snapshots() } // EmptyCheckpointSet is a checkpointer that has no records at all. diff --git a/exporters/otlp/internal/transform/span.go b/exporters/otlp/internal/transform/span.go index 597db9fff9b..bcf540e4e43 100644 --- a/exporters/otlp/internal/transform/span.go +++ b/exporters/otlp/internal/transform/span.go @@ -25,12 +25,12 @@ import ( ) const ( - maxMessageEventsPerSpan = 128 + maxEventsPerSpan = 128 ) -// SpanData transforms a slice of SpanSnapshot into a slice of OTLP +// Spans transforms a slice of OpenTelemetry spans into a slice of OTLP // ResourceSpans. -func SpanData(sdl []*tracesdk.SpanSnapshot) []*tracepb.ResourceSpans { +func Spans(sdl []tracesdk.ReadOnlySpan) []*tracepb.ResourceSpans { if len(sdl) == 0 { return nil } @@ -49,16 +49,16 @@ func SpanData(sdl []*tracesdk.SpanSnapshot) []*tracepb.ResourceSpans { continue } - rKey := sd.Resource.Equivalent() + rKey := sd.Resource().Equivalent() iKey := ilsKey{ r: rKey, - il: sd.InstrumentationLibrary, + il: sd.InstrumentationLibrary(), } ils, iOk := ilsm[iKey] if !iOk { // Either the resource or instrumentation library were unknown. ils = &tracepb.InstrumentationLibrarySpans{ - InstrumentationLibrary: instrumentationLibrary(sd.InstrumentationLibrary), + InstrumentationLibrary: instrumentationLibrary(sd.InstrumentationLibrary()), Spans: []*tracepb.Span{}, } } @@ -70,7 +70,7 @@ func SpanData(sdl []*tracesdk.SpanSnapshot) []*tracepb.ResourceSpans { resources++ // The resource was unknown. rs = &tracepb.ResourceSpans{ - Resource: Resource(sd.Resource), + Resource: Resource(sd.Resource()), InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{ils}, } rsm[rKey] = rs @@ -96,32 +96,32 @@ func SpanData(sdl []*tracesdk.SpanSnapshot) []*tracepb.ResourceSpans { } // span transforms a Span into an OTLP span. -func span(sd *tracesdk.SpanSnapshot) *tracepb.Span { +func span(sd tracesdk.ReadOnlySpan) *tracepb.Span { if sd == nil { return nil } - tid := sd.SpanContext.TraceID() - sid := sd.SpanContext.SpanID() + tid := sd.SpanContext().TraceID() + sid := sd.SpanContext().SpanID() s := &tracepb.Span{ TraceId: tid[:], SpanId: sid[:], - TraceState: sd.SpanContext.TraceState().String(), - Status: status(sd.Status.Code, sd.Status.Description), - StartTimeUnixNano: uint64(sd.StartTime.UnixNano()), - EndTimeUnixNano: uint64(sd.EndTime.UnixNano()), - Links: links(sd.Links), - Kind: spanKind(sd.SpanKind), - Name: sd.Name, - Attributes: Attributes(sd.Attributes), - Events: spanEvents(sd.MessageEvents), - DroppedAttributesCount: uint32(sd.DroppedAttributeCount), - DroppedEventsCount: uint32(sd.DroppedMessageEventCount), - DroppedLinksCount: uint32(sd.DroppedLinkCount), + TraceState: sd.SpanContext().TraceState().String(), + Status: status(sd.Status().Code, sd.Status().Description), + StartTimeUnixNano: uint64(sd.StartTime().UnixNano()), + EndTimeUnixNano: uint64(sd.EndTime().UnixNano()), + Links: links(sd.Links()), + Kind: spanKind(sd.SpanKind()), + Name: sd.Name(), + Attributes: Attributes(sd.Attributes()), + Events: spanEvents(sd.Events()), + DroppedAttributesCount: uint32(sd.DroppedAttributes()), + DroppedEventsCount: uint32(sd.DroppedEvents()), + DroppedLinksCount: uint32(sd.DroppedLinks()), } - if psid := sd.Parent.SpanID(); psid.IsValid() { + if psid := sd.Parent().SpanID(); psid.IsValid() { s.ParentSpanId = psid[:] } @@ -174,18 +174,18 @@ func spanEvents(es []tracesdk.Event) []*tracepb.Span_Event { } evCount := len(es) - if evCount > maxMessageEventsPerSpan { - evCount = maxMessageEventsPerSpan + if evCount > maxEventsPerSpan { + evCount = maxEventsPerSpan } events := make([]*tracepb.Span_Event, 0, evCount) - messageEvents := 0 + nEvents := 0 // Transform message events for _, e := range es { - if messageEvents >= maxMessageEventsPerSpan { + if nEvents >= maxEventsPerSpan { break } - messageEvents++ + nEvents++ events = append(events, &tracepb.Span_Event{ Name: e.Name, diff --git a/exporters/otlp/internal/transform/span_test.go b/exporters/otlp/internal/transform/span_test.go index a551880a0bf..d32fefed14e 100644 --- a/exporters/otlp/internal/transform/span_test.go +++ b/exporters/otlp/internal/transform/span_test.go @@ -32,6 +32,7 @@ import ( "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func TestSpanKind(t *testing.T) { @@ -101,15 +102,15 @@ func TestSpanEvent(t *testing.T) { } func TestExcessiveSpanEvents(t *testing.T) { - e := make([]tracesdk.Event, maxMessageEventsPerSpan+1) - for i := 0; i < maxMessageEventsPerSpan+1; i++ { + e := make([]tracesdk.Event, maxEventsPerSpan+1) + for i := 0; i < maxEventsPerSpan+1; i++ { e[i] = tracesdk.Event{Name: strconv.Itoa(i)} } - assert.Len(t, e, maxMessageEventsPerSpan+1) + assert.Len(t, e, maxEventsPerSpan+1) got := spanEvents(e) - assert.Len(t, got, maxMessageEventsPerSpan) + assert.Len(t, got, maxEventsPerSpan) // Ensure the drop order. - assert.Equal(t, strconv.Itoa(maxMessageEventsPerSpan-1), got[len(got)-1].Name) + assert.Equal(t, strconv.Itoa(maxEventsPerSpan-1), got[len(got)-1].Name) } func TestNilLinks(t *testing.T) { @@ -185,11 +186,11 @@ func TestNilSpan(t *testing.T) { } func TestNilSpanData(t *testing.T) { - assert.Nil(t, SpanData(nil)) + assert.Nil(t, Spans(nil)) } func TestEmptySpanData(t *testing.T) { - assert.Nil(t, SpanData(nil)) + assert.Nil(t, Spans(nil)) } func TestSpanData(t *testing.T) { @@ -199,7 +200,7 @@ func TestSpanData(t *testing.T) { startTime := time.Unix(1585674086, 1234) endTime := startTime.Add(10 * time.Second) traceState, _ := trace.TraceStateFromKeyValues(attribute.String("key1", "val1"), attribute.String("key2", "val2")) - spanData := &tracesdk.SpanSnapshot{ + spanData := tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}, @@ -215,7 +216,7 @@ func TestSpanData(t *testing.T) { Name: "span data to span data", StartTime: startTime, EndTime: endTime, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ {Time: startTime, Attributes: []attribute.KeyValue{ attribute.Int64("CompressedByteSize", 512), @@ -223,7 +224,7 @@ func TestSpanData(t *testing.T) { }, {Time: endTime, Attributes: []attribute.KeyValue{ - attribute.String("MessageEventType", "Recv"), + attribute.String("EventType", "Recv"), }, }, }, @@ -256,10 +257,10 @@ func TestSpanData(t *testing.T) { Attributes: []attribute.KeyValue{ attribute.Int64("timeout_ns", 12e9), }, - DroppedAttributeCount: 1, - DroppedMessageEventCount: 2, - DroppedLinkCount: 3, - Resource: resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)), + DroppedAttributes: 1, + DroppedEvents: 2, + DroppedLinks: 3, + Resource: resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)), InstrumentationLibrary: instrumentation.Library{ Name: "go.opentelemetry.io/test/otel", Version: "v0.0.1", @@ -279,7 +280,7 @@ func TestSpanData(t *testing.T) { StartTimeUnixNano: uint64(startTime.UnixNano()), EndTimeUnixNano: uint64(endTime.UnixNano()), Status: status(spanData.Status.Code, spanData.Status.Description), - Events: spanEvents(spanData.MessageEvents), + Events: spanEvents(spanData.Events), Links: links(spanData.Links), Attributes: Attributes(spanData.Attributes), DroppedAttributesCount: 1, @@ -287,7 +288,7 @@ func TestSpanData(t *testing.T) { DroppedLinksCount: 3, } - got := SpanData([]*tracesdk.SpanSnapshot{spanData}) + got := Spans(tracetest.SpanStubs{spanData}.Snapshots()) require.Len(t, got, 1) assert.Equal(t, got[0].GetResource(), Resource(spanData.Resource)) @@ -304,7 +305,7 @@ func TestSpanData(t *testing.T) { // Empty parent span ID should be treated as root span. func TestRootSpanData(t *testing.T) { - sd := SpanData([]*tracesdk.SpanSnapshot{{}}) + sd := Spans(tracetest.SpanStubs{{}}.Snapshots()) require.Len(t, sd, 1) rs := sd[0] got := rs.GetInstrumentationLibrarySpans()[0].GetSpans()[0].GetParentSpanId() @@ -314,5 +315,5 @@ func TestRootSpanData(t *testing.T) { } func TestSpanDataNilResource(t *testing.T) { - assert.NotPanics(t, func() { SpanData([]*tracesdk.SpanSnapshot{{}}) }) + assert.NotPanics(t, func() { Spans(tracetest.SpanStubs{{}}.Snapshots()) }) } diff --git a/exporters/otlp/otlp.go b/exporters/otlp/otlp.go index 098d93b423a..8595ea42bd8 100644 --- a/exporters/otlp/otlp.go +++ b/exporters/otlp/otlp.go @@ -129,10 +129,10 @@ func (e *Exporter) ExportKindFor(desc *metric.Descriptor, kind aggregation.Kind) return e.cfg.exportKindSelector.ExportKindFor(desc, kind) } -// ExportSpans transforms and batches trace SpanSnapshots into OTLP Trace and +// ExportSpans transforms and batches OpenTelemetry spans into OTLP Trace and // transmits them to the configured collector. -func (e *Exporter) ExportSpans(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { - return e.driver.ExportTraces(ctx, ss) +func (e *Exporter) ExportSpans(ctx context.Context, spans []tracesdk.ReadOnlySpan) error { + return e.driver.ExportTraces(ctx, spans) } // NewExportPipeline sets up a complete export pipeline diff --git a/exporters/otlp/otlp_span_test.go b/exporters/otlp/otlp_span_test.go index 9764280e2e6..1d4905180df 100644 --- a/exporters/otlp/otlp_span_test.go +++ b/exporters/otlp/otlp_span_test.go @@ -31,6 +31,7 @@ import ( "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func TestExportSpans(t *testing.T) { @@ -41,19 +42,19 @@ func TestExportSpans(t *testing.T) { endTime := startTime.Add(10 * time.Second) for _, test := range []struct { - sd []*tracesdk.SpanSnapshot + sd tracetest.SpanStubs want []*tracepb.ResourceSpans }{ { - []*tracesdk.SpanSnapshot(nil), + tracetest.SpanStubsFromReadOnlySpans(nil), []*tracepb.ResourceSpans(nil), }, { - []*tracesdk.SpanSnapshot{}, + tracetest.SpanStubs{}, []*tracepb.ResourceSpans(nil), }, { - []*tracesdk.SpanSnapshot{ + tracetest.SpanStubs{ { SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), @@ -338,7 +339,7 @@ func TestExportSpans(t *testing.T) { }, } { driver.Reset() - assert.NoError(t, exp.ExportSpans(context.Background(), test.sd)) + assert.NoError(t, exp.ExportSpans(context.Background(), test.sd.Snapshots())) assert.ElementsMatch(t, test.want, driver.rs) } } diff --git a/exporters/otlp/otlp_test.go b/exporters/otlp/otlp_test.go index 42c7cde8049..eee609522b6 100644 --- a/exporters/otlp/otlp_test.go +++ b/exporters/otlp/otlp_test.go @@ -29,16 +29,17 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/internal/transform" metricsdk "go.opentelemetry.io/otel/sdk/export/metric" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" tracepb "go.opentelemetry.io/proto/otlp/trace/v1" ) -func stubSpanSnapshot(count int) []*tracesdk.SpanSnapshot { - spans := make([]*tracesdk.SpanSnapshot, 0, count) +func readonlyspans(count int) []tracesdk.ReadOnlySpan { + spans := make(tracetest.SpanStubs, 0, count) for i := 0; i < count; i++ { - spans = append(spans, new(tracesdk.SpanSnapshot)) + spans = append(spans, tracetest.SpanStub{}) } - return spans + return spans.Snapshots() } type stubCheckpointSet struct { @@ -71,7 +72,7 @@ type stubProtocolDriver struct { injectedStopError error rm []metricsdk.Record - rs []tracesdk.SpanSnapshot + rs tracetest.SpanStubs } var _ otlp.ProtocolDriver = (*stubProtocolDriver)(nil) @@ -104,13 +105,13 @@ func (m *stubProtocolDriver) ExportMetrics(parent context.Context, cps metricsdk }) } -func (m *stubProtocolDriver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { +func (m *stubProtocolDriver) ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { m.tracesExported++ for _, rs := range ss { if rs == nil { continue } - m.rs = append(m.rs, *rs) + m.rs = append(m.rs, tracetest.SpanStubFromReadOnlySpan(rs)) } return nil } @@ -144,8 +145,8 @@ func (m *stubTransformingProtocolDriver) ExportMetrics(parent context.Context, c return nil } -func (m *stubTransformingProtocolDriver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { - for _, rs := range transform.SpanData(ss) { +func (m *stubTransformingProtocolDriver) ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { + for _, rs := range transform.Spans(ss) { if rs == nil { continue } @@ -289,7 +290,7 @@ func TestSplitDriver(t *testing.T) { assertExport := func(t testing.TB, ctx context.Context, driver otlp.ProtocolDriver) { t.Helper() assert.NoError(t, driver.ExportMetrics(ctx, stubCheckpointSet{recordCount}, metricsdk.StatelessExportKindSelector())) - assert.NoError(t, driver.ExportTraces(ctx, stubSpanSnapshot(spanCount))) + assert.NoError(t, driver.ExportTraces(ctx, readonlyspans(spanCount))) } t.Run("with metric/trace drivers configured", func(t *testing.T) { @@ -385,7 +386,7 @@ func TestSplitDriver(t *testing.T) { assert.NoError(t, driver.Start(ctx)) assert.NoError(t, driver.ExportMetrics(ctx, stubCheckpointSet{recordCount}, metricsdk.StatelessExportKindSelector())) - assert.NoError(t, driver.ExportTraces(ctx, stubSpanSnapshot(spanCount))) + assert.NoError(t, driver.ExportTraces(ctx, readonlyspans(spanCount))) assert.NoError(t, driver.Stop(ctx)) }) diff --git a/exporters/otlp/otlpgrpc/driver.go b/exporters/otlp/otlpgrpc/driver.go index 39544564ca9..4b700ff11f2 100644 --- a/exporters/otlp/otlpgrpc/driver.go +++ b/exporters/otlp/otlpgrpc/driver.go @@ -161,7 +161,7 @@ func (md *metricsDriver) uploadMetrics(ctx context.Context, protoMetrics []*metr // ExportTraces implements otlp.ProtocolDriver. It transforms spans to // protobuf binary format and sends the result to the collector. -func (d *driver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { +func (d *driver) ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { if !d.tracesDriver.connection.connected() { return fmt.Errorf("traces exporter is disconnected from the server %s: %w", d.tracesDriver.connection.sCfg.Endpoint, d.tracesDriver.connection.lastConnectError()) } @@ -170,7 +170,7 @@ func (d *driver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) ctx, tCancel := context.WithTimeout(ctx, d.tracesDriver.connection.sCfg.Timeout) defer tCancel() - protoSpans := transform.SpanData(ss) + protoSpans := transform.Spans(ss) if len(protoSpans) == 0 { return nil } diff --git a/exporters/otlp/otlpgrpc/otlp_integration_test.go b/exporters/otlp/otlpgrpc/otlp_integration_test.go index 4eb7306faa0..25dc243bad2 100644 --- a/exporters/otlp/otlpgrpc/otlp_integration_test.go +++ b/exporters/otlp/otlpgrpc/otlp_integration_test.go @@ -38,9 +38,12 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/internal/otlptest" "go.opentelemetry.io/otel/exporters/otlp/otlpgrpc" sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" commonpb "go.opentelemetry.io/proto/otlp/common/v1" ) +var roSpans = tracetest.SpanStubs{{Name: "Span 0"}}.Snapshots() + func TestNewExporter_endToEnd(t *testing.T) { tests := []struct { name string @@ -165,11 +168,11 @@ func TestNewExporter_collectorConnectionDiesThenReconnectsWhenInRestMode(t *test // first export, it will send disconnected message to the channel on export failure, // trigger almost immediate reconnection - require.Error(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "in the midst"}})) + require.Error(t, exp.ExportSpans(ctx, roSpans)) // second export, it will detect connection issue, change state of exporter to disconnected and // send message to disconnected channel but this time reconnection gouroutine will be in (rest mode, not listening to the disconnected channel) - require.Error(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "in the midst"}})) + require.Error(t, exp.ExportSpans(ctx, roSpans)) // as a result we have exporter in disconnected state waiting for disconnection message to reconnect @@ -184,12 +187,12 @@ func TestNewExporter_collectorConnectionDiesThenReconnectsWhenInRestMode(t *test for i := 0; i < n; i++ { // when disconnected exp.ExportSpans doesnt send disconnected messages again // it just quits and return last connection error - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Resurrected"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) } nmaSpans := nmc.getSpans() - // Expecting 10 SpanSnapshots that were sampled, given that + // Expecting 10 spans that were sampled, given that if g, w := len(nmaSpans), n; g != w { t.Fatalf("Connected collector: spans: got %d want %d", g, w) } @@ -214,7 +217,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { { name: "Do not retry if succeeded", fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) span := mc.getSpans() @@ -228,7 +231,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { status.Error(codes.OK, ""), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) span := mc.getSpans() @@ -250,7 +253,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { status.Error(codes.Unavailable, "backend under pressure"), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) span := mc.getSpans() @@ -270,7 +273,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { status.Error(codes.InvalidArgument, "invalid arguments"), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - require.Error(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}})) + require.Error(t, exp.ExportSpans(ctx, roSpans)) span := mc.getSpans() @@ -296,7 +299,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { status.Error(codes.DataLoss, ""), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) span := mc.getSpans() @@ -319,7 +322,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { newThrottlingError(codes.ResourceExhausted, time.Second*30), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - err := exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}}) + err := exp.ExportSpans(ctx, roSpans) require.Error(t, err) require.Equal(t, "context deadline exceeded", err.Error()) @@ -341,7 +344,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { newThrottlingError(codes.ResourceExhausted, time.Minute), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - err := exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}}) + err := exp.ExportSpans(ctx, roSpans) require.Error(t, err) require.Equal(t, "max elapsed time expired when respecting server throttle: rpc error: code = ResourceExhausted desc = ", err.Error()) @@ -368,7 +371,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { status.Error(codes.Unavailable, "unavailable"), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - err := exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}}) + err := exp.ExportSpans(ctx, roSpans) require.Error(t, err) require.Equal(t, "max elapsed time expired: rpc error: code = Unavailable desc = unavailable", err.Error()) @@ -388,7 +391,7 @@ func TestExporterExportFailureAndRecoveryModes(t *testing.T) { status.Error(codes.Unavailable, "unavailable"), }, fn: func(t *testing.T, ctx context.Context, exp *otlp.Exporter, mc *mockCollector) { - err := exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}}) + err := exp.ExportSpans(ctx, roSpans) require.Error(t, err) require.Equal(t, "rpc error: code = Unavailable desc = unavailable", err.Error()) @@ -451,7 +454,7 @@ func TestPermanentErrorsShouldNotBeRetried(t *testing.T) { exp := newGRPCExporter(t, ctx, mc.endpoint) - err := exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Spans"}}) + err := exp.ExportSpans(ctx, roSpans) require.Error(t, err) require.Len(t, mc.getSpans(), 0) require.Equal(t, 1, mc.traceSvc.requests, "trace service must receive 1 permanent error requests.") @@ -492,7 +495,7 @@ func TestNewExporter_collectorConnectionDiesThenReconnects(t *testing.T) { for j := 0; j < 3; j++ { // No endpoint up. - require.Error(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "in the midst"}})) + require.Error(t, exp.ExportSpans(ctx, roSpans)) // Now resurrect the collector by making a new one but reusing the // old endpoint, and the collector should reconnect automatically. @@ -503,11 +506,11 @@ func TestNewExporter_collectorConnectionDiesThenReconnects(t *testing.T) { n := 10 for i := 0; i < n; i++ { - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "Resurrected"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) } nmaSpans := nmc.getSpans() - // Expecting 10 SpanSnapshots that were sampled, given that + // Expecting 10 spans that were sampled, given that if g, w := len(nmaSpans), n; g != w { t.Fatalf("Round #%d: Connected collector: spans: got %d want %d", j, g, w) } @@ -565,7 +568,7 @@ func TestNewExporter_withHeaders(t *testing.T) { ctx := context.Background() exp := newGRPCExporter(t, ctx, mc.endpoint, otlpgrpc.WithHeaders(map[string]string{"header1": "value1"})) - require.NoError(t, exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "in the midst"}})) + require.NoError(t, exp.ExportSpans(ctx, roSpans)) defer func() { _ = exp.Shutdown(ctx) @@ -589,7 +592,7 @@ func TestNewExporter_WithTimeout(t *testing.T) { { name: "Timeout Spans", fn: func(exp *otlp.Exporter) error { - return exp.ExportSpans(context.Background(), []*sdktrace.SpanSnapshot{{Name: "timed out"}}) + return exp.ExportSpans(context.Background(), roSpans) }, timeout: time.Millisecond * 100, code: codes.DeadlineExceeded, @@ -608,7 +611,7 @@ func TestNewExporter_WithTimeout(t *testing.T) { { name: "No Timeout Spans", fn: func(exp *otlp.Exporter) error { - return exp.ExportSpans(context.Background(), []*sdktrace.SpanSnapshot{{Name: "timed out"}}) + return exp.ExportSpans(context.Background(), roSpans) }, timeout: time.Minute, spans: 1, @@ -673,7 +676,7 @@ func TestNewExporter_withInvalidSecurityConfiguration(t *testing.T) { t.Fatalf("failed to create a new collector exporter: %v", err) } - err = exp.ExportSpans(ctx, []*sdktrace.SpanSnapshot{{Name: "misconfiguration"}}) + err = exp.ExportSpans(ctx, roSpans) expectedErr := fmt.Sprintf("traces exporter is disconnected from the server %s: grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)", mc.endpoint) @@ -833,7 +836,7 @@ func TestDisconnected(t *testing.T) { }() assert.Error(t, exp.Export(ctx, otlptest.OneRecordCheckpointSet{})) - assert.Error(t, exp.ExportSpans(ctx, otlptest.SingleSpanSnapshot())) + assert.Error(t, exp.ExportSpans(ctx, otlptest.SingleReadOnlySpan())) } func TestEmptyData(t *testing.T) { diff --git a/exporters/otlp/otlphttp/driver.go b/exporters/otlp/otlphttp/driver.go index eb47c25650f..6ad5d678c25 100644 --- a/exporters/otlp/otlphttp/driver.go +++ b/exporters/otlp/otlphttp/driver.go @@ -187,8 +187,8 @@ func (d *driver) ExportMetrics(ctx context.Context, cps metricsdk.CheckpointSet, } // ExportTraces implements otlp.ProtocolDriver. -func (d *driver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { - protoSpans := transform.SpanData(ss) +func (d *driver) ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { + protoSpans := transform.Spans(ss) if len(protoSpans) == 0 { return nil } diff --git a/exporters/otlp/otlphttp/driver_test.go b/exporters/otlp/otlphttp/driver_test.go index 10190154c37..6fa94f67536 100644 --- a/exporters/otlp/otlphttp/driver_test.go +++ b/exporters/otlp/otlphttp/driver_test.go @@ -165,7 +165,7 @@ func TestRetry(t *testing.T) { defer func() { assert.NoError(t, exporter.Shutdown(ctx)) }() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.NoError(t, err) assert.Len(t, mc.GetSpans(), 1) } @@ -187,7 +187,7 @@ func TestTimeout(t *testing.T) { defer func() { assert.NoError(t, exporter.Shutdown(ctx)) }() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Equal(t, true, os.IsTimeout(err)) } @@ -212,7 +212,7 @@ func TestRetryFailed(t *testing.T) { defer func() { assert.NoError(t, exporter.Shutdown(ctx)) }() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Empty(t, mc.GetSpans()) } @@ -237,7 +237,7 @@ func TestNoRetry(t *testing.T) { defer func() { assert.NoError(t, exporter.Shutdown(ctx)) }() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Equal(t, fmt.Sprintf("failed to send traces to http://%s/v1/traces with HTTP status 400 Bad Request", mc.endpoint), err.Error()) assert.Empty(t, mc.GetSpans()) @@ -325,7 +325,7 @@ func TestUnreasonableMaxAttempts(t *testing.T) { defer func() { assert.NoError(t, exporter.Shutdown(ctx)) }() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Empty(t, mc.GetSpans()) }) @@ -361,7 +361,7 @@ func TestUnreasonableBackoff(t *testing.T) { defer func() { assert.NoError(t, exporter.Shutdown(ctx)) }() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Empty(t, mc.GetSpans()) } @@ -381,7 +381,7 @@ func TestCancelledContext(t *testing.T) { assert.NoError(t, exporter.Shutdown(ctx)) }() cancel() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Empty(t, mc.GetSpans()) } @@ -409,7 +409,7 @@ func TestDeadlineContext(t *testing.T) { }() ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() - err = exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err = exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Empty(t, mc.GetSpans()) } @@ -437,7 +437,7 @@ func TestStopWhileExporting(t *testing.T) { }() doneCh := make(chan struct{}) go func() { - err := exporter.ExportSpans(ctx, otlptest.SingleSpanSnapshot()) + err := exporter.ExportSpans(ctx, otlptest.SingleReadOnlySpan()) assert.Error(t, err) assert.Empty(t, mc.GetSpans()) close(doneCh) diff --git a/exporters/otlp/protocoldriver.go b/exporters/otlp/protocoldriver.go index 209b970d69a..5d15b378ce5 100644 --- a/exporters/otlp/protocoldriver.go +++ b/exporters/otlp/protocoldriver.go @@ -48,7 +48,7 @@ type ProtocolDriver interface { // format and send it to the collector. May be called // concurrently with ExportMetrics, so the manager needs to // take this into account by doing proper locking. - ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error + ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error } // SplitConfig is used to configure a split driver. @@ -151,7 +151,7 @@ func (d *splitDriver) ExportMetrics(ctx context.Context, cps metricsdk.Checkpoin // ExportTraces implements ProtocolDriver. It forwards the call to the // driver used for sending spans. -func (d *splitDriver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { +func (d *splitDriver) ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { return d.trace.ExportTraces(ctx, ss) } @@ -171,6 +171,6 @@ func (d *noopDriver) ExportMetrics(ctx context.Context, cps metricsdk.Checkpoint } // ExportTraces does nothing. -func (d *noopDriver) ExportTraces(ctx context.Context, ss []*tracesdk.SpanSnapshot) error { +func (d *noopDriver) ExportTraces(ctx context.Context, ss []tracesdk.ReadOnlySpan) error { return nil } diff --git a/exporters/stdout/trace.go b/exporters/stdout/trace.go index fbbee19928b..3aaefc54873 100644 --- a/exporters/stdout/trace.go +++ b/exporters/stdout/trace.go @@ -21,6 +21,7 @@ import ( "sync" "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" ) // Exporter is an implementation of trace.SpanSyncer that writes spans to stdout. @@ -31,8 +32,8 @@ type traceExporter struct { stopped bool } -// ExportSpans writes SpanSnapshots in json format to stdout. -func (e *traceExporter) ExportSpans(ctx context.Context, ss []*trace.SpanSnapshot) error { +// ExportSpans writes spans in json format to stdout. +func (e *traceExporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error { e.stoppedMu.RLock() stopped := e.stopped e.stoppedMu.RUnlock() @@ -40,10 +41,10 @@ func (e *traceExporter) ExportSpans(ctx context.Context, ss []*trace.SpanSnapsho return nil } - if e.config.DisableTraceExport || len(ss) == 0 { + if e.config.DisableTraceExport || len(spans) == 0 { return nil } - out, err := e.marshal(ss) + out, err := e.marshal(tracetest.SpanStubsFromReadOnlySpans(spans)) if err != nil { return err } diff --git a/exporters/stdout/trace_test.go b/exporters/stdout/trace_test.go index 6c2974a5c57..8b60790dfcb 100644 --- a/exporters/stdout/trace_test.go +++ b/exporters/stdout/trace_test.go @@ -22,18 +22,21 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/exporters/stdout" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) func TestExporter_ExportSpan(t *testing.T) { // write to buffer for testing var b bytes.Buffer - ex, err := stdout.NewExporter(stdout.WithWriter(&b)) + ex, err := stdout.NewExporter(stdout.WithWriter(&b), stdout.WithPrettyPrint()) if err != nil { t.Errorf("Error constructing stdout exporter %s", err) } @@ -47,108 +50,139 @@ func TestExporter_ExportSpan(t *testing.T) { doubleValue := 123.456 resource := resource.NewWithAttributes(attribute.String("rk1", "rv11")) - testSpan := &tracesdk.SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: traceID, - SpanID: spanID, - TraceState: traceState, - }), - Name: "/foo", - StartTime: now, - EndTime: now, - Attributes: []attribute.KeyValue{ - attribute.String("key", keyValue), - attribute.Float64("double", doubleValue), - }, - MessageEvents: []tracesdk.Event{ - {Name: "foo", Attributes: []attribute.KeyValue{attribute.String("key", keyValue)}, Time: now}, - {Name: "bar", Attributes: []attribute.KeyValue{attribute.Float64("double", doubleValue)}, Time: now}, + ro := tracetest.SpanStubs{ + { + SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceState: traceState, + }), + Name: "/foo", + StartTime: now, + EndTime: now, + Attributes: []attribute.KeyValue{ + attribute.String("key", keyValue), + attribute.Float64("double", doubleValue), + }, + Events: []tracesdk.Event{ + {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, + Status: tracesdk.Status{ + Code: codes.Error, + Description: "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 { + }.Snapshots() + if err := ex.ExportSpans(context.Background(), ro); err != nil { t.Fatal(err) } expectedSerializedNow, _ := json.Marshal(now) got := b.String() - expectedOutput := `[{"SpanContext":{` + - `"TraceID":"0102030405060708090a0b0c0d0e0f10",` + - `"SpanID":"0102030405060708","TraceFlags":"00",` + - `"TraceState":[` + - `{` + - `"Key":"key",` + - `"Value":{"Type":"STRING","Value":"val"}` + - `}],"Remote":false},` + - `"Parent":{` + - `"TraceID":"00000000000000000000000000000000",` + - `"SpanID":"0000000000000000",` + - `"TraceFlags":"00",` + - `"TraceState":null,` + - `"Remote":false` + - `},` + - `"SpanKind":1,` + - `"Name":"/foo",` + - `"StartTime":` + string(expectedSerializedNow) + "," + - `"EndTime":` + string(expectedSerializedNow) + "," + - `"Attributes":[` + - `{` + - `"Key":"key",` + - `"Value":{"Type":"STRING","Value":"value"}` + - `},` + - `{` + - `"Key":"double",` + - `"Value":{"Type":"FLOAT64","Value":123.456}` + - `}],` + - `"MessageEvents":[` + - `{` + - `"Name":"foo",` + - `"Attributes":[` + - `{` + - `"Key":"key",` + - `"Value":{"Type":"STRING","Value":"value"}` + - `}` + - `],` + - `"DroppedAttributeCount":0,` + - `"Time":` + string(expectedSerializedNow) + - `},` + - `{` + - `"Name":"bar",` + - `"Attributes":[` + - `{` + - `"Key":"double",` + - `"Value":{"Type":"FLOAT64","Value":123.456}` + - `}` + - `],` + - `"DroppedAttributeCount":0,` + - `"Time":` + string(expectedSerializedNow) + - `}` + - `],` + - `"Links":null,` + - `"Status":{"Code":"Error","Description":"interesting"},` + - `"DroppedAttributeCount":0,` + - `"DroppedMessageEventCount":0,` + - `"DroppedLinkCount":0,` + - `"ChildSpanCount":0,` + - `"Resource":[` + - `{` + - `"Key":"rk1",` + - `"Value":{"Type":"STRING","Value":"rv11"}` + - `}],` + - `"InstrumentationLibrary":{` + - `"Name":"",` + - `"Version":""` + - `}}]` + "\n" - - if got != expectedOutput { - t.Errorf("Want: %v but got: %v", expectedOutput, got) + expectedOutput := `[ + { + "Name": "/foo", + "SpanContext": { + "TraceID": "0102030405060708090a0b0c0d0e0f10", + "SpanID": "0102030405060708", + "TraceFlags": "00", + "TraceState": [ + { + "Key": "key", + "Value": { + "Type": "STRING", + "Value": "val" + } + } + ], + "Remote": false + }, + "Parent": { + "TraceID": "00000000000000000000000000000000", + "SpanID": "0000000000000000", + "TraceFlags": "00", + "TraceState": null, + "Remote": false + }, + "SpanKind": 1, + "StartTime": ` + string(expectedSerializedNow) + `, + "EndTime": ` + string(expectedSerializedNow) + `, + "Attributes": [ + { + "Key": "key", + "Value": { + "Type": "STRING", + "Value": "value" + } + }, + { + "Key": "double", + "Value": { + "Type": "FLOAT64", + "Value": 123.456 + } + } + ], + "Events": [ + { + "Name": "foo", + "Attributes": [ + { + "Key": "key", + "Value": { + "Type": "STRING", + "Value": "value" + } + } + ], + "DroppedAttributeCount": 0, + "Time": ` + string(expectedSerializedNow) + ` + }, + { + "Name": "bar", + "Attributes": [ + { + "Key": "double", + "Value": { + "Type": "FLOAT64", + "Value": 123.456 + } + } + ], + "DroppedAttributeCount": 0, + "Time": ` + string(expectedSerializedNow) + ` + } + ], + "Links": null, + "Status": { + "Code": "Error", + "Description": "interesting" + }, + "DroppedAttributes": 0, + "DroppedEvents": 0, + "DroppedLinks": 0, + "ChildSpanCount": 0, + "Resource": [ + { + "Key": "rk1", + "Value": { + "Type": "STRING", + "Value": "rv11" + } + } + ], + "InstrumentationLibrary": { + "Name": "", + "Version": "" + } } +] +` + assert.Equal(t, expectedOutput, got) } func TestExporterShutdownHonorsTimeout(t *testing.T) { diff --git a/exporters/trace/jaeger/agent_test.go b/exporters/trace/jaeger/agent_test.go index 44a230cc343..82a152e982c 100644 --- a/exporters/trace/jaeger/agent_test.go +++ b/exporters/trace/jaeger/agent_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" - tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" ) func TestNewAgentClientUDPWithParamsBadHostport(t *testing.T) { @@ -113,10 +113,7 @@ func TestJaegerAgentUDPLimitBatching(t *testing.T) { // 1500 spans, size 79559, does not fit within one UDP packet with the default size of 65000. n := 1500 - s := make([]*tracesdk.SpanSnapshot, n) - for i := 0; i < n; i++ { - s[i] = &tracesdk.SpanSnapshot{} - } + s := make(tracetest.SpanStubs, n).Snapshots() exp, err := NewRawExporter( WithAgentEndpoint(WithAgentHost("localhost"), WithAgentPort("6831")), @@ -129,11 +126,10 @@ func TestJaegerAgentUDPLimitBatching(t *testing.T) { } // generateALargeSpan generates a span with a long name. -func generateALargeSpan() *tracesdk.SpanSnapshot { - span := &tracesdk.SpanSnapshot{ +func generateALargeSpan() tracetest.SpanStub { + return tracetest.SpanStub{ Name: "a-longer-name-that-makes-it-exceeds-limit", } - return span } func TestSpanExceedsMaxPacketLimit(t *testing.T) { @@ -141,10 +137,9 @@ func TestSpanExceedsMaxPacketLimit(t *testing.T) { // 106 is the serialized size of a span with default values. maxSize := 106 - span := generateALargeSpan() - largeSpans := []*tracesdk.SpanSnapshot{span, {}} - normalSpans := []*tracesdk.SpanSnapshot{{}, {}} + largeSpans := tracetest.SpanStubs{generateALargeSpan(), {}}.Snapshots() + normalSpans := tracetest.SpanStubs{{}, {}}.Snapshots() exp, err := NewRawExporter( WithAgentEndpoint(WithAgentHost("localhost"), WithAgentPort("6831"), WithMaxPacketSize(maxSize+1)), @@ -161,7 +156,7 @@ func TestEmitBatchWithMultipleErrors(t *testing.T) { otel.SetErrorHandler(errorHandler{t}) span := generateALargeSpan() - largeSpans := []*tracesdk.SpanSnapshot{span, span} + largeSpans := tracetest.SpanStubs{span, span}.Snapshots() // make max packet size smaller than span maxSize := len(span.Name) exp, err := NewRawExporter( diff --git a/exporters/trace/jaeger/jaeger.go b/exporters/trace/jaeger/jaeger.go index aa21281b4a9..8eb09450237 100644 --- a/exporters/trace/jaeger/jaeger.go +++ b/exporters/trace/jaeger/jaeger.go @@ -104,7 +104,7 @@ type Exporter struct { var _ sdktrace.SpanExporter = (*Exporter)(nil) // ExportSpans transforms and exports OpenTelemetry spans to Jaeger. -func (e *Exporter) ExportSpans(ctx context.Context, spans []*sdktrace.SpanSnapshot) error { +func (e *Exporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { // Return fast if context is already canceled or Exporter shutdown. select { case <-ctx.Done(): @@ -148,41 +148,42 @@ func (e *Exporter) Shutdown(ctx context.Context) error { return e.uploader.shutdown(ctx) } -func spanSnapshotToThrift(ss *sdktrace.SpanSnapshot) *gen.Span { - tags := make([]*gen.Tag, 0, len(ss.Attributes)) - for _, kv := range ss.Attributes { +func spanToThrift(ss sdktrace.ReadOnlySpan) *gen.Span { + attr := ss.Attributes() + tags := make([]*gen.Tag, 0, len(attr)) + for _, kv := range attr { tag := keyValueToTag(kv) if tag != nil { tags = append(tags, tag) } } - if il := ss.InstrumentationLibrary; il.Name != "" { + if il := ss.InstrumentationLibrary(); il.Name != "" { tags = append(tags, getStringTag(keyInstrumentationLibraryName, il.Name)) if il.Version != "" { tags = append(tags, getStringTag(keyInstrumentationLibraryVersion, il.Version)) } } - if ss.SpanKind != trace.SpanKindInternal { + if ss.SpanKind() != trace.SpanKindInternal { tags = append(tags, - getStringTag(keySpanKind, ss.SpanKind.String()), + getStringTag(keySpanKind, ss.SpanKind().String()), ) } - 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.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.Status.Code == codes.Error { + if ss.Status().Code == codes.Error { tags = append(tags, getBoolTag(keyError, true)) } } var logs []*gen.Log - for _, a := range ss.MessageEvents { + for _, a := range ss.Events() { nTags := len(a.Attributes) if a.Name != "" { nTags++ @@ -212,7 +213,7 @@ func spanSnapshotToThrift(ss *sdktrace.SpanSnapshot) *gen.Span { } var refs []*gen.SpanRef - for _, link := range ss.Links { + for _, link := range ss.Links() { tid := link.SpanContext.TraceID() sid := link.SpanContext.SpanID() refs = append(refs, &gen.SpanRef{ @@ -223,18 +224,18 @@ func spanSnapshotToThrift(ss *sdktrace.SpanSnapshot) *gen.Span { }) } - tid := ss.SpanContext.TraceID() - sid := ss.SpanContext.SpanID() - psid := ss.Parent.SpanID() + tid := ss.SpanContext().TraceID() + sid := ss.SpanContext().SpanID() + psid := ss.Parent().SpanID() return &gen.Span{ TraceIdHigh: int64(binary.BigEndian.Uint64(tid[0:8])), TraceIdLow: int64(binary.BigEndian.Uint64(tid[8:16])), SpanId: int64(binary.BigEndian.Uint64(sid[:])), ParentSpanId: int64(binary.BigEndian.Uint64(psid[:])), - OperationName: ss.Name, // TODO: if span kind is added then add prefix "Sent"/"Recv" - Flags: int32(ss.SpanContext.TraceFlags()), - StartTime: ss.StartTime.UnixNano() / 1000, - Duration: ss.EndTime.Sub(ss.StartTime).Nanoseconds() / 1000, + OperationName: ss.Name(), // TODO: if span kind is added then add prefix "Sent"/"Recv" + Flags: int32(ss.SpanContext().TraceFlags()), + StartTime: ss.StartTime().UnixNano() / 1000, + Duration: ss.EndTime().Sub(ss.StartTime()).Nanoseconds() / 1000, Tags: tags, Logs: logs, References: refs, @@ -308,9 +309,8 @@ func getBoolTag(k string, b bool) *gen.Tag { } } -// jaegerBatchList transforms a slice of SpanSnapshot into a slice of jaeger -// Batch. -func jaegerBatchList(ssl []*sdktrace.SpanSnapshot, defaultServiceName string) []*gen.Batch { +// jaegerBatchList transforms a slice of spans into a slice of jaeger Batch. +func jaegerBatchList(ssl []sdktrace.ReadOnlySpan, defaultServiceName string) []*gen.Batch { if len(ssl) == 0 { return nil } @@ -322,15 +322,15 @@ func jaegerBatchList(ssl []*sdktrace.SpanSnapshot, defaultServiceName string) [] continue } - resourceKey := ss.Resource.Equivalent() + resourceKey := ss.Resource().Equivalent() batch, bOK := batchDict[resourceKey] if !bOK { batch = &gen.Batch{ - Process: process(ss.Resource, defaultServiceName), + Process: process(ss.Resource(), defaultServiceName), Spans: []*gen.Span{}, } } - batch.Spans = append(batch.Spans, spanSnapshotToThrift(ss)) + batch.Spans = append(batch.Spans, spanToThrift(ss)) batchDict[resourceKey] = batch } diff --git a/exporters/trace/jaeger/jaeger_benchmark_test.go b/exporters/trace/jaeger/jaeger_benchmark_test.go index 212279dc201..92a11d35941 100644 --- a/exporters/trace/jaeger/jaeger_benchmark_test.go +++ b/exporters/trace/jaeger/jaeger_benchmark_test.go @@ -22,6 +22,7 @@ import ( "go.opentelemetry.io/otel/sdk/instrumentation" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" ) @@ -49,12 +50,12 @@ func init() { }) } -func spans(n int) []*tracesdk.SpanSnapshot { +func spans(n int) []tracesdk.ReadOnlySpan { now := time.Now() - s := make([]*tracesdk.SpanSnapshot, n) + s := make(tracetest.SpanStubs, n) for i := 0; i < n; i++ { name := fmt.Sprintf("span %d", i) - s[i] = &tracesdk.SpanSnapshot{ + s[i] = tracetest.SpanStub{ SpanContext: spanContext, Name: name, StartTime: now, @@ -65,7 +66,7 @@ func spans(n int) []*tracesdk.SpanSnapshot { }, } } - return s + return s.Snapshots() } func benchmarkExportSpans(b *testing.B, o EndpointOption, i int) { diff --git a/exporters/trace/jaeger/jaeger_test.go b/exporters/trace/jaeger/jaeger_test.go index a503f323b93..8156c1fc8a2 100644 --- a/exporters/trace/jaeger/jaeger_test.go +++ b/exporters/trace/jaeger/jaeger_test.go @@ -36,6 +36,7 @@ import ( "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/semconv" "go.opentelemetry.io/otel/trace" ) @@ -219,12 +220,12 @@ func Test_spanSnapshotToThrift(t *testing.T) { tests := []struct { name string - data *sdktrace.SpanSnapshot + data tracetest.SpanStub want *gen.Span }{ { name: "no status description", - data: &sdktrace.SpanSnapshot{ + data: tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, @@ -258,7 +259,7 @@ func Test_spanSnapshotToThrift(t *testing.T) { }, { name: "no parent", - data: &sdktrace.SpanSnapshot{ + data: tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, @@ -279,7 +280,7 @@ func Test_spanSnapshotToThrift(t *testing.T) { attribute.Float64("double", doubleValue), attribute.Int64("int", intValue), }, - MessageEvents: []sdktrace.Event{ + Events: []sdktrace.Event{ { Name: eventNameValue, Attributes: []attribute.KeyValue{attribute.String("k1", keyValue)}, @@ -349,7 +350,7 @@ func Test_spanSnapshotToThrift(t *testing.T) { }, { name: "with parent", - data: &sdktrace.SpanSnapshot{ + data: tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, @@ -408,7 +409,7 @@ func Test_spanSnapshotToThrift(t *testing.T) { }, { name: "resources do not affect the tags", - data: &sdktrace.SpanSnapshot{ + data: tracetest.SpanStub{ SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: traceID, SpanID: spanID, @@ -452,7 +453,7 @@ func Test_spanSnapshotToThrift(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := spanSnapshotToThrift(tt.data) + got := spanToThrift(tt.data.Snapshot()) sort.Slice(got.Tags, func(i, j int) bool { return got.Tags[i].Key < got.Tags[j].Key }) @@ -496,7 +497,7 @@ func TestExporterExportSpansHonorsCancel(t *testing.T) { e, err := NewRawExporter(withTestCollectorEndpoint()) require.NoError(t, err) now := time.Now() - ss := []*sdktrace.SpanSnapshot{ + ss := tracetest.SpanStubs{ { Name: "s1", Resource: resource.NewWithAttributes( @@ -519,14 +520,14 @@ func TestExporterExportSpansHonorsCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - assert.EqualError(t, e.ExportSpans(ctx, ss), context.Canceled.Error()) + assert.EqualError(t, e.ExportSpans(ctx, ss.Snapshots()), context.Canceled.Error()) } func TestExporterExportSpansHonorsTimeout(t *testing.T) { e, err := NewRawExporter(withTestCollectorEndpoint()) require.NoError(t, err) now := time.Now() - ss := []*sdktrace.SpanSnapshot{ + ss := tracetest.SpanStubs{ { Name: "s1", Resource: resource.NewWithAttributes( @@ -550,7 +551,7 @@ func TestExporterExportSpansHonorsTimeout(t *testing.T) { defer cancel() <-ctx.Done() - assert.EqualError(t, e.ExportSpans(ctx, ss), context.DeadlineExceeded.Error()) + assert.EqualError(t, e.ExportSpans(ctx, ss.Snapshots()), context.DeadlineExceeded.Error()) } func TestJaegerBatchList(t *testing.T) { @@ -562,19 +563,19 @@ func TestJaegerBatchList(t *testing.T) { testCases := []struct { name string - spanSnapshotList []*sdktrace.SpanSnapshot + roSpans []sdktrace.ReadOnlySpan defaultServiceName string expectedBatchList []*gen.Batch }{ { name: "no span shots", - spanSnapshotList: nil, + roSpans: nil, expectedBatchList: nil, }, { name: "span's snapshot contains nil span", - spanSnapshotList: []*sdktrace.SpanSnapshot{ - { + roSpans: []sdktrace.ReadOnlySpan{ + tracetest.SpanStub{ Name: "s1", Resource: resource.NewWithAttributes( semconv.ServiceNameKey.String("name"), @@ -582,7 +583,7 @@ func TestJaegerBatchList(t *testing.T) { ), StartTime: now, EndTime: now, - }, + }.Snapshot(), nil, }, expectedBatchList: []*gen.Batch{ @@ -607,7 +608,7 @@ func TestJaegerBatchList(t *testing.T) { }, { name: "merge spans that have the same resources", - spanSnapshotList: []*sdktrace.SpanSnapshot{ + roSpans: tracetest.SpanStubs{ { Name: "s1", Resource: resource.NewWithAttributes( @@ -635,7 +636,7 @@ func TestJaegerBatchList(t *testing.T) { StartTime: now, EndTime: now, }, - }, + }.Snapshots(), expectedBatchList: []*gen.Batch{ { Process: &gen.Process{ @@ -682,7 +683,7 @@ func TestJaegerBatchList(t *testing.T) { }, { name: "no service name in spans", - spanSnapshotList: []*sdktrace.SpanSnapshot{ + roSpans: tracetest.SpanStubs{ { Name: "s1", Resource: resource.NewWithAttributes( @@ -691,8 +692,7 @@ func TestJaegerBatchList(t *testing.T) { StartTime: now, EndTime: now, }, - nil, - }, + }.Snapshots(), defaultServiceName: "default service name", expectedBatchList: []*gen.Batch{ { @@ -718,7 +718,7 @@ func TestJaegerBatchList(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - batchList := jaegerBatchList(tc.spanSnapshotList, tc.defaultServiceName) + batchList := jaegerBatchList(tc.roSpans, tc.defaultServiceName) assert.ElementsMatch(t, tc.expectedBatchList, batchList) }) diff --git a/exporters/trace/zipkin/model.go b/exporters/trace/zipkin/model.go index 51a8bc75ec2..bdd48d4f89b 100644 --- a/exporters/trace/zipkin/model.go +++ b/exporters/trace/zipkin/model.go @@ -51,7 +51,7 @@ func init() { } } -func toZipkinSpanModels(batch []*tracesdk.SpanSnapshot) []zkmodel.SpanModel { +func toZipkinSpanModels(batch []tracesdk.ReadOnlySpan) []zkmodel.SpanModel { models := make([]zkmodel.SpanModel, 0, len(batch)) for _, data := range batch { models = append(models, toZipkinSpanModel(data)) @@ -69,28 +69,28 @@ func getServiceName(attrs []attribute.KeyValue) string { return defaultServiceName } -func toZipkinSpanModel(data *tracesdk.SpanSnapshot) zkmodel.SpanModel { +func toZipkinSpanModel(data tracesdk.ReadOnlySpan) zkmodel.SpanModel { return zkmodel.SpanModel{ SpanContext: toZipkinSpanContext(data), - Name: data.Name, - Kind: toZipkinKind(data.SpanKind), - Timestamp: data.StartTime, - Duration: data.EndTime.Sub(data.StartTime), + Name: data.Name(), + Kind: toZipkinKind(data.SpanKind()), + Timestamp: data.StartTime(), + Duration: data.EndTime().Sub(data.StartTime()), Shared: false, LocalEndpoint: &zkmodel.Endpoint{ - ServiceName: getServiceName(data.Resource.Attributes()), + ServiceName: getServiceName(data.Resource().Attributes()), }, RemoteEndpoint: toZipkinRemoteEndpoint(data), - Annotations: toZipkinAnnotations(data.MessageEvents), + Annotations: toZipkinAnnotations(data.Events()), Tags: toZipkinTags(data), } } -func toZipkinSpanContext(data *tracesdk.SpanSnapshot) zkmodel.SpanContext { +func toZipkinSpanContext(data tracesdk.ReadOnlySpan) zkmodel.SpanContext { return zkmodel.SpanContext{ - TraceID: toZipkinTraceID(data.SpanContext.TraceID()), - ID: toZipkinID(data.SpanContext.SpanID()), - ParentID: toZipkinParentID(data.Parent.SpanID()), + TraceID: toZipkinTraceID(data.SpanContext().TraceID()), + ID: toZipkinID(data.SpanContext().SpanID()), + ParentID: toZipkinParentID(data.Parent().SpanID()), Debug: false, Sampled: nil, Err: nil, @@ -174,9 +174,10 @@ var extraZipkinTags = []string{ keyInstrumentationLibraryVersion, } -func toZipkinTags(data *tracesdk.SpanSnapshot) map[string]string { - m := make(map[string]string, len(data.Attributes)+len(extraZipkinTags)) - for _, kv := range data.Attributes { +func toZipkinTags(data tracesdk.ReadOnlySpan) map[string]string { + attr := data.Attributes() + m := make(map[string]string, len(attr)+len(extraZipkinTags)) + for _, kv := range attr { switch kv.Value.Type() { // For array attributes, serialize as JSON list string. case attribute.ARRAY: @@ -187,17 +188,17 @@ func toZipkinTags(data *tracesdk.SpanSnapshot) map[string]string { } } - if data.Status.Code != codes.Unset { - m["otel.status_code"] = data.Status.Code.String() + if data.Status().Code != codes.Unset { + m["otel.status_code"] = data.Status().Code.String() } - if data.Status.Code == codes.Error { - m["error"] = data.Status.Description + if data.Status().Code == codes.Error { + m["error"] = data.Status().Description } else { delete(m, "error") } - if il := data.InstrumentationLibrary; il.Name != "" { + if il := data.InstrumentationLibrary(); il.Name != "" { m[keyInstrumentationLibraryName] = il.Name if il.Version != "" { m[keyInstrumentationLibraryVersion] = il.Version @@ -223,15 +224,15 @@ var remoteEndpointKeyRank = map[attribute.Key]int{ semconv.DBNameKey: 6, } -func toZipkinRemoteEndpoint(data *sdktrace.SpanSnapshot) *zkmodel.Endpoint { +func toZipkinRemoteEndpoint(data sdktrace.ReadOnlySpan) *zkmodel.Endpoint { // Should be set only for client or producer kind - if data.SpanKind != trace.SpanKindClient && - data.SpanKind != trace.SpanKindProducer { + if sk := data.SpanKind(); sk != trace.SpanKindClient && sk != trace.SpanKindProducer { return nil } + attr := data.Attributes() var endpointAttr attribute.KeyValue - for _, kv := range data.Attributes { + for _, kv := range attr { rank, ok := remoteEndpointKeyRank[kv.Key] if !ok { continue @@ -256,7 +257,7 @@ func toZipkinRemoteEndpoint(data *sdktrace.SpanSnapshot) *zkmodel.Endpoint { } } - return remoteEndpointPeerIPWithPort(endpointAttr.Value.AsString(), data.Attributes) + return remoteEndpointPeerIPWithPort(endpointAttr.Value.AsString(), attr) } // Handles `net.peer.ip` remote endpoint separately (should include `net.peer.ip` diff --git a/exporters/trace/zipkin/model_test.go b/exporters/trace/zipkin/model_test.go index 999ae4f7859..c4e68f1128f 100644 --- a/exporters/trace/zipkin/model_test.go +++ b/exporters/trace/zipkin/model_test.go @@ -31,6 +31,7 @@ import ( "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/resource" tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/semconv" "go.opentelemetry.io/otel/trace" ) @@ -40,7 +41,7 @@ func TestModelConversion(t *testing.T) { semconv.ServiceNameKey.String("model-test"), ) - inputBatch := []*tracesdk.SpanSnapshot{ + inputBatch := tracetest.SpanStubs{ // typical span data { SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ @@ -60,7 +61,7 @@ func TestModelConversion(t *testing.T) { attribute.String("attr2", "bar"), attribute.Array("attr3", []int{0, 1, 2}), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -95,7 +96,7 @@ func TestModelConversion(t *testing.T) { attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -133,7 +134,7 @@ func TestModelConversion(t *testing.T) { attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -171,7 +172,7 @@ func TestModelConversion(t *testing.T) { attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -212,7 +213,7 @@ func TestModelConversion(t *testing.T) { attribute.String("net.peer.ip", "1.2.3.4"), attribute.Int64("net.peer.port", 9876), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -250,7 +251,7 @@ func TestModelConversion(t *testing.T) { attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -288,7 +289,7 @@ func TestModelConversion(t *testing.T) { attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -326,7 +327,7 @@ func TestModelConversion(t *testing.T) { attribute.Int64("attr1", 42), attribute.String("attr2", "bar"), }, - MessageEvents: nil, + Events: nil, Status: tracesdk.Status{ Code: codes.Error, Description: "404, file not found", @@ -350,7 +351,7 @@ func TestModelConversion(t *testing.T) { Attributes: []attribute.KeyValue{ attribute.String("error", "false"), }, - MessageEvents: []tracesdk.Event{ + Events: []tracesdk.Event{ { Time: time.Date(2020, time.March, 11, 19, 24, 30, 0, time.UTC), Name: "ev1", @@ -366,7 +367,7 @@ func TestModelConversion(t *testing.T) { }, Resource: resource, }, - } + }.Snapshots() expectedOutputBatch := []zkmodel.SpanModel{ // model for typical span data @@ -733,12 +734,12 @@ func TestTagsTransformation(t *testing.T) { tests := []struct { name string - data *tracesdk.SpanSnapshot + data tracetest.SpanStub want map[string]string }{ { name: "attributes", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ Attributes: []attribute.KeyValue{ attribute.String("key", keyValue), attribute.Float64("double", doubleValue), @@ -755,12 +756,12 @@ func TestTagsTransformation(t *testing.T) { }, { name: "no attributes", - data: &tracesdk.SpanSnapshot{}, + data: tracetest.SpanStub{}, want: nil, }, { name: "omit-noerror", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ Attributes: []attribute.KeyValue{ attribute.Bool("error", false), }, @@ -769,7 +770,7 @@ func TestTagsTransformation(t *testing.T) { }, { name: "statusCode", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ Attributes: []attribute.KeyValue{ attribute.String("key", keyValue), attribute.Bool("error", true), @@ -787,14 +788,14 @@ func TestTagsTransformation(t *testing.T) { }, { name: "instrLib-empty", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ InstrumentationLibrary: instrumentation.Library{}, }, want: nil, }, { name: "instrLib-noversion", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ Attributes: []attribute.KeyValue{}, InstrumentationLibrary: instrumentation.Library{ Name: instrLibName, @@ -806,7 +807,7 @@ func TestTagsTransformation(t *testing.T) { }, { name: "instrLib-with-version", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ Attributes: []attribute.KeyValue{}, InstrumentationLibrary: instrumentation.Library{ Name: instrLibName, @@ -821,7 +822,7 @@ func TestTagsTransformation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := toZipkinTags(tt.data) + got := toZipkinTags(tt.data.Snapshot()) if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("Diff%v", diff) } @@ -832,12 +833,12 @@ func TestTagsTransformation(t *testing.T) { func TestRemoteEndpointTransformation(t *testing.T) { tests := []struct { name string - data *tracesdk.SpanSnapshot + data tracetest.SpanStub want *zkmodel.Endpoint }{ { name: "nil-not-applicable", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindClient, Attributes: []attribute.KeyValue{}, }, @@ -845,7 +846,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "nil-not-found", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindConsumer, Attributes: []attribute.KeyValue{ attribute.String("attr", "test"), @@ -855,7 +856,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "peer-service-rank", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ semconv.PeerServiceKey.String("peer-service-test"), @@ -869,7 +870,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "http-host-rank", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ semconv.HTTPHostKey.String("http-host-test"), @@ -882,7 +883,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "db-name-rank", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ attribute.String("foo", "bar"), @@ -895,7 +896,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "peer-hostname-rank", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ keyPeerHostname.String("peer-hostname-test"), @@ -910,7 +911,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "peer-address-rank", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ keyPeerAddress.String("peer-address-test"), @@ -924,7 +925,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "net-peer-invalid-ip", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ semconv.NetPeerIPKey.String("INVALID"), @@ -934,7 +935,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "net-peer-ipv6-no-port", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ semconv.NetPeerIPKey.String("0:0:1:5ee:bad:c0de:0:0"), @@ -946,7 +947,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { }, { name: "net-peer-ipv4-port", - data: &tracesdk.SpanSnapshot{ + data: tracetest.SpanStub{ SpanKind: trace.SpanKindProducer, Attributes: []attribute.KeyValue{ semconv.NetPeerIPKey.String("1.2.3.4"), @@ -961,7 +962,7 @@ func TestRemoteEndpointTransformation(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := toZipkinRemoteEndpoint(tt.data) + got := toZipkinRemoteEndpoint(tt.data.Snapshot()) if diff := cmp.Diff(got, tt.want); diff != "" { t.Errorf("Diff%v", diff) } diff --git a/exporters/trace/zipkin/zipkin.go b/exporters/trace/zipkin/zipkin.go index bdfe1cd95b1..afb0c24d95b 100644 --- a/exporters/trace/zipkin/zipkin.go +++ b/exporters/trace/zipkin/zipkin.go @@ -31,9 +31,7 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" ) -// Exporter exports SpanSnapshots to the zipkin collector. It implements -// the SpanBatcher interface, so it needs to be used together with the -// WithBatcher option when setting up the exporter pipeline. +// Exporter exports spans to the zipkin collector. type Exporter struct { url string client *http.Client @@ -133,8 +131,8 @@ func InstallNewPipeline(collectorURL string, opts ...Option) error { return nil } -// ExportSpans exports SpanSnapshots to a Zipkin receiver. -func (e *Exporter) ExportSpans(ctx context.Context, ss []*sdktrace.SpanSnapshot) error { +// ExportSpans exports spans to a Zipkin receiver. +func (e *Exporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { e.stoppedMu.RLock() stopped := e.stopped e.stoppedMu.RUnlock() @@ -143,11 +141,11 @@ func (e *Exporter) ExportSpans(ctx context.Context, ss []*sdktrace.SpanSnapshot) return nil } - if len(ss) == 0 { + if len(spans) == 0 { e.logf("no spans to export") return nil } - models := toZipkinSpanModels(ss) + models := toZipkinSpanModels(spans) body, err := json.Marshal(models) if err != nil { return e.errf("failed to serialize zipkin models to JSON: %v", err) diff --git a/exporters/trace/zipkin/zipkin_test.go b/exporters/trace/zipkin/zipkin_test.go index bd63f721e9a..be7cf2ae454 100644 --- a/exporters/trace/zipkin/zipkin_test.go +++ b/exporters/trace/zipkin/zipkin_test.go @@ -34,6 +34,7 @@ import ( "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/semconv" "go.opentelemetry.io/otel/trace" ) @@ -233,19 +234,19 @@ func TestExportSpans(t *testing.T) { semconv.ServiceNameKey.String("exporter-test"), ) - spans := []*sdktrace.SpanSnapshot{ + spans := tracetest.SpanStubs{ // parent { SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}, }), - SpanKind: trace.SpanKindServer, - Name: "foo", - StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC), - EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC), - Attributes: nil, - MessageEvents: nil, + SpanKind: trace.SpanKindServer, + Name: "foo", + StartTime: time.Date(2020, time.March, 11, 19, 24, 0, 0, time.UTC), + EndTime: time.Date(2020, time.March, 11, 19, 25, 0, 0, time.UTC), + Attributes: nil, + Events: nil, Status: sdktrace.Status{ Code: codes.Error, Description: "404, file not found", @@ -262,19 +263,19 @@ func TestExportSpans(t *testing.T) { TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, SpanID: trace.SpanID{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}, }), - SpanKind: trace.SpanKindServer, - Name: "bar", - StartTime: time.Date(2020, time.March, 11, 19, 24, 15, 0, time.UTC), - EndTime: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC), - Attributes: nil, - MessageEvents: nil, + SpanKind: trace.SpanKindServer, + Name: "bar", + StartTime: time.Date(2020, time.March, 11, 19, 24, 15, 0, time.UTC), + EndTime: time.Date(2020, time.March, 11, 19, 24, 45, 0, time.UTC), + Attributes: nil, + Events: nil, Status: sdktrace.Status{ Code: codes.Error, Description: "403, forbidden", }, Resource: resource, }, - } + }.Snapshots() models := []zkmodel.SpanModel{ // model of parent { diff --git a/sdk/trace/batch_span_processor.go b/sdk/trace/batch_span_processor.go index 6687839ee43..6dc75acd864 100644 --- a/sdk/trace/batch_span_processor.go +++ b/sdk/trace/batch_span_processor.go @@ -63,15 +63,15 @@ type BatchSpanProcessorOptions struct { } // batchSpanProcessor is a SpanProcessor that batches asynchronously-received -// SpanSnapshots and sends them to a trace.Exporter when complete. +// spans and sends them to a trace.Exporter when complete. type batchSpanProcessor struct { e SpanExporter o BatchSpanProcessorOptions - queue chan *SpanSnapshot + queue chan ReadOnlySpan dropped uint32 - batch []*SpanSnapshot + batch []ReadOnlySpan batchMutex sync.Mutex timer *time.Timer stopWait sync.WaitGroup @@ -98,9 +98,9 @@ func NewBatchSpanProcessor(exporter SpanExporter, options ...BatchSpanProcessorO bsp := &batchSpanProcessor{ e: exporter, o: o, - batch: make([]*SpanSnapshot, 0, o.MaxExportBatchSize), + batch: make([]ReadOnlySpan, 0, o.MaxExportBatchSize), timer: time.NewTimer(o.BatchTimeout), - queue: make(chan *SpanSnapshot, o.MaxQueueSize), + queue: make(chan ReadOnlySpan, o.MaxQueueSize), stopCh: make(chan struct{}), } @@ -123,7 +123,7 @@ func (bsp *batchSpanProcessor) OnEnd(s ReadOnlySpan) { if bsp.e == nil { return } - bsp.enqueue(s.Snapshot()) + bsp.enqueue(s) } // Shutdown flushes the queue and waits until all spans are processed. @@ -294,8 +294,8 @@ func (bsp *batchSpanProcessor) drainQueue() { } } -func (bsp *batchSpanProcessor) enqueue(sd *SpanSnapshot) { - if !sd.SpanContext.IsSampled() { +func (bsp *batchSpanProcessor) enqueue(sd ReadOnlySpan) { + if !sd.SpanContext().IsSampled() { return } diff --git a/sdk/trace/batch_span_processor_test.go b/sdk/trace/batch_span_processor_test.go index 0cea6e29468..3da75aeaffc 100644 --- a/sdk/trace/batch_span_processor_test.go +++ b/sdk/trace/batch_span_processor_test.go @@ -32,7 +32,7 @@ import ( type testBatchExporter struct { mu sync.Mutex - spans []*sdktrace.SpanSnapshot + spans []sdktrace.ReadOnlySpan sizes []int batchCount int shutdownCount int @@ -43,12 +43,12 @@ type testBatchExporter struct { err error } -func (t *testBatchExporter) ExportSpans(ctx context.Context, ss []*sdktrace.SpanSnapshot) error { +func (t *testBatchExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { t.mu.Lock() defer t.mu.Unlock() if t.idx < len(t.errors) { - t.droppedCount += len(ss) + t.droppedCount += len(spans) err := t.errors[t.idx] t.idx++ return err @@ -63,8 +63,8 @@ func (t *testBatchExporter) ExportSpans(ctx context.Context, ss []*sdktrace.Span default: } - t.spans = append(t.spans, ss...) - t.sizes = append(t.sizes, len(ss)) + t.spans = append(t.spans, spans...) + t.sizes = append(t.sizes, len(spans)) t.batchCount++ return nil } @@ -421,7 +421,7 @@ func assertMaxSpanDiff(t *testing.T, want, got, maxDif int) { type indefiniteExporter struct{} func (indefiniteExporter) Shutdown(context.Context) error { return nil } -func (indefiniteExporter) ExportSpans(ctx context.Context, _ []*sdktrace.SpanSnapshot) error { +func (indefiniteExporter) ExportSpans(ctx context.Context, _ []sdktrace.ReadOnlySpan) error { <-ctx.Done() return ctx.Err() } diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 5b935e6a6af..9dc87f893b9 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -55,8 +55,7 @@ func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) { defer ssp.exporterMu.RUnlock() if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() { - ss := s.Snapshot() - if err := ssp.exporter.ExportSpans(context.Background(), []*SpanSnapshot{ss}); err != nil { + if err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}); err != nil { otel.Handle(err) } } diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go index bfdb1eecb9d..688cc4703bd 100644 --- a/sdk/trace/simple_span_processor_test.go +++ b/sdk/trace/simple_span_processor_test.go @@ -31,12 +31,12 @@ var ( ) type testExporter struct { - spans []*sdktrace.SpanSnapshot + spans []sdktrace.ReadOnlySpan shutdown bool } -func (t *testExporter) ExportSpans(ctx context.Context, ss []*sdktrace.SpanSnapshot) error { - t.spans = append(t.spans, ss...) +func (t *testExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error { + t.spans = append(t.spans, spans...) return nil } @@ -80,7 +80,7 @@ func TestSimpleSpanProcessorOnEnd(t *testing.T) { startSpan(tp).End() wantTraceID := tid - gotTraceID := te.spans[0].SpanContext.TraceID() + gotTraceID := te.spans[0].SpanContext().TraceID() if wantTraceID != gotTraceID { t.Errorf("SimplerSpanProcessor OnEnd() check: got %+v, want %+v\n", gotTraceID, wantTraceID) } diff --git a/sdk/trace/snapshot.go b/sdk/trace/snapshot.go new file mode 100644 index 00000000000..847617a382b --- /dev/null +++ b/sdk/trace/snapshot.go @@ -0,0 +1,138 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/trace" +) + +// snapshot is an record of a spans state at a particular checkpointed time. +// It is used as a read-only representation of that state. +type snapshot struct { + name string + spanContext trace.SpanContext + parent trace.SpanContext + spanKind trace.SpanKind + startTime time.Time + endTime time.Time + attributes []attribute.KeyValue + events []Event + links []trace.Link + status Status + childSpanCount int + droppedAttributeCount int + droppedEventCount int + droppedLinkCount int + resource *resource.Resource + instrumentationLibrary instrumentation.Library +} + +var _ ReadOnlySpan = snapshot{} + +func (s snapshot) private() {} + +// Name returns the name of the span. +func (s snapshot) Name() string { + return s.name +} + +// SpanContext returns the unique SpanContext that identifies the span. +func (s snapshot) SpanContext() trace.SpanContext { + return s.spanContext +} + +// Parent returns the unique SpanContext that identifies the parent of the +// span if one exists. If the span has no parent the returned SpanContext +// will be invalid. +func (s snapshot) Parent() trace.SpanContext { + return s.parent +} + +// SpanKind returns the role the span plays in a Trace. +func (s snapshot) SpanKind() trace.SpanKind { + return s.spanKind +} + +// StartTime returns the time the span started recording. +func (s snapshot) StartTime() time.Time { + return s.startTime +} + +// EndTime returns the time the span stopped recording. It will be zero if +// the span has not ended. +func (s snapshot) EndTime() time.Time { + return s.endTime +} + +// Attributes returns the defining attributes of the span. +func (s snapshot) Attributes() []attribute.KeyValue { + return s.attributes +} + +// Links returns all the links the span has to other spans. +func (s snapshot) Links() []trace.Link { + return s.links +} + +// Events returns all the events that occurred within in the spans +// lifetime. +func (s snapshot) Events() []Event { + return s.events +} + +// Status returns the spans status. +func (s snapshot) Status() Status { + return s.status +} + +// InstrumentationLibrary returns information about the instrumentation +// library that created the span. +func (s snapshot) InstrumentationLibrary() instrumentation.Library { + return s.instrumentationLibrary +} + +// Resource returns information about the entity that produced the span. +func (s snapshot) Resource() *resource.Resource { + return s.resource +} + +// DroppedAttributes returns the number of attributes dropped by the span +// due to limits being reached. +func (s snapshot) DroppedAttributes() int { + return s.droppedAttributeCount +} + +// DroppedLinks returns the number of links dropped by the span due to limits +// being reached. +func (s snapshot) DroppedLinks() int { + return s.droppedLinkCount +} + +// DroppedEvents returns the number of events dropped by the span due to +// limits being reached. +func (s snapshot) DroppedEvents() int { + return s.droppedEventCount +} + +// ChildSpanCount returns the count of spans that consider the span a +// direct parent. +func (s snapshot) ChildSpanCount() int { + return s.childSpanCount +} diff --git a/sdk/trace/span.go b/sdk/trace/span.go index c69964da0c1..374975d5f6a 100644 --- a/sdk/trace/span.go +++ b/sdk/trace/span.go @@ -34,24 +34,48 @@ import ( // ReadOnlySpan allows reading information from the data structure underlying a // trace.Span. It is used in places where reading information from a span is // necessary but changing the span isn't necessary or allowed. -// TODO: Should we make the methods unexported? The purpose of this interface -// is controlling access to `span` fields, not having multiple implementations. type ReadOnlySpan interface { + // Name returns the name of the span. Name() string + // SpanContext returns the unique SpanContext that identifies the span. SpanContext() trace.SpanContext + // Parent returns the unique SpanContext that identifies the parent of the + // span if one exists. If the span has no parent the returned SpanContext + // will be invalid. Parent() trace.SpanContext + // SpanKind returns the role the span plays in a Trace. SpanKind() trace.SpanKind + // StartTime returns the time the span started recording. StartTime() time.Time + // EndTime returns the time the span stopped recording. It will be zero if + // the span has not ended. EndTime() time.Time + // Attributes returns the defining attributes of the span. Attributes() []attribute.KeyValue + // Links returns all the links the span has to other spans. Links() []trace.Link + // Events returns all the events that occurred within in the spans + // lifetime. Events() []Event + // Status returns the spans status. Status() Status - Tracer() trace.Tracer - IsRecording() bool + // InstrumentationLibrary returns information about the instrumentation + // library that created the span. InstrumentationLibrary() instrumentation.Library + // Resource returns information about the entity that produced the span. Resource() *resource.Resource - Snapshot() *SpanSnapshot + // DroppedAttributes returns the number of attributes dropped by the span + // due to limits being reached. + DroppedAttributes() int + // DroppedLinks returns the number of links dropped by the span due to + // limits being reached. + DroppedLinks() int + // DroppedEvents returns the number of events dropped by the span due to + // limits being reached. + DroppedEvents() int + // ChildSpanCount returns the count of spans that consider the span a + // direct parent. + ChildSpanCount() int // A private method to prevent users implementing the // interface and so future additions to it will not @@ -112,8 +136,8 @@ type span struct { // an oldest entry is removed to create room for a new entry. attributes *attributesMap - // messageEvents are stored in FIFO queue capped by configured limit. - messageEvents *evictedQueue + // events are stored in FIFO queue capped by configured limit. + events *evictedQueue // links are stored in FIFO queue capped by configured limit. links *evictedQueue @@ -238,7 +262,7 @@ func (s *span) End(options ...trace.SpanOption) { mustExportOrProcess := ok && len(sps) > 0 if mustExportOrProcess { for _, sp := range sps { - sp.sp.OnEnd(s) + sp.sp.OnEnd(s.snapshot()) } } } @@ -294,7 +318,7 @@ func (s *span) addEvent(name string, o ...trace.EventOption) { s.mu.Lock() defer s.mu.Unlock() - s.messageEvents.add(Event{ + s.events.add(Event{ Name: name, Attributes: c.Attributes, DroppedAttributeCount: discarded, @@ -374,10 +398,10 @@ func (s *span) Links() []trace.Link { func (s *span) Events() []Event { s.mu.Lock() defer s.mu.Unlock() - if len(s.messageEvents.queue) == 0 { + if len(s.events.queue) == 0 { return []Event{} } - return s.interfaceArrayToMessageEventArray() + return s.interfaceArrayToEventArray() } // Status returns the status of this span. @@ -419,35 +443,66 @@ func (s *span) addLink(link trace.Link) { s.links.add(link) } -// Snapshot creates a snapshot representing the current state of the span as an -// export.SpanSnapshot and returns a pointer to it. -func (s *span) Snapshot() *SpanSnapshot { - var sd SpanSnapshot +// DroppedAttributes returns the number of attributes dropped by the span +// due to limits being reached. +func (s *span) DroppedAttributes() int { s.mu.Lock() defer s.mu.Unlock() + return s.attributes.droppedCount +} + +// DroppedLinks returns the number of links dropped by the span due to limits +// being reached. +func (s *span) DroppedLinks() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.links.droppedCount +} - sd.ChildSpanCount = s.childSpanCount - sd.EndTime = s.endTime - sd.InstrumentationLibrary = s.instrumentationLibrary - sd.Name = s.name - sd.Parent = s.parent - sd.Resource = s.resource - sd.SpanContext = s.spanContext - sd.SpanKind = s.spanKind - sd.StartTime = s.startTime - sd.Status = s.status +// DroppedEvents returns the number of events dropped by the span due to +// limits being reached. +func (s *span) DroppedEvents() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.events.droppedCount +} + +// ChildSpanCount returns the count of spans that consider the span a +// direct parent. +func (s *span) ChildSpanCount() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.childSpanCount +} + +// snapshot creates a read-only copy of the current state of the span. +func (s *span) snapshot() ReadOnlySpan { + var sd snapshot + s.mu.Lock() + defer s.mu.Unlock() + + sd.endTime = s.endTime + sd.instrumentationLibrary = s.instrumentationLibrary + sd.name = s.name + sd.parent = s.parent + sd.resource = s.resource + sd.spanContext = s.spanContext + sd.spanKind = s.spanKind + sd.startTime = s.startTime + sd.status = s.status + sd.childSpanCount = s.childSpanCount if s.attributes.evictList.Len() > 0 { - sd.Attributes = s.attributes.toKeyValue() - sd.DroppedAttributeCount = s.attributes.droppedCount + sd.attributes = s.attributes.toKeyValue() + sd.droppedAttributeCount = s.attributes.droppedCount } - if len(s.messageEvents.queue) > 0 { - sd.MessageEvents = s.interfaceArrayToMessageEventArray() - sd.DroppedMessageEventCount = s.messageEvents.droppedCount + if len(s.events.queue) > 0 { + sd.events = s.interfaceArrayToEventArray() + sd.droppedEventCount = s.events.droppedCount } if len(s.links.queue) > 0 { - sd.Links = s.interfaceArrayToLinksArray() - sd.DroppedLinkCount = s.links.droppedCount + sd.links = s.interfaceArrayToLinksArray() + sd.droppedLinkCount = s.links.droppedCount } return &sd } @@ -460,12 +515,12 @@ func (s *span) interfaceArrayToLinksArray() []trace.Link { return linkArr } -func (s *span) interfaceArrayToMessageEventArray() []Event { - messageEventArr := make([]Event, 0) - for _, value := range s.messageEvents.queue { - messageEventArr = append(messageEventArr, value.(Event)) +func (s *span) interfaceArrayToEventArray() []Event { + eventArr := make([]Event, 0) + for _, value := range s.events.queue { + eventArr = append(eventArr, value.(Event)) } - return messageEventArr + return eventArr } func (s *span) copyToCappedAttributes(attributes ...attribute.KeyValue) { @@ -517,7 +572,7 @@ func startSpanInternal(ctx context.Context, tr *tracer, name string, o *trace.Sp spanLimits := provider.spanLimits span.attributes = newAttributesMap(spanLimits.AttributeCountLimit) - span.messageEvents = newEvictedQueue(spanLimits.EventCountLimit) + span.events = newEvictedQueue(spanLimits.EventCountLimit) span.links = newEvictedQueue(spanLimits.LinkCountLimit) span.spanLimits = spanLimits @@ -573,44 +628,9 @@ func isSampled(s SamplingResult) bool { // Status is the classified state of a Span. type Status struct { - // Code is an identifier of a Span's state classification. + // Code is an identifier of a Spans state classification. Code codes.Code - // Message is a user hint about why the status was set. It is only + // Message is a user hint about why that 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, -// SpanSnapshot should be treated as immutable. Changes to the span from which -// the SpanSnapshot was created are NOT reflected in the SpanSnapshot. -type SpanSnapshot struct { - SpanContext trace.SpanContext - Parent trace.SpanContext - SpanKind trace.SpanKind - Name string - StartTime time.Time - // The wall clock time of EndTime will be adjusted to always be offset - // from StartTime by the duration of the span. - EndTime time.Time - Attributes []attribute.KeyValue - MessageEvents []Event - Links []trace.Link - Status Status - - // DroppedAttributeCount contains dropped attributes for the span itself. - DroppedAttributeCount int - DroppedMessageEventCount int - DroppedLinkCount int - - // ChildSpanCount holds the number of child span created for this span. - ChildSpanCount int - - // Resource contains attributes representing an entity that produced this span. - Resource *resource.Resource - - // InstrumentationLibrary defines the instrumentation library used to - // provide instrumentation. - InstrumentationLibrary instrumentation.Library -} diff --git a/sdk/trace/span_exporter.go b/sdk/trace/span_exporter.go index 40e121069d1..2d5400223e5 100644 --- a/sdk/trace/span_exporter.go +++ b/sdk/trace/span_exporter.go @@ -16,10 +16,10 @@ package trace // import "go.opentelemetry.io/otel/sdk/trace" import "context" -// SpanExporter handles the delivery of SpanSnapshot structs to external -// receivers. This is the final component in the trace export pipeline. +// SpanExporter handles the delivery of spans to external receivers. This is +// the final component in the trace export pipeline. type SpanExporter interface { - // ExportSpans exports a batch of SpanSnapshots. + // ExportSpans exports a batch of spans. // // This function is called synchronously, so there is no concurrency // safety requirement. However, due to the synchronous calling pattern, @@ -30,7 +30,7 @@ type SpanExporter interface { // calls this function will not implement any retry logic. All errors // returned by this function are considered unrecoverable and will be // reported to a configured error Handler. - ExportSpans(ctx context.Context, ss []*SpanSnapshot) error + ExportSpans(ctx context.Context, spans []ReadOnlySpan) error // Shutdown notifies the exporter of a pending halt to operations. The // exporter is expected to preform any cleanup or synchronization it // requires while honoring all timeouts and cancellations contained in diff --git a/sdk/trace/span_processor_filter_example_test.go b/sdk/trace/span_processor_filter_example_test.go index 3983a007ce0..6409bb57aaf 100644 --- a/sdk/trace/span_processor_filter_example_test.go +++ b/sdk/trace/span_processor_filter_example_test.go @@ -76,8 +76,8 @@ func (f InstrumentationBlacklist) OnEnd(s ReadOnlySpan) { type noopExporter struct{} -func (noopExporter) ExportSpans(context.Context, []*SpanSnapshot) error { return nil } -func (noopExporter) Shutdown(context.Context) error { return nil } +func (noopExporter) ExportSpans(context.Context, []ReadOnlySpan) error { return nil } +func (noopExporter) Shutdown(context.Context) error { return nil } func ExampleSpanProcessor_filtered() { exportSP := NewSimpleSpanProcessor(noopExporter{}) diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index 4789c20d109..a340ddb1bae 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -103,36 +103,36 @@ func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) { type testExporter struct { mu sync.RWMutex idx map[string]int - spans []*SpanSnapshot + spans []*snapshot } func NewTestExporter() *testExporter { return &testExporter{idx: make(map[string]int)} } -func (te *testExporter) ExportSpans(_ context.Context, ss []*SpanSnapshot) error { +func (te *testExporter) ExportSpans(_ context.Context, spans []ReadOnlySpan) error { te.mu.Lock() defer te.mu.Unlock() i := len(te.spans) - for _, s := range ss { - te.idx[s.Name] = i - te.spans = append(te.spans, s) + for _, s := range spans { + te.idx[s.Name()] = i + te.spans = append(te.spans, s.(*snapshot)) i++ } return nil } -func (te *testExporter) Spans() []*SpanSnapshot { +func (te *testExporter) Spans() []*snapshot { te.mu.RLock() defer te.mu.RUnlock() - cp := make([]*SpanSnapshot, len(te.spans)) + cp := make([]*snapshot, len(te.spans)) copy(cp, te.spans) return cp } -func (te *testExporter) GetSpan(name string) (*SpanSnapshot, bool) { +func (te *testExporter) GetSpan(name string) (*snapshot, bool) { te.mu.RLock() defer te.mu.RUnlock() i, ok := te.idx[name] @@ -389,19 +389,19 @@ func TestSetSpanAttributesOnStart(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Attributes: []attribute.KeyValue{ + parent: sc.WithRemote(true), + name: "span0", + attributes: []attribute.KeyValue{ attribute.String("key1", "value1"), attribute.String("key2", "value2"), }, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "StartSpanAttribute"}, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "StartSpanAttribute"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanAttributesOnStart: -got +want %s", diff) @@ -418,18 +418,18 @@ func TestSetSpanAttributes(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Attributes: []attribute.KeyValue{ + parent: sc.WithRemote(true), + name: "span0", + attributes: []attribute.KeyValue{ attribute.String("key1", "value1"), }, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "SpanAttribute"}, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "SpanAttribute"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanAttributes: -got +want %s", diff) @@ -454,8 +454,8 @@ func TestSamplerAttributesLocalChildSpan(t *testing.T) { gotSpan0, gotSpan1 := got[0], got[1] // Ensure sampler is called for local child spans by verifying the // attributes set by the sampler are set on the child span. - assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 2)}, gotSpan0.Attributes) - assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 1)}, gotSpan1.Attributes) + assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 2)}, gotSpan0.Attributes()) + assert.Equal(t, []attribute.KeyValue{attribute.Int("callCount", 1)}, gotSpan1.Attributes()) } func TestSetSpanAttributesOverLimit(t *testing.T) { @@ -474,20 +474,20 @@ func TestSetSpanAttributesOverLimit(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Attributes: []attribute.KeyValue{ + parent: sc.WithRemote(true), + name: "span0", + attributes: []attribute.KeyValue{ attribute.Bool("key1", false), attribute.Int64("key4", 4), }, - SpanKind: trace.SpanKindInternal, - DroppedAttributeCount: 1, - InstrumentationLibrary: instrumentation.Library{Name: "SpanAttributesOverLimit"}, + spanKind: trace.SpanKindInternal, + droppedAttributeCount: 1, + instrumentationLibrary: instrumentation.Library{Name: "SpanAttributesOverLimit"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff) @@ -508,19 +508,19 @@ func TestSetSpanAttributesWithInvalidKey(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Attributes: []attribute.KeyValue{ + parent: sc.WithRemote(true), + name: "span0", + attributes: []attribute.KeyValue{ attribute.Bool("key1", false), }, - SpanKind: trace.SpanKindInternal, - DroppedAttributeCount: 0, - InstrumentationLibrary: instrumentation.Library{Name: "SpanToSetInvalidKeyOrValue"}, + spanKind: trace.SpanKindInternal, + droppedAttributeCount: 0, + instrumentationLibrary: instrumentation.Library{Name: "SpanToSetInvalidKeyOrValue"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanAttributesWithInvalidKey: -got +want %s", diff) @@ -546,25 +546,25 @@ func TestEvents(t *testing.T) { t.Fatal(err) } - for i := range got.MessageEvents { - if !checkTime(&got.MessageEvents[i].Time) { + for i := range got.Events() { + if !checkTime(&got.Events()[i].Time) { t.Error("exporting span: expected nonzero Event Time") } } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - MessageEvents: []Event{ + parent: sc.WithRemote(true), + name: "span0", + events: []Event{ {Name: "foo", Attributes: []attribute.KeyValue{k1v1}}, {Name: "bar", Attributes: []attribute.KeyValue{k2v2, k3v3}}, }, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "Events"}, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "Events"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("Message Events: -got +want %s", diff) @@ -595,26 +595,26 @@ func TestEventsOverLimit(t *testing.T) { t.Fatal(err) } - for i := range got.MessageEvents { - if !checkTime(&got.MessageEvents[i].Time) { + for i := range got.Events() { + if !checkTime(&got.Events()[i].Time) { t.Error("exporting span: expected nonzero Event Time") } } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - MessageEvents: []Event{ + parent: sc.WithRemote(true), + name: "span0", + events: []Event{ {Name: "foo", Attributes: []attribute.KeyValue{k1v1}}, {Name: "bar", Attributes: []attribute.KeyValue{k2v2, k3v3}}, }, - DroppedMessageEventCount: 2, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "EventsOverLimit"}, + droppedEventCount: 2, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "EventsOverLimit"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("Message Event over limit: -got +want %s", diff) @@ -643,16 +643,16 @@ func TestLinks(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Links: links, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "Links"}, + parent: sc.WithRemote(true), + name: "span0", + links: links, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "Links"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("Link: -got +want %s", diff) @@ -684,20 +684,20 @@ func TestLinksOverLimit(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Links: []trace.Link{ + parent: sc.WithRemote(true), + name: "span0", + links: []trace.Link{ {SpanContext: sc2, Attributes: []attribute.KeyValue{k2v2}}, {SpanContext: sc3, Attributes: []attribute.KeyValue{k3v3}}, }, - DroppedLinkCount: 1, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "LinksOverLimit"}, + droppedLinkCount: 1, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "LinksOverLimit"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("Link over limit: -got +want %s", diff) @@ -717,8 +717,8 @@ func TestSetSpanName(t *testing.T) { t.Fatal(err) } - if got.Name != want { - t.Errorf("span.Name: got %q; want %q", got.Name, want) + if got.Name() != want { + t.Errorf("span.Name: got %q; want %q", got.Name(), want) } } @@ -733,19 +733,19 @@ func TestSetSpanStatus(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - Status: Status{ + parent: sc.WithRemote(true), + name: "span0", + spanKind: trace.SpanKindInternal, + status: Status{ Code: codes.Error, Description: "Error", }, - InstrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, + instrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanStatus: -got +want %s", diff) @@ -763,19 +763,19 @@ func TestSetSpanStatusWithoutMessageWhenStatusIsNotError(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - Status: Status{ + parent: sc.WithRemote(true), + name: "span0", + spanKind: trace.SpanKindInternal, + status: Status{ Code: codes.Ok, Description: "", }, - InstrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, + instrumentationLibrary: instrumentation.Library{Name: "SpanStatus"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanStatus: -got +want %s", diff) @@ -784,6 +784,7 @@ func TestSetSpanStatusWithoutMessageWhenStatusIsNotError(t *testing.T) { func cmpDiff(x, y interface{}) string { return cmp.Diff(x, y, + cmp.AllowUnexported(snapshot{}), cmp.AllowUnexported(attribute.Value{}), cmp.AllowUnexported(Event{}), cmp.AllowUnexported(trace.TraceState{})) @@ -843,16 +844,15 @@ func startLocalSpan(tp *TracerProvider, ctx context.Context, trName, name string } // endSpan is a test utility function that ends the span in the context and -// returns the exported export.SpanSnapshot. +// returns the exported span. // It requires that span be sampled using one of these methods // 1. Passing parent span context in context // 2. Use WithSampler(AlwaysSample()) // 3. Configuring AlwaysSample() as default sampler // // It also does some basic tests on the span. -// It also clears spanID in the export.SpanSnapshot to make the comparison -// easier. -func endSpan(te *testExporter, span trace.Span) (*SpanSnapshot, error) { +// It also clears spanID in the to make the comparison easier. +func endSpan(te *testExporter, span trace.Span) (*snapshot, error) { if !span.IsRecording() { return nil, fmt.Errorf("IsRecording: got false, want true") } @@ -864,14 +864,14 @@ func endSpan(te *testExporter, span trace.Span) (*SpanSnapshot, error) { return nil, fmt.Errorf("got %d exported spans, want one span", te.Len()) } got := te.Spans()[0] - if !got.SpanContext.SpanID().IsValid() { + if !got.SpanContext().SpanID().IsValid() { return nil, fmt.Errorf("exporting span: expected nonzero SpanID") } - got.SpanContext = got.SpanContext.WithSpanID(trace.SpanID{}) - if !checkTime(&got.StartTime) { + got.spanContext = got.SpanContext().WithSpanID(trace.SpanID{}) + if !checkTime(&got.startTime) { return nil, fmt.Errorf("exporting span: expected nonzero StartTime") } - if !checkTime(&got.EndTime) { + if !checkTime(&got.endTime) { return nil, fmt.Errorf("exporting span: expected nonzero EndTime") } return got, nil @@ -939,16 +939,16 @@ func TestStartSpanAfterEnd(t *testing.T) { t.Fatal("span-2 not recorded") } - if got, want := gotSpan1.SpanContext.TraceID(), gotParent.SpanContext.TraceID(); got != want { + if got, want := gotSpan1.SpanContext().TraceID(), gotParent.SpanContext().TraceID(); got != want { t.Errorf("span-1.TraceID=%q; want %q", got, want) } - if got, want := gotSpan2.SpanContext.TraceID(), gotParent.SpanContext.TraceID(); got != want { + if got, want := gotSpan2.SpanContext().TraceID(), gotParent.SpanContext().TraceID(); got != want { t.Errorf("span-2.TraceID=%q; want %q", got, want) } - if got, want := gotSpan1.Parent.SpanID(), gotParent.SpanContext.SpanID(); got != want { + if got, want := gotSpan1.Parent().SpanID(), gotParent.SpanContext().SpanID(); got != want { t.Errorf("span-1.ParentSpanID=%q; want %q (parent.SpanID)", got, want) } - if got, want := gotSpan2.Parent.SpanID(), gotSpan1.SpanContext.SpanID(); got != want { + if got, want := gotSpan2.Parent().SpanID(), gotSpan1.SpanContext().SpanID(); got != want { t.Errorf("span-2.ParentSpanID=%q; want %q (span1.SpanID)", got, want) } } @@ -988,16 +988,16 @@ func TestChildSpanCount(t *testing.T) { t.Fatal("span-3 not recorded") } - if got, want := gotSpan3.ChildSpanCount, 0; got != want { + if got, want := gotSpan3.ChildSpanCount(), 0; got != want { t.Errorf("span-3.ChildSpanCount=%d; want %d", got, want) } - if got, want := gotSpan2.ChildSpanCount, 0; got != want { + if got, want := gotSpan2.ChildSpanCount(), 0; got != want { t.Errorf("span-2.ChildSpanCount=%d; want %d", got, want) } - if got, want := gotSpan1.ChildSpanCount, 1; got != want { + if got, want := gotSpan1.ChildSpanCount(), 1; got != want { t.Errorf("span-1.ChildSpanCount=%d; want %d", got, want) } - if got, want := gotParent.ChildSpanCount, 2; got != want { + if got, want := gotParent.ChildSpanCount(), 2; got != want { t.Errorf("parent.ChildSpanCount=%d; want %d", got, want) } } @@ -1075,11 +1075,11 @@ func TestCustomStartEndTime(t *testing.T) { t.Fatalf("got %d exported spans, want one span", te.Len()) } got := te.Spans()[0] - if got.StartTime != startTime { - t.Errorf("expected start time to be %s, got %s", startTime, got.StartTime) + if got.StartTime() != startTime { + t.Errorf("expected start time to be %s, got %s", startTime, got.StartTime()) } - if got.EndTime != endTime { - t.Errorf("expected end time to be %s, got %s", endTime, got.EndTime) + if got.EndTime() != endTime { + t.Errorf("expected end time to be %s, got %s", endTime, got.EndTime()) } } @@ -1114,16 +1114,16 @@ func TestRecordError(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Status: Status{Code: codes.Unset}, - SpanKind: trace.SpanKindInternal, - MessageEvents: []Event{ + parent: sc.WithRemote(true), + name: "span0", + status: Status{Code: codes.Unset}, + spanKind: trace.SpanKindInternal, + events: []Event{ { Name: semconv.ExceptionEventName, Time: errTime, @@ -1133,7 +1133,7 @@ func TestRecordError(t *testing.T) { }, }, }, - InstrumentationLibrary: instrumentation.Library{Name: "RecordError"}, + instrumentationLibrary: instrumentation.Library{Name: "RecordError"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SpanErrorOptions: -got +want %s", diff) @@ -1153,19 +1153,19 @@ func TestRecordErrorNil(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - Status: Status{ + parent: sc.WithRemote(true), + name: "span0", + spanKind: trace.SpanKindInternal, + status: Status{ Code: codes.Unset, Description: "", }, - InstrumentationLibrary: instrumentation.Library{Name: "RecordErrorNil"}, + instrumentationLibrary: instrumentation.Library{Name: "RecordErrorNil"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SpanErrorOptions: -got +want %s", diff) @@ -1183,8 +1183,8 @@ func TestWithSpanKind(t *testing.T) { t.Error(err.Error()) } - if spanData.SpanKind != trace.SpanKindInternal { - t.Errorf("Default value of Spankind should be Internal: got %+v, want %+v\n", spanData.SpanKind, trace.SpanKindInternal) + if spanData.SpanKind() != trace.SpanKindInternal { + t.Errorf("Default value of Spankind should be Internal: got %+v, want %+v\n", spanData.SpanKind(), trace.SpanKindInternal) } sks := []trace.SpanKind{ @@ -1204,8 +1204,8 @@ func TestWithSpanKind(t *testing.T) { t.Error(err.Error()) } - if spanData.SpanKind != sk { - t.Errorf("WithSpanKind check: got %+v, want %+v\n", spanData.SpanKind, sks) + if spanData.SpanKind() != sk { + t.Errorf("WithSpanKind check: got %+v, want %+v\n", spanData.SpanKind(), sks) } } } @@ -1263,19 +1263,19 @@ func TestWithResource(t *testing.T) { if err != nil { t.Error(err.Error()) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Attributes: []attribute.KeyValue{ + parent: sc.WithRemote(true), + name: "span0", + attributes: []attribute.KeyValue{ attribute.String("key1", "value1"), }, - SpanKind: trace.SpanKindInternal, - Resource: tc.want, - InstrumentationLibrary: instrumentation.Library{Name: "WithResource"}, + spanKind: trace.SpanKindInternal, + resource: tc.want, + instrumentationLibrary: instrumentation.Library{Name: "WithResource"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("WithResource:\n -got +want %s", diff) @@ -1299,15 +1299,15 @@ func TestWithInstrumentationVersion(t *testing.T) { t.Error(err.Error()) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{ + parent: sc.WithRemote(true), + name: "span0", + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{ Name: "WithInstrumentationVersion", Version: "v0.1.0", }, @@ -1332,9 +1332,9 @@ func TestSpanCapturesPanic(t *testing.T) { require.PanicsWithError(t, "error message", f) spans := te.Spans() require.Len(t, spans, 1) - require.Len(t, spans[0].MessageEvents, 1) - assert.Equal(t, spans[0].MessageEvents[0].Name, semconv.ExceptionEventName) - assert.Equal(t, spans[0].MessageEvents[0].Attributes, []attribute.KeyValue{ + require.Len(t, spans[0].Events(), 1) + assert.Equal(t, spans[0].Events()[0].Name, semconv.ExceptionEventName) + assert.Equal(t, spans[0].Events()[0].Attributes, []attribute.KeyValue{ semconv.ExceptionTypeKey.String("*errors.errorString"), semconv.ExceptionMessageKey.String("error message"), }) @@ -1365,14 +1365,14 @@ func TestReadOnlySpan(t *testing.T) { }) st := time.Now() - ctx, span := tr.Start(ctx, "foo", trace.WithTimestamp(st), + ctx, s := tr.Start(ctx, "foo", trace.WithTimestamp(st), trace.WithLinks(trace.Link{SpanContext: linked})) - span.SetAttributes(kv) - span.AddEvent("foo", trace.WithAttributes(kv)) - span.SetStatus(codes.Ok, "foo") + s.SetAttributes(kv) + s.AddEvent("foo", trace.WithAttributes(kv)) + s.SetStatus(codes.Ok, "foo") // Verify span implements ReadOnlySpan. - ro, ok := span.(ReadOnlySpan) + ro, ok := s.(ReadOnlySpan) require.True(t, ok) assert.Equal(t, "foo", ro.Name()) @@ -1394,21 +1394,21 @@ func TestReadOnlySpan(t *testing.T) { assert.Equal(t, kv.Value, ro.Resource().Attributes()[0].Value) // Verify changes to the original span are reflected in the ReadOnlySpan. - span.SetName("bar") + s.SetName("bar") assert.Equal(t, "bar", ro.Name()) - // Verify Snapshot() returns snapshots that are independent from the + // Verify snapshot() returns snapshots that are independent from the // original span and from one another. - d1 := ro.Snapshot() - span.AddEvent("baz") - d2 := ro.Snapshot() - for _, e := range d1.MessageEvents { + d1 := s.(*span).snapshot() + s.AddEvent("baz") + d2 := s.(*span).snapshot() + for _, e := range d1.Events() { if e.Name == "baz" { t.Errorf("Didn't expect to find 'baz' event") } } var exists bool - for _, e := range d2.MessageEvents { + for _, e := range d2.Events() { if e.Name == "baz" { exists = true } @@ -1418,7 +1418,7 @@ func TestReadOnlySpan(t *testing.T) { } et := st.Add(time.Millisecond) - span.End(trace.WithTimestamp(et)) + s.End(trace.WithTimestamp(et)) assert.Equal(t, et, ro.EndTime()) } @@ -1481,21 +1481,21 @@ func TestAddEventsWithMoreAttributesThanLimit(t *testing.T) { t.Fatal(err) } - for i := range got.MessageEvents { - if !checkTime(&got.MessageEvents[i].Time) { + for i := range got.Events() { + if !checkTime(&got.Events()[i].Time) { t.Error("exporting span: expected nonzero Event Time") } } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Attributes: nil, - MessageEvents: []Event{ + parent: sc.WithRemote(true), + name: "span0", + attributes: nil, + events: []Event{ { Name: "test1", Attributes: []attribute.KeyValue{ @@ -1512,8 +1512,8 @@ func TestAddEventsWithMoreAttributesThanLimit(t *testing.T) { DroppedAttributeCount: 2, }, }, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "AddSpanEventWithOverLimitedAttributes"}, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "AddSpanEventWithOverLimitedAttributes"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff) @@ -1546,14 +1546,14 @@ func TestAddLinksWithMoreAttributesThanLimit(t *testing.T) { t.Fatal(err) } - want := &SpanSnapshot{ - SpanContext: trace.NewSpanContext(trace.SpanContextConfig{ + want := &snapshot{ + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ TraceID: tid, TraceFlags: 0x1, }), - Parent: sc.WithRemote(true), - Name: "span0", - Links: []trace.Link{ + parent: sc.WithRemote(true), + name: "span0", + links: []trace.Link{ { SpanContext: sc1, Attributes: []attribute.KeyValue{k1v1}, @@ -1565,8 +1565,8 @@ func TestAddLinksWithMoreAttributesThanLimit(t *testing.T) { DroppedAttributeCount: 2, }, }, - SpanKind: trace.SpanKindInternal, - InstrumentationLibrary: instrumentation.Library{Name: "Links"}, + spanKind: trace.SpanKindInternal, + instrumentationLibrary: instrumentation.Library{Name: "Links"}, } if diff := cmpDiff(got, want); diff != "" { t.Errorf("Link: -got +want %s", diff) @@ -1706,7 +1706,7 @@ func TestSamplerTraceState(t *testing.T) { return } - receivedState := got[0].SpanContext.TraceState() + receivedState := got[0].SpanContext().TraceState() if diff := cmpDiff(receivedState, ts.want); diff != "" { t.Errorf("TraceState not propagated: -got +want %s", diff) diff --git a/sdk/trace/tracetest/span.go b/sdk/trace/tracetest/span.go new file mode 100644 index 00000000000..964b317c6f0 --- /dev/null +++ b/sdk/trace/tracetest/span.go @@ -0,0 +1,163 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracetest // import "go.opentelemetry.io/otel/sdk/trace/tracetest" + +import ( + "time" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +type SpanStubs []SpanStub + +// SpanStubFromReadOnlySpan returns SpanStubs populated from ro. +func SpanStubsFromReadOnlySpans(ro []tracesdk.ReadOnlySpan) SpanStubs { + if len(ro) == 0 { + return nil + } + + s := make(SpanStubs, 0, len(ro)) + for _, r := range ro { + s = append(s, SpanStubFromReadOnlySpan(r)) + } + + return s +} + +// Snapshots returns s as a slice of ReadOnlySpans. +func (s SpanStubs) Snapshots() []tracesdk.ReadOnlySpan { + if len(s) == 0 { + return nil + } + + ro := make([]tracesdk.ReadOnlySpan, len(s)) + for i := 0; i < len(s); i++ { + ro[i] = s[i].Snapshot() + } + return ro +} + +// SpanStub is a stand-in for a Span. +type SpanStub struct { + Name string + SpanContext trace.SpanContext + Parent trace.SpanContext + SpanKind trace.SpanKind + StartTime time.Time + EndTime time.Time + Attributes []attribute.KeyValue + Events []tracesdk.Event + Links []trace.Link + Status tracesdk.Status + DroppedAttributes int + DroppedEvents int + DroppedLinks int + ChildSpanCount int + Resource *resource.Resource + InstrumentationLibrary instrumentation.Library +} + +// SpanStubFromReadOnlySpan returns a SpanStub populated from ro. +func SpanStubFromReadOnlySpan(ro tracesdk.ReadOnlySpan) SpanStub { + if ro == nil { + return SpanStub{} + } + + return SpanStub{ + Name: ro.Name(), + SpanContext: ro.SpanContext(), + Parent: ro.Parent(), + SpanKind: ro.SpanKind(), + StartTime: ro.StartTime(), + EndTime: ro.EndTime(), + Attributes: ro.Attributes(), + Events: ro.Events(), + Links: ro.Links(), + Status: ro.Status(), + DroppedAttributes: ro.DroppedAttributes(), + DroppedEvents: ro.DroppedEvents(), + DroppedLinks: ro.DroppedLinks(), + ChildSpanCount: ro.ChildSpanCount(), + Resource: ro.Resource(), + InstrumentationLibrary: ro.InstrumentationLibrary(), + } +} + +// Snapshot returns a read-only copy of the SpanStub. +func (s SpanStub) Snapshot() tracesdk.ReadOnlySpan { + return spanSnapshot{ + name: s.Name, + spanContext: s.SpanContext, + parent: s.Parent, + spanKind: s.SpanKind, + startTime: s.StartTime, + endTime: s.EndTime, + attributes: s.Attributes, + events: s.Events, + links: s.Links, + status: s.Status, + droppedAttributes: s.DroppedAttributes, + droppedEvents: s.DroppedEvents, + droppedLinks: s.DroppedLinks, + childSpanCount: s.ChildSpanCount, + resource: s.Resource, + instrumentationLibrary: s.InstrumentationLibrary, + } +} + +type spanSnapshot struct { + // Embed the interface to implement the private method. + tracesdk.ReadOnlySpan + + name string + spanContext trace.SpanContext + parent trace.SpanContext + spanKind trace.SpanKind + startTime time.Time + endTime time.Time + attributes []attribute.KeyValue + events []tracesdk.Event + links []trace.Link + status tracesdk.Status + droppedAttributes int + droppedEvents int + droppedLinks int + childSpanCount int + resource *resource.Resource + instrumentationLibrary instrumentation.Library +} + +func (s spanSnapshot) Name() string { return s.name } +func (s spanSnapshot) SpanContext() trace.SpanContext { return s.spanContext } +func (s spanSnapshot) Parent() trace.SpanContext { return s.parent } +func (s spanSnapshot) SpanKind() trace.SpanKind { return s.spanKind } +func (s spanSnapshot) StartTime() time.Time { return s.startTime } +func (s spanSnapshot) EndTime() time.Time { return s.endTime } +func (s spanSnapshot) Attributes() []attribute.KeyValue { return s.attributes } +func (s spanSnapshot) Links() []trace.Link { return s.links } +func (s spanSnapshot) Events() []tracesdk.Event { return s.events } +func (s spanSnapshot) Status() tracesdk.Status { return s.status } +func (s spanSnapshot) DroppedAttributes() int { return s.droppedAttributes } +func (s spanSnapshot) DroppedLinks() int { return s.droppedLinks } +func (s spanSnapshot) DroppedEvents() int { return s.droppedEvents } +func (s spanSnapshot) ChildSpanCount() int { return s.childSpanCount } +func (s spanSnapshot) Resource() *resource.Resource { return s.resource } +func (s spanSnapshot) InstrumentationLibrary() instrumentation.Library { + return s.instrumentationLibrary +} diff --git a/sdk/trace/tracetest/test.go b/sdk/trace/tracetest/test.go index ad1d2f226f3..104489e79fd 100644 --- a/sdk/trace/tracetest/test.go +++ b/sdk/trace/tracetest/test.go @@ -31,12 +31,12 @@ func NewNoopExporter() *NoopExporter { return new(NoopExporter) } -// NoopExporter is an exporter that drops all received SpanSnapshots and -// performs no action. +// NoopExporter is an exporter that drops all received spans and performs no +// action. type NoopExporter struct{} -// ExportSpans handles export of SpanSnapshots by dropping them. -func (nsb *NoopExporter) ExportSpans(context.Context, []*trace.SpanSnapshot) error { return nil } +// ExportSpans handles export of spans by dropping them. +func (nsb *NoopExporter) ExportSpans(context.Context, []trace.ReadOnlySpan) error { return nil } // Shutdown stops the exporter by doing nothing. func (nsb *NoopExporter) Shutdown(context.Context) error { return nil } @@ -51,18 +51,18 @@ func NewInMemoryExporter() *InMemoryExporter { // InMemoryExporter is an exporter that stores all received spans in-memory. type InMemoryExporter struct { mu sync.Mutex - ss []*trace.SpanSnapshot + ss SpanStubs } -// ExportSpans handles export of SpanSnapshots by storing them in memory. -func (imsb *InMemoryExporter) ExportSpans(_ context.Context, ss []*trace.SpanSnapshot) error { +// ExportSpans handles export of spans by storing them in memory. +func (imsb *InMemoryExporter) ExportSpans(_ context.Context, spans []trace.ReadOnlySpan) error { imsb.mu.Lock() defer imsb.mu.Unlock() - imsb.ss = append(imsb.ss, ss...) + imsb.ss = append(imsb.ss, SpanStubsFromReadOnlySpans(spans)...) return nil } -// Shutdown stops the exporter by clearing SpanSnapshots held in memory. +// Shutdown stops the exporter by clearing spans held in memory. func (imsb *InMemoryExporter) Shutdown(context.Context) error { imsb.Reset() return nil @@ -76,10 +76,10 @@ func (imsb *InMemoryExporter) Reset() { } // GetSpans returns the current in-memory stored spans. -func (imsb *InMemoryExporter) GetSpans() []*trace.SpanSnapshot { +func (imsb *InMemoryExporter) GetSpans() SpanStubs { imsb.mu.Lock() defer imsb.mu.Unlock() - ret := make([]*trace.SpanSnapshot, len(imsb.ss)) + ret := make(SpanStubs, len(imsb.ss)) copy(ret, imsb.ss) return ret } diff --git a/sdk/trace/tracetest/test_test.go b/sdk/trace/tracetest/test_test.go index 8fee38b65fc..e718207f467 100644 --- a/sdk/trace/tracetest/test_test.go +++ b/sdk/trace/tracetest/test_test.go @@ -16,12 +16,11 @@ package tracetest import ( "context" + "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/sdk/trace" ) // TestNoop tests only that the no-op does not crash in different scenarios. @@ -29,8 +28,8 @@ func TestNoop(t *testing.T) { nsb := NewNoopExporter() require.NoError(t, nsb.ExportSpans(context.Background(), nil)) - require.NoError(t, nsb.ExportSpans(context.Background(), make([]*trace.SpanSnapshot, 10))) - require.NoError(t, nsb.ExportSpans(context.Background(), make([]*trace.SpanSnapshot, 0, 10))) + require.NoError(t, nsb.ExportSpans(context.Background(), make(SpanStubs, 10).Snapshots())) + require.NoError(t, nsb.ExportSpans(context.Background(), make(SpanStubs, 0, 10).Snapshots())) } func TestNewInMemoryExporter(t *testing.T) { @@ -39,23 +38,23 @@ func TestNewInMemoryExporter(t *testing.T) { require.NoError(t, imsb.ExportSpans(context.Background(), nil)) assert.Len(t, imsb.GetSpans(), 0) - input := make([]*trace.SpanSnapshot, 10) + input := make(SpanStubs, 10) for i := 0; i < 10; i++ { - input[i] = new(trace.SpanSnapshot) + input[i] = SpanStub{Name: fmt.Sprintf("span %d", i)} } - require.NoError(t, imsb.ExportSpans(context.Background(), input)) + require.NoError(t, imsb.ExportSpans(context.Background(), input.Snapshots())) sds := imsb.GetSpans() assert.Len(t, sds, 10) for i, sd := range sds { - assert.Same(t, input[i], sd) + assert.Equal(t, input[i], sd) } imsb.Reset() // Ensure that operations on the internal storage does not change the previously returned value. assert.Len(t, sds, 10) assert.Len(t, imsb.GetSpans(), 0) - require.NoError(t, imsb.ExportSpans(context.Background(), input[0:1])) + require.NoError(t, imsb.ExportSpans(context.Background(), input.Snapshots()[0:1])) sds = imsb.GetSpans() assert.Len(t, sds, 1) - assert.Same(t, input[0], sds[0]) + assert.Equal(t, input[0], sds[0]) }