mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-16 02:47:20 +02:00
Add Schema URL support to Resource (#1938)
This implements specification requirement: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#resource-creation - Changes `resource.NewWithAttributes` to require a schema URL. The old function is still available as `resource.NewSchemaless`. This is a breaking change. We want to encourage using schema URL and make it a conscious choice to have a resource without schema. - Merge schema URLs acccording to the spec in resource.Merge. - Several builtin resource detectors now correctly populate the schema URL. Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
parent
0827aa6265
commit
46d9687a35
@ -38,6 +38,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
These functions replace the `Set`, `Value`, `ContextWithValue`, `ContextWithoutValue`, and `ContextWithEmpty` functions from that package and directly work with the new `Baggage` type. (#1967)
|
||||
- The `OTEL_SERVICE_NAME` environment variable is the preferred source for `service.name`, used by the environment resource detector if a service name is present both there and in `OTEL_RESOURCE_ATTRIBUTES`. (#1969)
|
||||
- Creates package `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` implementing a HTTP `otlptrace.Client` and offers convenience functions, `NewExportPipeline` and `InstallNewPipeline`, to setup and install a `otlptrace.Exporter` in tracing. (#1963)
|
||||
- Changes `go.opentelemetry.io/otel/sdk/resource.NewWithAttributes` to require a schema URL. The old function is still available as `resource.NewSchemaless`. This is a breaking change. (#1938)
|
||||
- Several builtin resource detectors now correctly populate the schema URL. (#1938)
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -126,7 +126,7 @@ func convertResource(res *ocresource.Resource) *resource.Resource {
|
||||
for k, v := range res.Labels {
|
||||
labels = append(labels, attribute.KeyValue{Key: attribute.Key(k), Value: attribute.StringValue(v)})
|
||||
}
|
||||
return resource.NewWithAttributes(labels...)
|
||||
return resource.NewSchemaless(labels...)
|
||||
}
|
||||
|
||||
// convertDescriptor converts an OpenCensus Descriptor to an OpenTelemetry Descriptor
|
||||
|
@ -155,7 +155,7 @@ func TestExportMetrics(t *testing.T) {
|
||||
export.NewRecord(
|
||||
&basicDesc,
|
||||
attribute.EmptySet(),
|
||||
resource.NewWithAttributes(),
|
||||
resource.NewSchemaless(),
|
||||
&ocExactAggregator{
|
||||
points: []aggregation.Point{
|
||||
{
|
||||
@ -187,7 +187,7 @@ func TestExportMetrics(t *testing.T) {
|
||||
export.NewRecord(
|
||||
&basicDesc,
|
||||
attribute.EmptySet(),
|
||||
resource.NewWithAttributes(),
|
||||
resource.NewSchemaless(),
|
||||
&ocExactAggregator{
|
||||
points: []aggregation.Point{
|
||||
{
|
||||
@ -222,7 +222,7 @@ func TestExportMetrics(t *testing.T) {
|
||||
export.NewRecord(
|
||||
&basicDesc,
|
||||
attribute.EmptySet(),
|
||||
resource.NewWithAttributes(),
|
||||
resource.NewSchemaless(),
|
||||
&ocExactAggregator{
|
||||
points: []aggregation.Point{
|
||||
{
|
||||
@ -349,7 +349,7 @@ func TestConvertResource(t *testing.T) {
|
||||
input: &ocresource.Resource{
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
expected: resource.NewWithAttributes(),
|
||||
expected: resource.NewSchemaless(),
|
||||
},
|
||||
{
|
||||
desc: "resource with labels",
|
||||
@ -359,7 +359,7 @@ func TestConvertResource(t *testing.T) {
|
||||
"tick": "tock",
|
||||
},
|
||||
},
|
||||
expected: resource.NewWithAttributes(
|
||||
expected: resource.NewSchemaless(
|
||||
attribute.KeyValue{Key: attribute.Key("foo"), Value: attribute.StringValue("bar")},
|
||||
attribute.KeyValue{Key: attribute.Key("tick"), Value: attribute.StringValue("tock")},
|
||||
),
|
||||
|
@ -51,6 +51,7 @@ func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
|
||||
tracesdk.WithBatcher(exp),
|
||||
// Record information about this application in an Resource.
|
||||
tracesdk.WithResource(resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String(service),
|
||||
attribute.String("environment", environment),
|
||||
attribute.Int64("ID", id),
|
||||
|
@ -54,6 +54,7 @@ func initTracer(url string) func() {
|
||||
tp := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSpanProcessor(batcher),
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String("zipkin-test"),
|
||||
)),
|
||||
)
|
||||
|
@ -84,7 +84,7 @@ func TestPrometheusExporter(t *testing.T) {
|
||||
DefaultHistogramBoundaries: []float64{-0.5, 1},
|
||||
},
|
||||
controller.WithCollectPeriod(0),
|
||||
controller.WithResource(resource.NewWithAttributes(attribute.String("R", "V"))),
|
||||
controller.WithResource(resource.NewSchemaless(attribute.String("R", "V"))),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -67,7 +67,7 @@ func (OneRecordCheckpointSet) ForEach(kindSelector exportmetric.ExportKindSelect
|
||||
metric.CounterInstrumentKind,
|
||||
number.Int64Kind,
|
||||
)
|
||||
res := resource.NewWithAttributes(attribute.String("a", "b"))
|
||||
res := resource.NewSchemaless(attribute.String("a", "b"))
|
||||
agg := sum.New(1)
|
||||
if err := agg[0].Update(context.Background(), number.NewInt64Number(42), &desc); err != nil {
|
||||
return err
|
||||
@ -106,7 +106,7 @@ func SingleReadOnlySpan() []tracesdk.ReadOnlySpan {
|
||||
DroppedEvents: 0,
|
||||
DroppedLinks: 0,
|
||||
ChildSpanCount: 0,
|
||||
Resource: resource.NewWithAttributes(attribute.String("a", "b")),
|
||||
Resource: resource.NewSchemaless(attribute.String("a", "b")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "bar",
|
||||
Version: "0.0.0",
|
||||
|
@ -50,13 +50,13 @@ func RunEndToEndTest(ctx context.Context, t *testing.T, exp *otlp.Exporter, mcTr
|
||||
),
|
||||
}
|
||||
tp1 := sdktrace.NewTracerProvider(append(pOpts,
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
sdktrace.WithResource(resource.NewSchemaless(
|
||||
attribute.String("rk1", "rv11)"),
|
||||
attribute.Int64("rk2", 5),
|
||||
)))...)
|
||||
|
||||
tp2 := sdktrace.NewTracerProvider(append(pOpts,
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
sdktrace.WithResource(resource.NewSchemaless(
|
||||
attribute.String("rk1", "rv12)"),
|
||||
attribute.Float64("rk3", 6.5),
|
||||
)))...)
|
||||
|
@ -167,6 +167,7 @@ func sink(ctx context.Context, in <-chan result) ([]*metricpb.ResourceMetrics, e
|
||||
Resource *resourcepb.Resource
|
||||
// Group by instrumentation library name and then the MetricDescriptor.
|
||||
InstrumentationLibraryBatches map[instrumentation.Library]map[string]*metricpb.Metric
|
||||
SchemaURL string
|
||||
}
|
||||
|
||||
// group by unique Resource string.
|
||||
@ -184,6 +185,9 @@ func sink(ctx context.Context, in <-chan result) ([]*metricpb.ResourceMetrics, e
|
||||
Resource: Resource(res.Resource),
|
||||
InstrumentationLibraryBatches: make(map[instrumentation.Library]map[string]*metricpb.Metric),
|
||||
}
|
||||
if res.Resource != nil {
|
||||
rb.SchemaURL = res.Resource.SchemaURL()
|
||||
}
|
||||
grouped[rID] = rb
|
||||
}
|
||||
|
||||
@ -220,6 +224,7 @@ func sink(ctx context.Context, in <-chan result) ([]*metricpb.ResourceMetrics, e
|
||||
|
||||
var rms []*metricpb.ResourceMetrics
|
||||
for _, rb := range grouped {
|
||||
// TODO: populate ResourceMetrics.SchemaURL when the field is added to the Protobuf message.
|
||||
rm := &metricpb.ResourceMetrics{Resource: rb.Resource}
|
||||
for il, mb := range rb.InstrumentationLibraryBatches {
|
||||
ilm := &metricpb.InstrumentationLibraryMetrics{
|
||||
|
@ -40,7 +40,7 @@ func TestEmptyResource(t *testing.T) {
|
||||
func TestResourceAttributes(t *testing.T) {
|
||||
attrs := []attribute.KeyValue{attribute.Int("one", 1), attribute.Int("two", 2)}
|
||||
|
||||
got := Resource(resource.NewWithAttributes(attrs...)).GetAttributes()
|
||||
got := Resource(resource.NewSchemaless(attrs...)).GetAttributes()
|
||||
if !assert.Len(t, attrs, 2) {
|
||||
return
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ func Spans(sdl []tracesdk.ReadOnlySpan) []*tracepb.ResourceSpans {
|
||||
Resource: Resource(sd.Resource()),
|
||||
InstrumentationLibrarySpans: []*tracepb.InstrumentationLibrarySpans{ils},
|
||||
}
|
||||
// TODO: populate ResourceSpans.SchemaURL when the field is added to the Protobuf message.
|
||||
rsm[rKey] = rs
|
||||
continue
|
||||
}
|
||||
|
@ -261,7 +261,7 @@ func TestSpanData(t *testing.T) {
|
||||
DroppedAttributes: 1,
|
||||
DroppedEvents: 2,
|
||||
DroppedLinks: 3,
|
||||
Resource: resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)),
|
||||
Resource: resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "go.opentelemetry.io/test/otel",
|
||||
Version: "v0.0.1",
|
||||
|
@ -80,8 +80,8 @@ var (
|
||||
baseKeyValues = []attribute.KeyValue{attribute.String("host", "test.com")}
|
||||
cpuKey = attribute.Key("CPU")
|
||||
|
||||
testInstA = resource.NewWithAttributes(attribute.String("instance", "tester-a"))
|
||||
testInstB = resource.NewWithAttributes(attribute.String("instance", "tester-b"))
|
||||
testInstA = resource.NewSchemaless(attribute.String("instance", "tester-a"))
|
||||
testInstB = resource.NewSchemaless(attribute.String("instance", "tester-b"))
|
||||
|
||||
testHistogramBoundaries = []float64{2.0, 4.0, 8.0}
|
||||
|
||||
|
@ -73,7 +73,7 @@ func TestExportSpans(t *testing.T) {
|
||||
Code: codes.Ok,
|
||||
Description: "Ok",
|
||||
},
|
||||
Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")),
|
||||
Resource: resource.NewSchemaless(attribute.String("instance", "tester-a")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "lib-a",
|
||||
Version: "v0.1.0",
|
||||
@ -97,7 +97,7 @@ func TestExportSpans(t *testing.T) {
|
||||
Code: codes.Ok,
|
||||
Description: "Ok",
|
||||
},
|
||||
Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")),
|
||||
Resource: resource.NewSchemaless(attribute.String("instance", "tester-a")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "lib-b",
|
||||
Version: "v0.1.0",
|
||||
@ -126,7 +126,7 @@ func TestExportSpans(t *testing.T) {
|
||||
Code: codes.Ok,
|
||||
Description: "Ok",
|
||||
},
|
||||
Resource: resource.NewWithAttributes(attribute.String("instance", "tester-a")),
|
||||
Resource: resource.NewSchemaless(attribute.String("instance", "tester-a")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "lib-a",
|
||||
Version: "v0.1.0",
|
||||
@ -150,7 +150,7 @@ func TestExportSpans(t *testing.T) {
|
||||
Code: codes.Error,
|
||||
Description: "Unauthenticated",
|
||||
},
|
||||
Resource: resource.NewWithAttributes(attribute.String("instance", "tester-b")),
|
||||
Resource: resource.NewSchemaless(attribute.String("instance", "tester-b")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "lib-a",
|
||||
Version: "v1.1.0",
|
||||
|
@ -53,7 +53,7 @@ func SingleReadOnlySpan() []tracesdk.ReadOnlySpan {
|
||||
DroppedEvents: 0,
|
||||
DroppedLinks: 0,
|
||||
ChildSpanCount: 0,
|
||||
Resource: resource.NewWithAttributes(attribute.String("a", "b")),
|
||||
Resource: resource.NewSchemaless(attribute.String("a", "b")),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "bar",
|
||||
Version: "0.0.0",
|
||||
|
@ -40,13 +40,13 @@ func RunEndToEndTest(ctx context.Context, t *testing.T, exp *otlptrace.Exporter,
|
||||
),
|
||||
}
|
||||
tp1 := sdktrace.NewTracerProvider(append(pOpts,
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
sdktrace.WithResource(resource.NewSchemaless(
|
||||
attribute.String("rk1", "rv11)"),
|
||||
attribute.Int64("rk2", 5),
|
||||
)))...)
|
||||
|
||||
tp2 := sdktrace.NewTracerProvider(append(pOpts,
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
sdktrace.WithResource(resource.NewSchemaless(
|
||||
attribute.String("rk1", "rv12)"),
|
||||
attribute.Float64("rk3", 6.5),
|
||||
)))...)
|
||||
|
@ -40,7 +40,7 @@ func TestEmptyResource(t *testing.T) {
|
||||
func TestResourceAttributes(t *testing.T) {
|
||||
attrs := []attribute.KeyValue{attribute.Int("one", 1), attribute.Int("two", 2)}
|
||||
|
||||
got := Resource(resource.NewWithAttributes(attrs...)).GetAttributes()
|
||||
got := Resource(resource.NewSchemaless(attrs...)).GetAttributes()
|
||||
if !assert.Len(t, attrs, 2) {
|
||||
return
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ func TestSpanData(t *testing.T) {
|
||||
DroppedAttributes: 1,
|
||||
DroppedEvents: 2,
|
||||
DroppedLinks: 3,
|
||||
Resource: resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)),
|
||||
Resource: resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)),
|
||||
InstrumentationLibrary: instrumentation.Library{
|
||||
Name: "go.opentelemetry.io/test/otel",
|
||||
Version: "v0.0.1",
|
||||
|
@ -46,7 +46,7 @@ type testFixture struct {
|
||||
output *bytes.Buffer
|
||||
}
|
||||
|
||||
var testResource = resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
var testResource = resource.NewSchemaless(attribute.String("R", "V"))
|
||||
|
||||
func newFixture(t *testing.T, opts ...stdout.Option) testFixture {
|
||||
buf := &bytes.Buffer{}
|
||||
@ -270,11 +270,11 @@ func TestStdoutResource(t *testing.T) {
|
||||
}
|
||||
testCases := []testCase{
|
||||
newCase("R1=V1,R2=V2,A=B,C=D",
|
||||
resource.NewWithAttributes(attribute.String("R1", "V1"), attribute.String("R2", "V2")),
|
||||
resource.NewSchemaless(attribute.String("R1", "V1"), attribute.String("R2", "V2")),
|
||||
attribute.String("A", "B"),
|
||||
attribute.String("C", "D")),
|
||||
newCase("R1=V1,R2=V2",
|
||||
resource.NewWithAttributes(attribute.String("R1", "V1"), attribute.String("R2", "V2")),
|
||||
resource.NewSchemaless(attribute.String("R1", "V1"), attribute.String("R2", "V2")),
|
||||
),
|
||||
newCase("A=B,C=D",
|
||||
nil,
|
||||
@ -284,7 +284,7 @@ func TestStdoutResource(t *testing.T) {
|
||||
// We explicitly do not de-duplicate between resources
|
||||
// and metric labels in this exporter.
|
||||
newCase("R1=V1,R2=V2,R1=V3,R2=V4",
|
||||
resource.NewWithAttributes(attribute.String("R1", "V1"), attribute.String("R2", "V2")),
|
||||
resource.NewSchemaless(attribute.String("R1", "V1"), attribute.String("R2", "V2")),
|
||||
attribute.String("R1", "V3"),
|
||||
attribute.String("R2", "V4")),
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func TestExporter_ExportSpan(t *testing.T) {
|
||||
traceState, _ := oteltest.TraceStateFromKeyValues(attribute.String("key", "val"))
|
||||
keyValue := "value"
|
||||
doubleValue := 123.456
|
||||
resource := resource.NewWithAttributes(attribute.String("rk1", "rv11"))
|
||||
resource := resource.NewSchemaless(attribute.String("rk1", "rv11"))
|
||||
|
||||
ro := tracetest.SpanStubs{
|
||||
{
|
||||
|
@ -166,7 +166,7 @@ func TestExporterExportSpan(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
tp := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithBatcher(exp),
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
sdktrace.WithResource(resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String(serviceName),
|
||||
attribute.String(tagKey, tagVal),
|
||||
)),
|
||||
@ -421,7 +421,7 @@ func Test_spanSnapshotToThrift(t *testing.T) {
|
||||
Name: "/foo",
|
||||
StartTime: now,
|
||||
EndTime: now,
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
attribute.String("rk1", rv1),
|
||||
attribute.Int64("rk2", rv2),
|
||||
semconv.ServiceNameKey.String("service name"),
|
||||
@ -500,7 +500,7 @@ func TestExporterExportSpansHonorsCancel(t *testing.T) {
|
||||
ss := tracetest.SpanStubs{
|
||||
{
|
||||
Name: "s1",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
@ -509,7 +509,7 @@ func TestExporterExportSpansHonorsCancel(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "s2",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r2").String("v2"),
|
||||
),
|
||||
@ -530,7 +530,7 @@ func TestExporterExportSpansHonorsTimeout(t *testing.T) {
|
||||
ss := tracetest.SpanStubs{
|
||||
{
|
||||
Name: "s1",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
@ -539,7 +539,7 @@ func TestExporterExportSpansHonorsTimeout(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "s2",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r2").String("v2"),
|
||||
),
|
||||
@ -577,7 +577,7 @@ func TestJaegerBatchList(t *testing.T) {
|
||||
roSpans: []sdktrace.ReadOnlySpan{
|
||||
tracetest.SpanStub{
|
||||
Name: "s1",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
@ -611,7 +611,7 @@ func TestJaegerBatchList(t *testing.T) {
|
||||
roSpans: tracetest.SpanStubs{
|
||||
{
|
||||
Name: "s1",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
@ -620,7 +620,7 @@ func TestJaegerBatchList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "s2",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
@ -629,7 +629,7 @@ func TestJaegerBatchList(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Name: "s3",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("name"),
|
||||
attribute.Key("r2").String("v2"),
|
||||
),
|
||||
@ -686,7 +686,7 @@ func TestJaegerBatchList(t *testing.T) {
|
||||
roSpans: tracetest.SpanStubs{
|
||||
{
|
||||
Name: "s1",
|
||||
Resource: resource.NewWithAttributes(
|
||||
Resource: resource.NewSchemaless(
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
StartTime: now,
|
||||
@ -736,7 +736,7 @@ func TestProcess(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "resources contain service name",
|
||||
res: resource.NewWithAttributes(
|
||||
res: resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("service name"),
|
||||
attribute.Key("r1").String("v1"),
|
||||
),
|
||||
@ -750,7 +750,7 @@ func TestProcess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "resources don't have service name",
|
||||
res: resource.NewWithAttributes(attribute.Key("r1").String("v1")),
|
||||
res: resource.NewSchemaless(attribute.Key("r1").String("v1")),
|
||||
defaultServiceName: "default service name",
|
||||
expectedProcess: &gen.Process{
|
||||
ServiceName: "default service name",
|
||||
|
@ -37,7 +37,7 @@ import (
|
||||
)
|
||||
|
||||
func TestModelConversion(t *testing.T) {
|
||||
resource := resource.NewWithAttributes(
|
||||
resource := resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("model-test"),
|
||||
)
|
||||
|
||||
|
@ -230,7 +230,7 @@ func logStoreLogger(s *logStore) *log.Logger {
|
||||
}
|
||||
|
||||
func TestExportSpans(t *testing.T) {
|
||||
resource := resource.NewWithAttributes(
|
||||
resource := resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("exporter-test"),
|
||||
)
|
||||
|
||||
@ -400,7 +400,7 @@ func TestNewExportPipelineWithOptions(t *testing.T) {
|
||||
|
||||
tp, err := NewExportPipeline(collector.url,
|
||||
WithSDKOptions(
|
||||
sdktrace.WithResource(resource.NewWithAttributes(
|
||||
sdktrace.WithResource(resource.NewSchemaless(
|
||||
semconv.ServiceNameKey.String("zipkin-test"),
|
||||
)),
|
||||
sdktrace.WithSpanLimits(sdktrace.SpanLimits{
|
||||
|
@ -17,6 +17,7 @@ package basic // import "go.opentelemetry.io/otel/sdk/metric/controller/basic"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
export "go.opentelemetry.io/otel/sdk/export/metric"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
@ -67,7 +68,10 @@ type Option interface {
|
||||
// WithResource sets the Resource configuration option of a Config by merging it
|
||||
// with the Resource configuration in the environment.
|
||||
func WithResource(r *resource.Resource) Option {
|
||||
res := resource.Merge(resource.Environment(), r)
|
||||
res, err := resource.Merge(resource.Environment(), r)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
return resourceOption{res}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
func TestWithResource(t *testing.T) {
|
||||
r := resource.NewWithAttributes(attribute.String("A", "a"))
|
||||
r := resource.NewSchemaless(attribute.String("A", "a"))
|
||||
|
||||
c := &config{}
|
||||
WithResource(r).apply(c)
|
||||
|
@ -82,18 +82,18 @@ func TestControllerUsesResource(t *testing.T) {
|
||||
wanted: resource.Default().Encoded(attribute.DefaultEncoder())},
|
||||
{
|
||||
name: "explicit resource",
|
||||
options: []controller.Option{controller.WithResource(resource.NewWithAttributes(attribute.String("R", "S")))},
|
||||
options: []controller.Option{controller.WithResource(resource.NewSchemaless(attribute.String("R", "S")))},
|
||||
wanted: "R=S,T=U,key=value"},
|
||||
{
|
||||
name: "last resource wins",
|
||||
options: []controller.Option{
|
||||
controller.WithResource(resource.Default()),
|
||||
controller.WithResource(resource.NewWithAttributes(attribute.String("R", "S"))),
|
||||
controller.WithResource(resource.NewSchemaless(attribute.String("R", "S"))),
|
||||
},
|
||||
wanted: "R=S,T=U,key=value"},
|
||||
{
|
||||
name: "overlapping attributes with environment resource",
|
||||
options: []controller.Option{controller.WithResource(resource.NewWithAttributes(attribute.String("T", "V")))},
|
||||
options: []controller.Option{controller.WithResource(resource.NewSchemaless(attribute.String("T", "V")))},
|
||||
wanted: "T=V,key=value"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
|
@ -37,7 +37,7 @@ import (
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
var testResource = resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
var testResource = resource.NewSchemaless(attribute.String("R", "V"))
|
||||
|
||||
type handler struct {
|
||||
sync.Mutex
|
||||
|
@ -35,7 +35,7 @@ import (
|
||||
)
|
||||
|
||||
var Must = metric.Must
|
||||
var testResource = resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
var testResource = resource.NewSchemaless(attribute.String("R", "V"))
|
||||
|
||||
type handler struct {
|
||||
sync.Mutex
|
||||
|
@ -126,7 +126,7 @@ func testProcessor(
|
||||
// Note: this selector uses the instrument name to dictate
|
||||
// aggregation kind.
|
||||
selector := processorTest.AggregatorSelector()
|
||||
res := resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
res := resource.NewSchemaless(attribute.String("R", "V"))
|
||||
|
||||
labs1 := []attribute.KeyValue{attribute.String("L1", "V")}
|
||||
labs2 := []attribute.KeyValue{attribute.String("L2", "V")}
|
||||
@ -368,7 +368,7 @@ func TestBasicTimestamps(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStatefulNoMemoryCumulative(t *testing.T) {
|
||||
res := resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
res := resource.NewSchemaless(attribute.String("R", "V"))
|
||||
ekindSel := export.CumulativeExportKindSelector()
|
||||
|
||||
desc := metric.NewDescriptor("inst.sum", metric.CounterInstrumentKind, number.Int64Kind)
|
||||
@ -402,7 +402,7 @@ func TestStatefulNoMemoryCumulative(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStatefulNoMemoryDelta(t *testing.T) {
|
||||
res := resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
res := resource.NewSchemaless(attribute.String("R", "V"))
|
||||
ekindSel := export.DeltaExportKindSelector()
|
||||
|
||||
desc := metric.NewDescriptor("inst.sum", metric.SumObserverInstrumentKind, number.Int64Kind)
|
||||
@ -441,7 +441,7 @@ func TestMultiObserverSum(t *testing.T) {
|
||||
export.DeltaExportKindSelector(),
|
||||
} {
|
||||
|
||||
res := resource.NewWithAttributes(attribute.String("R", "V"))
|
||||
res := resource.NewSchemaless(attribute.String("R", "V"))
|
||||
desc := metric.NewDescriptor("observe.sum", metric.SumObserverInstrumentKind, number.Int64Kind)
|
||||
selector := processorTest.AggregatorSelector()
|
||||
|
||||
|
@ -32,7 +32,7 @@ func generateTestData(proc export.Processor) {
|
||||
ctx := context.Background()
|
||||
accum := metricsdk.NewAccumulator(
|
||||
proc,
|
||||
resource.NewWithAttributes(attribute.String("R", "V")),
|
||||
resource.NewSchemaless(attribute.String("R", "V")),
|
||||
)
|
||||
meter := metric.WrapMeterImpl(accum, "testing")
|
||||
|
||||
|
@ -75,7 +75,7 @@ func TestFilterProcessor(t *testing.T) {
|
||||
)
|
||||
accum := metricsdk.NewAccumulator(
|
||||
reducer.New(testFilter{}, processorTest.Checkpointer(testProc)),
|
||||
resource.NewWithAttributes(attribute.String("R", "V")),
|
||||
resource.NewSchemaless(attribute.String("R", "V")),
|
||||
)
|
||||
generateData(accum)
|
||||
|
||||
@ -92,7 +92,7 @@ func TestFilterBasicProcessor(t *testing.T) {
|
||||
basicProc := basic.New(processorTest.AggregatorSelector(), export.CumulativeExportKindSelector())
|
||||
accum := metricsdk.NewAccumulator(
|
||||
reducer.New(testFilter{}, basicProc),
|
||||
resource.NewWithAttributes(attribute.String("R", "V")),
|
||||
resource.NewSchemaless(attribute.String("R", "V")),
|
||||
)
|
||||
exporter := processorTest.NewExporter(basicProc, attribute.DefaultEncoder())
|
||||
|
||||
|
@ -53,7 +53,10 @@ func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
autoDetectedRes = Merge(autoDetectedRes, res)
|
||||
autoDetectedRes, err = Merge(autoDetectedRes, res)
|
||||
if err != nil {
|
||||
errInfo = append(errInfo, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var aggregatedError error
|
||||
|
63
sdk/resource/auto_test.go
Normal file
63
sdk/resource/auto_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
// 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/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
)
|
||||
|
||||
func TestDetect(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
schema1, schema2 string
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
name: "different schema urls",
|
||||
schema1: "https://opentelemetry.io/schemas/1.3.0",
|
||||
schema2: "https://opentelemetry.io/schemas/1.4.0",
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "same schema url",
|
||||
schema1: "https://opentelemetry.io/schemas/1.4.0",
|
||||
schema2: "https://opentelemetry.io/schemas/1.4.0",
|
||||
isErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) {
|
||||
d1 := resource.StringDetector(c.schema1, semconv.HostNameKey, os.Hostname)
|
||||
d2 := resource.StringDetector(c.schema2, semconv.HostNameKey, os.Hostname)
|
||||
r, err := resource.Detect(context.Background(), d1, d2)
|
||||
assert.NotNil(t, r)
|
||||
if c.isErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ func makeLabels(n int) (_, _ *resource.Resource) {
|
||||
}
|
||||
|
||||
}
|
||||
return resource.NewWithAttributes(l1...), resource.NewWithAttributes(l2...)
|
||||
return resource.NewSchemaless(l1...), resource.NewSchemaless(l2...)
|
||||
}
|
||||
|
||||
func benchmarkMergeResource(b *testing.B, size int) {
|
||||
@ -57,7 +57,7 @@ func benchmarkMergeResource(b *testing.B, size int) {
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = resource.Merge(r1, r2)
|
||||
_, _ = resource.Merge(r1, r2)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,9 @@ type (
|
||||
host struct{}
|
||||
|
||||
stringDetector struct {
|
||||
K attribute.Key
|
||||
F func() (string, error)
|
||||
schemaURL string
|
||||
K attribute.Key
|
||||
F func() (string, error)
|
||||
}
|
||||
|
||||
defaultServiceNameDetector struct{}
|
||||
@ -58,6 +59,7 @@ var (
|
||||
// Detect returns a *Resource that describes the OpenTelemetry SDK used.
|
||||
func (telemetrySDK) Detect(context.Context) (*Resource, error) {
|
||||
return NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.TelemetrySDKNameKey.String("opentelemetry"),
|
||||
semconv.TelemetrySDKLanguageKey.String("go"),
|
||||
semconv.TelemetrySDKVersionKey.String(otel.Version()),
|
||||
@ -66,13 +68,14 @@ 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) {
|
||||
return StringDetector(semconv.HostNameKey, os.Hostname).Detect(ctx)
|
||||
return StringDetector(semconv.SchemaURL, 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 attribute.Key, f func() (string, error)) Detector {
|
||||
return stringDetector{K: k, F: f}
|
||||
// containing the string as a value corresponding to k. The resulting Resource
|
||||
// will have the specified schemaURL.
|
||||
func StringDetector(schemaURL string, k attribute.Key, f func() (string, error)) Detector {
|
||||
return stringDetector{schemaURL: schemaURL, K: k, F: f}
|
||||
}
|
||||
|
||||
// Detect implements Detector.
|
||||
@ -85,12 +88,13 @@ func (sd stringDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
if !a.Valid() {
|
||||
return nil, fmt.Errorf("invalid attribute: %q -> %q", a.Key, a.Value.Emit())
|
||||
}
|
||||
return NewWithAttributes(sd.K.String(value)), nil
|
||||
return NewWithAttributes(sd.schemaURL, sd.K.String(value)), nil
|
||||
}
|
||||
|
||||
// Detect implements Detector
|
||||
func (defaultServiceNameDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
return StringDetector(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey,
|
||||
func() (string, error) {
|
||||
executable, err := os.Executable()
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
|
||||
func TestBuiltinStringDetector(t *testing.T) {
|
||||
E := fmt.Errorf("no K")
|
||||
res, err := resource.StringDetector(attribute.Key("K"), func() (string, error) {
|
||||
res, err := resource.StringDetector("", attribute.Key("K"), func() (string, error) {
|
||||
return "", E
|
||||
}).Detect(context.Background())
|
||||
require.True(t, errors.Is(err, E))
|
||||
@ -44,14 +44,14 @@ func TestStringDetectorErrors(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
desc: "explicit error from func should be returned",
|
||||
s: resource.StringDetector(attribute.Key("K"), func() (string, error) {
|
||||
s: resource.StringDetector("", attribute.Key("K"), func() (string, error) {
|
||||
return "", fmt.Errorf("K-IS-MISSING")
|
||||
}),
|
||||
errContains: "K-IS-MISSING",
|
||||
},
|
||||
{
|
||||
desc: "empty key is an invalid",
|
||||
s: resource.StringDetector(attribute.Key(""), func() (string, error) {
|
||||
s: resource.StringDetector("", attribute.Key(""), func() (string, error) {
|
||||
return "not-empty", nil
|
||||
}),
|
||||
errContains: "invalid attribute: \"\" -> \"not-empty\"",
|
||||
|
@ -24,6 +24,8 @@ import (
|
||||
type config struct {
|
||||
// detectors that will be evaluated.
|
||||
detectors []Detector
|
||||
// SchemaURL to associate with the Resource.
|
||||
schemaURL string
|
||||
}
|
||||
|
||||
// Option is the interface that applies a configuration option.
|
||||
@ -42,7 +44,7 @@ type detectAttributes struct {
|
||||
}
|
||||
|
||||
func (d detectAttributes) Detect(context.Context) (*Resource, error) {
|
||||
return NewWithAttributes(d.attributes...), nil
|
||||
return NewSchemaless(d.attributes...), nil
|
||||
}
|
||||
|
||||
// WithDetectors adds detectors to be evaluated for the configured resource.
|
||||
@ -65,7 +67,7 @@ func WithBuiltinDetectors() Option {
|
||||
fromEnv{})
|
||||
}
|
||||
|
||||
// WithFromEnv adds attributes from environment variables to the configured resource.
|
||||
// WithFromEnv adds attributes from environment variables to the configured resource.
|
||||
func WithFromEnv() Option {
|
||||
return WithDetectors(fromEnv{})
|
||||
}
|
||||
@ -79,3 +81,14 @@ func WithHost() Option {
|
||||
func WithTelemetrySDK() Option {
|
||||
return WithDetectors(telemetrySDK{})
|
||||
}
|
||||
|
||||
// WithSchemaURL sets the schema URL for the configured resource.
|
||||
func WithSchemaURL(schemaURL string) Option {
|
||||
return schemaURLOption(schemaURL)
|
||||
}
|
||||
|
||||
type schemaURLOption string
|
||||
|
||||
func (o schemaURLOption) apply(cfg *config) {
|
||||
cfg.schemaURL = string(o)
|
||||
}
|
||||
|
@ -57,14 +57,22 @@ func (fromEnv) Detect(context.Context) (*Resource, error) {
|
||||
var res *Resource
|
||||
|
||||
if svcName != "" {
|
||||
res = NewWithAttributes(semconv.ServiceNameKey.String(svcName))
|
||||
res = NewSchemaless(semconv.ServiceNameKey.String(svcName))
|
||||
}
|
||||
|
||||
r2, err := constructOTResources(attrs)
|
||||
|
||||
// Ensure that the resource with the service name from OTEL_SERVICE_NAME
|
||||
// takes precedence, if it was defined.
|
||||
return Merge(r2, res), err
|
||||
res, err2 := Merge(r2, res)
|
||||
|
||||
if err == nil {
|
||||
err = err2
|
||||
} else if err2 != nil {
|
||||
err = fmt.Errorf("detecting resources: %s", []string{err.Error(), err2.Error()})
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func constructOTResources(s string) (*Resource, error) {
|
||||
@ -84,5 +92,5 @@ func constructOTResources(s string) (*Resource, error) {
|
||||
if len(invalid) > 0 {
|
||||
err = fmt.Errorf("%w: %v", errMissingValue, invalid)
|
||||
}
|
||||
return NewWithAttributes(attrs...), err
|
||||
return NewSchemaless(attrs...), err
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ func TestDetectOnePair(t *testing.T) {
|
||||
detector := &fromEnv{}
|
||||
res, err := detector.Detect(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, NewWithAttributes(attribute.String("key", "value")), res)
|
||||
assert.Equal(t, NewSchemaless(attribute.String("key", "value")), res)
|
||||
}
|
||||
|
||||
func TestDetectMultiPairs(t *testing.T) {
|
||||
@ -51,7 +51,7 @@ func TestDetectMultiPairs(t *testing.T) {
|
||||
detector := &fromEnv{}
|
||||
res, err := detector.Detect(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, res, NewWithAttributes(
|
||||
assert.Equal(t, res, NewSchemaless(
|
||||
attribute.String("key", "value"),
|
||||
attribute.String("k", "v"),
|
||||
attribute.String("a", "x"),
|
||||
@ -83,7 +83,7 @@ func TestMissingKeyError(t *testing.T) {
|
||||
res, err := detector.Detect(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err, fmt.Errorf("%w: %v", errMissingValue, "[key]"))
|
||||
assert.Equal(t, res, NewWithAttributes(
|
||||
assert.Equal(t, res, NewSchemaless(
|
||||
attribute.String("key", "value"),
|
||||
))
|
||||
}
|
||||
@ -99,7 +99,7 @@ func TestDetectServiceNameFromEnv(t *testing.T) {
|
||||
detector := &fromEnv{}
|
||||
res, err := detector.Detect(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, res, NewWithAttributes(
|
||||
assert.Equal(t, res, NewSchemaless(
|
||||
attribute.String("key", "value"),
|
||||
semconv.ServiceNameKey.String("bar"),
|
||||
))
|
||||
|
@ -29,6 +29,7 @@ func (osTypeDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
osType := runtimeOS()
|
||||
|
||||
return NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.OSTypeKey.String(strings.ToLower(osType)),
|
||||
), nil
|
||||
}
|
||||
|
@ -115,14 +115,14 @@ type processRuntimeDescriptionDetector struct{}
|
||||
// Detect returns a *Resource that describes the process identifier (PID) of the
|
||||
// executing process.
|
||||
func (processPIDDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
return NewWithAttributes(semconv.ProcessPIDKey.Int(pid())), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessPIDKey.Int(pid())), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes the name of the process executable.
|
||||
func (processExecutableNameDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
executableName := filepath.Base(commandArgs()[0])
|
||||
|
||||
return NewWithAttributes(semconv.ProcessExecutableNameKey.String(executableName)), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessExecutableNameKey.String(executableName)), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes the full path of the process executable.
|
||||
@ -132,13 +132,13 @@ func (processExecutablePathDetector) Detect(ctx context.Context) (*Resource, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewWithAttributes(semconv.ProcessExecutablePathKey.String(executablePath)), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessExecutablePathKey.String(executablePath)), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes all the command arguments as received
|
||||
// by the process.
|
||||
func (processCommandArgsDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
return NewWithAttributes(semconv.ProcessCommandArgsKey.Array(commandArgs())), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessCommandArgsKey.Array(commandArgs())), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes the username of the user that owns the
|
||||
@ -149,18 +149,18 @@ func (processOwnerDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewWithAttributes(semconv.ProcessOwnerKey.String(owner.Username)), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessOwnerKey.String(owner.Username)), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes the name of the compiler used to compile
|
||||
// this process image.
|
||||
func (processRuntimeNameDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
return NewWithAttributes(semconv.ProcessRuntimeNameKey.String(runtimeName())), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessRuntimeNameKey.String(runtimeName())), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes the version of the runtime of this process.
|
||||
func (processRuntimeVersionDetector) Detect(ctx context.Context) (*Resource, error) {
|
||||
return NewWithAttributes(semconv.ProcessRuntimeVersionKey.String(runtimeVersion())), nil
|
||||
return NewWithAttributes(semconv.SchemaURL, semconv.ProcessRuntimeVersionKey.String(runtimeVersion())), nil
|
||||
}
|
||||
|
||||
// Detect returns a *Resource that describes the runtime of this process.
|
||||
@ -169,6 +169,7 @@ func (processRuntimeDescriptionDetector) Detect(ctx context.Context) (*Resource,
|
||||
"go version %s %s/%s", runtimeVersion(), runtimeOS(), runtimeArch())
|
||||
|
||||
return NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ProcessRuntimeDescriptionKey.String(runtimeDescription),
|
||||
), nil
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ package resource // import "go.opentelemetry.io/otel/sdk/resource"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
@ -29,7 +31,8 @@ import (
|
||||
// (`*resource.Resource`). The `nil` value is equivalent to an empty
|
||||
// Resource.
|
||||
type Resource struct {
|
||||
attrs attribute.Set
|
||||
attrs attribute.Set
|
||||
schemaURL string
|
||||
}
|
||||
|
||||
var (
|
||||
@ -43,6 +46,10 @@ var (
|
||||
}(Detect(context.Background(), defaultServiceNameDetector{}, fromEnv{}, telemetrySDK{}))
|
||||
)
|
||||
|
||||
var (
|
||||
errMergeConflictSchemaURL = errors.New("cannot merge resource due to conflicting Schema URL")
|
||||
)
|
||||
|
||||
// New returns a Resource combined from the user-provided detectors.
|
||||
func New(ctx context.Context, opts ...Option) (*Resource, error) {
|
||||
cfg := config{}
|
||||
@ -50,13 +57,34 @@ func New(ctx context.Context, opts ...Option) (*Resource, error) {
|
||||
opt.apply(&cfg)
|
||||
}
|
||||
|
||||
return Detect(ctx, cfg.detectors...)
|
||||
resource, err := Detect(ctx, cfg.detectors...)
|
||||
|
||||
var err2 error
|
||||
resource, err2 = Merge(resource, &Resource{schemaURL: cfg.schemaURL})
|
||||
if err == nil {
|
||||
err = err2
|
||||
} else if err2 != nil {
|
||||
err = fmt.Errorf("detecting resources: %s", []string{err.Error(), err2.Error()})
|
||||
}
|
||||
|
||||
return resource, err
|
||||
}
|
||||
|
||||
// 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.
|
||||
func NewWithAttributes(attrs ...attribute.KeyValue) *Resource {
|
||||
// NewWithAttributes creates a resource from attrs and associates the resource with a
|
||||
// schema URL. If attrs contains duplicate keys, the last value will be used. If attrs
|
||||
// contains any invalid items those items will be dropped. The attrs are assumed to be
|
||||
// in a schema identified by schemaURL.
|
||||
func NewWithAttributes(schemaURL string, attrs ...attribute.KeyValue) *Resource {
|
||||
resource := NewSchemaless(attrs...)
|
||||
resource.schemaURL = schemaURL
|
||||
return resource
|
||||
}
|
||||
|
||||
// NewSchemaless 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. The resource will not be associated with a schema URL. If the schema
|
||||
// of the attrs is known use NewWithAttributes instead.
|
||||
func NewSchemaless(attrs ...attribute.KeyValue) *Resource {
|
||||
if len(attrs) == 0 {
|
||||
return &emptyResource
|
||||
}
|
||||
@ -72,7 +100,7 @@ func NewWithAttributes(attrs ...attribute.KeyValue) *Resource {
|
||||
return &emptyResource
|
||||
}
|
||||
|
||||
return &Resource{s} //nolint
|
||||
return &Resource{attrs: s} //nolint
|
||||
}
|
||||
|
||||
// String implements the Stringer interface and provides a
|
||||
@ -96,6 +124,10 @@ func (r *Resource) Attributes() []attribute.KeyValue {
|
||||
return r.attrs.ToSlice()
|
||||
}
|
||||
|
||||
func (r *Resource) SchemaURL() string {
|
||||
return r.schemaURL
|
||||
}
|
||||
|
||||
// Iter returns an interator of the Resource attributes.
|
||||
// This is ideal to use if you do not want a copy of the attributes.
|
||||
func (r *Resource) Iter() attribute.Iterator {
|
||||
@ -121,15 +153,32 @@ func (r *Resource) Equal(eq *Resource) bool {
|
||||
// If there are common keys between resource a and b, then the value
|
||||
// from resource b will overwrite the value from resource a, even
|
||||
// if resource b's value is empty.
|
||||
func Merge(a, b *Resource) *Resource {
|
||||
//
|
||||
// The SchemaURL of the resources will be merged according to the spec rules:
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/bad49c714a62da5493f2d1d9bafd7ebe8c8ce7eb/specification/resource/sdk.md#merge
|
||||
// If the resources have different non-empty schemaURL an empty resource and an error
|
||||
// will be returned.
|
||||
func Merge(a, b *Resource) (*Resource, error) {
|
||||
if a == nil && b == nil {
|
||||
return Empty()
|
||||
return Empty(), nil
|
||||
}
|
||||
if a == nil {
|
||||
return b
|
||||
return b, nil
|
||||
}
|
||||
if b == nil {
|
||||
return a
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Merge the schema URL.
|
||||
var schemaURL string
|
||||
if a.schemaURL == "" {
|
||||
schemaURL = b.schemaURL
|
||||
} else if b.schemaURL == "" {
|
||||
schemaURL = a.schemaURL
|
||||
} else if a.schemaURL == b.schemaURL {
|
||||
schemaURL = a.schemaURL
|
||||
} else {
|
||||
return Empty(), errMergeConflictSchemaURL
|
||||
}
|
||||
|
||||
// Note: 'b' attributes will overwrite 'a' with last-value-wins in attribute.Key()
|
||||
@ -139,7 +188,8 @@ func Merge(a, b *Resource) *Resource {
|
||||
for mi.Next() {
|
||||
combine = append(combine, mi.Label())
|
||||
}
|
||||
return NewWithAttributes(combine...)
|
||||
merged := NewWithAttributes(schemaURL, combine...)
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
// Empty returns an instance of Resource with no attributes. It is
|
||||
|
@ -17,12 +17,14 @@ package resource_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
@ -65,7 +67,7 @@ func TestNewWithAttributes(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) {
|
||||
res := resource.NewWithAttributes(c.in...)
|
||||
res := resource.NewSchemaless(c.in...)
|
||||
if diff := cmp.Diff(
|
||||
res.Attributes(),
|
||||
c.want,
|
||||
@ -78,86 +80,121 @@ func TestNewWithAttributes(t *testing.T) {
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
a, b *resource.Resource
|
||||
want []attribute.KeyValue
|
||||
name string
|
||||
a, b *resource.Resource
|
||||
want []attribute.KeyValue
|
||||
isErr bool
|
||||
schemaURL string
|
||||
}{
|
||||
{
|
||||
name: "Merge 2 nils",
|
||||
a: nil,
|
||||
b: nil,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Merge with no overlap, no nil",
|
||||
a: resource.NewWithAttributes(kv11, kv31),
|
||||
b: resource.NewWithAttributes(kv21, kv41),
|
||||
a: resource.NewSchemaless(kv11, kv31),
|
||||
b: resource.NewSchemaless(kv21, kv41),
|
||||
want: []attribute.KeyValue{kv11, kv21, kv31, kv41},
|
||||
},
|
||||
{
|
||||
name: "Merge with no overlap, no nil, not interleaved",
|
||||
a: resource.NewWithAttributes(kv11, kv21),
|
||||
b: resource.NewWithAttributes(kv31, kv41),
|
||||
a: resource.NewSchemaless(kv11, kv21),
|
||||
b: resource.NewSchemaless(kv31, kv41),
|
||||
want: []attribute.KeyValue{kv11, kv21, kv31, kv41},
|
||||
},
|
||||
{
|
||||
name: "Merge with common key order1",
|
||||
a: resource.NewWithAttributes(kv11),
|
||||
b: resource.NewWithAttributes(kv12, kv21),
|
||||
a: resource.NewSchemaless(kv11),
|
||||
b: resource.NewSchemaless(kv12, kv21),
|
||||
want: []attribute.KeyValue{kv12, kv21},
|
||||
},
|
||||
{
|
||||
name: "Merge with common key order2",
|
||||
a: resource.NewWithAttributes(kv12, kv21),
|
||||
b: resource.NewWithAttributes(kv11),
|
||||
a: resource.NewSchemaless(kv12, kv21),
|
||||
b: resource.NewSchemaless(kv11),
|
||||
want: []attribute.KeyValue{kv11, kv21},
|
||||
},
|
||||
{
|
||||
name: "Merge with common key order4",
|
||||
a: resource.NewWithAttributes(kv11, kv21, kv41),
|
||||
b: resource.NewWithAttributes(kv31, kv41),
|
||||
a: resource.NewSchemaless(kv11, kv21, kv41),
|
||||
b: resource.NewSchemaless(kv31, kv41),
|
||||
want: []attribute.KeyValue{kv11, kv21, kv31, kv41},
|
||||
},
|
||||
{
|
||||
name: "Merge with no keys",
|
||||
a: resource.NewWithAttributes(),
|
||||
b: resource.NewWithAttributes(),
|
||||
a: resource.NewSchemaless(),
|
||||
b: resource.NewSchemaless(),
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "Merge with first resource no keys",
|
||||
a: resource.NewWithAttributes(),
|
||||
b: resource.NewWithAttributes(kv21),
|
||||
a: resource.NewSchemaless(),
|
||||
b: resource.NewSchemaless(kv21),
|
||||
want: []attribute.KeyValue{kv21},
|
||||
},
|
||||
{
|
||||
name: "Merge with second resource no keys",
|
||||
a: resource.NewWithAttributes(kv11),
|
||||
b: resource.NewWithAttributes(),
|
||||
a: resource.NewSchemaless(kv11),
|
||||
b: resource.NewSchemaless(),
|
||||
want: []attribute.KeyValue{kv11},
|
||||
},
|
||||
{
|
||||
name: "Merge with first resource nil",
|
||||
a: nil,
|
||||
b: resource.NewWithAttributes(kv21),
|
||||
b: resource.NewSchemaless(kv21),
|
||||
want: []attribute.KeyValue{kv21},
|
||||
},
|
||||
{
|
||||
name: "Merge with second resource nil",
|
||||
a: resource.NewWithAttributes(kv11),
|
||||
a: resource.NewSchemaless(kv11),
|
||||
b: nil,
|
||||
want: []attribute.KeyValue{kv11},
|
||||
},
|
||||
{
|
||||
name: "Merge with first resource value empty string",
|
||||
a: resource.NewWithAttributes(kv42),
|
||||
b: resource.NewWithAttributes(kv41),
|
||||
a: resource.NewSchemaless(kv42),
|
||||
b: resource.NewSchemaless(kv41),
|
||||
want: []attribute.KeyValue{kv41},
|
||||
},
|
||||
{
|
||||
name: "Merge with second resource value empty string",
|
||||
a: resource.NewWithAttributes(kv41),
|
||||
b: resource.NewWithAttributes(kv42),
|
||||
a: resource.NewSchemaless(kv41),
|
||||
b: resource.NewSchemaless(kv42),
|
||||
want: []attribute.KeyValue{kv42},
|
||||
},
|
||||
{
|
||||
name: "Merge with first resource with schema",
|
||||
a: resource.NewWithAttributes("https://opentelemetry.io/schemas/1.4.0", kv41),
|
||||
b: resource.NewSchemaless(kv42),
|
||||
want: []attribute.KeyValue{kv42},
|
||||
schemaURL: "https://opentelemetry.io/schemas/1.4.0",
|
||||
},
|
||||
{
|
||||
name: "Merge with second resource with schema",
|
||||
a: resource.NewSchemaless(kv41),
|
||||
b: resource.NewWithAttributes("https://opentelemetry.io/schemas/1.4.0", kv42),
|
||||
want: []attribute.KeyValue{kv42},
|
||||
schemaURL: "https://opentelemetry.io/schemas/1.4.0",
|
||||
},
|
||||
{
|
||||
name: "Merge with different schemas",
|
||||
a: resource.NewWithAttributes("https://opentelemetry.io/schemas/1.4.0", kv41),
|
||||
b: resource.NewWithAttributes("https://opentelemetry.io/schemas/1.3.0", kv42),
|
||||
want: nil,
|
||||
isErr: true,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) {
|
||||
res := resource.Merge(c.a, c.b)
|
||||
res, err := resource.Merge(c.a, c.b)
|
||||
if c.isErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.EqualValues(t, c.schemaURL, res.SchemaURL())
|
||||
if diff := cmp.Diff(
|
||||
res.Attributes(),
|
||||
c.want,
|
||||
@ -249,7 +286,7 @@ func TestString(t *testing.T) {
|
||||
want: "B=b",
|
||||
},
|
||||
} {
|
||||
if got := resource.NewWithAttributes(test.kvs...).String(); got != test.want {
|
||||
if got := resource.NewSchemaless(test.kvs...).String(); got != test.want {
|
||||
t.Errorf("Resource(%v).String() = %q, want %q", test.kvs, got, test.want)
|
||||
}
|
||||
}
|
||||
@ -258,7 +295,7 @@ 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"))
|
||||
r := resource.NewSchemaless(attribute.Int64("A", 1), attribute.String("C", "D"))
|
||||
data, err := json.Marshal(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t,
|
||||
@ -274,9 +311,11 @@ func TestNew(t *testing.T) {
|
||||
options []resource.Option
|
||||
|
||||
resourceValues map[string]string
|
||||
schemaURL string
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
name: "No Options returns empty resrouce",
|
||||
name: "No Options returns empty resource",
|
||||
envars: "key=value,other=attr",
|
||||
options: nil,
|
||||
resourceValues: map[string]string{},
|
||||
@ -298,6 +337,7 @@ func TestNew(t *testing.T) {
|
||||
resourceValues: map[string]string{
|
||||
"host.name": hostname(),
|
||||
},
|
||||
schemaURL: semconv.SchemaURL,
|
||||
},
|
||||
{
|
||||
name: "Only Env",
|
||||
@ -321,6 +361,7 @@ func TestNew(t *testing.T) {
|
||||
"telemetry.sdk.language": "go",
|
||||
"telemetry.sdk.version": otel.Version(),
|
||||
},
|
||||
schemaURL: semconv.SchemaURL,
|
||||
},
|
||||
{
|
||||
name: "WithAttributes",
|
||||
@ -346,6 +387,46 @@ func TestNew(t *testing.T) {
|
||||
"key": "value",
|
||||
"other": "attr",
|
||||
},
|
||||
schemaURL: semconv.SchemaURL,
|
||||
},
|
||||
{
|
||||
name: "With schema url",
|
||||
envars: "",
|
||||
options: []resource.Option{
|
||||
resource.WithAttributes(attribute.String("A", "B")),
|
||||
resource.WithSchemaURL("https://opentelemetry.io/schemas/1.0.0"),
|
||||
},
|
||||
resourceValues: map[string]string{
|
||||
"A": "B",
|
||||
},
|
||||
schemaURL: "https://opentelemetry.io/schemas/1.0.0",
|
||||
},
|
||||
{
|
||||
name: "With conflicting schema urls",
|
||||
envars: "",
|
||||
options: []resource.Option{
|
||||
resource.WithDetectors(
|
||||
resource.StringDetector("https://opentelemetry.io/schemas/1.0.0", semconv.HostNameKey, os.Hostname),
|
||||
),
|
||||
resource.WithSchemaURL("https://opentelemetry.io/schemas/1.1.0"),
|
||||
},
|
||||
resourceValues: map[string]string{},
|
||||
schemaURL: "",
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "With conflicting detector schema urls",
|
||||
envars: "",
|
||||
options: []resource.Option{
|
||||
resource.WithDetectors(
|
||||
resource.StringDetector("https://opentelemetry.io/schemas/1.0.0", semconv.HostNameKey, os.Hostname),
|
||||
resource.StringDetector("https://opentelemetry.io/schemas/1.1.0", semconv.HostNameKey, func() (string, error) { return "", errors.New("fail") }),
|
||||
),
|
||||
resource.WithSchemaURL("https://opentelemetry.io/schemas/1.2.0"),
|
||||
},
|
||||
resourceValues: map[string]string{},
|
||||
schemaURL: "",
|
||||
isErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tc {
|
||||
@ -359,8 +440,19 @@ func TestNew(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
res, err := resource.New(ctx, tt.options...)
|
||||
|
||||
require.NoError(t, err)
|
||||
if tt.isErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.EqualValues(t, tt.resourceValues, toMap(res))
|
||||
|
||||
// TODO: do we need to ensure that resource is never nil and eliminate the
|
||||
// following if?
|
||||
if res != nil {
|
||||
assert.EqualValues(t, tt.schemaURL, res.SchemaURL())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +279,11 @@ func WithSpanProcessor(sp SpanProcessor) TracerProviderOption {
|
||||
// resource.Default() Resource by default.
|
||||
func WithResource(r *resource.Resource) TracerProviderOption {
|
||||
return traceProviderOptionFunc(func(cfg *tracerProviderConfig) {
|
||||
cfg.resource = resource.Merge(resource.Environment(), r)
|
||||
var err error
|
||||
cfg.resource, err = resource.Merge(resource.Environment(), r)
|
||||
if err != nil {
|
||||
otel.Handle(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1210,6 +1210,12 @@ func TestWithSpanKind(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mergeResource(t *testing.T, r1, r2 *resource.Resource) *resource.Resource {
|
||||
r, err := resource.Merge(r1, r2)
|
||||
assert.NoError(t, err)
|
||||
return r
|
||||
}
|
||||
|
||||
func TestWithResource(t *testing.T) {
|
||||
store, err := ottest.SetEnvVariables(map[string]string{
|
||||
envVar: "key=value,rk5=7",
|
||||
@ -1235,20 +1241,20 @@ func TestWithResource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "explicit resource",
|
||||
options: []TracerProviderOption{WithResource(resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))},
|
||||
want: resource.Merge(resource.Environment(), resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))),
|
||||
options: []TracerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5)))},
|
||||
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk2", 5))),
|
||||
},
|
||||
{
|
||||
name: "last resource wins",
|
||||
options: []TracerProviderOption{
|
||||
WithResource(resource.NewWithAttributes(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))),
|
||||
WithResource(resource.NewWithAttributes(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10)))},
|
||||
want: resource.Merge(resource.Environment(), resource.NewWithAttributes(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
|
||||
WithResource(resource.NewSchemaless(attribute.String("rk1", "vk1"), attribute.Int64("rk2", 5))),
|
||||
WithResource(resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10)))},
|
||||
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk3", "rv3"), attribute.Int64("rk4", 10))),
|
||||
},
|
||||
{
|
||||
name: "overlapping attributes with environment resource",
|
||||
options: []TracerProviderOption{WithResource(resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))},
|
||||
want: resource.Merge(resource.Environment(), resource.NewWithAttributes(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))),
|
||||
options: []TracerProviderOption{WithResource(resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10)))},
|
||||
want: mergeResource(t, resource.Environment(), resource.NewSchemaless(attribute.String("rk1", "rv1"), attribute.Int64("rk5", 10))),
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
@ -1345,7 +1351,7 @@ func TestSpanCapturesPanic(t *testing.T) {
|
||||
func TestReadOnlySpan(t *testing.T) {
|
||||
kv := attribute.String("foo", "bar")
|
||||
|
||||
tp := NewTracerProvider(WithResource(resource.NewWithAttributes(kv)))
|
||||
tp := NewTracerProvider(WithResource(resource.NewSchemaless(kv)))
|
||||
tr := tp.Tracer("ReadOnlySpan", trace.WithInstrumentationVersion("3"))
|
||||
|
||||
// Initialize parent context.
|
||||
|
22
semconv/schema.go
Normal file
22
semconv/schema.go
Normal file
@ -0,0 +1,22 @@
|
||||
// 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 semconv
|
||||
|
||||
// SchemaURL is the schema URL that matches the version of the semantic conventions
|
||||
// that this package defines. This package defines semantic conventions for spec
|
||||
// v1.3.0 which was released before the concept of schemas was introduce, thus the
|
||||
// schema URL is empty. Semconv packages starting from v1.4.0 must declare non-empty
|
||||
// schema URL in the form https://opentelemetry.io/schemas/<version>
|
||||
const SchemaURL = ""
|
Loading…
Reference in New Issue
Block a user