Skip to content

Commit 9e1b015

Browse files
basti1302dmathieu
andauthored
fix(metric, log): merge explicit resource with environment variables (#5773)
fixes #5764 --------- Co-authored-by: Damien Mathieu <[email protected]>
1 parent 8dca9cc commit 9e1b015

File tree

7 files changed

+140
-5
lines changed

7 files changed

+140
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1111
### Added
1212

1313
- Support `OTEL_EXPORTER_OTLP_LOGS_INSECURE` and `OTEL_EXPORTER_OTLP_INSECURE` environments in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`. (#5739)
14+
- The `WithResource` option for `NewMeterProvider` now merges the provided resources with the ones from environment variables. (#5773)
15+
- The `WithResource` option for `NewLoggerProvider` now merges the provided resources with the ones from environment variables. (#5773)
1416

1517
### Fixed
1618

sdk/log/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.22
55
require (
66
github.com/go-logr/logr v1.4.2
77
github.com/go-logr/stdr v1.2.2
8+
github.com/google/go-cmp v0.6.0
89
github.com/stretchr/testify v1.9.0
910
go.opentelemetry.io/otel v1.29.0
1011
go.opentelemetry.io/otel/log v0.5.0

sdk/log/provider.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"sync"
1010
"sync/atomic"
1111

12+
"go.opentelemetry.io/otel"
1213
"go.opentelemetry.io/otel/internal/global"
1314
"go.opentelemetry.io/otel/log"
1415
"go.opentelemetry.io/otel/log/embedded"
@@ -196,7 +197,11 @@ func (fn loggerProviderOptionFunc) apply(c providerConfig) providerConfig {
196197
// go.opentelemetry.io/otel/sdk/resource package will be used.
197198
func WithResource(res *resource.Resource) LoggerProviderOption {
198199
return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig {
199-
cfg.resource = res
200+
var err error
201+
cfg.resource, err = resource.Merge(resource.Environment(), res)
202+
if err != nil {
203+
otel.Handle(err)
204+
}
200205
return cfg
201206
})
202207
}

sdk/log/provider_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/go-logr/logr"
1414
"github.com/go-logr/logr/testr"
15+
"github.com/google/go-cmp/cmp"
1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/require"
1718

@@ -20,10 +21,13 @@ import (
2021
"go.opentelemetry.io/otel/internal/global"
2122
"go.opentelemetry.io/otel/log"
2223
"go.opentelemetry.io/otel/log/noop"
24+
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
2325
"go.opentelemetry.io/otel/sdk/log/internal/x"
2426
"go.opentelemetry.io/otel/sdk/resource"
2527
)
2628

29+
const envVarResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES"
30+
2731
type processor struct {
2832
Name string
2933
Err error
@@ -172,6 +176,65 @@ func TestNewLoggerProviderConfiguration(t *testing.T) {
172176
}
173177
}
174178

179+
func mergeResource(t *testing.T, r1, r2 *resource.Resource) *resource.Resource {
180+
r, err := resource.Merge(r1, r2)
181+
assert.NoError(t, err)
182+
return r
183+
}
184+
185+
func TestWithResource(t *testing.T) {
186+
store, err := ottest.SetEnvVariables(map[string]string{
187+
envVarResourceAttributes: "key=value,rk5=7",
188+
})
189+
require.NoError(t, err)
190+
defer func() { require.NoError(t, store.Restore()) }()
191+
192+
cases := []struct {
193+
name string
194+
options []LoggerProviderOption
195+
want *resource.Resource
196+
msg string
197+
}{
198+
{
199+
name: "explicitly empty resource",
200+
options: []LoggerProviderOption{WithResource(resource.Empty())},
201+
want: resource.Environment(),
202+
},
203+
{
204+
name: "uses default if no resource option",
205+
options: []LoggerProviderOption{},
206+
want: resource.Default(),
207+
},
208+
{
209+
name: "explicit resource",
210+
options: []LoggerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))},
211+
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))),
212+
},
213+
{
214+
name: "last resource wins",
215+
options: []LoggerProviderOption{
216+
WithResource(resource.NewSchemaless(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))),
217+
WithResource(resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
218+
},
219+
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
220+
},
221+
{
222+
name: "overlapping attributes with environment resource",
223+
options: []LoggerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))},
224+
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))),
225+
},
226+
}
227+
for _, tc := range cases {
228+
tc := tc
229+
t.Run(tc.name, func(t *testing.T) {
230+
got := newProviderConfig(tc.options).resource
231+
if diff := cmp.Diff(got, tc.want); diff != "" {
232+
t.Errorf("WithResource:\n -got +want %s", diff)
233+
}
234+
})
235+
}
236+
}
237+
175238
func TestLoggerProviderConcurrentSafe(t *testing.T) {
176239
const goRoutineN = 10
177240

sdk/metric/config.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"sync"
1010

11+
"go.opentelemetry.io/otel"
1112
"go.opentelemetry.io/otel/sdk/resource"
1213
)
1314

@@ -103,7 +104,11 @@ func (o optionFunc) apply(conf config) config {
103104
// go.opentelemetry.io/otel/sdk/resource package will be used.
104105
func WithResource(res *resource.Resource) Option {
105106
return optionFunc(func(conf config) config {
106-
conf.res = res
107+
var err error
108+
conf.res, err = resource.Merge(resource.Environment(), res)
109+
if err != nil {
110+
otel.Handle(err)
111+
}
107112
return conf
108113
})
109114
}

sdk/metric/config_test.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import (
88
"fmt"
99
"testing"
1010

11+
"github.com/google/go-cmp/cmp"
1112
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314

15+
"go.opentelemetry.io/otel/attribute"
16+
ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
1417
"go.opentelemetry.io/otel/sdk/metric/metricdata"
1518
"go.opentelemetry.io/otel/sdk/resource"
1619
)
@@ -25,6 +28,8 @@ type reader struct {
2528
shutdownFunc func(context.Context) error
2629
}
2730

31+
const envVarResourceAttributes = "OTEL_RESOURCE_ATTRIBUTES"
32+
2833
var _ Reader = (*reader)(nil)
2934

3035
func (r *reader) aggregation(kind InstrumentKind) Aggregation { // nolint:revive // import-shadow for method scoped by type.
@@ -108,10 +113,63 @@ func TestUnifyMultiError(t *testing.T) {
108113
assert.Equal(t, unify(funcs)(context.Background()), target)
109114
}
110115

116+
func mergeResource(t *testing.T, r1, r2 *resource.Resource) *resource.Resource {
117+
r, err := resource.Merge(r1, r2)
118+
assert.NoError(t, err)
119+
return r
120+
}
121+
111122
func TestWithResource(t *testing.T) {
112-
res := resource.NewSchemaless()
113-
c := newConfig([]Option{WithResource(res)})
114-
assert.Same(t, res, c.res)
123+
store, err := ottest.SetEnvVariables(map[string]string{
124+
envVarResourceAttributes: "key=value,rk5=7",
125+
})
126+
require.NoError(t, err)
127+
defer func() { require.NoError(t, store.Restore()) }()
128+
129+
cases := []struct {
130+
name string
131+
options []Option
132+
want *resource.Resource
133+
msg string
134+
}{
135+
{
136+
name: "explicitly empty resource",
137+
options: []Option{WithResource(resource.Empty())},
138+
want: resource.Environment(),
139+
},
140+
{
141+
name: "uses default if no resource option",
142+
options: []Option{},
143+
want: resource.Default(),
144+
},
145+
{
146+
name: "explicit resource",
147+
options: []Option{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))},
148+
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))),
149+
},
150+
{
151+
name: "last resource wins",
152+
options: []Option{
153+
WithResource(resource.NewSchemaless(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))),
154+
WithResource(resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
155+
},
156+
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
157+
},
158+
{
159+
name: "overlapping attributes with environment resource",
160+
options: []Option{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))},
161+
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))),
162+
},
163+
}
164+
for _, tc := range cases {
165+
tc := tc
166+
t.Run(tc.name, func(t *testing.T) {
167+
got := newConfig(tc.options).res
168+
if diff := cmp.Diff(got, tc.want); diff != "" {
169+
t.Errorf("WithResource:\n -got +want %s", diff)
170+
}
171+
})
172+
}
115173
}
116174

117175
func TestWithReader(t *testing.T) {

sdk/metric/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.22
55
require (
66
github.com/go-logr/logr v1.4.2
77
github.com/go-logr/stdr v1.2.2
8+
github.com/google/go-cmp v0.6.0
89
github.com/stretchr/testify v1.9.0
910
go.opentelemetry.io/otel v1.29.0
1011
go.opentelemetry.io/otel/metric v1.29.0

0 commit comments

Comments
 (0)