Following the discussion here, we should take another look at the way we use export.SpanData in sdk/trace.
The spec mentions two types of interfaces to a span at the SDK level:
Right now we use export.SpanData in the SDK as a "container" for any information about a span which may need to be read, for example for the sake of exporting a span. We store a pointer to a SpanData struct in a span's data field and we make a copy of the SpanData using makeSpanData() when we want to prevent modifications to an ended span:
We should see if there is a cleaner, Go-idiomatic way to express the two "behaviors" of the span inside the SDK and refactor accordingly.
@MrAlias please feel free to assign me.
After reading the relevant parts of the spec and looking at the way things are done at the Java library, I'm thinking about the following design:
trace.Span at the API as the write-only span type used for instrumentation.ReadableSpan interface to the SDK which would allow reading all data from the internal state of the (unexported) span type. This interface returns data directly from the original span created by the tracer, not from a snapshot of the span, meaning it will keep reflecting changes to the original span if relevant.OnEnd.span field - or have one method which returns a big struct with all the data.ReadWriteSpan interface to the SDK which would embed trace.Span and ReadableSpan to provide the union of the methods from these two interfaces.OnStart as required by the spec. This means span processors will be able to _modify spans_.span type. Any write operations currently done on a SpanData instance should be changed so that they are performed on the span.SpanData type to be an immutable snapshot of a span's internal state.span and SpanData DRY while maintaining an abstraction which makes sense.@MrAlias @krnowak - happy to hear your thoughts. To save time I'm starting to work in this direction and will add comments if I encounter any problems.
This sounds like a good design to me.
Note: Need to figure out if it's better to have a granular list of methods - one for each span field - or have one method which returns a big struct with all the data.
Talked to @krnowak offline. He suggests using getter methods which "mirror" the setters we already have in trace.Span. Might want to expose only fields which are actually needed right now and not every single trace.Span field.
Also - we might want to improve the naming of the new interfaces. We could maybe do ReadOnlySpan and ReadWriteSpan which is more consistent language-wise.
~WIP is here: https://github.com/johananl/opentelemetry-go/tree/johananl/refactor-spandata~
TODO:
span.ReadOnlySpan interface and use in the appropriate places instead of export.SpanData.ReadWriteSpan interface and use in the appropriate places instead of export.SpanData.export.SpanData is immutable and used only for exporting purposes (the docs should be clear about this).Resource via ReadOnlySpan.ReadOnlySpan and ReadWriteSpan.
Most helpful comment
Talked to @krnowak offline. He suggests using getter methods which "mirror" the setters we already have in
trace.Span. Might want to expose only fields which are actually needed right now and not every singletrace.Spanfield.