diff --git a/CHANGELOG.md b/CHANGELOG.md index 64563c53f46..baa79ddf4b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- Add `Resource` method to `SDK` in `go.opentelemetry.io/contrib/otelconf/v0.3.0` to expose the resolved SDK resource from declarative configuration. (#8660) - Configuration file can now be set via `OTEL_CONFIG_FILE` in `go.opentelemetry.io/contrib/otelconf`. (#8639) - Added support for `service` resource detector in `go.opentelemetry.io/contrib/otelconf`. (#8674) - Added support for `attribute_count_limit` and `attribute_value_length_limit` in tracer provider configuration in `go.opentelemetry.io/contrib/otelconf`. (#8687) diff --git a/otelconf/v0.3.0/config.go b/otelconf/v0.3.0/config.go index f4bbe39f8e3..a7ae24ed090 100644 --- a/otelconf/v0.3.0/config.go +++ b/otelconf/v0.3.0/config.go @@ -16,6 +16,7 @@ import ( noopmetric "go.opentelemetry.io/otel/metric/noop" sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" + sdkresource "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" nooptrace "go.opentelemetry.io/otel/trace/noop" @@ -52,6 +53,7 @@ type SDK struct { meterProvider metric.MeterProvider tracerProvider trace.TracerProvider loggerProvider log.LoggerProvider + resource *sdkresource.Resource shutdown shutdownFunc } @@ -70,6 +72,16 @@ func (s *SDK) LoggerProvider() log.LoggerProvider { return s.loggerProvider } +// Resource returns a copy of the resolved SDK resource configured in this SDK. +// The copy preserves the immutability of the SDK-owned resource. +func (s *SDK) Resource() *sdkresource.Resource { + if s.resource == nil { + return nil + } + res := *s.resource + return &res +} + // Shutdown calls shutdown on all configured providers. func (s *SDK) Shutdown(ctx context.Context) error { return s.shutdown(ctx) @@ -79,6 +91,7 @@ var noopSDK = SDK{ loggerProvider: nooplog.LoggerProvider{}, meterProvider: noopmetric.MeterProvider{}, tracerProvider: nooptrace.TracerProvider{}, + resource: sdkresource.Empty(), shutdown: func(context.Context) error { return nil }, } @@ -115,6 +128,7 @@ func NewSDK(opts ...ConfigurationOption) (SDK, error) { meterProvider: mp, tracerProvider: tp, loggerProvider: lp, + resource: r, shutdown: func(ctx context.Context) error { return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx)) }, diff --git a/otelconf/v0.3.0/config_resource_test.go b/otelconf/v0.3.0/config_resource_test.go new file mode 100644 index 00000000000..35b9dd0aece --- /dev/null +++ b/otelconf/v0.3.0/config_resource_test.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otelconf + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" + sdkresource "go.opentelemetry.io/otel/sdk/resource" +) + +func TestSDKResource(t *testing.T) { + t.Run("returns nil for zero value sdk", func(t *testing.T) { + var sdk SDK + assert.Nil(t, sdk.Resource()) + }) + + t.Run("returns resource even when providers are not configured", func(t *testing.T) { + sdk, err := NewSDK( + WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ + Resource: &Resource{ + Attributes: []AttributeNameValue{ + {Name: "service.name", Value: "collector"}, + }, + }, + }), + ) + assert.NoError(t, err) + + res := sdk.Resource() + assert.NotNil(t, res) + assert.Contains(t, res.Attributes(), attribute.String("service.name", "collector")) + }) + + t.Run("returns empty resource for disabled sdk", func(t *testing.T) { + sdk, err := NewSDK( + WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ + Disabled: ptr(true), + }), + ) + assert.NoError(t, err) + assert.NotNil(t, sdk.Resource()) + assert.Empty(t, sdk.Resource().Attributes()) + }) + + t.Run("returns a defensive copy", func(t *testing.T) { + sdk, err := NewSDK( + WithOpenTelemetryConfiguration(OpenTelemetryConfiguration{ + Resource: &Resource{ + Attributes: []AttributeNameValue{ + {Name: "service.name", Value: "collector"}, + }, + }, + }), + ) + assert.NoError(t, err) + + res := sdk.Resource() + assert.NotNil(t, res) + + newRes := sdkresource.NewSchemaless(attribute.String("service.name", "mutated")) + *res = *newRes + + assert.Contains(t, res.Attributes(), attribute.String("service.name", "mutated")) + assert.Contains(t, sdk.Resource().Attributes(), attribute.String("service.name", "collector")) + assert.NotContains(t, sdk.Resource().Attributes(), attribute.String("service.name", "mutated")) + }) +}