Repository: open-telemetry/opentelemetry-go (go)

Issue:
Change SpanID to a byte array
This is a follow up from #183.

We changed the `TraceID` to a byte array because it is more intuitive and easier to use. We should do it to `SpanID` as well.

Pull request:
api(trace): change SpanID to byte array
Fixes #239 

Patch:
diff --git a/api/core/span_context.go b/api/core/span_context.go
index ed06b718..d6fb398c 100644
--- a/api/core/span_context.go
+++ b/api/core/span_context.go
@@ -18,8 +18,6 @@ import (
 	"bytes"
 	"encoding/hex"
 	"encoding/json"
-	"errors"
-	"fmt"
 )
 
 const (
@@ -30,25 +28,89 @@ const (
 	// for SpanContext TraceFlags field when a trace is sampled.
 	TraceFlagsSampled = traceFlagsBitMaskSampled
 	TraceFlagsUnused  = traceFlagsBitMaskUnused
+
+	ErrInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase"
+
+	ErrInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32"
+	ErrNilTraceID           errorConst = "trace-id can't be all zero"
+
+	ErrInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16"
+	ErrNilSpanID           errorConst = "span-id can't be all zero"
 )
 
+type errorConst string
+
+func (e errorConst) Error() string {
+	return string(e)
+}
+
 type TraceID [16]byte
 
 var nilTraceID TraceID
+var _ json.Marshaler = nilTraceID
 
-func (t TraceID) isValid() bool {
+func (t TraceID) IsValid() bool {
 	return !bytes.Equal(t[:], nilTraceID[:])
 }
 
+// MarshalJSON implements a custom marshal function to encode TraceID as a hex string
+func (t TraceID) MarshalJSON() ([]byte, error) {
+	return json.Marshal(hex.EncodeToString(t[:]))
+}
+
+type SpanID [8]byte
+
+var nilSpanID SpanID
+var _ json.Marshaler = nilSpanID
+
+func (s SpanID) IsValid() bool {
+	return !bytes.Equal(s[:], nilSpanID[:])
+}
+
+// MarshalJSON implements a custom marshal function to encode SpanID as a hex string
+func (s SpanID) MarshalJSON() ([]byte, error) {
+	return json.Marshal(hex.EncodeToString(s[:]))
+}
+
 // TraceIDFromHex returns a TraceID from a hex string if it is compliant
 // with the w3c trace-context specification.
 // See more at https://www.w3.org/TR/trace-context/#trace-id
 func TraceIDFromHex(h string) (TraceID, error) {
 	t := TraceID{}
 	if len(h) != 32 {
-		return t, errors.New("hex encoded trace-id must have length equals to 32")
+		return t, ErrInvalidTraceIDLength
+	}
+
+	if err := decodeHex(h, t[:]); err != nil {
+		return t, err
+	}
+
+	if !t.IsValid() {
+		return t, ErrNilTraceID
 	}
+	return t, nil
+}
+
+// SpanIDFromHex returns a SpanID from a hex string if it is compliant
+// with the w3c trace-context specification.
+// See more at https://www.w3.org/TR/trace-context/#parent-id
+func SpanIDFromHex(h string) (SpanID, error) {
+	s := SpanID{}
+	if len(h) != 16 {
+		return s, ErrInvalidSpanIDLength
+	}
+
+	if err := decodeHex(h, s[:]); err != nil {
+		return s, err
+	}
+
+	if !s.IsValid() {
+		return s, ErrNilSpanID
+	}
+	return s, nil
+}
 
+func decodeHex(h string, b []byte) error {
 	for _, r := range h {
 		switch {
 		case 'a' <= r && r <= 'f':
@@ -56,65 +118,45 @@ func TraceIDFromHex(h string) (TraceID, error) {
 		case '0' <= r && r <= '9':
 			continue
 		default:
-			return t, errors.New("trace-id can only contain [0-9a-f] characters, all lowercase")
+			return ErrInvalidHexID
 		}
 	}
 
-	b, err := hex.DecodeString(h)
+	decoded, err := hex.DecodeString(h)
 	if err != nil {
-		return t, err
+		return err
 	}
-	copy(t[:], b)
 
-	if !t.isValid() {
-		return t, errors.New("trace-id can't be all zero")
-	}
-	return t, nil
+	copy(b[:], decoded)
+	return nil
 }
 
 type SpanContext struct {
 	TraceID    TraceID
-	SpanID     uint64
+	SpanID     SpanID
 	TraceFlags byte
 }
 
-var _ json.Marshaler = (*SpanContext)(nil)
-
 // EmptySpanContext is meant for internal use to return invalid span context during error conditions.
 func EmptySpanContext() SpanContext {
 	return SpanContext{}
 }
 
-// MarshalJSON implements a custom marshal function to encode SpanContext
-// in a human readable format with hex encoded TraceID and SpanID.
-func (sc SpanContext) MarshalJSON() ([]byte, error) {
-	type JSONSpanContext struct {
-		TraceID    string
-		SpanID     string
-		TraceFlags byte
-	}
-
-	return json.Marshal(JSONSpanContext{
-		TraceID:    sc.TraceIDString(),
-		SpanID:     sc.SpanIDString(),
-		TraceFlags: sc.TraceFlags,
-	})
-}
-
 func (sc SpanContext) IsValid() bool {
 	return sc.HasTraceID() && sc.HasSpanID()
 }
 
 func (sc SpanContext) HasTraceID() bool {
-	return sc.TraceID.isValid()
+	return sc.TraceID.IsValid()
 }
 
 func (sc SpanContext) HasSpanID() bool {
-	return sc.SpanID != 0
+	return sc.SpanID.IsValid()
 }
 
 func (sc SpanContext) SpanIDString() string {
-	return fmt.Sprintf("%.16x", sc.SpanID)
+	return hex.EncodeToString(sc.SpanID[:])
+
 }
 
 func (sc SpanContext) TraceIDString() string {
diff --git a/experimental/bridge/opentracing/bridge.go b/experimental/bridge/opentracing/bridge.go
index f9d1d85b..48befac6 100644
--- a/experimental/bridge/opentracing/bridge.go
+++ b/experimental/bridge/opentracing/bridge.go
@@ -18,7 +18,6 @@ import (
 	"context"
 	"fmt"
 	"net/http"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -512,7 +511,7 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
 		return ot.ErrInvalidCarrier
 	}
 	hhcarrier.Set(traceIDHeader, bridgeSC.otelSpanContext.TraceIDString())
-	hhcarrier.Set(spanIDHeader, spanIDToString(bridgeSC.otelSpanContext.SpanID))
+	hhcarrier.Set(spanIDHeader, bridgeSC.otelSpanContext.SpanIDString())
 	hhcarrier.Set(traceFlagsHeader, traceFlagsToString(bridgeSC.otelSpanContext.TraceFlags))
 	bridgeSC.ForeachBaggageItem(func(k, v string) bool {
 		// we assume that keys are already canonicalized
@@ -522,10 +521,6 @@ func (t *BridgeTracer) Inject(sm ot.SpanContext, format interface{}, carrier int
 	return nil
 }
 
-func spanIDToString(spanID uint64) string {
-	return fmt.Sprintf("%.16x", spanID)
-}
-
 func traceFlagsToString(opts byte) string {
 	var parts []string
 	if opts&otelcore.TraceFlagsSampled == otelcore.TraceFlagsSampled {
@@ -557,7 +552,7 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
 			}
 			bridgeSC.otelSpanContext.TraceID = traceID
 		case spanIDHeader:
-			spanID, err := spanIDFromString(v)
+			spanID, err := otelcore.SpanIDFromHex(v)
 			if err != nil {
 				return err
 			}
@@ -581,13 +576,6 @@ func (t *BridgeTracer) Extract(format interface{}, carrier interface{}) (ot.Span
 	return bridgeSC, nil
 }
 
-func spanIDFromString(s string) (uint64, error) {
-	if len(s) != 16 {
-		return 0, fmt.Errorf("invalid span ID")
-	}
-	return strconv.ParseUint(s, 16, 64)
-}
-
 func stringToTraceFlags(s string) byte {
 	var opts byte
 	for _, part := range strings.Split(s, ",") {
diff --git a/experimental/bridge/opentracing/internal/mock.go b/experimental/bridge/opentracing/internal/mock.go
index 4d5bb422..7b798172 100644
--- a/experimental/bridge/opentracing/internal/mock.go
+++ b/experimental/bridge/opentracing/internal/mock.go
@@ -47,7 +47,7 @@ type MockTracer struct {
 	Resources             oteldctx.Map
 	FinishedSpans         []*MockSpan
 	SpareTraceIDs         []otelcore.TraceID
-	SpareSpanIDs          []uint64
+	SpareSpanIDs          []otelcore.SpanID
 	SpareContextKeyValues []MockContextKeyValue
 
 	randLock sync.Mutex
@@ -153,11 +153,11 @@ func (t *MockTracer) getTraceID(ctx context.Context, spanOpts *oteltrace.SpanOpt
 	return t.getRandTraceID()
 }
 
-func (t *MockTracer) getParentSpanID(ctx context.Context, spanOpts *oteltrace.SpanOptions) uint64 {
+func (t *MockTracer) getParentSpanID(ctx context.Context, spanOpts *oteltrace.SpanOptions) otelcore.SpanID {
 	if parent := t.getParentSpanContext(ctx, spanOpts); parent.IsValid() {
 		return parent.SpanID
 	}
-	return 0
+	return otelcore.SpanID{}
 }
 
 func (t *MockTracer) getParentSpanContext(ctx context.Context, spanOpts *oteltrace.SpanOptions) otelcore.SpanContext {
@@ -171,7 +171,7 @@ func (t *MockTracer) getParentSpanContext(ctx context.Context, spanOpts *oteltra
 	return otelcore.EmptySpanContext()
 }
 
-func (t *MockTracer) getSpanID() uint64 {
+func (t *MockTracer) getSpanID() otelcore.SpanID {
 	if len(t.SpareSpanIDs) > 0 {
 		spanID := t.SpareSpanIDs[0]
 		t.SpareSpanIDs = t.SpareSpanIDs[1:]
@@ -180,13 +180,17 @@ func (t *MockTracer) getSpanID() uint64 {
 		}
 		return spanID
 	}
-	return t.getRandUint64()
+	return t.getRandSpanID()
 }
 
-func (t *MockTracer) getRandUint64() uint64 {
+func (t *MockTracer) getRandSpanID() otelcore.SpanID {
 	t.randLock.Lock()
 	defer t.randLock.Unlock()
-	return t.rand.Uint64()
+
+	sid := otelcore.SpanID{}
+	t.rand.Read(sid[:])
+
+	return sid
 }
 
 func (t *MockTracer) getRandTraceID() otelcore.TraceID {
@@ -220,7 +224,7 @@ type MockSpan struct {
 	Attributes   oteldctx.Map
 	StartTime    time.Time
 	EndTime      time.Time
-	ParentSpanID uint64
+	ParentSpanID otelcore.SpanID
 	Events       []MockEvent
 }
 
diff --git a/exporter/trace/jaeger/jaeger.go b/exporter/trace/jaeger/jaeger.go
index fda0869c..6c74184a 100644
--- a/exporter/trace/jaeger/jaeger.go
+++ b/exporter/trace/jaeger/jaeger.go
@@ -199,7 +199,7 @@ func spanDataToThrift(data *export.SpanData) *gen.Span {
 		refs = append(refs, &gen.SpanRef{
 			TraceIdHigh: int64(binary.BigEndian.Uint64(link.TraceID[0:8])),
 			TraceIdLow:  int64(binary.BigEndian.Uint64(link.TraceID[8:16])),
-			SpanId:      int64(link.SpanID),
+			SpanId:      int64(binary.BigEndian.Uint64(link.SpanID[:])),
 			// TODO(paivagustavo): properly set the reference type when specs are defined
 			//  see https://github.com/open-telemetry/opentelemetry-specification/issues/65
 			RefType: gen.SpanRefType_CHILD_OF,
@@ -209,8 +209,8 @@ func spanDataToThrift(data *export.SpanData) *gen.Span {
 	return &gen.Span{
 		TraceIdHigh:   int64(binary.BigEndian.Uint64(data.SpanContext.TraceID[0:8])),
 		TraceIdLow:    int64(binary.BigEndian.Uint64(data.SpanContext.TraceID[8:16])),
-		SpanId:        int64(data.SpanContext.SpanID),
-		ParentSpanId:  int64(data.ParentSpanID),
+		SpanId:        int64(binary.BigEndian.Uint64(data.SpanContext.SpanID[:])),
+		ParentSpanId:  int64(binary.BigEndian.Uint64(data.ParentSpanID[:])),
 		OperationName: data.Name, // TODO: if span kind is added then add prefix "Sent"/"Recv"
 		Flags:         int32(data.SpanContext.TraceFlags),
 		StartTime:     data.StartTime.UnixNano() / 1000,
diff --git a/exporter/trace/stackdriver/trace_proto.go b/exporter/trace/stackdriver/trace_proto.go
index bb8033ce..c7e2310f 100644
--- a/exporter/trace/stackdriver/trace_proto.go
+++ b/exporter/trace/stackdriver/trace_proto.go
@@ -82,7 +82,7 @@ func protoFromSpanData(s *export.SpanData, projectID string) *tracepb.Span {
 		EndTime:                 timestampProto(s.EndTime),
 		SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: !s.HasRemoteParent},
 	}
-	if s.ParentSpanID != s.SpanContext.SpanID && s.ParentSpanID != 0 {
+	if s.ParentSpanID != s.SpanContext.SpanID && s.ParentSpanID.IsValid() {
 		sp.ParentSpanId = fmt.Sprintf("%.16x", s.ParentSpanID)
 	}
 	if s.Status != codes.OK {
diff --git a/internal/trace/mock_tracer.go b/internal/trace/mock_tracer.go
index 721e95c7..20263869 100644
--- a/internal/trace/mock_tracer.go
+++ b/internal/trace/mock_tracer.go
@@ -17,6 +17,7 @@ package trace
 import (
 	"context"
 	"crypto/rand"
+	"encoding/binary"
 	"sync/atomic"
 
 	"go.opentelemetry.io/api/core"
@@ -77,7 +78,8 @@ func (mt *MockTracer) Start(ctx context.Context, name string, o ...apitrace.Span
 	} else {
 		sc = opts.Relation.SpanContext
 	}
-	sc.SpanID = atomic.AddUint64(mt.StartSpanID, 1)
+
+	binary.BigEndian.PutUint64(sc.SpanID[:], atomic.AddUint64(mt.StartSpanID, 1))
 	span = &MockSpan{
 		sc:     sc,
 		tracer: mt,
diff --git a/propagation/binary_propagator.go b/propagation/binary_propagator.go
index 91991c73..ca41e209 100644
--- a/propagation/binary_propagator.go
+++ b/propagation/binary_propagator.go
@@ -15,8 +15,6 @@
 package propagation
 
 import (
-	"encoding/binary"
-
 	"go.opentelemetry.io/api/core"
 	apipropagation "go.opentelemetry.io/api/propagation"
 )
@@ -40,7 +38,7 @@ func (bp binaryPropagator) ToBytes(sc core.SpanContext) []byte {
 	var b [29]byte
 	copy(b[2:18], sc.TraceID[:])
 	b[18] = 1
-	binary.BigEndian.PutUint64(b[19:27], sc.SpanID)
+	copy(b[19:27], sc.SpanID[:])
 	b[27] = 2
 	b[28] = sc.TraceFlags
 	return b[:]
@@ -60,7 +58,7 @@ func (bp binaryPropagator) FromBytes(b []byte) (sc core.SpanContext) {
 		return core.EmptySpanContext()
 	}
 	if len(b) >= 9 && b[0] == 1 {
-		sc.SpanID = binary.BigEndian.Uint64(b[1:9])
+		copy(sc.SpanID[:], b[1:9])
 		b = b[9:]
 	}
 	if len(b) >= 2 && b[0] == 2 {
diff --git a/propagation/http_b3_propagator.go b/propagation/http_b3_propagator.go
index 22b7c3c0..9e0f3193 100644
--- a/propagation/http_b3_propagator.go
+++ b/propagation/http_b3_propagator.go
@@ -17,8 +17,6 @@ package propagation
 import (
 	"context"
 	"fmt"
-	"regexp"
-	"strconv"
 	"strings"
 
 	"go.opentelemetry.io/api/trace"
@@ -57,8 +55,6 @@ type HTTPB3Propagator struct {
 
 var _ apipropagation.TextFormatPropagator = HTTPB3Propagator{}
 
-var hexStr16ByteRegex = regexp.MustCompile("^[a-f0-9]{16}$")
-
 func (b3 HTTPB3Propagator) Inject(ctx context.Context, supplier apipropagation.Supplier) {
 	sc := trace.CurrentSpan(ctx).SpanContext()
 	if sc.IsValid() {
@@ -98,12 +94,12 @@ func (b3 HTTPB3Propagator) GetAllKeys() []string {
 }
 
 func (b3 HTTPB3Propagator) extract(supplier apipropagation.Supplier) core.SpanContext {
-	tid, ok := b3.extractTraceID(supplier.Get(B3TraceIDHeader))
-	if !ok {
+	tid, err := core.TraceIDFromHex(supplier.Get(B3TraceIDHeader))
+	if err != nil {
 		return core.EmptySpanContext()
 	}
-	sid, ok := b3.extractSpanID(supplier.Get(B3SpanIDHeader))
-	if !ok {
+	sid, err := core.SpanIDFromHex(supplier.Get(B3SpanIDHeader))
+	if err != nil {
 		return core.EmptySpanContext()
 	}
 	sampled, ok := b3.extractSampledState(supplier.Get(B3SampledHeader))
@@ -148,26 +144,27 @@ func (b3 HTTPB3Propagator) extractSingleHeader(supplier apipropagation.Supplier)
 		return core.EmptySpanContext()
 	}
 
-	var ok bool
-	sc.TraceID, ok = b3.extractTraceID(parts[0])
-	if !ok {
+	var err error
+	sc.TraceID, err = core.TraceIDFromHex(parts[0])
+	if err != nil {
 		return core.EmptySpanContext()
 	}
 
-	sc.SpanID, ok = b3.extractSpanID(parts[1])
-	if !ok {
+	sc.SpanID, err = core.SpanIDFromHex(parts[1])
+	if err != nil {
 		return core.EmptySpanContext()
 	}
 
 	if l > 2 {
+		var ok bool
 		sc.TraceFlags, ok = b3.extractSampledState(parts[2])
 		if !ok {
 			return core.EmptySpanContext()
 		}
 	}
 	if l == 4 {
-		_, ok = b3.extractSpanID(parts[3])
-		if !ok {
+		_, err = core.SpanIDFromHex(parts[3])
+		if err != nil {
 			return core.EmptySpanContext()
 		}
 	}
@@ -179,21 +176,6 @@ func (b3 HTTPB3Propagator) extractSingleHeader(supplier apipropagation.Supplier)
 	return sc
 }
 
-// extractTraceID parses the value of the X-B3-TraceId b3Header.
-func (b3 HTTPB3Propagator) extractTraceID(tid string) (traceID core.TraceID, ok bool) {
-	traceID, err := core.TraceIDFromHex(tid)
-	return traceID, err == nil
-}
-
-// extractSpanID parses the value of the X-B3-SpanId or X-B3-ParentSpanId headers.
-func (b3 HTTPB3Propagator) extractSpanID(sid string) (spanID uint64, ok bool) {
-	if hexStr16ByteRegex.MatchString(sid) {
-		spanID, _ = strconv.ParseUint(sid, 16, 64)
-		ok = true
-	}
-	return spanID, ok
-}
-
 // extractSampledState parses the value of the X-B3-Sampled b3Header.
 func (b3 HTTPB3Propagator) extractSampledState(sampled string) (flag byte, ok bool) {
 	switch sampled {
diff --git a/propagation/http_trace_context_propagator.go b/propagation/http_trace_context_propagator.go
index 8dc62d9f..ee4b4b07 100644
--- a/propagation/http_trace_context_propagator.go
+++ b/propagation/http_trace_context_propagator.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"net/url"
 	"regexp"
-	"strconv"
 	"strings"
 
 	"go.opentelemetry.io/api/core"
@@ -119,7 +118,7 @@ func (hp HTTPTraceContextPropagator) extractSpanContext(
 
 	var sc core.SpanContext
 
-	_, err = hex.Decode(sc.TraceID[0:16], []byte(sections[1][0:32]))
+	sc.TraceID, err = core.TraceIDFromHex(sections[1][:32])
 	if err != nil {
 		return core.EmptySpanContext()
 	}
@@ -127,11 +126,10 @@ func (hp HTTPTraceContextPropagator) extractSpanContext(
 	if len(sections[2]) != 16 {
 		return core.EmptySpanContext()
 	}
-	result, err := strconv.ParseUint(sections[2][0:], 16, 64)
+	sc.SpanID, err = core.SpanIDFromHex(sections[2][:])
 	if err != nil {
 		return core.EmptySpanContext()
 	}
-	sc.SpanID = result
 
 	if len(sections[3]) != 2 {
 		return core.EmptySpanContext()
diff --git a/sdk/export/span.go b/sdk/export/span.go
index 4d2d513d..bc270fed 100644
--- a/sdk/export/span.go
+++ b/sdk/export/span.go
@@ -26,7 +26,7 @@ import (
 // SpanData contains all the information collected by a span.
 type SpanData struct {
 	SpanContext  core.SpanContext
-	ParentSpanID uint64
+	ParentSpanID core.SpanID
 	SpanKind     apitrace.SpanKind
 	Name         string
 	StartTime    time.Time
diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go
index e150f7b9..63f14f17 100644
--- a/sdk/trace/id_generator.go
+++ b/sdk/trace/id_generator.go
@@ -17,7 +17,6 @@ package trace
 import (
 	"math/rand"
 	"sync"
-	"sync/atomic"
 
 	"go.opentelemetry.io/api/core"
 	"go.opentelemetry.io/sdk/trace/internal"
@@ -25,31 +24,18 @@ import (
 
 type defaultIDGenerator struct {
 	sync.Mutex
-
-	// Please keep these as the first fields
-	// so that these 8 byte fields will be aligned on addresses
-	// divisible by 8, on both 32-bit and 64-bit machines when
-	// performing atomic increments and accesses.
-	// See:
-	// * https://github.com/census-instrumentation/opencensus-go/issues/587
-	// * https://github.com/census-instrumentation/opencensus-go/issues/865
-	// * https://golang.org/pkg/sync/atomic/#pkg-note-BUG
-	nextSpanID uint64
-	spanIDInc  uint64
-
-	traceIDAdd  [2]uint64
-	traceIDRand *rand.Rand
+	randSource *rand.Rand
 }
 
 var _ internal.IDGenerator = &defaultIDGenerator{}
 
 // NewSpanID returns a non-zero span ID from a randomly-chosen sequence.
-func (gen *defaultIDGenerator) NewSpanID() uint64 {
-	var id uint64
-	for id == 0 {
-		id = atomic.AddUint64(&gen.nextSpanID, gen.spanIDInc)
-	}
-	return id
+func (gen *defaultIDGenerator) NewSpanID() core.SpanID {
+	gen.Lock()
+	defer gen.Unlock()
+	sid := core.SpanID{}
+	gen.randSource.Read(sid[:])
+	return sid
 }
 
 // NewTraceID returns a non-zero trace ID from a randomly-chosen sequence.
@@ -57,9 +43,7 @@ func (gen *defaultIDGenerator) NewSpanID() uint64 {
 func (gen *defaultIDGenerator) NewTraceID() core.TraceID {
 	gen.Lock()
 	defer gen.Unlock()
-	// Construct the trace ID from two outputs of traceIDRand, with a constant
-	// added to each half for additional entropy.
 	tid := core.TraceID{}
-	gen.traceIDRand.Read(tid[:])
+	gen.randSource.Read(tid[:])
 	return tid
 }
diff --git a/sdk/trace/internal/internal.go b/sdk/trace/internal/internal.go
index 091ff838..9a77d4bb 100644
--- a/sdk/trace/internal/internal.go
+++ b/sdk/trace/internal/internal.go
@@ -20,5 +20,5 @@ import "go.opentelemetry.io/api/core"
 // IDGenerator allows custom generators for TraceId and SpanId.
 type IDGenerator interface {
 	NewTraceID() core.TraceID
-	NewSpanID() uint64
+	NewSpanID() core.SpanID
 }
diff --git a/sdk/trace/sampling.go b/sdk/trace/sampling.go
index e90a46dc..62ff14ae 100644
--- a/sdk/trace/sampling.go
+++ b/sdk/trace/sampling.go
@@ -29,7 +29,7 @@ type Sampler func(SamplingParameters) SamplingDecision
 type SamplingParameters struct {
 	ParentContext   core.SpanContext
 	TraceID         core.TraceID
-	SpanID          uint64
+	SpanID          core.SpanID
 	Name            string
 	HasRemoteParent bool
 }
diff --git a/sdk/trace/span.go b/sdk/trace/span.go
index f7a2b88b..80c6813b 100644
--- a/sdk/trace/span.go
+++ b/sdk/trace/span.go
@@ -178,7 +178,7 @@ func (s *span) SetName(name string) {
 	spanName := s.tracer.spanNameWithPrefix(name)
 	s.data.Name = spanName
 	// SAMPLING
-	noParent := s.data.ParentSpanID == 0
+	noParent := !s.data.ParentSpanID.IsValid()
 	var ctx core.SpanContext
 	if noParent {
 		ctx = core.EmptySpanContext()
diff --git a/sdk/trace/trace.go b/sdk/trace/trace.go
index 0708b248..29b08d37 100644
--- a/sdk/trace/trace.go
+++ b/sdk/trace/trace.go
@@ -24,14 +24,8 @@ import (
 
 func defIDGenerator() internal.IDGenerator {
 	gen := &defaultIDGenerator{}
-	// initialize traceID and spanID generators.
 	var rngSeed int64
-	for _, p := range []interface{}{
-		&rngSeed, &gen.traceIDAdd, &gen.nextSpanID, &gen.spanIDInc,
-	} {
-		_ = binary.Read(crand.Reader, binary.LittleEndian, p)
-	}
-	gen.traceIDRand = rand.New(rand.NewSource(rngSeed))
-	gen.spanIDInc |= 1
+	_ = binary.Read(crand.Reader, binary.LittleEndian, &rngSeed)
+	gen.randSource = rand.New(rand.NewSource(rngSeed))
 	return gen
-}
\ No newline at end of file
+}


Test patch:
diff --git a/api/core/span_context_test.go b/api/core/span_context_test.go
index 3485f560..7cdc3f90 100644
--- a/api/core/span_context_test.go
+++ b/api/core/span_context_test.go
@@ -24,28 +24,28 @@ func TestIsValid(t *testing.T) {
 	for _, testcase := range []struct {
 		name string
 		tid  core.TraceID
-		sid  uint64
+		sid  core.SpanID
 		want bool
 	}{
 		{
 			name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
-			tid:  core.TraceID([16]byte{1}),
-			sid:  uint64(42),
+			tid:  [16]byte{1},
+			sid:  [8]byte{42},
 			want: true,
 		}, {
 			name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
 			tid:  core.TraceID([16]byte{}),
-			sid:  uint64(0),
+			sid:  [8]byte{},
 			want: false,
 		}, {
 			name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
 			tid:  core.TraceID([16]byte{}),
-			sid:  uint64(42),
+			sid:  [8]byte{42},
 			want: false,
 		}, {
 			name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
 			tid:  core.TraceID([16]byte{1}),
-			sid:  uint64(0),
+			sid:  [8]byte{},
 			want: false,
 		},
 	} {
@@ -141,7 +141,7 @@ func TestHasSpanID(t *testing.T) {
 	}{
 		{
 			name: "SpanContext.HasSpanID() returns true if self.SpanID != 0",
-			sc:   core.SpanContext{SpanID: uint64(42)},
+			sc:   core.SpanContext{SpanID: [8]byte{42}},
 			want: true,
 		}, {
 			name: "SpanContext.HasSpanID() returns false if self.SpanID == 0",
@@ -167,8 +167,8 @@ func TestSpanIDString(t *testing.T) {
 	}{
 		{
 			name: "SpanContext.SpanIDString returns string representation of self.TraceID values > 0",
-			sc:   core.SpanContext{SpanID: uint64(42)},
-			want: `000000000000002a`,
+			sc:   core.SpanContext{SpanID: [8]byte{42}},
+			want: `2a00000000000000`,
 		}, {
 			name: "SpanContext.SpanIDString returns string representation of self.TraceID values == 0",
 			sc:   core.SpanContext{},
diff --git a/experimental/bridge/opentracing/mix_test.go b/experimental/bridge/opentracing/mix_test.go
index 3f2b32d4..ba354ac9 100644
--- a/experimental/bridge/opentracing/mix_test.go
+++ b/experimental/bridge/opentracing/mix_test.go
@@ -132,7 +132,7 @@ func TestMixedAPIs(t *testing.T) {
 
 type simpleTest struct {
 	traceID otelcore.TraceID
-	spanIDs []uint64
+	spanIDs []otelcore.SpanID
 }
 
 func newSimpleTest() *simpleTest {
@@ -166,10 +166,10 @@ func (st *simpleTest) noop(t *testing.T, ctx context.Context) {
 
 type currentActiveSpanTest struct {
 	traceID otelcore.TraceID
-	spanIDs []uint64
+	spanIDs []otelcore.SpanID
 
-	recordedCurrentOtelSpanIDs []uint64
-	recordedActiveOTSpanIDs    []uint64
+	recordedCurrentOtelSpanIDs []otelcore.SpanID
+	recordedActiveOTSpanIDs    []otelcore.SpanID
 }
 
 func newCurrentActiveSpanTest() *currentActiveSpanTest {
@@ -220,7 +220,7 @@ func (cast *currentActiveSpanTest) recordSpans(t *testing.T, ctx context.Context
 	spanID := oteltrace.CurrentSpan(ctx).SpanContext().SpanID
 	cast.recordedCurrentOtelSpanIDs = append(cast.recordedCurrentOtelSpanIDs, spanID)
 
-	spanID = 0
+	spanID = otelcore.SpanID{}
 	if bridgeSpan, ok := ot.SpanFromContext(ctx).(*bridgeSpan); ok {
 		spanID = bridgeSpan.otelSpan.SpanContext().SpanID
 	}
@@ -466,23 +466,23 @@ func (tm *tracerMessTest) recordTracers(t *testing.T, ctx context.Context) {
 
 // helpers
 
-func checkTraceAndSpans(t *testing.T, tracer *internal.MockTracer, expectedTraceID otelcore.TraceID, expectedSpanIDs []uint64) {
+func checkTraceAndSpans(t *testing.T, tracer *internal.MockTracer, expectedTraceID otelcore.TraceID, expectedSpanIDs []otelcore.SpanID) {
 	expectedSpanCount := len(expectedSpanIDs)
 
 	// reverse spanIDs, since first span ID belongs to root, that
 	// finishes last
-	spanIDs := make([]uint64, len(expectedSpanIDs))
+	spanIDs := make([]otelcore.SpanID, len(expectedSpanIDs))
 	copy(spanIDs, expectedSpanIDs)
 	reverse(len(spanIDs), func(i, j int) {
 		spanIDs[i], spanIDs[j] = spanIDs[j], spanIDs[i]
 	})
 	// the last finished span has no parent
-	parentSpanIDs := append(spanIDs[1:], 0)
+	parentSpanIDs := append(spanIDs[1:], otelcore.SpanID{})
 
-	sks := map[uint64]oteltrace.SpanKind{
-		3456: oteltrace.SpanKindProducer,
-		2345: oteltrace.SpanKindInternal,
-		1234: oteltrace.SpanKindClient,
+	sks := map[otelcore.SpanID]oteltrace.SpanKind{
+		{125}: oteltrace.SpanKindProducer,
+		{124}: oteltrace.SpanKindInternal,
+		{123}: oteltrace.SpanKindClient,
 	}
 
 	if len(tracer.FinishedSpans) != expectedSpanCount {
@@ -517,23 +517,16 @@ func simpleTraceID() otelcore.TraceID {
 	return [16]byte{123, 42}
 }
 
-func simpleSpanIDs(count int) []uint64 {
-	base := []uint64{
-		1234,
-		2345,
-		3456,
-		4567,
-		5678,
-		6789,
+func simpleSpanIDs(count int) []otelcore.SpanID {
+	base := []otelcore.SpanID{
+		{123},
+		{124},
+		{125},
+		{126},
+		{127},
+		{128},
 	}
-	if count <= len(base) {
-		return base[:count]
-	}
-	count -= len(base)
-	for i := 0; i < count; i++ {
-		base = append(base, base[i]*10)
-	}
-	return base
+	return base[:count]
 }
 
 func min(a, b int) int {
diff --git a/exporter/trace/jaeger/jaeger_test.go b/exporter/trace/jaeger/jaeger_test.go
index de2670d4..87a3a0fe 100644
--- a/exporter/trace/jaeger/jaeger_test.go
+++ b/exporter/trace/jaeger/jaeger_test.go
@@ -37,10 +37,10 @@ import (
 func Test_spanDataToThrift(t *testing.T) {
 	now := time.Now()
 	traceID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
-	spanID := uint64(0x0102030405060708)
+	spanID, _ := core.SpanIDFromHex("0102030405060708")
 
 	linkTraceID, _ := core.TraceIDFromHex("0102030405060709090a0b0c0d0e0f11")
-	linkSpanID := uint64(0x0102030405060709)
+	linkSpanID, _ := core.SpanIDFromHex("0102030405060709")
 
 	keyValue := "value"
 	statusCodeValue := int64(2)
@@ -102,7 +102,7 @@ func Test_spanDataToThrift(t *testing.T) {
 						RefType:     gen.SpanRefType_CHILD_OF,
 						TraceIdHigh: int64(binary.BigEndian.Uint64(linkTraceID[0:8])),
 						TraceIdLow:  int64(binary.BigEndian.Uint64(linkTraceID[8:16])),
-						SpanId:      int64(linkSpanID),
+						SpanId:      int64(binary.BigEndian.Uint64(linkSpanID[:])),
 					},
 				},
 				// TODO [rghetia]: check Logs when event is added.
diff --git a/exporter/trace/stdout/stdout_test.go b/exporter/trace/stdout/stdout_test.go
index 10101834..4585bdf7 100644
--- a/exporter/trace/stdout/stdout_test.go
+++ b/exporter/trace/stdout/stdout_test.go
@@ -41,7 +41,7 @@ func TestExporter_ExportSpan(t *testing.T) {
 	// setup test span
 	now := time.Now()
 	traceID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
-	spanID := uint64(0x0102030405060708)
+	spanID, _ := core.SpanIDFromHex("0102030405060708")
 	keyValue := "value"
 	doubleValue := float64(123.456)
 
@@ -74,7 +74,7 @@ func TestExporter_ExportSpan(t *testing.T) {
 	expectedOutput := `{"SpanContext":{` +
 		`"TraceID":"0102030405060708090a0b0c0d0e0f10",` +
 		`"SpanID":"0102030405060708","TraceFlags":0},` +
-		`"ParentSpanID":0,` +
+		`"ParentSpanID":"0000000000000000",` +
 		`"SpanKind":"internal",` +
 		`"Name":"/foo",` +
 		`"StartTime":` + string(expectedSerializedNow) + "," +
diff --git a/propagation/binary_propagator_test.go b/propagation/binary_propagator_test.go
index dcf3bb45..e040d127 100644
--- a/propagation/binary_propagator_test.go
+++ b/propagation/binary_propagator_test.go
@@ -15,7 +15,6 @@
 package propagation_test
 
 import (
-	"encoding/hex"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -25,11 +24,9 @@ import (
 )
 
 func TestExtractSpanContextFromBytes(t *testing.T) {
-	bTraceID, _ := hex.DecodeString("4bf92f3577b34da6a3ce929d0e0e4736")
-	traceID := core.TraceID{}
-	copy(traceID[:], bTraceID)
+	traceID, _ := core.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
+	spanID, _ := core.SpanIDFromHex("00f067aa0ba902b7")
 
-	spanID := uint64(0x00f067aa0ba902b7)
 	propagator := propagation.BinaryPropagator()
 	tests := []struct {
 		name   string
@@ -123,11 +120,9 @@ func TestExtractSpanContextFromBytes(t *testing.T) {
 }
 
 func TestConvertSpanContextToBytes(t *testing.T) {
-	bTraceID, _ := hex.DecodeString("4bf92f3577b34da6a3ce929d0e0e4736")
-	traceID := core.TraceID{}
-	copy(traceID[:], bTraceID)
+	traceID, _ := core.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
+	spanID, _ := core.SpanIDFromHex("00f067aa0ba902b7")
 
-	spanID := uint64(0x00f067aa0ba902b7)
 	propagator := propagation.BinaryPropagator()
 	tests := []struct {
 		name  string
diff --git a/propagation/http_trace_context_propagator_benchmark_test.go b/propagation/http_trace_context_propagator_benchmark_test.go
index 06144bc3..063dfb8a 100644
--- a/propagation/http_trace_context_propagator_benchmark_test.go
+++ b/propagation/http_trace_context_propagator_benchmark_test.go
@@ -2,7 +2,6 @@ package propagation
 
 import (
 	"context"
-	"encoding/hex"
 	"net/http"
 	"testing"
 
@@ -25,13 +24,9 @@ func BenchmarkInject(b *testing.B) {
 
 func injectSubBenchmarks(b *testing.B, fn func(context.Context, *testing.B)) {
 	b.Run("SampledSpanContext", func(b *testing.B) {
-		var (
-			id      uint64
-			spanID  = uint64(0x00f067aa0ba902b7)
-			traceID = core.TraceID{}
-		)
-		bt, _ := hex.DecodeString("4bf92f3577b34da6a3ce929d0e0e4736")
-		copy(traceID[:], bt)
+		var id uint64
+		spanID, _ := core.SpanIDFromHex("00f067aa0ba902b7")
+		traceID, _ := core.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
 
 		mockTracer := &mocktrace.MockTracer{
 			Sampled:     false,
diff --git a/propagation/http_trace_context_propagator_test.go b/propagation/http_trace_context_propagator_test.go
index 898a167a..5567270e 100644
--- a/propagation/http_trace_context_propagator_test.go
+++ b/propagation/http_trace_context_propagator_test.go
@@ -32,7 +32,7 @@ import (
 
 var (
 	traceID = mustTraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
-	spanID  = uint64(0x00f067aa0ba902b7)
+	spanID  = mustSpanIDFromHex("00f067aa0ba902b7")
 )
 
 func mustTraceIDFromHex(s string) (t core.TraceID) {
@@ -40,6 +40,11 @@ func mustTraceIDFromHex(s string) (t core.TraceID) {
 	return
 }
 
+func mustSpanIDFromHex(s string) (t core.SpanID) {
+	t, _ = core.SpanIDFromHex(s)
+	return
+}
+
 func TestExtractValidTraceContextFromHTTPReq(t *testing.T) {
 	var propagator propagation.HTTPTraceContextPropagator
 	tests := []struct {
diff --git a/sdk/trace/batch_span_processor_test.go b/sdk/trace/batch_span_processor_test.go
index cdef3c2e..46b98799 100644
--- a/sdk/trace/batch_span_processor_test.go
+++ b/sdk/trace/batch_span_processor_test.go
@@ -194,7 +194,7 @@ func generateSpan(t *testing.T, tr apitrace.Tracer, option testOption) {
 
 func getSpanContext() core.SpanContext {
 	tid, _ := core.TraceIDFromHex("01020304050607080102040810203040")
-	sid := uint64(0x0102040810203040)
+	sid, _ := core.SpanIDFromHex("0102040810203040")
 	return core.SpanContext{
 		TraceID:    tid,
 		SpanID:     sid,
diff --git a/sdk/trace/benchmark_test.go b/sdk/trace/benchmark_test.go
index 543ac84a..21194e07 100644
--- a/sdk/trace/benchmark_test.go
+++ b/sdk/trace/benchmark_test.go
@@ -161,8 +161,8 @@ func BenchmarkTraceID_DotString(b *testing.B) {
 
 func BenchmarkSpanID_DotString(b *testing.B) {
 	traceBenchmark(b, func(b *testing.B) {
-		sc := core.SpanContext{SpanID: 1}
-		want := "0000000000000001"
+		sc := core.SpanContext{SpanID: core.SpanID{1}}
+		want := "1000000000000000"
 		for i := 0; i < b.N; i++ {
 			if got := sc.SpanIDString(); got != want {
 				b.Fatalf("got = %q want = %q", got, want)
@@ -188,4 +188,4 @@ func getTracer(b *testing.B, name string) apitrace.Tracer {
 		b.Fatalf("Failed to create trace provider for test %s\n", name)
 	}
 	return tp.GetTracer(name)
-}
\ No newline at end of file
+}
diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go
index 6d8a1b84..d67913ba 100644
--- a/sdk/trace/simple_span_processor_test.go
+++ b/sdk/trace/simple_span_processor_test.go
@@ -59,7 +59,7 @@ func TestSimpleSpanProcessorOnEnd(t *testing.T) {
 	tp.RegisterSpanProcessor(ssp)
 	tr := tp.GetTracer("SimpleSpanProcessor")
 	tid, _ := core.TraceIDFromHex("01020304050607080102040810203040")
-	sid := uint64(0x0102040810203040)
+	sid, _ := core.SpanIDFromHex("0102040810203040")
 	sc := core.SpanContext{
 		TraceID:    tid,
 		SpanID:     sid,
diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go
index e30e6421..a59423e5 100644
--- a/sdk/trace/trace_test.go
+++ b/sdk/trace/trace_test.go
@@ -35,11 +35,12 @@ import (
 
 var (
 	tid core.TraceID
-	sid = uint64(0x0102040810203040)
+	sid core.SpanID
 )
 
 func init() {
 	tid, _ = core.TraceIDFromHex("01020304050607080102040810203040")
+	sid, _ = core.SpanIDFromHex("0102040810203040")
 }
 
 func TestTracerFollowsExpectedAPIBehaviour(t *testing.T) {
@@ -394,8 +395,8 @@ func TestAddLinks(t *testing.T) {
 	k1v1 := key.New("key1").String("value1")
 	k2v2 := key.New("key2").String("value2")
 
-	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
-	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
+	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
 
 	link1 := apitrace.Link{SpanContext: sc1, Attributes: []core.KeyValue{k1v1}}
 	link2 := apitrace.Link{SpanContext: sc2, Attributes: []core.KeyValue{k2v2}}
@@ -435,8 +436,8 @@ func TestLinks(t *testing.T) {
 	k2v2 := key.New("key2").String("value2")
 	k3v3 := key.New("key3").String("value3")
 
-	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
-	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
+	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
 
 	span.Link(sc1, key.New("key1").String("value1"))
 	span.Link(sc2,
@@ -471,9 +472,9 @@ func TestLinksOverLimit(t *testing.T) {
 	te := &testExporter{}
 	cfg := Config{MaxLinksPerSpan: 2}
 
-	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
-	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
-	sc3 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: 0x3}
+	sc1 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
+	sc2 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
+	sc3 := core.SpanContext{TraceID: core.TraceID([16]byte{1, 1}), SpanID: core.SpanID{3}}
 
 	tp, _ := NewProvider(WithConfig(cfg), WithSyncer(te))
 	span := startSpan(tp, "LinksOverLimit")
@@ -632,10 +633,10 @@ func endSpan(te *testExporter, span apitrace.Span) (*export.SpanData, error) {
 		return nil, fmt.Errorf("got exported spans %#v, want one span", te.spans)
 	}
 	got := te.spans[0]
-	if got.SpanContext.SpanID == 0 {
+	if !got.SpanContext.SpanID.IsValid() {
 		return nil, fmt.Errorf("exporting span: expected nonzero SpanID")
 	}
-	got.SpanContext.SpanID = 0
+	got.SpanContext.SpanID = core.SpanID{}
 	if !checkTime(&got.StartTime) {
 		return nil, fmt.Errorf("exporting span: expected nonzero StartTime")
 	}
@@ -755,6 +756,7 @@ func TestExecutionTracerTaskEnd(t *testing.T) {
 	spans = append(spans, s) // never sample
 
 	tID, _ := core.TraceIDFromHex("0102030405060708090a0b0c0d0e0f")
+	sID, _ := core.SpanIDFromHex("0001020304050607")
 
 	_, apiSpan = tr.Start(
 		context.Background(),
@@ -762,7 +764,7 @@ func TestExecutionTracerTaskEnd(t *testing.T) {
 		apitrace.ChildOf(
 			core.SpanContext{
 				TraceID:    tID,
-				SpanID:     uint64(0x0001020304050607),
+				SpanID:     sID,
 				TraceFlags: 0,
 			},
 		),