1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-08-15 20:03:15 +02:00

Redefine ExportSpans of SpanExporter with ReadOnlySpan (#1873)

* Remove TODO from ReadOnlySpan interface

* Remove the Tracer method from the ReadOnlySpan

This is not required by the specification nor the use of this interface.

* Remove IsRecording from the ReadOnlySpan interface

A read-only span value does not need to know if updates to it will be
recorded. It by definition cannot be updated so no point in
communicating if an update would be recorded.

* Document the ReadOnlySpan interface

* Rename messageEvent* to just event*

* Move the SpanSnapshot into its own file

* Update ReadOnlySpan interface with meta info methods

Add the DroppedAttributes, DroppedLinks, DroppedEvents, and
ChildSpanCount methods to the interface to return additional information
about the span not specified by the specification, but that we are
already providing.

* Add SpanStub to the sdk/trace/tracetest pkg

* Redefine ExportSpans of SpanExporter with ReadOnlySpan

* Rename SpanSnapshot to snapshot and purge docs

* Remove Snapshot method from snapshot type

This method is a hold-over from previous version of the ReadOnlySpan
interface is not needed.

* Update CHANGELOG with changes
This commit is contained in:
Tyler Yahn
2021-05-04 23:45:13 +00:00
committed by GitHub
parent c99d5e999c
commit cbcd4b1a3d
34 changed files with 1046 additions and 675 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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,

View File

@@ -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()) })
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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))
})

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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),
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,
},
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},
},
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) {

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)
})

View File

@@ -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`

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
{

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)
}
}

View File

@@ -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)
}

138
sdk/trace/snapshot.go Normal file
View File

@@ -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
}

View File

@@ -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
}
// 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.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
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
}

View File

@@ -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

View File

@@ -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{})

View File

@@ -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)

163
sdk/trace/tracetest/span.go Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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])
}