diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ee1ba0c4b..b87369dddf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Make `NewSplitDriver` from `go.opentelemetry.io/otel/exporters/otlp` take variadic arguments instead of a `SplitConfig` item. `NewSplitDriver` now automatically implements an internal `noopDriver` for `SplitConfig` fields that are not initialized. (#1798) +- `resource.New()` now creates a Resource without builtin detectors. Previous behavior is now achieved by using `WithBuiltinDetectors` Option. (#1810) - Move the `Event` type from the `go.opentelemetry.io/otel` package to the `go.opentelemetry.io/otel/sdk/trace` package. (#1846) - BatchSpanProcessor now report export failures when calling `ForceFlush()` method. (#1860) @@ -33,6 +34,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Removed +- Remove `resource.WithoutBuiltin()`. Use `resource.New()`. (#1810) +- Unexported types `resource.FromEnv`, `resource.Host`, and `resource.TelemetrySDK`, Use the corresponding `With*()` to use individually. (#1810) + ### Fixed - Only report errors from the `"go.opentelemetry.io/otel/sdk/resource".Environment` function when they are not `nil`. (#1850, #1851) diff --git a/exporters/metric/prometheus/example_test.go b/exporters/metric/prometheus/example_test.go index bdf1e206b9c..e34de6a2c1e 100644 --- a/exporters/metric/prometheus/example_test.go +++ b/exporters/metric/prometheus/example_test.go @@ -40,7 +40,6 @@ func ExampleNewExportPipeline() { // Create a resource, with builtin attributes plus R=V. res, err := resource.New( context.Background(), - resource.WithoutBuiltin(), // Test-only! resource.WithAttributes(attribute.String("R", "V")), ) if err != nil { diff --git a/sdk/resource/builtin.go b/sdk/resource/builtin.go index c80ab697c6f..9ad790883f4 100644 --- a/sdk/resource/builtin.go +++ b/sdk/resource/builtin.go @@ -26,19 +26,19 @@ import ( ) type ( - // TelemetrySDK is a Detector that provides information about + // 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{} + telemetrySDK struct{} - // Host is a Detector that provides information about the host + // 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{} + host struct{} stringDetector struct { K attribute.Key @@ -49,14 +49,14 @@ type ( ) var ( - _ Detector = TelemetrySDK{} - _ Detector = Host{} + _ Detector = telemetrySDK{} + _ Detector = host{} _ Detector = stringDetector{} _ Detector = defaultServiceNameDetector{} ) // Detect returns a *Resource that describes the OpenTelemetry SDK used. -func (TelemetrySDK) Detect(context.Context) (*Resource, error) { +func (telemetrySDK) Detect(context.Context) (*Resource, error) { return NewWithAttributes( semconv.TelemetrySDKNameKey.String("opentelemetry"), semconv.TelemetrySDKLanguageKey.String("go"), @@ -65,7 +65,7 @@ func (TelemetrySDK) Detect(context.Context) (*Resource, error) { } // Detect returns a *Resource that describes the host being run on. -func (Host) Detect(ctx context.Context) (*Resource, error) { +func (host) Detect(ctx context.Context) (*Resource, error) { return StringDetector(semconv.HostNameKey, os.Hostname).Detect(ctx) } diff --git a/sdk/resource/builtin_test.go b/sdk/resource/builtin_test.go index 0e93aa17828..223bd0df800 100644 --- a/sdk/resource/builtin_test.go +++ b/sdk/resource/builtin_test.go @@ -61,7 +61,6 @@ func TestStringDetectorErrors(t *testing.T) { for _, test := range tests { res, err := resource.New( context.Background(), - resource.WithoutBuiltin(), resource.WithAttributes(attribute.String("A", "B")), resource.WithDetectors(test.s), ) diff --git a/sdk/resource/config.go b/sdk/resource/config.go index 0365caa26a8..d83a3ece263 100644 --- a/sdk/resource/config.go +++ b/sdk/resource/config.go @@ -24,18 +24,6 @@ import ( 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. @@ -81,85 +69,24 @@ 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{Detector: d} -} - -type telemetrySDKOption struct { - option - Detector +// WithBuiltinDetectors adds the built detectors to the configured resource. +func WithBuiltinDetectors() Option { + return WithDetectors(telemetrySDK{}, + host{}, + fromEnv{}) } -// 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{Detector: d} -} - -type hostOption struct { - option - Detector -} - -// Apply implements Option. -func (o hostOption) Apply(cfg *config) { - cfg.host = o.Detector +// WithFromEnv adds attributes from environment variables to the configured resource. +func WithFromEnv() Option { + return WithDetectors(fromEnv{}) } -// WithFromEnv overrides the builtin detector for -// OTEL_RESOURCE_ATTRIBUTES. Use nil to disable environment checking. -func WithFromEnv(d Detector) Option { - return fromEnvOption{Detector: d} -} - -type fromEnvOption struct { - option - 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 { - option -} - -// Apply implements Option. -func (o noBuiltinOption) Apply(cfg *config) { - cfg.host = nil - cfg.telemetrySDK = nil - cfg.fromEnv = nil +// WithHost adds attributes from the host to the configured resource. +func WithHost() Option { + return WithDetectors(host{}) } -// 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...) +// WithTelemetrySDK adds TelemetrySDK version info to the configured resource. +func WithTelemetrySDK() Option { + return WithDetectors(telemetrySDK{}) } diff --git a/sdk/resource/config_test.go b/sdk/resource/config_test.go deleted file mode 100644 index 046a39bd3fa..00000000000 --- a/sdk/resource/config_test.go +++ /dev/null @@ -1,139 +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 resource_test - -import ( - "context" - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - ottest "go.opentelemetry.io/otel/internal/internaltest" - "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", - "telemetry.sdk.language": "go", - "telemetry.sdk.version": otel.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", - "telemetry.sdk.language": "go", - "telemetry.sdk.version": otel.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", - "telemetry.sdk.language": "go", - "telemetry.sdk.version": otel.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", - "telemetry.sdk.language": "go", - "telemetry.sdk.version": otel.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(attribute.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 -} diff --git a/sdk/resource/env.go b/sdk/resource/env.go index defb455382b..35b99c129e2 100644 --- a/sdk/resource/env.go +++ b/sdk/resource/env.go @@ -31,18 +31,18 @@ var ( errMissingValue = fmt.Errorf("%w: missing value", ErrPartialResource) ) -// FromEnv is a Detector that implements the Detector and collects +// fromEnv is a Detector that implements the Detector and collects // 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 -var _ Detector = FromEnv{} +var _ Detector = fromEnv{} // Detect collects resources from environment -func (FromEnv) Detect(context.Context) (*Resource, error) { +func (fromEnv) Detect(context.Context) (*Resource, error) { attrs := strings.TrimSpace(os.Getenv(envVar)) if attrs == "" { diff --git a/sdk/resource/env_test.go b/sdk/resource/env_test.go index 50b788c2ff3..78bd68346b2 100644 --- a/sdk/resource/env_test.go +++ b/sdk/resource/env_test.go @@ -33,7 +33,7 @@ func TestDetectOnePair(t *testing.T) { require.NoError(t, err) defer func() { require.NoError(t, store.Restore()) }() - detector := &FromEnv{} + detector := &fromEnv{} res, err := detector.Detect(context.Background()) require.NoError(t, err) assert.Equal(t, NewWithAttributes(attribute.String("key", "value")), res) @@ -47,7 +47,7 @@ func TestDetectMultiPairs(t *testing.T) { require.NoError(t, err) defer func() { require.NoError(t, store.Restore()) }() - detector := &FromEnv{} + detector := &fromEnv{} res, err := detector.Detect(context.Background()) require.NoError(t, err) assert.Equal(t, res, NewWithAttributes( @@ -65,7 +65,7 @@ func TestEmpty(t *testing.T) { require.NoError(t, err) defer func() { require.NoError(t, store.Restore()) }() - detector := &FromEnv{} + detector := &fromEnv{} res, err := detector.Detect(context.Background()) require.NoError(t, err) assert.Equal(t, Empty(), res) @@ -78,7 +78,7 @@ func TestMissingKeyError(t *testing.T) { require.NoError(t, err) defer func() { require.NoError(t, store.Restore()) }() - detector := &FromEnv{} + detector := &fromEnv{} res, err := detector.Detect(context.Background()) assert.Error(t, err) assert.Equal(t, err, fmt.Errorf("%w: %v", errMissingValue, "[key]")) diff --git a/sdk/resource/os_test.go b/sdk/resource/os_test.go index 7de00b4f6d6..909defdb28c 100644 --- a/sdk/resource/os_test.go +++ b/sdk/resource/os_test.go @@ -38,7 +38,6 @@ func TestWithOSType(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithOSType(), ) diff --git a/sdk/resource/process_test.go b/sdk/resource/process_test.go index d4692293d3e..947378ddc1d 100644 --- a/sdk/resource/process_test.go +++ b/sdk/resource/process_test.go @@ -148,7 +148,6 @@ func testWithProcessPID(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessPID(), ) @@ -162,7 +161,6 @@ func testWithProcessExecutableName(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessExecutableName(), ) @@ -176,7 +174,6 @@ func testWithProcessExecutablePath(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessExecutablePath(), ) @@ -190,7 +187,6 @@ func testWithProcessCommandArgs(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessCommandArgs(), ) @@ -204,7 +200,6 @@ func testWithProcessOwner(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessOwner(), ) @@ -218,7 +213,6 @@ func testWithProcessRuntimeName(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessRuntimeName(), ) @@ -232,7 +226,6 @@ func testWithProcessRuntimeVersion(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessRuntimeVersion(), ) @@ -246,7 +239,6 @@ func testWithProcessRuntimeDescription(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessRuntimeDescription(), ) @@ -260,7 +252,6 @@ func testWithProcess(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcess(), ) @@ -281,7 +272,6 @@ func testWithProcessExecutablePathError(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessExecutablePath(), ) @@ -293,7 +283,6 @@ func testWithProcessOwnerError(t *testing.T) { ctx := context.Background() res, err := resource.New(ctx, - resource.WithoutBuiltin(), resource.WithProcessOwner(), ) diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go index 3d056b56b46..baf9f2c977e 100644 --- a/sdk/resource/resource.go +++ b/sdk/resource/resource.go @@ -40,9 +40,19 @@ var ( otel.Handle(err) } return r - }(Detect(context.Background(), defaultServiceNameDetector{}, FromEnv{}, TelemetrySDK{})) + }(Detect(context.Background(), defaultServiceNameDetector{}, fromEnv{}, telemetrySDK{})) ) +// New returns a Resource combined from the user-provided detectors. +func New(ctx context.Context, opts ...Option) (*Resource, error) { + cfg := config{} + for _, opt := range opts { + opt.Apply(&cfg) + } + + return Detect(ctx, cfg.detectors...) +} + // NewWithAttributes creates a resource from attrs. If attrs contains // duplicate keys, the last value will be used. If attrs contains any invalid // items those items will be dropped. @@ -147,7 +157,7 @@ func Default() *Resource { // Environment returns an instance of Resource with attributes // extracted from the OTEL_RESOURCE_ATTRIBUTES environment variable. func Environment() *Resource { - detector := &FromEnv{} + detector := &fromEnv{} resource, err := detector.Detect(context.Background()) if err != nil { otel.Handle(err) diff --git a/sdk/resource/resource_test.go b/sdk/resource/resource_test.go index 00f0c6cca18..c426e56f0da 100644 --- a/sdk/resource/resource_test.go +++ b/sdk/resource/resource_test.go @@ -15,8 +15,10 @@ package resource_test import ( + "context" "encoding/json" "fmt" + "os" "strings" "testing" @@ -25,6 +27,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + ottest "go.opentelemetry.io/otel/internal/internaltest" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/semconv" ) @@ -38,7 +41,7 @@ var ( kv42 = attribute.String("k4", "") ) -func TestNew(t *testing.T) { +func TestNewWithAttributes(t *testing.T) { cases := []struct { name string in []attribute.KeyValue @@ -252,6 +255,8 @@ func TestString(t *testing.T) { } } +const envVar = "OTEL_RESOURCE_ATTRIBUTES" + func TestMarshalJSON(t *testing.T) { r := resource.NewWithAttributes(attribute.Int64("A", 1), attribute.String("C", "D")) data, err := json.Marshal(r) @@ -260,3 +265,175 @@ func TestMarshalJSON(t *testing.T) { `[{"Key":"A","Value":{"Type":"INT64","Value":1}},{"Key":"C","Value":{"Type":"STRING","Value":"D"}}]`, string(data)) } + +func TestNew(t *testing.T) { + tc := []struct { + name string + envars string + detectors []resource.Detector + options []resource.Option + + resourceValues map[string]string + }{ + { + name: "No Options returns empty resrouce", + envars: "key=value,other=attr", + options: nil, + resourceValues: map[string]string{}, + }, + { + name: "Nil Detectors works", + envars: "key=value,other=attr", + options: []resource.Option{ + resource.WithDetectors(), + }, + resourceValues: map[string]string{}, + }, + { + name: "Only Host", + envars: "from=here", + options: []resource.Option{ + resource.WithHost(), + }, + resourceValues: map[string]string{ + "host.name": hostname(), + }, + }, + { + name: "Only Env", + envars: "key=value,other=attr", + options: []resource.Option{ + resource.WithFromEnv(), + }, + resourceValues: map[string]string{ + "key": "value", + "other": "attr", + }, + }, + { + name: "Only TelemetrySDK", + envars: "", + options: []resource.Option{ + resource.WithTelemetrySDK(), + }, + resourceValues: map[string]string{ + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.language": "go", + "telemetry.sdk.version": otel.Version(), + }, + }, + { + name: "WithAttributes", + envars: "key=value,other=attr", + options: []resource.Option{ + resource.WithAttributes(attribute.String("A", "B")), + }, + resourceValues: map[string]string{ + "A": "B", + }, + }, + { + name: "Builtins", + envars: "key=value,other=attr", + options: []resource.Option{ + resource.WithBuiltinDetectors(), + }, + resourceValues: map[string]string{ + "host.name": hostname(), + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.language": "go", + "telemetry.sdk.version": otel.Version(), + "key": "value", + "other": "attr", + }, + }, + } + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + store, err := ottest.SetEnvVariables(map[string]string{ + envVar: tt.envars, + }) + require.NoError(t, err) + defer func() { require.NoError(t, store.Restore()) }() + + ctx := context.Background() + res, err := resource.New(ctx, tt.options...) + + require.NoError(t, err) + require.EqualValues(t, tt.resourceValues, toMap(res)) + }) + } +} + +func TestNewWithBuiltinDetectors(t *testing.T) { + tc := []struct { + name string + envars string + detectors []resource.Detector + options []resource.Option + + resourceValues map[string]string + }{ + { + name: "No Options returns builtin", + envars: "key=value,other=attr", + options: nil, + resourceValues: map[string]string{ + "host.name": hostname(), + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.language": "go", + "telemetry.sdk.version": otel.Version(), + "key": "value", + "other": "attr", + }, + }, + { + name: "WithAttributes", + envars: "key=value,other=attr", + options: []resource.Option{ + resource.WithAttributes(attribute.String("A", "B")), + }, + resourceValues: map[string]string{ + "host.name": hostname(), + "telemetry.sdk.name": "opentelemetry", + "telemetry.sdk.language": "go", + "telemetry.sdk.version": otel.Version(), + "key": "value", + "other": "attr", + "A": "B", + }, + }, + } + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + store, err := ottest.SetEnvVariables(map[string]string{ + envVar: tt.envars, + }) + require.NoError(t, err) + defer func() { require.NoError(t, store.Restore()) }() + + ctx := context.Background() + options := append([]resource.Option{resource.WithBuiltinDetectors()}, tt.options...) + res, err := resource.New(ctx, options...) + + require.NoError(t, err) + require.EqualValues(t, tt.resourceValues, 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 +}