1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-07-15 01:04:25 +02:00

Change resource.New() to use functional options; add builtin attributes for (host.*, telemetry.sdk.*) (#1235)

* Add a resource.Configure() with functional options

* Add a changelog

* Add tests for builtin resources

* Rename to WithoutBuiltin

* Add new test; restore environment after tests

* Apply feedback

* Apply suggestions from code review

❤️

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

* Comment edits

* Rename New, former method NewFromAttributes

* NewFromAttributes->NewWithAttributes

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
Joshua MacDonald
2020-10-31 11:16:55 -07:00
committed by GitHub
parent 034195692a
commit 187adeb251
36 changed files with 553 additions and 140 deletions

View File

@ -35,6 +35,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Move the `go.opentelemetry.io/otel/api/global` package to `go.opentelemetry.io/otel/global`. (#1262) - Move the `go.opentelemetry.io/otel/api/global` package to `go.opentelemetry.io/otel/global`. (#1262)
- Rename correlation context header from `"otcorrelations"` to `"baggage"` to match the OpenTelemetry specification. (#1267) - Rename correlation context header from `"otcorrelations"` to `"baggage"` to match the OpenTelemetry specification. (#1267)
- Fix `Code.UnmarshalJSON` to work with valid json only. (#1276) - Fix `Code.UnmarshalJSON` to work with valid json only. (#1276)
- The `resource.New()` method changes signature to support builtin attributes and functional options, including `telemetry.sdk.*` and
`host.name` semantic conventions; the former method is renamed `resource.NewWithAttributes`. (#1235)
### Removed ### Removed

View File

@ -41,6 +41,7 @@ import (
// Initializes an OTLP exporter, and configures the corresponding trace and // Initializes an OTLP exporter, and configures the corresponding trace and
// metric providers. // metric providers.
func initProvider() func() { func initProvider() func() {
ctx := context.Background()
// If the OpenTelemetry Collector is running on a local cluster (minikube or // If the OpenTelemetry Collector is running on a local cluster (minikube or
// microk8s), it should be accessible through the NodePort service at the // microk8s), it should be accessible through the NodePort service at the
@ -54,13 +55,18 @@ func initProvider() func() {
) )
handleErr(err, "failed to create exporter") handleErr(err, "failed to create exporter")
res, err := resource.New(ctx,
resource.WithAttributes(
// the service name used to display traces in backends
semconv.ServiceNameKey.String("test-service"),
),
)
handleErr(err, "failed to create resource")
bsp := sdktrace.NewBatchSpanProcessor(exp) bsp := sdktrace.NewBatchSpanProcessor(exp)
tracerProvider := sdktrace.NewTracerProvider( tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}), sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithResource(resource.New( sdktrace.WithResource(res),
// the service name used to display traces in backends
semconv.ServiceNameKey.String("test-service"),
)),
sdktrace.WithSpanProcessor(bsp), sdktrace.WithSpanProcessor(bsp),
) )
@ -80,7 +86,6 @@ func initProvider() func() {
pusher.Start() pusher.Start()
return func() { return func() {
ctx := context.Background()
handleErr(tracerProvider.Shutdown(ctx), "failed to shutdown provider") handleErr(tracerProvider.Shutdown(ctx), "failed to shutdown provider")
handleErr(exp.Shutdown(ctx), "failed to stop exporter") handleErr(exp.Shutdown(ctx), "failed to stop exporter")
pusher.Stop() // pushes any last exports to the receiver pusher.Stop() // pushes any last exports to the receiver

View File

@ -37,10 +37,20 @@ import (
// TODO: Address this issue. // TODO: Address this issue.
func ExampleNewExportPipeline() { func ExampleNewExportPipeline() {
// Create a resource, with builtin attributes plus R=V.
res, err := resource.New(
context.Background(),
resource.WithoutBuiltin(), // Test-only!
resource.WithAttributes(label.String("R", "V")),
)
if err != nil {
panic(err)
}
// Create a meter // Create a meter
exporter, err := prometheus.NewExportPipeline( exporter, err := prometheus.NewExportPipeline(
prometheus.Config{}, prometheus.Config{},
pull.WithResource(resource.New(label.String("R", "V"))), pull.WithResource(res),
) )
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -39,7 +39,7 @@ func TestPrometheusExporter(t *testing.T) {
DefaultHistogramBoundaries: []float64{-0.5, 1}, DefaultHistogramBoundaries: []float64{-0.5, 1},
}, },
pull.WithCachePeriod(0), pull.WithCachePeriod(0),
pull.WithResource(resource.New(label.String("R", "V"))), pull.WithResource(resource.NewWithAttributes(label.String("R", "V"))),
) )
require.NoError(t, err) require.NoError(t, err)

View File

@ -320,7 +320,7 @@ func TestRecordAggregatorIncompatibleErrors(t *testing.T) {
makeMpb := func(kind aggregation.Kind, agg aggregation.Aggregation) (*metricpb.Metric, error) { makeMpb := func(kind aggregation.Kind, agg aggregation.Aggregation) (*metricpb.Metric, error) {
desc := otel.NewDescriptor("things", otel.CounterInstrumentKind, otel.Int64NumberKind) desc := otel.NewDescriptor("things", otel.CounterInstrumentKind, otel.Int64NumberKind)
labels := label.NewSet() labels := label.NewSet()
res := resource.New() res := resource.Empty()
test := &testAgg{ test := &testAgg{
kind: kind, kind: kind,
agg: agg, agg: agg,
@ -357,7 +357,7 @@ func TestRecordAggregatorUnexpectedErrors(t *testing.T) {
makeMpb := func(kind aggregation.Kind, agg aggregation.Aggregation) (*metricpb.Metric, error) { makeMpb := func(kind aggregation.Kind, agg aggregation.Aggregation) (*metricpb.Metric, error) {
desc := otel.NewDescriptor("things", otel.CounterInstrumentKind, otel.Int64NumberKind) desc := otel.NewDescriptor("things", otel.CounterInstrumentKind, otel.Int64NumberKind)
labels := label.NewSet() labels := label.NewSet()
res := resource.New() res := resource.Empty()
return Record(export.NewRecord(&desc, &labels, res, agg, intervalStart, intervalEnd)) return Record(export.NewRecord(&desc, &labels, res, agg, intervalStart, intervalEnd))
} }

View File

@ -40,7 +40,7 @@ func TestEmptyResource(t *testing.T) {
func TestResourceAttributes(t *testing.T) { func TestResourceAttributes(t *testing.T) {
attrs := []label.KeyValue{label.Int("one", 1), label.Int("two", 2)} attrs := []label.KeyValue{label.Int("one", 1), label.Int("two", 2)}
got := Resource(resource.New(attrs...)).GetAttributes() got := Resource(resource.NewWithAttributes(attrs...)).GetAttributes()
if !assert.Len(t, attrs, 2) { if !assert.Len(t, attrs, 2) {
return return
} }

View File

@ -252,7 +252,7 @@ func TestSpanData(t *testing.T) {
DroppedAttributeCount: 1, DroppedAttributeCount: 1,
DroppedMessageEventCount: 2, DroppedMessageEventCount: 2,
DroppedLinkCount: 3, DroppedLinkCount: 3,
Resource: resource.New(label.String("rk1", "rv1"), label.Int64("rk2", 5)), Resource: resource.NewWithAttributes(label.String("rk1", "rv1"), label.Int64("rk2", 5)),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: "go.opentelemetry.io/test/otel", Name: "go.opentelemetry.io/test/otel",
Version: "v0.0.1", Version: "v0.0.1",

View File

@ -94,13 +94,13 @@ func newExporterEndToEndTest(t *testing.T, additionalOpts []otlp.ExporterOption)
), ),
} }
tp1 := sdktrace.NewTracerProvider(append(pOpts, tp1 := sdktrace.NewTracerProvider(append(pOpts,
sdktrace.WithResource(resource.New( sdktrace.WithResource(resource.NewWithAttributes(
label.String("rk1", "rv11)"), label.String("rk1", "rv11)"),
label.Int64("rk2", 5), label.Int64("rk2", 5),
)))...) )))...)
tp2 := sdktrace.NewTracerProvider(append(pOpts, tp2 := sdktrace.NewTracerProvider(append(pOpts,
sdktrace.WithResource(resource.New( sdktrace.WithResource(resource.NewWithAttributes(
label.String("rk1", "rv12)"), label.String("rk1", "rv12)"),
label.Float32("rk3", 6.5), label.Float32("rk3", 6.5),
)))...) )))...)

View File

@ -104,8 +104,8 @@ var (
baseKeyValues = []label.KeyValue{label.String("host", "test.com")} baseKeyValues = []label.KeyValue{label.String("host", "test.com")}
cpuKey = label.Key("CPU") cpuKey = label.Key("CPU")
testInstA = resource.New(label.String("instance", "tester-a")) testInstA = resource.NewWithAttributes(label.String("instance", "tester-a"))
testInstB = resource.New(label.String("instance", "tester-b")) testInstB = resource.NewWithAttributes(label.String("instance", "tester-b"))
testHistogramBoundaries = []float64{2.0, 4.0, 8.0} testHistogramBoundaries = []float64{2.0, 4.0, 8.0}

View File

@ -97,7 +97,7 @@ func TestExportSpans(t *testing.T) {
}, },
StatusCode: codes.Ok, StatusCode: codes.Ok,
StatusMessage: "Ok", StatusMessage: "Ok",
Resource: resource.New(label.String("instance", "tester-a")), Resource: resource.NewWithAttributes(label.String("instance", "tester-a")),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: "lib-a", Name: "lib-a",
Version: "v0.1.0", Version: "v0.1.0",
@ -119,7 +119,7 @@ func TestExportSpans(t *testing.T) {
}, },
StatusCode: codes.Ok, StatusCode: codes.Ok,
StatusMessage: "Ok", StatusMessage: "Ok",
Resource: resource.New(label.String("instance", "tester-a")), Resource: resource.NewWithAttributes(label.String("instance", "tester-a")),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: "lib-b", Name: "lib-b",
Version: "v0.1.0", Version: "v0.1.0",
@ -142,7 +142,7 @@ func TestExportSpans(t *testing.T) {
}, },
StatusCode: codes.Ok, StatusCode: codes.Ok,
StatusMessage: "Ok", StatusMessage: "Ok",
Resource: resource.New(label.String("instance", "tester-a")), Resource: resource.NewWithAttributes(label.String("instance", "tester-a")),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: "lib-a", Name: "lib-a",
Version: "v0.1.0", Version: "v0.1.0",
@ -164,7 +164,7 @@ func TestExportSpans(t *testing.T) {
}, },
StatusCode: codes.Error, StatusCode: codes.Error,
StatusMessage: "Unauthenticated", StatusMessage: "Unauthenticated",
Resource: resource.New(label.String("instance", "tester-b")), Resource: resource.NewWithAttributes(label.String("instance", "tester-b")),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: "lib-a", Name: "lib-a",
Version: "v1.1.0", Version: "v1.1.0",

View File

@ -48,7 +48,7 @@ type testFixture struct {
output *bytes.Buffer output *bytes.Buffer
} }
var testResource = resource.New(label.String("R", "V")) var testResource = resource.NewWithAttributes(label.String("R", "V"))
func newFixture(t *testing.T, opts ...stdout.Option) testFixture { func newFixture(t *testing.T, opts ...stdout.Option) testFixture {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -290,11 +290,11 @@ func TestStdoutResource(t *testing.T) {
} }
testCases := []testCase{ testCases := []testCase{
newCase("R1=V1,R2=V2,A=B,C=D", newCase("R1=V1,R2=V2,A=B,C=D",
resource.New(label.String("R1", "V1"), label.String("R2", "V2")), resource.NewWithAttributes(label.String("R1", "V1"), label.String("R2", "V2")),
label.String("A", "B"), label.String("A", "B"),
label.String("C", "D")), label.String("C", "D")),
newCase("R1=V1,R2=V2", newCase("R1=V1,R2=V2",
resource.New(label.String("R1", "V1"), label.String("R2", "V2")), resource.NewWithAttributes(label.String("R1", "V1"), label.String("R2", "V2")),
), ),
newCase("A=B,C=D", newCase("A=B,C=D",
nil, nil,
@ -304,7 +304,7 @@ func TestStdoutResource(t *testing.T) {
// We explicitly do not de-duplicate between resources // We explicitly do not de-duplicate between resources
// and metric labels in this exporter. // and metric labels in this exporter.
newCase("R1=V1,R2=V2,R1=V3,R2=V4", newCase("R1=V1,R2=V2,R1=V3,R2=V4",
resource.New(label.String("R1", "V1"), label.String("R2", "V2")), resource.NewWithAttributes(label.String("R1", "V1"), label.String("R2", "V2")),
label.String("R1", "V3"), label.String("R1", "V3"),
label.String("R2", "V4")), label.String("R2", "V4")),
} }

View File

@ -44,7 +44,7 @@ func TestExporter_ExportSpan(t *testing.T) {
spanID, _ := otel.SpanIDFromHex("0102030405060708") spanID, _ := otel.SpanIDFromHex("0102030405060708")
keyValue := "value" keyValue := "value"
doubleValue := 123.456 doubleValue := 123.456
resource := resource.New(label.String("rk1", "rv11")) resource := resource.NewWithAttributes(label.String("rk1", "rv11"))
testSpan := &export.SpanData{ testSpan := &export.SpanData{
SpanContext: otel.SpanContext{ SpanContext: otel.SpanContext{

View File

@ -409,7 +409,7 @@ func Test_spanDataToThrift(t *testing.T) {
StatusCode: codes.Error, StatusCode: codes.Error,
StatusMessage: statusMessage, StatusMessage: statusMessage,
SpanKind: otel.SpanKindClient, SpanKind: otel.SpanKindClient,
Resource: resource.New(label.String("rk1", rv1), label.Int64("rk2", rv2)), Resource: resource.NewWithAttributes(label.String("rk1", rv1), label.Int64("rk2", rv2)),
InstrumentationLibrary: instrumentation.Library{ InstrumentationLibrary: instrumentation.Library{
Name: instrLibName, Name: instrLibName,
Version: instrLibVersion, Version: instrLibVersion,

View File

@ -42,7 +42,7 @@ func newFixture(b *testing.B) *benchFixture {
AggregatorSelector: processortest.AggregatorSelector(), AggregatorSelector: processortest.AggregatorSelector(),
} }
bf.accumulator = sdk.NewAccumulator(bf) bf.accumulator = sdk.NewAccumulator(bf, nil)
bf.meter = otel.WrapMeterImpl(bf.accumulator, "benchmarks") bf.meter = otel.WrapMeterImpl(bf.accumulator, "benchmarks")
return bf return bf
} }

View File

@ -1,45 +0,0 @@
// 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 metric
import (
"go.opentelemetry.io/otel/sdk/resource"
)
// Config contains configuration for an SDK.
type Config struct {
// Resource describes all the metric records processed by the
// Accumulator.
Resource *resource.Resource
}
// Option is the interface that applies the value to a configuration option.
type Option interface {
// Apply sets the Option value of a Config.
Apply(*Config)
}
// WithResource sets the Resource configuration option of a Config.
func WithResource(res *resource.Resource) Option {
return resourceOption{res}
}
type resourceOption struct {
*resource.Resource
}
func (o resourceOption) Apply(config *Config) {
config.Resource = o.Resource
}

View File

@ -60,7 +60,7 @@ func New(checkpointer export.Checkpointer, options ...Option) *Controller {
} }
accum := sdk.NewAccumulator( accum := sdk.NewAccumulator(
checkpointer, checkpointer,
sdk.WithResource(config.Resource), config.Resource,
) )
return &Controller{ return &Controller{
accumulator: accum, accumulator: accum,

View File

@ -24,7 +24,7 @@ import (
) )
func TestWithResource(t *testing.T) { func TestWithResource(t *testing.T) {
r := resource.New(label.String("A", "a")) r := resource.NewWithAttributes(label.String("A", "a"))
c := &Config{} c := &Config{}
WithResource(r).Apply(c) WithResource(r).Apply(c)

View File

@ -61,7 +61,7 @@ func New(checkpointer export.Checkpointer, exporter export.Exporter, opts ...Opt
impl := sdk.NewAccumulator( impl := sdk.NewAccumulator(
checkpointer, checkpointer,
sdk.WithResource(c.Resource), c.Resource,
) )
return &Controller{ return &Controller{
provider: registry.NewMeterProvider(impl), provider: registry.NewMeterProvider(impl),

View File

@ -36,7 +36,7 @@ import (
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
) )
var testResource = resource.New(label.String("R", "V")) var testResource = resource.NewWithAttributes(label.String("R", "V"))
type handler struct { type handler struct {
sync.Mutex sync.Mutex

View File

@ -34,7 +34,7 @@ import (
) )
var Must = otel.Must var Must = otel.Must
var testResource = resource.New(label.String("R", "V")) var testResource = resource.NewWithAttributes(label.String("R", "V"))
type handler struct { type handler struct {
sync.Mutex sync.Mutex
@ -96,7 +96,7 @@ func newSDK(t *testing.T) (otel.Meter, *metricsdk.Accumulator, *correctnessProce
} }
accum := metricsdk.NewAccumulator( accum := metricsdk.NewAccumulator(
processor, processor,
metricsdk.WithResource(testResource), testResource,
) )
meter := otel.WrapMeterImpl(accum, "test") meter := otel.WrapMeterImpl(accum, "test")
return meter, accum, processor return meter, accum, processor

View File

@ -121,7 +121,7 @@ func testProcessor(
// Note: this selector uses the instrument name to dictate // Note: this selector uses the instrument name to dictate
// aggregation kind. // aggregation kind.
selector := processorTest.AggregatorSelector() selector := processorTest.AggregatorSelector()
res := resource.New(label.String("R", "V")) res := resource.NewWithAttributes(label.String("R", "V"))
labs1 := []label.KeyValue{label.String("L1", "V")} labs1 := []label.KeyValue{label.String("L1", "V")}
labs2 := []label.KeyValue{label.String("L2", "V")} labs2 := []label.KeyValue{label.String("L2", "V")}
@ -361,7 +361,7 @@ func TestBasicTimestamps(t *testing.T) {
} }
func TestStatefulNoMemoryCumulative(t *testing.T) { func TestStatefulNoMemoryCumulative(t *testing.T) {
res := resource.New(label.String("R", "V")) res := resource.NewWithAttributes(label.String("R", "V"))
ekind := export.CumulativeExporter ekind := export.CumulativeExporter
desc := otel.NewDescriptor("inst.sum", otel.CounterInstrumentKind, otel.Int64NumberKind) desc := otel.NewDescriptor("inst.sum", otel.CounterInstrumentKind, otel.Int64NumberKind)
@ -395,7 +395,7 @@ func TestStatefulNoMemoryCumulative(t *testing.T) {
} }
func TestStatefulNoMemoryDelta(t *testing.T) { func TestStatefulNoMemoryDelta(t *testing.T) {
res := resource.New(label.String("R", "V")) res := resource.NewWithAttributes(label.String("R", "V"))
ekind := export.DeltaExporter ekind := export.DeltaExporter
desc := otel.NewDescriptor("inst.sum", otel.SumObserverInstrumentKind, otel.Int64NumberKind) desc := otel.NewDescriptor("inst.sum", otel.SumObserverInstrumentKind, otel.Int64NumberKind)
@ -435,7 +435,7 @@ func TestMultiObserverSum(t *testing.T) {
export.DeltaExporter, export.DeltaExporter,
} { } {
res := resource.New(label.String("R", "V")) res := resource.NewWithAttributes(label.String("R", "V"))
desc := otel.NewDescriptor("observe.sum", otel.SumObserverInstrumentKind, otel.Int64NumberKind) desc := otel.NewDescriptor("observe.sum", otel.SumObserverInstrumentKind, otel.Int64NumberKind)
selector := processorTest.AggregatorSelector() selector := processorTest.AggregatorSelector()

View File

@ -32,9 +32,7 @@ func generateTestData(proc export.Processor) {
ctx := context.Background() ctx := context.Background()
accum := metricsdk.NewAccumulator( accum := metricsdk.NewAccumulator(
proc, proc,
metricsdk.WithResource( resource.NewWithAttributes(label.String("R", "V")),
resource.New(label.String("R", "V")),
),
) )
meter := otel.WrapMeterImpl(accum, "testing") meter := otel.WrapMeterImpl(accum, "testing")

View File

@ -75,9 +75,7 @@ func TestFilterProcessor(t *testing.T) {
) )
accum := metricsdk.NewAccumulator( accum := metricsdk.NewAccumulator(
reducer.New(testFilter{}, processorTest.Checkpointer(testProc)), reducer.New(testFilter{}, processorTest.Checkpointer(testProc)),
metricsdk.WithResource( resource.NewWithAttributes(label.String("R", "V")),
resource.New(label.String("R", "V")),
),
) )
generateData(accum) generateData(accum)
@ -94,9 +92,7 @@ func TestFilterBasicProcessor(t *testing.T) {
basicProc := basic.New(processorTest.AggregatorSelector(), export.CumulativeExporter) basicProc := basic.New(processorTest.AggregatorSelector(), export.CumulativeExporter)
accum := metricsdk.NewAccumulator( accum := metricsdk.NewAccumulator(
reducer.New(testFilter{}, basicProc), reducer.New(testFilter{}, basicProc),
metricsdk.WithResource( resource.NewWithAttributes(label.String("R", "V")),
resource.New(label.String("R", "V")),
),
) )
exporter := processorTest.NewExporter(basicProc, label.DefaultEncoder()) exporter := processorTest.NewExporter(basicProc, label.DefaultEncoder())

View File

@ -305,16 +305,11 @@ func (s *syncInstrument) RecordOne(ctx context.Context, number api.Number, kvs [
// processor will call Collect() when it receives a request to scrape // processor will call Collect() when it receives a request to scrape
// current metric values. A push-based processor should configure its // current metric values. A push-based processor should configure its
// own periodic collection. // own periodic collection.
func NewAccumulator(processor export.Processor, opts ...Option) *Accumulator { func NewAccumulator(processor export.Processor, resource *resource.Resource) *Accumulator {
c := &Config{}
for _, opt := range opts {
opt.Apply(c)
}
return &Accumulator{ return &Accumulator{
processor: processor, processor: processor,
asyncInstruments: internal.NewAsyncInstrumentState(), asyncInstruments: internal.NewAsyncInstrumentState(),
resource: c.Resource, resource: resource,
} }
} }

View File

@ -292,7 +292,8 @@ func stressTest(t *testing.T, impl testImpl) {
AggregatorSelector: processortest.AggregatorSelector(), AggregatorSelector: processortest.AggregatorSelector(),
} }
cc := concurrency() cc := concurrency()
sdk := NewAccumulator(fixture)
sdk := NewAccumulator(fixture, nil)
meter := otel.WrapMeterImpl(sdk, "stress_test") meter := otel.WrapMeterImpl(sdk, "stress_test")
fixture.wg.Add(cc + 1) fixture.wg.Add(cc + 1)

View File

@ -43,6 +43,9 @@ func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
var autoDetectedRes *Resource var autoDetectedRes *Resource
var errInfo []string var errInfo []string
for _, detector := range detectors { for _, detector := range detectors {
if detector == nil {
continue
}
res, err := detector.Detect(ctx) res, err := detector.Detect(ctx)
if err != nil { if err != nil {
errInfo = append(errInfo, err.Error()) errInfo = append(errInfo, err.Error())

View File

@ -47,7 +47,7 @@ func makeLabels(n int) (_, _ *resource.Resource) {
} }
} }
return resource.New(l1...), resource.New(l2...) return resource.NewWithAttributes(l1...), resource.NewWithAttributes(l2...)
} }
func benchmarkMergeResource(b *testing.B, size int) { func benchmarkMergeResource(b *testing.B, size int) {

81
sdk/resource/builtin.go Normal file
View File

@ -0,0 +1,81 @@
// 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 resource
import (
"context"
"fmt"
"os"
"go.opentelemetry.io/otel/label"
opentelemetry "go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/semconv"
)
type (
// TelemetrySDK is a Detector that provides information about
// the OpenTelemetry SDK used. This Detector is included as a
// builtin. If these resource attributes are not wanted, use
// the WithTelemetrySDK(nil) or WithoutBuiltin() options to
// explicitly disable them.
TelemetrySDK struct{}
// Host is a Detector that provides information about the host
// being run on. This Detector is included as a builtin. If
// these resource attributes are not wanted, use the
// WithHost(nil) or WithoutBuiltin() options to explicitly
// disable them.
Host struct{}
stringDetector struct {
K label.Key
F func() (string, error)
}
)
var (
_ Detector = TelemetrySDK{}
_ Detector = Host{}
_ Detector = stringDetector{}
)
// Detect returns a *Resource that describes the OpenTelemetry SDK used.
func (TelemetrySDK) Detect(context.Context) (*Resource, error) {
return NewWithAttributes(
semconv.TelemetrySDKNameKey.String("opentelemetry-go"),
semconv.TelemetrySDKLanguageKey.String("go"),
semconv.TelemetrySDKVersionKey.String(opentelemetry.Version()),
), nil
}
// Detect returns a *Resource that describes the host being run on.
func (Host) Detect(ctx context.Context) (*Resource, error) {
return StringDetector(semconv.HostNameKey, os.Hostname).Detect(ctx)
}
// StringDetector returns a Detector that will produce a *Resource
// containing the string as a value corresponding to k.
func StringDetector(k label.Key, f func() (string, error)) Detector {
return stringDetector{K: k, F: f}
}
// Detect implements Detector.
func (sd stringDetector) Detect(ctx context.Context) (*Resource, error) {
value, err := sd.F()
if err != nil {
return nil, fmt.Errorf("%s: %w", string(sd.K), err)
}
return NewWithAttributes(sd.K.String(value)), nil
}

View File

@ -0,0 +1,59 @@
// 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 resource_test
import (
"context"
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/label"
"go.opentelemetry.io/otel/sdk/resource"
)
func TestBuiltinStringDetector(t *testing.T) {
E := fmt.Errorf("no K")
res, err := resource.StringDetector(label.Key("K"), func() (string, error) {
return "", E
}).Detect(context.Background())
require.True(t, errors.Is(err, E))
require.NotEqual(t, E, err)
require.Nil(t, res)
}
func TestBuiltinStringConfig(t *testing.T) {
res, err := resource.New(
context.Background(),
resource.WithoutBuiltin(),
resource.WithAttributes(label.String("A", "B")),
resource.WithDetectors(resource.StringDetector(label.Key("K"), func() (string, error) {
return "", fmt.Errorf("K-IS-MISSING")
})),
)
require.Error(t, err)
require.Contains(t, err.Error(), "K-IS-MISSING")
require.NotNil(t, res)
m := map[string]string{}
for _, kv := range res.Attributes() {
m[string(kv.Key)] = kv.Value.Emit()
}
require.EqualValues(t, map[string]string{
"A": "B",
}, m)
}

150
sdk/resource/config.go Normal file
View File

@ -0,0 +1,150 @@
// 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 resource
import (
"context"
"go.opentelemetry.io/otel/label"
)
// config contains configuration for Resource creation.
type config struct {
// detectors that will be evaluated.
detectors []Detector
// telemetrySDK is used to specify non-default
// `telemetry.sdk.*` attributes.
telemetrySDK Detector
// HostResource is used to specify non-default `host.*`
// attributes.
host Detector
// FromEnv is used to specify non-default OTEL_RESOURCE_ATTRIBUTES
// attributes.
fromEnv Detector
}
// Option is the interface that applies a configuration option.
type Option interface {
// Apply sets the Option value of a config.
Apply(*config)
}
// WithAttributes adds attributes to the configured Resource.
func WithAttributes(attributes ...label.KeyValue) Option {
return WithDetectors(detectAttributes{attributes})
}
type detectAttributes struct {
attributes []label.KeyValue
}
func (d detectAttributes) Detect(context.Context) (*Resource, error) {
return NewWithAttributes(d.attributes...), nil
}
// WithDetectors adds detectors to be evaluated for the configured resource.
func WithDetectors(detectors ...Detector) Option {
return detectorsOption{detectors}
}
type detectorsOption struct {
detectors []Detector
}
// Apply implements Option.
func (o detectorsOption) Apply(cfg *config) {
cfg.detectors = append(cfg.detectors, o.detectors...)
}
// WithTelemetrySDK overrides the builtin `telemetry.sdk.*`
// attributes. Use nil to disable these attributes entirely.
func WithTelemetrySDK(d Detector) Option {
return telemetrySDKOption{d}
}
type telemetrySDKOption struct {
Detector
}
// Apply implements Option.
func (o telemetrySDKOption) Apply(cfg *config) {
cfg.telemetrySDK = o.Detector
}
// WithHost overrides the builtin `host.*` attributes. Use nil to
// disable these attributes entirely.
func WithHost(d Detector) Option {
return hostOption{d}
}
type hostOption struct {
Detector
}
// Apply implements Option.
func (o hostOption) Apply(cfg *config) {
cfg.host = o.Detector
}
// WithFromEnv overrides the builtin detector for
// OTEL_RESOURCE_ATTRIBUTES. Use nil to disable environment checking.
func WithFromEnv(d Detector) Option {
return fromEnvOption{d}
}
type fromEnvOption struct {
Detector
}
// Apply implements Option.
func (o fromEnvOption) Apply(cfg *config) {
cfg.fromEnv = o.Detector
}
// WithoutBuiltin disables all the builtin detectors, including the
// telemetry.sdk.*, host.*, and the environment detector.
func WithoutBuiltin() Option {
return noBuiltinOption{}
}
type noBuiltinOption struct{}
// Apply implements Option.
func (o noBuiltinOption) Apply(cfg *config) {
cfg.host = nil
cfg.telemetrySDK = nil
cfg.fromEnv = nil
}
// New returns a Resource combined from the provided attributes,
// user-provided detectors and builtin detectors.
func New(ctx context.Context, opts ...Option) (*Resource, error) {
cfg := config{
telemetrySDK: TelemetrySDK{},
host: Host{},
fromEnv: FromEnv{},
}
for _, opt := range opts {
opt.Apply(&cfg)
}
detectors := append(
[]Detector{cfg.telemetrySDK, cfg.host, cfg.fromEnv},
cfg.detectors...,
)
return Detect(ctx, detectors...)
}

139
sdk/resource/config_test.go Normal file
View File

@ -0,0 +1,139 @@
// 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 resource_test
import (
"context"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/testing"
"go.opentelemetry.io/otel/label"
opentelemetry "go.opentelemetry.io/otel/sdk"
"go.opentelemetry.io/otel/sdk/resource"
)
const envVar = "OTEL_RESOURCE_ATTRIBUTES"
func TestDefaultConfig(t *testing.T) {
store, err := ottest.SetEnvVariables(map[string]string{
envVar: "",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
ctx := context.Background()
res, err := resource.New(ctx)
require.NoError(t, err)
require.EqualValues(t, map[string]string{
"host.name": hostname(),
"telemetry.sdk.name": "opentelemetry-go",
"telemetry.sdk.language": "go",
"telemetry.sdk.version": opentelemetry.Version(),
}, toMap(res))
}
func TestDefaultConfigNoHost(t *testing.T) {
store, err := ottest.SetEnvVariables(map[string]string{
envVar: "",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
ctx := context.Background()
res, err := resource.New(ctx, resource.WithHost(nil))
require.NoError(t, err)
require.EqualValues(t, map[string]string{
"telemetry.sdk.name": "opentelemetry-go",
"telemetry.sdk.language": "go",
"telemetry.sdk.version": opentelemetry.Version(),
}, toMap(res))
}
func TestDefaultConfigNoEnv(t *testing.T) {
store, err := ottest.SetEnvVariables(map[string]string{
envVar: "from=here",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
ctx := context.Background()
res, err := resource.New(ctx, resource.WithFromEnv(nil))
require.NoError(t, err)
require.EqualValues(t, map[string]string{
"host.name": hostname(),
"telemetry.sdk.name": "opentelemetry-go",
"telemetry.sdk.language": "go",
"telemetry.sdk.version": opentelemetry.Version(),
}, toMap(res))
}
func TestDefaultConfigWithEnv(t *testing.T) {
store, err := ottest.SetEnvVariables(map[string]string{
envVar: "key=value,other=attr",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
ctx := context.Background()
res, err := resource.New(ctx)
require.NoError(t, err)
require.EqualValues(t, map[string]string{
"key": "value",
"other": "attr",
"host.name": hostname(),
"telemetry.sdk.name": "opentelemetry-go",
"telemetry.sdk.language": "go",
"telemetry.sdk.version": opentelemetry.Version(),
}, toMap(res))
}
func TestWithoutBuiltin(t *testing.T) {
store, err := ottest.SetEnvVariables(map[string]string{
envVar: "key=value,other=attr",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
ctx := context.Background()
res, err := resource.New(
ctx,
resource.WithoutBuiltin(),
resource.WithAttributes(label.String("hello", "collector")),
)
require.NoError(t, err)
require.EqualValues(t, map[string]string{
"hello": "collector",
}, toMap(res))
}
func toMap(res *resource.Resource) map[string]string {
m := map[string]string{}
for _, attr := range res.Attributes() {
m[string(attr.Key)] = attr.Value.Emit()
}
return m
}
func hostname() string {
hn, err := os.Hostname()
if err != nil {
return fmt.Sprintf("hostname(%s)", err)
}
return hn
}

View File

@ -27,19 +27,22 @@ import (
const envVar = "OTEL_RESOURCE_ATTRIBUTES" const envVar = "OTEL_RESOURCE_ATTRIBUTES"
var ( var (
//errMissingValue is returned when a resource value is missing. // errMissingValue is returned when a resource value is missing.
errMissingValue = fmt.Errorf("%w: missing value", ErrPartialResource) errMissingValue = fmt.Errorf("%w: missing value", ErrPartialResource)
) )
// FromEnv is a detector that implements the Detector and collects resources // FromEnv is a Detector that implements the Detector and collects
// from environment // resources from environment. This Detector is included as a
// builtin. If these resource attributes are not wanted, use the
// WithFromEnv(nil) or WithoutBuiltin() options to explicitly disable
// them.
type FromEnv struct{} type FromEnv struct{}
// compile time assertion that FromEnv implements Detector interface // compile time assertion that FromEnv implements Detector interface
var _ Detector = (*FromEnv)(nil) var _ Detector = FromEnv{}
// Detect collects resources from environment // Detect collects resources from environment
func (d *FromEnv) Detect(context.Context) (*Resource, error) { func (FromEnv) Detect(context.Context) (*Resource, error) {
labels := strings.TrimSpace(os.Getenv(envVar)) labels := strings.TrimSpace(os.Getenv(envVar))
if labels == "" { if labels == "" {
@ -65,5 +68,5 @@ func constructOTResources(s string) (*Resource, error) {
if len(invalid) > 0 { if len(invalid) > 0 {
err = fmt.Errorf("%w: %v", errMissingValue, invalid) err = fmt.Errorf("%w: %v", errMissingValue, invalid)
} }
return New(labels...), err return NewWithAttributes(labels...), err
} }

View File

@ -17,32 +17,40 @@ package resource
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/testing"
"go.opentelemetry.io/otel/label" "go.opentelemetry.io/otel/label"
) )
func TestDetectOnePair(t *testing.T) { func TestDetectOnePair(t *testing.T) {
os.Setenv(envVar, "key=value") store, err := ottest.SetEnvVariables(map[string]string{
envVar: "key=value",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
detector := &FromEnv{} detector := &FromEnv{}
res, err := detector.Detect(context.Background()) res, err := detector.Detect(context.Background())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, New(label.String("key", "value")), res) assert.Equal(t, NewWithAttributes(label.String("key", "value")), res)
} }
func TestDetectMultiPairs(t *testing.T) { func TestDetectMultiPairs(t *testing.T) {
os.Setenv("x", "1") store, err := ottest.SetEnvVariables(map[string]string{
os.Setenv(envVar, "key=value, k = v , a= x, a=z") "x": "1",
envVar: "key=value, k = v , a= x, a=z",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
detector := &FromEnv{} detector := &FromEnv{}
res, err := detector.Detect(context.Background()) res, err := detector.Detect(context.Background())
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, res, New( assert.Equal(t, res, NewWithAttributes(
label.String("key", "value"), label.String("key", "value"),
label.String("k", "v"), label.String("k", "v"),
label.String("a", "x"), label.String("a", "x"),
@ -51,7 +59,11 @@ func TestDetectMultiPairs(t *testing.T) {
} }
func TestEmpty(t *testing.T) { func TestEmpty(t *testing.T) {
os.Setenv(envVar, " ") store, err := ottest.SetEnvVariables(map[string]string{
envVar: " ",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
detector := &FromEnv{} detector := &FromEnv{}
res, err := detector.Detect(context.Background()) res, err := detector.Detect(context.Background())
@ -60,13 +72,17 @@ func TestEmpty(t *testing.T) {
} }
func TestMissingKeyError(t *testing.T) { func TestMissingKeyError(t *testing.T) {
os.Setenv(envVar, "key=value,key") store, err := ottest.SetEnvVariables(map[string]string{
envVar: "key=value,key",
})
require.NoError(t, err)
defer func() { require.NoError(t, store.Restore()) }()
detector := &FromEnv{} detector := &FromEnv{}
res, err := detector.Detect(context.Background()) res, err := detector.Detect(context.Background())
assert.Error(t, err) assert.Error(t, err)
assert.Equal(t, err, fmt.Errorf("%w: %v", errMissingValue, "[key]")) assert.Equal(t, err, fmt.Errorf("%w: %v", errMissingValue, "[key]"))
assert.Equal(t, res, New( assert.Equal(t, res, NewWithAttributes(
label.String("key", "value"), label.String("key", "value"),
)) ))
} }

View File

@ -31,10 +31,10 @@ type Resource struct {
var emptyResource Resource var emptyResource Resource
// New creates a resource from a set of attributes. If there are // NewWithAttributes creates a resource from a set of attributes. If there are
// duplicate keys present in the list of attributes, then the last // duplicate keys present in the list of attributes, then the last
// value found for the key is preserved. // value found for the key is preserved.
func New(kvs ...label.KeyValue) *Resource { func NewWithAttributes(kvs ...label.KeyValue) *Resource {
return &Resource{ return &Resource{
labels: label.NewSet(kvs...), labels: label.NewSet(kvs...),
} }
@ -103,7 +103,7 @@ func Merge(a, b *Resource) *Resource {
for mi.Next() { for mi.Next() {
combine = append(combine, mi.Label()) combine = append(combine, mi.Label())
} }
return New(combine...) return NewWithAttributes(combine...)
} }
// Empty returns an instance of Resource with no attributes. It is // Empty returns an instance of Resource with no attributes. It is

View File

@ -58,7 +58,7 @@ func TestNew(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) { t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) {
res := resource.New(c.in...) res := resource.NewWithAttributes(c.in...)
if diff := cmp.Diff( if diff := cmp.Diff(
res.Attributes(), res.Attributes(),
c.want, c.want,
@ -77,61 +77,61 @@ func TestMerge(t *testing.T) {
}{ }{
{ {
name: "Merge with no overlap, no nil", name: "Merge with no overlap, no nil",
a: resource.New(kv11, kv31), a: resource.NewWithAttributes(kv11, kv31),
b: resource.New(kv21, kv41), b: resource.NewWithAttributes(kv21, kv41),
want: []label.KeyValue{kv11, kv21, kv31, kv41}, want: []label.KeyValue{kv11, kv21, kv31, kv41},
}, },
{ {
name: "Merge with no overlap, no nil, not interleaved", name: "Merge with no overlap, no nil, not interleaved",
a: resource.New(kv11, kv21), a: resource.NewWithAttributes(kv11, kv21),
b: resource.New(kv31, kv41), b: resource.NewWithAttributes(kv31, kv41),
want: []label.KeyValue{kv11, kv21, kv31, kv41}, want: []label.KeyValue{kv11, kv21, kv31, kv41},
}, },
{ {
name: "Merge with common key order1", name: "Merge with common key order1",
a: resource.New(kv11), a: resource.NewWithAttributes(kv11),
b: resource.New(kv12, kv21), b: resource.NewWithAttributes(kv12, kv21),
want: []label.KeyValue{kv11, kv21}, want: []label.KeyValue{kv11, kv21},
}, },
{ {
name: "Merge with common key order2", name: "Merge with common key order2",
a: resource.New(kv12, kv21), a: resource.NewWithAttributes(kv12, kv21),
b: resource.New(kv11), b: resource.NewWithAttributes(kv11),
want: []label.KeyValue{kv12, kv21}, want: []label.KeyValue{kv12, kv21},
}, },
{ {
name: "Merge with common key order4", name: "Merge with common key order4",
a: resource.New(kv11, kv21, kv41), a: resource.NewWithAttributes(kv11, kv21, kv41),
b: resource.New(kv31, kv41), b: resource.NewWithAttributes(kv31, kv41),
want: []label.KeyValue{kv11, kv21, kv31, kv41}, want: []label.KeyValue{kv11, kv21, kv31, kv41},
}, },
{ {
name: "Merge with no keys", name: "Merge with no keys",
a: resource.New(), a: resource.NewWithAttributes(),
b: resource.New(), b: resource.NewWithAttributes(),
want: nil, want: nil,
}, },
{ {
name: "Merge with first resource no keys", name: "Merge with first resource no keys",
a: resource.New(), a: resource.NewWithAttributes(),
b: resource.New(kv21), b: resource.NewWithAttributes(kv21),
want: []label.KeyValue{kv21}, want: []label.KeyValue{kv21},
}, },
{ {
name: "Merge with second resource no keys", name: "Merge with second resource no keys",
a: resource.New(kv11), a: resource.NewWithAttributes(kv11),
b: resource.New(), b: resource.NewWithAttributes(),
want: []label.KeyValue{kv11}, want: []label.KeyValue{kv11},
}, },
{ {
name: "Merge with first resource nil", name: "Merge with first resource nil",
a: nil, a: nil,
b: resource.New(kv21), b: resource.NewWithAttributes(kv21),
want: []label.KeyValue{kv21}, want: []label.KeyValue{kv21},
}, },
{ {
name: "Merge with second resource nil", name: "Merge with second resource nil",
a: resource.New(kv11), a: resource.NewWithAttributes(kv11),
b: nil, b: nil,
want: []label.KeyValue{kv11}, want: []label.KeyValue{kv11},
}, },
@ -207,14 +207,14 @@ func TestString(t *testing.T) {
want: `A\=a\\\,B=b`, want: `A\=a\\\,B=b`,
}, },
} { } {
if got := resource.New(test.kvs...).String(); got != test.want { if got := resource.NewWithAttributes(test.kvs...).String(); got != test.want {
t.Errorf("Resource(%v).String() = %q, want %q", test.kvs, got, test.want) t.Errorf("Resource(%v).String() = %q, want %q", test.kvs, got, test.want)
} }
} }
} }
func TestMarshalJSON(t *testing.T) { func TestMarshalJSON(t *testing.T) {
r := resource.New(label.Int64("A", 1), label.String("C", "D")) r := resource.NewWithAttributes(label.Int64("A", 1), label.String("C", "D"))
data, err := json.Marshal(r) data, err := json.Marshal(r)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, require.Equal(t,

View File

@ -1169,7 +1169,7 @@ func TestWithResource(t *testing.T) {
te := NewTestExporter() te := NewTestExporter()
tp := NewTracerProvider(WithSyncer(te), tp := NewTracerProvider(WithSyncer(te),
WithConfig(Config{DefaultSampler: AlwaysSample()}), WithConfig(Config{DefaultSampler: AlwaysSample()}),
WithResource(resource.New(label.String("rk1", "rv1"), label.Int64("rk2", 5)))) WithResource(resource.NewWithAttributes(label.String("rk1", "rv1"), label.Int64("rk2", 5))))
span := startSpan(tp, "WithResource") span := startSpan(tp, "WithResource")
span.SetAttributes(label.String("key1", "value1")) span.SetAttributes(label.String("key1", "value1"))
got, err := endSpan(te, span) got, err := endSpan(te, span)
@ -1189,7 +1189,7 @@ func TestWithResource(t *testing.T) {
}, },
SpanKind: otel.SpanKindInternal, SpanKind: otel.SpanKindInternal,
HasRemoteParent: true, HasRemoteParent: true,
Resource: resource.New(label.String("rk1", "rv1"), label.Int64("rk2", 5)), Resource: resource.NewWithAttributes(label.String("rk1", "rv1"), label.Int64("rk2", 5)),
InstrumentationLibrary: instrumentation.Library{Name: "WithResource"}, InstrumentationLibrary: instrumentation.Library{Name: "WithResource"},
} }
if diff := cmpDiff(got, want); diff != "" { if diff := cmpDiff(got, want); diff != "" {