You've already forked opentelemetry-go
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:
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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),
|
||||||
)))...)
|
)))...)
|
||||||
|
@ -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}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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")),
|
||||||
}
|
}
|
||||||
|
@ -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{
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
@ -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
81
sdk/resource/builtin.go
Normal 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
|
||||||
|
}
|
59
sdk/resource/builtin_test.go
Normal file
59
sdk/resource/builtin_test.go
Normal 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
150
sdk/resource/config.go
Normal 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
139
sdk/resource/config_test.go
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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 != "" {
|
||||||
|
Reference in New Issue
Block a user