Repository: open-telemetry/opentelemetry-go
Pull request #805 (2020-06-09 19:35:19)

Support Instrumentation Library Info in Trace Export Pipeline
- Add instrumentation library info to the SDK's `SpanData` export type.
- Update the SDK to pass the instrumentation library from the `Tracer` to the exported `SpanData`.
- Update the OTLP exporter and tests to support exporting instrumentation library information in the exported `ResourceSpans`.
- Update the Jaeger exporter to include the instrumentation library information as span attributes.

Part of #630

jmacd: I guess this will break if https://github.com/open-telemetry/opentelemetry-proto/pull/150 merges.
MrAlias: > I guess this will break if [open-telemetry/opentelemetry-proto#150](https://github.com/open-telemetry/opentelemetry-proto/pull/150) merges.

True story, at least for the OTLP exporter. :shrug: 

Patch:
diff --git a/exporters/otlp/internal/transform/instrumentation.go b/exporters/otlp/internal/transform/instrumentation.go
new file mode 100644
index 00000000..28bb4c4d
--- /dev/null
+++ b/exporters/otlp/internal/transform/instrumentation.go
@@ -0,0 +1,31 @@
+// 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 transform
+
+import (
+	commonpb "github.com/open-telemetry/opentelemetry-proto/gen/go/common/v1"
+
+	"go.opentelemetry.io/otel/sdk/instrumentation"
+)
+
+func instrumentationLibrary(il instrumentation.Library) *commonpb.InstrumentationLibrary {
+	if il == (instrumentation.Library{}) {
+		return nil
+	}
+	return &commonpb.InstrumentationLibrary{
+		Name:    il.Name,
+		Version: il.Version,
+	}
+}
diff --git a/exporters/otlp/internal/transform/span.go b/exporters/otlp/internal/transform/span.go
index 5a58b835..4221a53c 100644
--- a/exporters/otlp/internal/transform/span.go
+++ b/exporters/otlp/internal/transform/span.go
@@ -22,6 +22,7 @@ import (
 	"go.opentelemetry.io/otel/api/label"
 	apitrace "go.opentelemetry.io/otel/api/trace"
 	export "go.opentelemetry.io/otel/sdk/export/trace"
+	"go.opentelemetry.io/otel/sdk/instrumentation"
 )
 
 const (
@@ -33,30 +34,61 @@ func SpanData(sdl []*export.SpanData) []*tracepb.ResourceSpans {
 	if len(sdl) == 0 {
 		return nil
 	}
-	// Group by the distinct representation of the Resource.
+
 	rsm := make(map[label.Distinct]*tracepb.ResourceSpans)
 
+	type ilsKey struct {
+		r  label.Distinct
+		il instrumentation.Library
+	}
+	ilsm := make(map[ilsKey]*tracepb.InstrumentationLibrarySpans)
+
+	var resources int
 	for _, sd := range sdl {
-		if sd != nil {
-			key := sd.Resource.Equivalent()
-
-			rs, ok := rsm[key]
-			if !ok {
-				rs = &tracepb.ResourceSpans{
-					Resource: Resource(sd.Resource),
-					InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{
-						{
-							Spans: []*tracepb.Span{},
-						},
-					},
-				}
-				rsm[key] = rs
+		if sd == nil {
+			continue
+		}
+
+		rKey := sd.Resource.Equivalent()
+		iKey := ilsKey{
+			r:  rKey,
+			il: sd.InstrumentationLibrary,
+		}
+		ils, iOk := ilsm[iKey]
+		if !iOk {
+			// Either the resource or instrumentation library were unknown.
+			ils = &tracepb.InstrumentationLibrarySpans{
+				InstrumentationLibrary: instrumentationLibrary(sd.InstrumentationLibrary),
+				Spans:                  []*tracepb.Span{},
 			}
-			rs.InstrumentationLibrarySpans[0].Spans =
-				append(rs.InstrumentationLibrarySpans[0].Spans, span(sd))
+		}
+		ils.Spans = append(ils.Spans, span(sd))
+		ilsm[iKey] = ils
+
+		rs, rOk := rsm[rKey]
+		if !rOk {
+			resources++
+			// The resource was unknown.
+			rs = &tracepb.ResourceSpans{
+				Resource:                    Resource(sd.Resource),
+				InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{ils},
+			}
+			rsm[rKey] = rs
+			continue
+		}
+
+		// The resource has been seen before. Check if the instrumentation
+		// library lookup was unknown because if so we need to add it to the
+		// ResourceSpans. Otherwise, the instrumentation library has already
+		// been seen and the append we did above will be included it in the
+		// InstrumentationLibrarySpans reference.
+		if !iOk {
+			rs.InstrumentationLibrarySpans = append(rs.InstrumentationLibrarySpans, ils)
 		}
 	}
-	rss := make([]*tracepb.ResourceSpans, 0, len(rsm))
+
+	// Transform the categorized map into a slice
+	rss := make([]*tracepb.ResourceSpans, 0, resources)
 	for _, rs := range rsm {
 		rss = append(rss, rs)
 	}
diff --git a/exporters/trace/jaeger/jaeger.go b/exporters/trace/jaeger/jaeger.go
index 0ad93897..7d27165d 100644
--- a/exporters/trace/jaeger/jaeger.go
+++ b/exporters/trace/jaeger/jaeger.go
@@ -196,6 +196,13 @@ func spanDataToThrift(data *export.SpanData) *gen.Span {
 		}
 	}
 
+	if il := data.InstrumentationLibrary; il.Name != "" {
+		tags = append(tags, getStringTag("instrumentation.name", il.Name))
+		if il.Version != "" {
+			tags = append(tags, getStringTag("instrumentation.version", il.Name))
+		}
+	}
+
 	tags = append(tags,
 		getInt64Tag("status.code", int64(data.StatusCode)),
 		getStringTag("status.message", data.StatusMessage),
diff --git a/sdk/export/trace/trace.go b/sdk/export/trace/trace.go
index b608c532..144da751 100644
--- a/sdk/export/trace/trace.go
+++ b/sdk/export/trace/trace.go
@@ -22,6 +22,7 @@ import (
 
 	"go.opentelemetry.io/otel/api/kv"
 	apitrace "go.opentelemetry.io/otel/api/trace"
+	"go.opentelemetry.io/otel/sdk/instrumentation"
 	"go.opentelemetry.io/otel/sdk/resource"
 )
 
@@ -71,6 +72,10 @@ type SpanData struct {
 
 	// Resource contains attributes representing an entity that produced this span.
 	Resource *resource.Resource
+
+	// InstrumentationLibrary defines the instrumentation library used to
+	// providing instrumentation.
+	InstrumentationLibrary instrumentation.Library
 }
 
 // Event is used to describe an Event with a message string and set of
diff --git a/sdk/trace/span.go b/sdk/trace/span.go
index 88f874b7..9a18f12d 100644
--- a/sdk/trace/span.go
+++ b/sdk/trace/span.go
@@ -342,12 +342,13 @@ func startSpanInternal(tr *tracer, name string, parent apitrace.SpanContext, rem
 		startTime = time.Now()
 	}
 	span.data = &export.SpanData{
-		SpanContext:     span.spanContext,
-		StartTime:       startTime,
-		SpanKind:        apitrace.ValidateSpanKind(o.SpanKind),
-		Name:            name,
-		HasRemoteParent: remoteParent,
-		Resource:        cfg.Resource,
+		SpanContext:            span.spanContext,
+		StartTime:              startTime,
+		SpanKind:               apitrace.ValidateSpanKind(o.SpanKind),
+		Name:                   name,
+		HasRemoteParent:        remoteParent,
+		Resource:               cfg.Resource,
+		InstrumentationLibrary: tr.instrumentationLibrary,
 	}
 	span.attributes = newAttributesMap(cfg.MaxAttributesPerSpan)
 	span.messageEvents = newEvictedQueue(cfg.MaxEventsPerSpan)


Test patch:
diff --git a/exporters/otlp/internal/transform/span_test.go b/exporters/otlp/internal/transform/span_test.go
index 1fb6a987..4f7f75ef 100644
--- a/exporters/otlp/internal/transform/span_test.go
+++ b/exporters/otlp/internal/transform/span_test.go
@@ -23,11 +23,13 @@ import (
 	"github.com/google/go-cmp/cmp"
 	tracepb "github.com/open-telemetry/opentelemetry-proto/gen/go/trace/v1"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	"google.golang.org/grpc/codes"
 
 	"go.opentelemetry.io/otel/api/kv"
 	apitrace "go.opentelemetry.io/otel/api/trace"
 	export "go.opentelemetry.io/otel/sdk/export/trace"
+	"go.opentelemetry.io/otel/sdk/instrumentation"
 	"go.opentelemetry.io/otel/sdk/resource"
 )
 
@@ -322,6 +324,10 @@ func TestSpanData(t *testing.T) {
 		DroppedMessageEventCount: 2,
 		DroppedLinkCount:         3,
 		Resource:                 resource.New(kv.String("rk1", "rv1"), kv.Int64("rk2", 5)),
+		InstrumentationLibrary: instrumentation.Library{
+			Name:    "go.opentelemetry.io/test/otel",
+			Version: "v0.0.1",
+		},
 	}
 
 	// Not checking resource as the underlying map of our Resource makes
@@ -345,16 +351,14 @@ func TestSpanData(t *testing.T) {
 	}
 
 	got := SpanData([]*export.SpanData{spanData})
-	if !assert.Len(t, got, 1) {
-		return
-	}
+	require.Len(t, got, 1)
 
-	// Break the span down as large diffs can be hard to read.
-	actualSpans := got[0].GetInstrumentationLibrarySpans()
-	if !assert.Len(t, actualSpans, 1) && !assert.Len(t, actualSpans[0].Spans, 1) {
-		return
-	}
-	actualSpan := actualSpans[0].Spans[0]
+	assert.Equal(t, got[0].GetResource(), Resource(spanData.Resource))
+	ilSpans := got[0].GetInstrumentationLibrarySpans()
+	require.Len(t, ilSpans, 1)
+	assert.Equal(t, ilSpans[0].GetInstrumentationLibrary(), instrumentationLibrary(spanData.InstrumentationLibrary))
+	require.Len(t, ilSpans[0].Spans, 1)
+	actualSpan := ilSpans[0].Spans[0]
 
 	if diff := cmp.Diff(expectedSpan, actualSpan, cmp.Comparer(proto.Equal)); diff != "" {
 		t.Fatalf("transformed span differs %v\n", diff)
@@ -363,7 +367,9 @@ func TestSpanData(t *testing.T) {
 
 // Empty parent span ID should be treated as root span.
 func TestRootSpanData(t *testing.T) {
-	rs := SpanData([]*export.SpanData{{}})[0]
+	sd := SpanData([]*export.SpanData{{}})
+	require.Len(t, sd, 1)
+	rs := sd[0]
 	got := rs.GetInstrumentationLibrarySpans()[0].GetSpans()[0].GetParentSpanId()
 
 	// Empty means root span.
diff --git a/exporters/otlp/otlp_span_test.go b/exporters/otlp/otlp_span_test.go
index 1e63d21d..f2715641 100644
--- a/exporters/otlp/otlp_span_test.go
+++ b/exporters/otlp/otlp_span_test.go
@@ -30,6 +30,7 @@ import (
 	"go.opentelemetry.io/otel/api/kv"
 	apitrace "go.opentelemetry.io/otel/api/trace"
 	tracesdk "go.opentelemetry.io/otel/sdk/export/trace"
+	"go.opentelemetry.io/otel/sdk/instrumentation"
 	"go.opentelemetry.io/otel/sdk/resource"
 )
 
@@ -96,6 +97,32 @@ func TestExportSpans(t *testing.T) {
 					StatusCode:    codes.OK,
 					StatusMessage: "Ok",
 					Resource:      resource.New(kv.String("instance", "tester-a")),
+					InstrumentationLibrary: instrumentation.Library{
+						Name:    "lib-a",
+						Version: "v0.1.0",
+					},
+				},
+				{
+					SpanContext: apitrace.SpanContext{
+						TraceID:    apitrace.ID([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}),
+						SpanID:     apitrace.SpanID([8]byte{0, 0, 0, 0, 0, 0, 0, 1}),
+						TraceFlags: byte(1),
+					},
+					SpanKind:  apitrace.SpanKindServer,
+					Name:      "secondary parent process",
+					StartTime: startTime,
+					EndTime:   endTime,
+					Attributes: []kv.KeyValue{
+						kv.String("user", "alice"),
+						kv.Bool("authenticated", true),
+					},
+					StatusCode:    codes.OK,
+					StatusMessage: "Ok",
+					Resource:      resource.New(kv.String("instance", "tester-a")),
+					InstrumentationLibrary: instrumentation.Library{
+						Name:    "lib-b",
+						Version: "v0.1.0",
+					},
 				},
 				{
 					SpanContext: apitrace.SpanContext{
@@ -115,6 +142,10 @@ func TestExportSpans(t *testing.T) {
 					StatusCode:    codes.OK,
 					StatusMessage: "Ok",
 					Resource:      resource.New(kv.String("instance", "tester-a")),
+					InstrumentationLibrary: instrumentation.Library{
+						Name:    "lib-a",
+						Version: "v0.1.0",
+					},
 				},
 				{
 					SpanContext: apitrace.SpanContext{
@@ -133,6 +164,10 @@ func TestExportSpans(t *testing.T) {
 					StatusCode:    codes.Unauthenticated,
 					StatusMessage: "Unauthenticated",
 					Resource:      resource.New(kv.String("instance", "tester-b")),
+					InstrumentationLibrary: instrumentation.Library{
+						Name:    "lib-a",
+						Version: "v1.1.0",
+					},
 				},
 			},
 			[]tracepb.ResourceSpans{
@@ -148,6 +183,10 @@ func TestExportSpans(t *testing.T) {
 					},
 					InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{
 						{
+							InstrumentationLibrary: &commonpb.InstrumentationLibrary{
+								Name:    "lib-a",
+								Version: "v0.1.0",
+							},
 							Spans: []*tracepb.Span{
 								{
 									TraceId:           []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
@@ -200,6 +239,38 @@ func TestExportSpans(t *testing.T) {
 								},
 							},
 						},
+						{
+							InstrumentationLibrary: &commonpb.InstrumentationLibrary{
+								Name:    "lib-b",
+								Version: "v0.1.0",
+							},
+							Spans: []*tracepb.Span{
+								{
+									TraceId:           []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
+									SpanId:            []byte{0, 0, 0, 0, 0, 0, 0, 1},
+									Name:              "secondary parent process",
+									Kind:              tracepb.Span_SERVER,
+									StartTimeUnixNano: uint64(startTime.UnixNano()),
+									EndTimeUnixNano:   uint64(endTime.UnixNano()),
+									Attributes: []*commonpb.AttributeKeyValue{
+										{
+											Key:         "user",
+											Type:        commonpb.AttributeKeyValue_STRING,
+											StringValue: "alice",
+										},
+										{
+											Key:       "authenticated",
+											Type:      commonpb.AttributeKeyValue_BOOL,
+											BoolValue: true,
+										},
+									},
+									Status: &tracepb.Status{
+										Code:    tracepb.Status_Ok,
+										Message: "Ok",
+									},
+								},
+							},
+						},
 					},
 				},
 				{
@@ -214,6 +285,10 @@ func TestExportSpans(t *testing.T) {
 					},
 					InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{
 						{
+							InstrumentationLibrary: &commonpb.InstrumentationLibrary{
+								Name:    "lib-a",
+								Version: "v1.1.0",
+							},
 							Spans: []*tracepb.Span{
 								{
 									TraceId:           []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
diff --git a/exporters/trace/stdout/stdout_test.go b/exporters/trace/stdout/stdout_test.go
index 91d5c74d..3531bc2c 100644
--- a/exporters/trace/stdout/stdout_test.go
+++ b/exporters/trace/stdout/stdout_test.go
@@ -122,7 +122,11 @@ func TestExporter_ExportSpan(t *testing.T) {
 		`{` +
 		`"Key":"rk1",` +
 		`"Value":{"Type":"STRING","Value":"rv11"}` +
-		`}]}` + "\n"
+		`}],` +
+		`"InstrumentationLibrary":{` +
+		`"Name":"",` +
+		`"Version":""` +
+		`}}` + "\n"
 
 	if got != expectedOutput {
 		t.Errorf("Want: %v but got: %v", expectedOutput, got)
diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go
index 9efa0f6a..86a8398b 100644
--- a/sdk/trace/trace_test.go
+++ b/sdk/trace/trace_test.go
@@ -36,6 +36,7 @@ import (
 	apitrace "go.opentelemetry.io/otel/api/trace"
 	ottest "go.opentelemetry.io/otel/internal/testing"
 	export "go.opentelemetry.io/otel/sdk/export/trace"
+	"go.opentelemetry.io/otel/sdk/instrumentation"
 	"go.opentelemetry.io/otel/sdk/resource"
 )
 
@@ -301,8 +302,9 @@ func TestSetSpanAttributesOnStart(t *testing.T) {
 			kv.String("key1", "value1"),
 			kv.String("key2", "value2"),
 		},
-		SpanKind:        apitrace.SpanKindInternal,
-		HasRemoteParent: true,
+		SpanKind:               apitrace.SpanKindInternal,
+		HasRemoteParent:        true,
+		InstrumentationLibrary: instrumentation.Library{Name: "StartSpanAttribute"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("SetSpanAttributesOnStart: -got +want %s", diff)
@@ -329,8 +331,9 @@ func TestSetSpanAttributes(t *testing.T) {
 		Attributes: []kv.KeyValue{
 			kv.String("key1", "value1"),
 		},
-		SpanKind:        apitrace.SpanKindInternal,
-		HasRemoteParent: true,
+		SpanKind:               apitrace.SpanKindInternal,
+		HasRemoteParent:        true,
+		InstrumentationLibrary: instrumentation.Library{Name: "SpanAttribute"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("SetSpanAttributes: -got +want %s", diff)
@@ -365,9 +368,10 @@ func TestSetSpanAttributesOverLimit(t *testing.T) {
 			kv.Bool("key1", false),
 			kv.Int64("key4", 4),
 		},
-		SpanKind:              apitrace.SpanKindInternal,
-		HasRemoteParent:       true,
-		DroppedAttributeCount: 1,
+		SpanKind:               apitrace.SpanKindInternal,
+		HasRemoteParent:        true,
+		DroppedAttributeCount:  1,
+		InstrumentationLibrary: instrumentation.Library{Name: "SpanAttributesOverLimit"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("SetSpanAttributesOverLimit: -got +want %s", diff)
@@ -411,7 +415,8 @@ func TestEvents(t *testing.T) {
 			{Name: "foo", Attributes: []kv.KeyValue{k1v1}},
 			{Name: "bar", Attributes: []kv.KeyValue{k2v2, k3v3}},
 		},
-		SpanKind: apitrace.SpanKindInternal,
+		SpanKind:               apitrace.SpanKindInternal,
+		InstrumentationLibrary: instrumentation.Library{Name: "Events"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("Message Events: -got +want %s", diff)
@@ -463,6 +468,7 @@ func TestEventsOverLimit(t *testing.T) {
 		DroppedMessageEventCount: 2,
 		HasRemoteParent:          true,
 		SpanKind:                 apitrace.SpanKindInternal,
+		InstrumentationLibrary:   instrumentation.Library{Name: "EventsOverLimit"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("Message Event over limit: -got +want %s", diff)
@@ -505,7 +511,8 @@ func TestLinks(t *testing.T) {
 			{SpanContext: sc1, Attributes: []kv.KeyValue{k1v1}},
 			{SpanContext: sc2, Attributes: []kv.KeyValue{k2v2, k3v3}},
 		},
-		SpanKind: apitrace.SpanKindInternal,
+		SpanKind:               apitrace.SpanKindInternal,
+		InstrumentationLibrary: instrumentation.Library{Name: "Links"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("Link: -got +want %s", diff)
@@ -547,9 +554,10 @@ func TestLinksOverLimit(t *testing.T) {
 			{SpanContext: sc2, Attributes: []kv.KeyValue{k2v2}},
 			{SpanContext: sc3, Attributes: []kv.KeyValue{k3v3}},
 		},
-		DroppedLinkCount: 1,
-		HasRemoteParent:  true,
-		SpanKind:         apitrace.SpanKindInternal,
+		DroppedLinkCount:       1,
+		HasRemoteParent:        true,
+		SpanKind:               apitrace.SpanKindInternal,
+		InstrumentationLibrary: instrumentation.Library{Name: "LinksOverLimit"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("Link over limit: -got +want %s", diff)
@@ -594,12 +602,13 @@ func TestSetSpanStatus(t *testing.T) {
 			TraceID:    tid,
 			TraceFlags: 0x1,
 		},
-		ParentSpanID:    sid,
-		Name:            "span0",
-		SpanKind:        apitrace.SpanKindInternal,
-		StatusCode:      codes.Canceled,
-		StatusMessage:   "canceled",
-		HasRemoteParent: true,
+		ParentSpanID:           sid,
+		Name:                   "span0",
+		SpanKind:               apitrace.SpanKindInternal,
+		StatusCode:             codes.Canceled,
+		StatusMessage:          "canceled",
+		HasRemoteParent:        true,
+		InstrumentationLibrary: instrumentation.Library{Name: "SpanStatus"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("SetSpanStatus: -got +want %s", diff)
@@ -951,6 +960,7 @@ func TestRecordError(t *testing.T) {
 					},
 				},
 			},
+			InstrumentationLibrary: instrumentation.Library{Name: "RecordError"},
 		}
 		if diff := cmpDiff(got, want); diff != "" {
 			t.Errorf("SpanErrorOptions: -got +want %s", diff)
@@ -997,6 +1007,7 @@ func TestRecordErrorWithStatus(t *testing.T) {
 				},
 			},
 		},
+		InstrumentationLibrary: instrumentation.Library{Name: "RecordErrorWithStatus"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("SpanErrorOptions: -got +want %s", diff)
@@ -1020,12 +1031,13 @@ func TestRecordErrorNil(t *testing.T) {
 			TraceID:    tid,
 			TraceFlags: 0x1,
 		},
-		ParentSpanID:    sid,
-		Name:            "span0",
-		SpanKind:        apitrace.SpanKindInternal,
-		HasRemoteParent: true,
-		StatusCode:      codes.OK,
-		StatusMessage:   "",
+		ParentSpanID:           sid,
+		Name:                   "span0",
+		SpanKind:               apitrace.SpanKindInternal,
+		HasRemoteParent:        true,
+		StatusCode:             codes.OK,
+		StatusMessage:          "",
+		InstrumentationLibrary: instrumentation.Library{Name: "RecordErrorNil"},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("SpanErrorOptions: -got +want %s", diff)
@@ -1092,9 +1104,44 @@ func TestWithResource(t *testing.T) {
 		Attributes: []kv.KeyValue{
 			kv.String("key1", "value1"),
 		},
+		SpanKind:               apitrace.SpanKindInternal,
+		HasRemoteParent:        true,
+		Resource:               resource.New(kv.String("rk1", "rv1"), kv.Int64("rk2", 5)),
+		InstrumentationLibrary: instrumentation.Library{Name: "WithResource"},
+	}
+	if diff := cmpDiff(got, want); diff != "" {
+		t.Errorf("WithResource:\n  -got +want %s", diff)
+	}
+}
+
+func TestWithInstrumentationVersion(t *testing.T) {
+	var te testExporter
+	tp, _ := NewProvider(WithSyncer(&te))
+
+	ctx := context.Background()
+	ctx = apitrace.ContextWithRemoteSpanContext(ctx, remoteSpanContext())
+	_, span := tp.Tracer(
+		"WithInstrumentationVersion",
+		apitrace.WithInstrumentationVersion("v0.1.0"),
+	).Start(ctx, "span0", apitrace.WithRecord())
+	got, err := endSpan(&te, span)
+	if err != nil {
+		t.Error(err.Error())
+	}
+
+	want := &export.SpanData{
+		SpanContext: apitrace.SpanContext{
+			TraceID:    tid,
+			TraceFlags: 0x1,
+		},
+		ParentSpanID:    sid,
+		Name:            "span0",
 		SpanKind:        apitrace.SpanKindInternal,
 		HasRemoteParent: true,
-		Resource:        resource.New(kv.String("rk1", "rv1"), kv.Int64("rk2", 5)),
+		InstrumentationLibrary: instrumentation.Library{
+			Name:    "WithInstrumentationVersion",
+			Version: "v0.1.0",
+		},
 	}
 	if diff := cmpDiff(got, want); diff != "" {
 		t.Errorf("WithResource:\n  -got +want %s", diff)