From 4e5b156ea7ec5f344f1a5cef89995e260a7eb349 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 18 Jun 2025 10:13:39 -0400 Subject: [PATCH 01/42] Reboot the entity prototype - Create Entity/EntityBuilder class in internal package - Update serialization code Need to discuss how to work with Resource going forward. --- .../internal/otlp/EntityRefMarshaler.java | 83 ++++++++++++++++++ .../internal/otlp/ResourceMarshaler.java | 19 ++-- .../internal/otlp/EntityRefMarshalerTest.java | 86 +++++++++++++++++++ .../sdk/resources/internal/Entity.java | 78 +++++++++++++++++ .../sdk/resources/internal/EntityBuilder.java | 78 +++++++++++++++++ 5 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java create mode 100644 exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java new file mode 100644 index 00000000000..52debcb6a96 --- /dev/null +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp; + +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.exporter.internal.marshal.MarshalerUtil; +import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize; +import io.opentelemetry.exporter.internal.marshal.Serializer; +import io.opentelemetry.proto.common.v1.internal.EntityRef; +import io.opentelemetry.sdk.resources.internal.Entity; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import javax.annotation.Nullable; + +/** + * A Marshaler of {@link io.opentelemetry.sdk.resources.Entity}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class EntityRefMarshaler extends MarshalerWithSize { + @Nullable private final byte[] schemaUrlUtf8; + private final byte[] typeUtf8; + private final byte[][] idKeysUtf8; + private final byte[][] descriptionKeysUtf8; + + @Override + protected void writeTo(Serializer output) throws IOException { + if (schemaUrlUtf8 != null) { + output.writeString(EntityRef.SCHEMA_URL, schemaUrlUtf8); + } + output.writeString(EntityRef.TYPE, typeUtf8); + output.writeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); + output.writeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8); + } + + /** Consttructs an entity reference marshaler from a full entity. */ + public static EntityRefMarshaler createForEntity(Entity e) { + byte[] schemaUrlUtf8 = null; + if (!StringUtils.isNullOrEmpty(e.getSchemaUrl())) { + schemaUrlUtf8 = e.getSchemaUrl().getBytes(StandardCharsets.UTF_8); + } + return new EntityRefMarshaler( + schemaUrlUtf8, + e.getType().getBytes(StandardCharsets.UTF_8), + e.getIdentifyingAttributes().asMap().keySet().stream() + .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) + .toArray(byte[][]::new), + e.getAttributes().asMap().keySet().stream() + .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) + .toArray(byte[][]::new)); + } + + private EntityRefMarshaler( + @Nullable byte[] schemaUrlUtf8, + byte[] typeUtf8, + byte[][] idKeysUtf8, + byte[][] descriptionKeysUtf8) { + super(calculateSize(schemaUrlUtf8, typeUtf8, idKeysUtf8, descriptionKeysUtf8)); + this.schemaUrlUtf8 = schemaUrlUtf8; + this.typeUtf8 = typeUtf8; + this.idKeysUtf8 = idKeysUtf8; + this.descriptionKeysUtf8 = descriptionKeysUtf8; + } + + private static int calculateSize( + @Nullable byte[] schemaUrlUtf8, + byte[] typeUtf8, + byte[][] idKeysUtf8, + byte[][] descriptionKeysUtf8) { + int size = 0; + if (schemaUrlUtf8 != null) { + size += MarshalerUtil.sizeBytes(EntityRef.SCHEMA_URL, schemaUrlUtf8); + } + size += MarshalerUtil.sizeBytes(EntityRef.TYPE, typeUtf8); + MarshalerUtil.sizeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); + MarshalerUtil.sizeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8); + return size; + } +} diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java index b3395448a79..f3e6bcb9659 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java @@ -37,7 +37,9 @@ public static ResourceMarshaler create(io.opentelemetry.sdk.resources.Resource r RealResourceMarshaler realMarshaler = new RealResourceMarshaler( - KeyValueMarshaler.createForAttributes(resource.getAttributes())); + KeyValueMarshaler.createForAttributes(resource.getAttributes()), + // TODO(jsuereth): This will support EntityRef in the future. + new EntityRefMarshaler[] {}); ByteArrayOutputStream binaryBos = new ByteArrayOutputStream(realMarshaler.getBinarySerializedSize()); @@ -70,19 +72,26 @@ public void writeTo(Serializer output) throws IOException { private static final class RealResourceMarshaler extends MarshalerWithSize { private final KeyValueMarshaler[] attributes; + private final MarshalerWithSize[] entityRefs; - private RealResourceMarshaler(KeyValueMarshaler[] attributes) { - super(calculateSize(attributes)); + private RealResourceMarshaler(KeyValueMarshaler[] attributes, MarshalerWithSize[] entityRefs) { + super(calculateSize(attributes, entityRefs)); this.attributes = attributes; + this.entityRefs = entityRefs; } @Override protected void writeTo(Serializer output) throws IOException { output.serializeRepeatedMessage(Resource.ATTRIBUTES, attributes); + output.serializeRepeatedMessage(Resource.ENTITY_REFS, entityRefs); } - private static int calculateSize(KeyValueMarshaler[] attributeMarshalers) { - return MarshalerUtil.sizeRepeatedMessage(Resource.ATTRIBUTES, attributeMarshalers); + private static int calculateSize( + KeyValueMarshaler[] attributeMarshalers, MarshalerWithSize[] entityRefs) { + int size = 0; + size += MarshalerUtil.sizeRepeatedMessage(Resource.ATTRIBUTES, attributeMarshalers); + size += size += MarshalerUtil.sizeRepeatedMessage(Resource.ENTITY_REFS, entityRefs); + return size; } } } diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java new file mode 100644 index 00000000000..459dee9385d --- /dev/null +++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal.otlp; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.JsonFormat; +import io.opentelemetry.exporter.internal.marshal.Marshaler; +import io.opentelemetry.proto.common.v1.EntityRef; +import io.opentelemetry.sdk.resources.internal.Entity; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class EntityRefMarshalerTest { + @Test + void toEntityRefs() { + Entity e = + Entity.builder("test") + .setSchemaUrl("test-url") + .withDescriptive(attr -> attr.put("desc.key", "desc.value")) + .withIdentifying(attr -> attr.put("id.key", "id.value")) + .build(); + EntityRef proto = parse(EntityRef.getDefaultInstance(), EntityRefMarshaler.createForEntity(e)); + assertThat(proto.getType()).isEqualTo("test"); + assertThat(proto.getSchemaUrl()).isEqualTo("test-url"); + assertThat(proto.getIdKeysList()).containsExactly("id.key"); + assertThat(proto.getDescriptionKeysList()).containsExactly("desc.key"); + } + + @SuppressWarnings("unchecked") + private static T parse(T prototype, Marshaler marshaler) { + byte[] serialized = toByteArray(marshaler); + T result; + try { + result = (T) prototype.newBuilderForType().mergeFrom(serialized).build(); + } catch (InvalidProtocolBufferException e) { + throw new UncheckedIOException(e); + } + // Our marshaler should produce the exact same length of serialized output (for example, field + // default values are not outputted), so we check that here. The output itself may have slightly + // different ordering, mostly due to the way we don't output oneof values in field order all the + // tieme. If the lengths are equal and the resulting protos are equal, the marshaling is + // guaranteed to be valid. + assertThat(result.getSerializedSize()).isEqualTo(serialized.length); + + // Compare JSON + String json = toJson(marshaler); + Message.Builder builder = prototype.newBuilderForType(); + try { + JsonFormat.parser().merge(json, builder); + } catch (InvalidProtocolBufferException e) { + throw new UncheckedIOException(e); + } + assertThat(builder.build()).isEqualTo(result); + + return result; + } + + private static byte[] toByteArray(Marshaler marshaler) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + marshaler.writeBinaryTo(bos); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return bos.toByteArray(); + } + + private static String toJson(Marshaler marshaler) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + marshaler.writeJsonTo(bos); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return new String(bos.toByteArray(), StandardCharsets.UTF_8); + } +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java new file mode 100644 index 00000000000..98c61a62de6 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.common.Attributes; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Entity represents an object of interest associated with produced telemetry: traces, metrics or + * logs. + * + *

For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service + * entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we + * want to associate metrics with in this case. + * + *

Entities may be also associated with produced telemetry indirectly. For example a service that + * produces telemetry is also related with a process in which the service runs, so we say that the + * Service entity is related to the Process entity. The process normally also runs on a host, so we + * say that the Process entity is related to the Host entity. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@Immutable +@AutoValue +public abstract class Entity { + /** + * Returns the entity type string of this entity. Must not be null. + * + * @return the entity type. + */ + public abstract String getType(); + + /** + * Returns a map of attributes that identify the entity. + * + * @return a map of attributes. + */ + public abstract Attributes getIdentifyingAttributes(); + + /** + * Returns a map of attributes that describe the entity. + * + * @return a map of attributes. + */ + public abstract Attributes getAttributes(); + + /** + * Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity + * does not abide by schema conventions (i.e. is custom). + * + * @return An OpenTelemetry schema URL. + * @since 1.4.0 + */ + @Nullable + public abstract String getSchemaUrl(); + + static final Entity create( + String entityType, + Attributes identifying, + Attributes descriptive, + @Nullable String schemaUrl) { + return new AutoValue_Entity(entityType, identifying, descriptive, schemaUrl); + } + + public final EntityBuilder toBuilder() { + return new EntityBuilder(this); + } + + public static final EntityBuilder builder(String entityType) { + return new EntityBuilder(entityType); + } +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java new file mode 100644 index 00000000000..238f8f8c30b --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.function.Consumer; +import javax.annotation.Nullable; + +/** + * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as + * well as type and schema_url. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public class EntityBuilder { + private final String entityType; + private final AttributesBuilder attributesBuilder; + private final AttributesBuilder identifyingBuilder; + @Nullable private String schemaUrl; + + EntityBuilder(String entityType) { + this.entityType = entityType; + this.attributesBuilder = Attributes.builder(); + this.identifyingBuilder = Attributes.builder(); + } + + EntityBuilder(Entity seed) { + this.entityType = seed.getType(); + this.schemaUrl = seed.getSchemaUrl(); + this.identifyingBuilder = seed.getIdentifyingAttributes().toBuilder(); + this.attributesBuilder = seed.getAttributes().toBuilder(); + } + + /** + * Assign an OpenTelemetry schema URL to the resulting Entity. + * + * @param schemaUrl The URL of the OpenTelemetry schema being used to create this Entity. + * @return this + */ + public EntityBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + /** + * Modify the descriptive attributes of this Entity. + * + * @param f A thunk which manipulates descriptive attributes. + * @return this + */ + public EntityBuilder withDescriptive(Consumer f) { + f.accept(this.attributesBuilder); + return this; + } + + /** + * Modify the identifying attributes of this Entity. + * + * @param f A thunk which manipulates identifying attributes. + * @return this + */ + public EntityBuilder withIdentifying(Consumer f) { + f.accept(this.identifyingBuilder); + return this; + } + + /** Create the {@link Entity} from this. */ + public Entity build() { + // TODO - Better Checks, e.g. identifying attributes are non-zero. + return Entity.create( + entityType, identifyingBuilder.build(), attributesBuilder.build(), schemaUrl); + } +} From 869b4d8be1a2fab4328f92745ba5e36ea88395d5 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 12:29:44 -0400 Subject: [PATCH 02/42] Add base entity class, merge logic and test --- .../internal/otlp/EntityRefMarshaler.java | 4 +- .../internal/otlp/EntityRefMarshalerTest.java | 4 +- .../prometheus/Otel2PrometheusConverter.java | 27 +-- .../prometheus/CollectorIntegrationTest.java | 8 +- .../opentelemetry/sdk/resources/Resource.java | 47 +---- .../internal/AttributeCheckUtil.java | 63 +++++++ .../sdk/resources/internal/Entity.java | 33 +++- .../sdk/resources/internal/EntityBuilder.java | 26 ++- .../sdk/resources/internal/EntityUtil.java | 92 ++++++++++ .../resources/internal/EntityUtilTest.java | 163 ++++++++++++++++++ .../sdk/testing/assertj/EntityAssert.java | 45 +++++ .../assertj/OpenTelemetryAssertions.java | 6 + 12 files changed, 433 insertions(+), 85 deletions(-) create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java create mode 100644 sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java create mode 100644 sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java index 52debcb6a96..50809f9cfb7 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java @@ -46,10 +46,10 @@ public static EntityRefMarshaler createForEntity(Entity e) { return new EntityRefMarshaler( schemaUrlUtf8, e.getType().getBytes(StandardCharsets.UTF_8), - e.getIdentifyingAttributes().asMap().keySet().stream() + e.getId().asMap().keySet().stream() .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) .toArray(byte[][]::new), - e.getAttributes().asMap().keySet().stream() + e.getDescription().asMap().keySet().stream() .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) .toArray(byte[][]::new)); } diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java index 459dee9385d..eea9cd494d4 100644 --- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java +++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java @@ -25,8 +25,8 @@ void toEntityRefs() { Entity e = Entity.builder("test") .setSchemaUrl("test-url") - .withDescriptive(attr -> attr.put("desc.key", "desc.value")) - .withIdentifying(attr -> attr.put("id.key", "id.value")) + .withDescription(attr -> attr.put("desc.key", "desc.value")) + .withId(attr -> attr.put("id.key", "id.value")) .build(); EntityRef proto = parse(EntityRef.getDefaultInstance(), EntityRefMarshaler.createForEntity(e)); assertThat(proto.getType()).isEqualTo("test"); diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java index afa04a81394..d1b83b092d3 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java @@ -125,7 +125,8 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeEnabled && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { + if (otelScopeEnabled + && !metricData.getInstrumentationScopeInfo().getDescription().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } @@ -209,7 +210,7 @@ private GaugeSnapshot convertLongGauge( data.add( new GaugeDataPointSnapshot( (double) longData.getValue(), - convertAttributes(resource, scope, longData.getAttributes()), + convertAttributes(resource, scope, longData.getDescription()), convertLongExemplar(longData.getExemplars()))); } return new GaugeSnapshot(metadata, data); @@ -225,7 +226,7 @@ private CounterSnapshot convertLongCounter( data.add( new CounterDataPointSnapshot( (double) longData.getValue(), - convertAttributes(resource, scope, longData.getAttributes()), + convertAttributes(resource, scope, longData.getDescription()), convertLongExemplar(longData.getExemplars()), longData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -242,7 +243,7 @@ private GaugeSnapshot convertDoubleGauge( data.add( new GaugeDataPointSnapshot( doubleData.getValue(), - convertAttributes(resource, scope, doubleData.getAttributes()), + convertAttributes(resource, scope, doubleData.getDescription()), convertDoubleExemplar(doubleData.getExemplars()))); } return new GaugeSnapshot(metadata, data); @@ -258,7 +259,7 @@ private CounterSnapshot convertDoubleCounter( data.add( new CounterDataPointSnapshot( doubleData.getValue(), - convertAttributes(resource, scope, doubleData.getAttributes()), + convertAttributes(resource, scope, doubleData.getDescription()), convertDoubleExemplar(doubleData.getExemplars()), doubleData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -279,7 +280,7 @@ private HistogramSnapshot convertHistogram( new HistogramDataPointSnapshot( ClassicHistogramBuckets.of(boundaries, histogramData.getCounts()), histogramData.getSum(), - convertAttributes(resource, scope, histogramData.getAttributes()), + convertAttributes(resource, scope, histogramData.getDescription()), convertDoubleExemplars(histogramData.getExemplars()), histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -301,7 +302,7 @@ private HistogramSnapshot convertExponentialHistogram( "Dropping histogram " + metadata.getName() + " with attributes " - + histogramData.getAttributes() + + histogramData.getDescription() + " because it has scale < -4 which is unsupported in Prometheus"); return null; } @@ -315,7 +316,7 @@ private HistogramSnapshot convertExponentialHistogram( convertExponentialHistogramBuckets(histogramData.getPositiveBuckets(), scaleDown), convertExponentialHistogramBuckets(histogramData.getNegativeBuckets(), scaleDown), histogramData.getSum(), - convertAttributes(resource, scope, histogramData.getAttributes()), + convertAttributes(resource, scope, histogramData.getDescription()), convertDoubleExemplars(histogramData.getExemplars()), histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -359,7 +360,7 @@ private SummarySnapshot convertSummary( summaryData.getCount(), summaryData.getSum(), convertQuantiles(summaryData.getValues()), - convertAttributes(resource, scope, summaryData.getAttributes()), + convertAttributes(resource, scope, summaryData.getDescription()), Exemplars.EMPTY, // Exemplars for Summaries not implemented yet. summaryData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -437,7 +438,7 @@ private InfoSnapshot makeTargetInfo(Resource resource) { convertAttributes( null, // resource attributes are only copied for point's attributes null, // scope attributes are only needed for point's attributes - resource.getAttributes())))); + resource.getDescription())))); } private InfoSnapshot makeScopeInfo(Set scopes) { @@ -448,7 +449,7 @@ private InfoSnapshot makeScopeInfo(Set scopes) { convertAttributes( null, // resource attributes are only copied for point's attributes scope, - scope.getAttributes()))); + scope.getDescription()))); } return new InfoSnapshot(new MetricMetadata("otel_scope"), prometheusScopeInfos); } @@ -503,7 +504,7 @@ private Labels convertAttributes( } if (resource != null) { - Attributes resourceAttributes = resource.getAttributes(); + Attributes resourceAttributes = resource.getDescription(); for (AttributeKey attributeKey : allowedAttributeKeys) { Object attributeValue = resourceAttributes.get(attributeKey); if (attributeValue != null) { @@ -536,7 +537,7 @@ private List> filterAllowedResourceAttributeKeys(@Nullable Resou List> allowedAttributeKeys = resourceAttributesToAllowedKeysCache.computeIfAbsent( - resource.getAttributes(), + resource.getDescription(), resourceAttributes -> resourceAttributes.asMap().keySet().stream() .filter(o -> allowedResourceAttributesFilter.test(o.getKey())) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java index 3e3ee1774df..bcfe6067ee7 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java @@ -140,19 +140,19 @@ void endToEnd() { // Resource attributes from the metric SDK resource translated to target_info stringKeyValue( "service_name", - Objects.requireNonNull(resource.getAttributes().get(stringKey("service.name")))), + Objects.requireNonNull(resource.getDescription().get(stringKey("service.name")))), stringKeyValue( "telemetry_sdk_name", Objects.requireNonNull( - resource.getAttributes().get(stringKey("telemetry.sdk.name")))), + resource.getDescription().get(stringKey("telemetry.sdk.name")))), stringKeyValue( "telemetry_sdk_language", Objects.requireNonNull( - resource.getAttributes().get(stringKey("telemetry.sdk.language")))), + resource.getDescription().get(stringKey("telemetry.sdk.language")))), stringKeyValue( "telemetry_sdk_version", Objects.requireNonNull( - resource.getAttributes().get(stringKey("telemetry.sdk.version"))))); + resource.getDescription().get(stringKey("telemetry.sdk.version"))))); assertThat(resourceMetrics.getScopeMetricsCount()).isEqualTo(2); ScopeMetrics testScopeMetrics = diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java index c406ef87a5e..22e1ec98913 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java @@ -9,9 +9,8 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.internal.StringUtils; -import io.opentelemetry.api.internal.Utils; import io.opentelemetry.sdk.common.internal.OtelVersion; +import io.opentelemetry.sdk.resources.internal.AttributeCheckUtil; import java.util.Objects; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -34,13 +33,6 @@ public abstract class Resource { private static final AttributeKey TELEMETRY_SDK_VERSION = AttributeKey.stringKey("telemetry.sdk.version"); - private static final int MAX_LENGTH = 255; - private static final String ERROR_MESSAGE_INVALID_CHARS = - " should be a ASCII string with a length greater than 0 and not exceed " - + MAX_LENGTH - + " characters."; - private static final String ERROR_MESSAGE_INVALID_VALUE = - " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters."; private static final Resource EMPTY = create(Attributes.empty()); private static final Resource TELEMETRY_SDK; @@ -91,7 +83,7 @@ public static Resource empty() { * @return a {@code Resource}. * @throws NullPointerException if {@code attributes} is null. * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable - * ASCII string or exceed {@link #MAX_LENGTH} characters. + * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. */ public static Resource create(Attributes attributes) { return create(attributes, null); @@ -105,10 +97,10 @@ public static Resource create(Attributes attributes) { * @return a {@code Resource}. * @throws NullPointerException if {@code attributes} is null. * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable - * ASCII string or exceed {@link #MAX_LENGTH} characters. + * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. */ public static Resource create(Attributes attributes, @Nullable String schemaUrl) { - checkAttributes(Objects.requireNonNull(attributes, "attributes")); + AttributeCheckUtil.checkAttributes(Objects.requireNonNull(attributes, "attributes")); return new AutoValue_Resource(schemaUrl, attributes); } @@ -174,37 +166,6 @@ public Resource merge(@Nullable Resource other) { return create(attrBuilder.build(), getSchemaUrl()); } - private static void checkAttributes(Attributes attributes) { - attributes.forEach( - (key, value) -> { - Utils.checkArgument( - isValidAndNotEmpty(key), "Attribute key" + ERROR_MESSAGE_INVALID_CHARS); - Objects.requireNonNull(value, "Attribute value" + ERROR_MESSAGE_INVALID_VALUE); - }); - } - - /** - * Determines whether the given {@code String} is a valid printable ASCII string with a length not - * exceed {@link #MAX_LENGTH} characters. - * - * @param name the name to be validated. - * @return whether the name is valid. - */ - private static boolean isValid(String name) { - return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); - } - - /** - * Determines whether the given {@code String} is a valid printable ASCII string with a length - * greater than 0 and not exceed {@link #MAX_LENGTH} characters. - * - * @param name the name to be validated. - * @return whether the name is valid. - */ - private static boolean isValidAndNotEmpty(AttributeKey name) { - return !name.getKey().isEmpty() && isValid(name.getKey()); - } - /** * Returns a new {@link ResourceBuilder} instance for creating arbitrary {@link Resource}. * diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java new file mode 100644 index 00000000000..966c5b58277 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.api.internal.Utils; +import java.util.Objects; + +/** + * Helpers to check resource attributes. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class AttributeCheckUtil { + private AttributeCheckUtil() {} + + // Note: Max length is actually configurable by specification. + private static final int MAX_LENGTH = 255; + private static final String ERROR_MESSAGE_INVALID_CHARS = + " should be a ASCII string with a length greater than 0 and not exceed " + + MAX_LENGTH + + " characters."; + private static final String ERROR_MESSAGE_INVALID_VALUE = + " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters."; + + /** Determine if the set of attributes if valid for Resource / Entity. */ + public static void checkAttributes(Attributes attributes) { + attributes.forEach( + (key, value) -> { + Utils.checkArgument( + isValidAndNotEmpty(key), "Attribute key" + ERROR_MESSAGE_INVALID_CHARS); + Objects.requireNonNull(value, "Attribute value" + ERROR_MESSAGE_INVALID_VALUE); + }); + } + + /** + * Determines whether the given {@code String} is a valid printable ASCII string with a length + * greater than 0 and not exceed {@link #MAX_LENGTH} characters. + * + * @param name the name to be validated. + * @return whether the name is valid. + */ + public static boolean isValidAndNotEmpty(AttributeKey name) { + return !name.getKey().isEmpty() && isValid(name.getKey()); + } + + /** + * Determines whether the given {@code String} is a valid printable ASCII string with a length not + * exceed {@link #MAX_LENGTH} characters. + * + * @param name the name to be validated. + * @return whether the name is valid. + */ + public static boolean isValid(String name) { + return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); + } +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java index 98c61a62de6..f5c3935571d 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java @@ -41,14 +41,14 @@ public abstract class Entity { * * @return a map of attributes. */ - public abstract Attributes getIdentifyingAttributes(); + public abstract Attributes getId(); /** * Returns a map of attributes that describe the entity. * * @return a map of attributes. */ - public abstract Attributes getAttributes(); + public abstract Attributes getDescription(); /** * Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity @@ -60,18 +60,37 @@ public abstract class Entity { @Nullable public abstract String getSchemaUrl(); + /** + * Returns a {@link Entity}. + * + * @param entityType the entity type string of this entity. + * @param id a map of attributes that identify the entity. + * @param description a map of attributes that describe the entity. + * @return a {@code Entity}. + * @throws NullPointerException if {@code id} or {@code description} is null. + * @throws IllegalArgumentException if entityType string, attribute key or attribute value is not + * a valid printable ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. + */ static final Entity create( - String entityType, - Attributes identifying, - Attributes descriptive, - @Nullable String schemaUrl) { - return new AutoValue_Entity(entityType, identifying, descriptive, schemaUrl); + String entityType, Attributes id, Attributes description, @Nullable String schemaUrl) { + AttributeCheckUtil.isValid(entityType); + AttributeCheckUtil.checkAttributes(id); + AttributeCheckUtil.checkAttributes(description); + return new AutoValue_Entity(entityType, id, description, schemaUrl); } + /** + * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. + */ public final EntityBuilder toBuilder() { return new EntityBuilder(this); } + /** + * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. + * + * @param entityType the entity type string of this entity. + */ public static final EntityBuilder builder(String entityType) { return new EntityBuilder(entityType); } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java index 238f8f8c30b..94a2ea20333 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java @@ -17,23 +17,23 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public class EntityBuilder { +public final class EntityBuilder { private final String entityType; - private final AttributesBuilder attributesBuilder; - private final AttributesBuilder identifyingBuilder; + private final AttributesBuilder descriptionBuilder; + private final AttributesBuilder idBuilder; @Nullable private String schemaUrl; EntityBuilder(String entityType) { this.entityType = entityType; - this.attributesBuilder = Attributes.builder(); - this.identifyingBuilder = Attributes.builder(); + this.descriptionBuilder = Attributes.builder(); + this.idBuilder = Attributes.builder(); } EntityBuilder(Entity seed) { this.entityType = seed.getType(); this.schemaUrl = seed.getSchemaUrl(); - this.identifyingBuilder = seed.getIdentifyingAttributes().toBuilder(); - this.attributesBuilder = seed.getAttributes().toBuilder(); + this.idBuilder = seed.getId().toBuilder(); + this.descriptionBuilder = seed.getDescription().toBuilder(); } /** @@ -53,8 +53,8 @@ public EntityBuilder setSchemaUrl(String schemaUrl) { * @param f A thunk which manipulates descriptive attributes. * @return this */ - public EntityBuilder withDescriptive(Consumer f) { - f.accept(this.attributesBuilder); + public EntityBuilder withDescription(Consumer f) { + f.accept(this.descriptionBuilder); return this; } @@ -64,15 +64,13 @@ public EntityBuilder withDescriptive(Consumer f) { * @param f A thunk which manipulates identifying attributes. * @return this */ - public EntityBuilder withIdentifying(Consumer f) { - f.accept(this.identifyingBuilder); + public EntityBuilder withId(Consumer f) { + f.accept(this.idBuilder); return this; } /** Create the {@link Entity} from this. */ public Entity build() { - // TODO - Better Checks, e.g. identifying attributes are non-zero. - return Entity.create( - entityType, identifyingBuilder.build(), attributesBuilder.build(), schemaUrl); + return Entity.create(entityType, idBuilder.build(), descriptionBuilder.build(), schemaUrl); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java new file mode 100644 index 00000000000..b41bc34b9dc --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * Helper class for dealing with Entities. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class EntityUtil { + private static final Logger logger = Logger.getLogger(EntityUtil.class.getName()); + + private EntityUtil() {} + + /** + * Merges entities according to specification rules. + * + * @param base the initial set of entities. + * @param additional Additional entities to merge with base set. + * @return A new set of entities with no duplicate types. + */ + public static final Collection mergeEntities( + Collection base, Collection additional) { + if (base.isEmpty()) { + return additional; + } + if (additional.isEmpty()) { + return base; + } + Map entities = new HashMap<>(); + base.forEach(e -> entities.put(e.getType(), e)); + for (Entity e : additional) { + if (!entities.containsKey(e.getType())) { + entities.put(e.getType(), e); + } else { + Entity old = entities.get(e.getType()); + // If the entity identity is the same, but schema_url is different: drop the new entity d' + // Note: We could offer configuration in this case + if (old.getSchemaUrl() == null || !old.getSchemaUrl().equals(e.getSchemaUrl())) { + logger.info( + "Discovered conflicting entities. Entity [" + + old.getType() + + "] has different schema url [" + + old.getSchemaUrl() + + "], new entity with schema url[" + + e.getSchemaUrl() + + "] is dropped."); + } else if (!old.getId().equals(e.getId())) { + // If the entity identity is different: drop the new entity d'. + logger.info( + "Discovered conflicting entities. Entity [" + + old.getType() + + "] has identity [" + + old.getId() + + "], new entity [" + + e.getId() + + "] is dropped."); + } else { + // If the entity identity and schema_url are the same, merge the descriptive attributes + // of d' into e': + // For each descriptive attribute da' in d' + // If da'.key does not exist in e', then add da' to ei + // otherwise, ignore. + Entity next = + old.toBuilder() + .withDescription( + builder -> { + // Clean existing attributes. + builder.removeIf(ignore -> true); + // For attributes, last one wins. + // To ensure the previous attributes override, + // we write them second. + builder.putAll(e.getDescription()); + builder.putAll(old.getDescription()); + }) + .build(); + entities.put(next.getType(), next); + } + } + } + return entities.values(); + } +} diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java new file mode 100644 index 00000000000..b38aafd2585 --- /dev/null +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -0,0 +1,163 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.Collection; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link EntityUtil} */ +class EntityUtilTest { + @Test + void testMerge_entities_same_types_and_id() { + Collection base = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("one") + .withId(id -> id.put("a.id", "a")) + .withDescription(builder -> builder.put("a.desc1", "a")) + .build()); + Collection added = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("one") + .withId( + builder -> { + builder.put("a.id", "a"); + }) + .withDescription( + builder -> { + builder.put("a.desc2", "b"); + }) + .build()); + Collection merged = EntityUtil.mergeEntities(base, added); + assertThat(merged).hasSize(1); + assertThat(merged) + .anySatisfy( + entity -> + assertThat(entity) + .hasType("a") + .hasSchemaUrl("one") + .hasIdSatisfying(id -> assertThat(id).containsEntry("a.id", "a")) + .hasDescriptionSatisfying( + desc -> + assertThat(desc) + .containsEntry("a.desc1", "a") + .containsEntry("a.desc2", "b"))); + } + + @Test + void testMerge_entities_same_types_and_id_different_schema() { + Collection base = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("one") + .withId(id -> id.put("a.id", "a")) + .withDescription(builder -> builder.put("a.desc1", "a")) + .build()); + Collection added = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("two") + .withId( + builder -> { + builder.put("a.id", "a"); + }) + .withDescription( + builder -> { + builder.put("a.desc2", "b"); + }) + .build()); + Collection merged = EntityUtil.mergeEntities(base, added); + assertThat(merged).hasSize(1); + assertThat(merged) + .anySatisfy( + entity -> + assertThat(entity) + .hasType("a") + .hasSchemaUrl("one") + .hasIdSatisfying(id -> assertThat(id).containsEntry("a.id", "a")) + .hasDescriptionSatisfying( + desc -> + assertThat(desc) + .containsEntry("a.desc1", "a") + // Don't merge between versions. + .doesNotContainKey("a.desc2"))); + } + + @Test + void testMerge_entities_same_types_different_id() { + Collection base = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("one") + .withId( + builder -> { + builder.put("a.id", "a"); + }) + .withDescription( + builder -> { + builder.put("a.desc1", "a"); + }) + .build()); + Collection added = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("one") + .withId( + builder -> { + builder.put("a.id", "b"); + }) + .withDescription( + builder -> { + builder.put("a.desc2", "b"); + }) + .build()); + Collection merged = EntityUtil.mergeEntities(base, added); + assertThat(merged).hasSize(1); + assertThat(merged) + .satisfiesExactly( + e -> + assertThat(e) + .hasSchemaUrl("one") + .hasIdSatisfying(id -> assertThat(id).containsEntry("a.id", "a")) + .hasDescriptionSatisfying( + desc -> + assertThat(desc) + .containsEntry("a.desc1", "a") + .doesNotContainKey("a.desc2"))); + } + + @Test + void testMerge_entities_separate_types_and_schema() { + Collection base = + Arrays.asList( + Entity.builder("a") + .setSchemaUrl("one") + .withId( + builder -> { + builder.put("a.id", "a"); + }) + .build()); + Collection added = + Arrays.asList( + Entity.builder("b") + .setSchemaUrl("two") + .withId( + builder -> { + builder.put("b.id", "b"); + }) + .build()); + Collection merged = EntityUtil.mergeEntities(base, added); + // Make sure we keep both entities when no conflict. + assertThat(merged) + .satisfiesExactlyInAnyOrder( + a -> assertThat(a).hasType("a"), b -> assertThat(b).hasType("b")); + } +} diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java new file mode 100644 index 00000000000..3dd0789847e --- /dev/null +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.testing.assertj; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.resources.internal.Entity; +import javax.annotation.Nullable; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.ThrowingConsumer; + +/** Assertions for {@link Entity}. */ +public class EntityAssert extends AbstractAssert { + EntityAssert(@Nullable Entity actual) { + super(actual, EntityAssert.class); + } + + /** Asserts that the entity type is equal to a given string. */ + public EntityAssert hasType(String entityType) { + assertThat(actual.getType()).isEqualTo(entityType); + return this; + } + + /** Asserts that the entity id satisfies the given asserts */ + public EntityAssert hasIdSatisfying(ThrowingConsumer asserts) { + asserts.accept(actual.getId()); + return this; + } + + /** Asserts that the entity description satisfies the given asserts */ + public EntityAssert hasDescriptionSatisfying(ThrowingConsumer asserts) { + asserts.accept(actual.getDescription()); + return this; + } + + /** Asserts that the entity schemaUrl is equal to a given string. */ + public EntityAssert hasSchemaUrl(String schemaUrl) { + assertThat(actual.getSchemaUrl()).isEqualTo(schemaUrl); + return this; + } +} diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java index 6d2e99735c2..ed2dda36414 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.resources.internal.Entity; import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.SpanData; import java.util.AbstractMap; @@ -51,6 +52,11 @@ public static MetricAssert assertThat(@Nullable MetricData metricData) { return new MetricAssert(metricData); } + /** Returns an assertion for {@link Entity}. */ + public static EntityAssert assertThat(@Nullable Entity entity) { + return new EntityAssert(entity); + } + /** Returns an assertion for {@link EventDataAssert}. */ public static EventDataAssert assertThat(@Nullable EventData eventData) { return new EventDataAssert(eventData); From 9f9dc92777c7b82264fbe563d37dbf5abfd7c995 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 12:38:30 -0400 Subject: [PATCH 03/42] Move Entity and EntityBuilder to be pure interfaces --- .../sdk/resources/internal/Entity.java | 31 ++-------- .../sdk/resources/internal/EntityBuilder.java | 40 ++---------- .../sdk/resources/internal/SdkEntity.java | 54 ++++++++++++++++ .../resources/internal/SdkEntityBuilder.java | 61 +++++++++++++++++++ 4 files changed, 124 insertions(+), 62 deletions(-) create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java index f5c3935571d..d657c294107 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java @@ -5,7 +5,6 @@ package io.opentelemetry.sdk.resources.internal; -import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -27,8 +26,7 @@ * at any time. */ @Immutable -@AutoValue -public abstract class Entity { +public interface Entity { /** * Returns the entity type string of this entity. Must not be null. * @@ -60,38 +58,17 @@ public abstract class Entity { @Nullable public abstract String getSchemaUrl(); - /** - * Returns a {@link Entity}. - * - * @param entityType the entity type string of this entity. - * @param id a map of attributes that identify the entity. - * @param description a map of attributes that describe the entity. - * @return a {@code Entity}. - * @throws NullPointerException if {@code id} or {@code description} is null. - * @throws IllegalArgumentException if entityType string, attribute key or attribute value is not - * a valid printable ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. - */ - static final Entity create( - String entityType, Attributes id, Attributes description, @Nullable String schemaUrl) { - AttributeCheckUtil.isValid(entityType); - AttributeCheckUtil.checkAttributes(id); - AttributeCheckUtil.checkAttributes(description); - return new AutoValue_Entity(entityType, id, description, schemaUrl); - } - /** * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. */ - public final EntityBuilder toBuilder() { - return new EntityBuilder(this); - } + EntityBuilder toBuilder(); /** * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. * * @param entityType the entity type string of this entity. */ - public static final EntityBuilder builder(String entityType) { - return new EntityBuilder(entityType); + public static EntityBuilder builder(String entityType) { + return SdkEntity.builder(entityType); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java index 94a2ea20333..fbb4cc40dca 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java @@ -8,7 +8,6 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import java.util.function.Consumer; -import javax.annotation.Nullable; /** * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as @@ -17,35 +16,14 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public final class EntityBuilder { - private final String entityType; - private final AttributesBuilder descriptionBuilder; - private final AttributesBuilder idBuilder; - @Nullable private String schemaUrl; - - EntityBuilder(String entityType) { - this.entityType = entityType; - this.descriptionBuilder = Attributes.builder(); - this.idBuilder = Attributes.builder(); - } - - EntityBuilder(Entity seed) { - this.entityType = seed.getType(); - this.schemaUrl = seed.getSchemaUrl(); - this.idBuilder = seed.getId().toBuilder(); - this.descriptionBuilder = seed.getDescription().toBuilder(); - } - +public interface EntityBuilder { /** * Assign an OpenTelemetry schema URL to the resulting Entity. * * @param schemaUrl The URL of the OpenTelemetry schema being used to create this Entity. * @return this */ - public EntityBuilder setSchemaUrl(String schemaUrl) { - this.schemaUrl = schemaUrl; - return this; - } + EntityBuilder setSchemaUrl(String schemaUrl); /** * Modify the descriptive attributes of this Entity. @@ -53,10 +31,7 @@ public EntityBuilder setSchemaUrl(String schemaUrl) { * @param f A thunk which manipulates descriptive attributes. * @return this */ - public EntityBuilder withDescription(Consumer f) { - f.accept(this.descriptionBuilder); - return this; - } + EntityBuilder withDescription(Consumer f); /** * Modify the identifying attributes of this Entity. @@ -64,13 +39,8 @@ public EntityBuilder withDescription(Consumer f) { * @param f A thunk which manipulates identifying attributes. * @return this */ - public EntityBuilder withId(Consumer f) { - f.accept(this.idBuilder); - return this; - } + EntityBuilder withId(Consumer f); /** Create the {@link Entity} from this. */ - public Entity build() { - return Entity.create(entityType, idBuilder.build(), descriptionBuilder.build(), schemaUrl); - } + Entity build(); } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java new file mode 100644 index 00000000000..a81a37c6601 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.common.Attributes; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * SDK implementation of Entity. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@Immutable +@AutoValue +public abstract class SdkEntity implements Entity { + /** + * Returns a {@link Entity}. + * + * @param entityType the entity type string of this entity. + * @param id a map of attributes that identify the entity. + * @param description a map of attributes that describe the entity. + * @return a {@code Entity}. + * @throws NullPointerException if {@code id} or {@code description} is null. + * @throws IllegalArgumentException if entityType string, attribute key or attribute value is not + * a valid printable ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. + */ + static final Entity create( + String entityType, Attributes id, Attributes description, @Nullable String schemaUrl) { + AttributeCheckUtil.isValid(entityType); + AttributeCheckUtil.checkAttributes(id); + AttributeCheckUtil.checkAttributes(description); + return new AutoValue_SdkEntity(entityType, id, description, schemaUrl); + } + + @Override + public final EntityBuilder toBuilder() { + return new SdkEntityBuilder(this); + } + + /** + * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. + * + * @param entityType the entity type string of this entity. + */ + public static final EntityBuilder builder(String entityType) { + return new SdkEntityBuilder(entityType); + } +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java new file mode 100644 index 00000000000..3d235c0b316 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.function.Consumer; +import javax.annotation.Nullable; + +/** + * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as + * well as type and schema_url. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class SdkEntityBuilder implements EntityBuilder { + private final String entityType; + private final AttributesBuilder descriptionBuilder; + private final AttributesBuilder idBuilder; + @Nullable private String schemaUrl; + + SdkEntityBuilder(String entityType) { + this.entityType = entityType; + this.descriptionBuilder = Attributes.builder(); + this.idBuilder = Attributes.builder(); + } + + SdkEntityBuilder(Entity seed) { + this.entityType = seed.getType(); + this.schemaUrl = seed.getSchemaUrl(); + this.idBuilder = seed.getId().toBuilder(); + this.descriptionBuilder = seed.getDescription().toBuilder(); + } + + @Override + public EntityBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public EntityBuilder withDescription(Consumer f) { + f.accept(this.descriptionBuilder); + return this; + } + + @Override + public EntityBuilder withId(Consumer f) { + f.accept(this.idBuilder); + return this; + } + + @Override + public Entity build() { + return SdkEntity.create(entityType, idBuilder.build(), descriptionBuilder.build(), schemaUrl); + } +} From fb8e571906968d32a370ec49af5d9b679509eabd Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 13:47:07 -0400 Subject: [PATCH 04/42] Add more merge helper methods and tests --- .../sdk/resources/internal/Entity.java | 8 +- .../sdk/resources/internal/EntityUtil.java | 89 +++++++++++++++++++ .../internal/RawAttributeMergeResult.java | 32 +++++++ .../internal/ResourceWithEntity.java | 62 +++++++++++++ .../internal/ResourceWithEntityBuilder.java | 28 ++++++ .../resources/internal/EntityUtilTest.java | 52 +++++++++++ 6 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java create mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java index d657c294107..e8c14cfe835 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java @@ -32,21 +32,21 @@ public interface Entity { * * @return the entity type. */ - public abstract String getType(); + String getType(); /** * Returns a map of attributes that identify the entity. * * @return a map of attributes. */ - public abstract Attributes getId(); + Attributes getId(); /** * Returns a map of attributes that describe the entity. * * @return a map of attributes. */ - public abstract Attributes getDescription(); + Attributes getDescription(); /** * Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity @@ -56,7 +56,7 @@ public interface Entity { * @since 1.4.0 */ @Nullable - public abstract String getSchemaUrl(); + String getSchemaUrl(); /** * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index b41bc34b9dc..1a2e50e52a0 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -5,10 +5,17 @@ package io.opentelemetry.sdk.resources.internal; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; /** * Helper class for dealing with Entities. @@ -21,6 +28,88 @@ public final class EntityUtil { private EntityUtil() {} + /** Returns true if any entity in the collection has the attribute key, in id or description. */ + static final boolean hasAttributeKey(Collection entities, AttributeKey key) { + return entities.stream() + .anyMatch( + e -> e.getId().asMap().containsKey(key) || e.getDescription().asMap().containsKey(key)); + } + + /** Decides on a final SchemaURL for OTLP Resource based on entities chosen. */ + @Nullable + static final String mergeResourceSchemaUrl( + Collection entities, @Nullable String baseUrl, @Nullable String nextUrl) { + // Check if entities all share the same URL. + Set entitySchemas = + entities.stream().map(Entity::getSchemaUrl).collect(Collectors.toSet()); + // If we have no entities, we preserve previous schema url behavior. + String result = baseUrl; + if (entitySchemas.size() == 1) { + // Updated Entities use same schema, we can preserve it. + result = entitySchemas.iterator().next(); + } else if (entitySchemas.size() > 1) { + // Entities use different schemas, resource must treat this as no schema_url. + result = null; + } + + // If schema url of merging resource is null, we use our current result. + if (nextUrl == null) { + return result; + } + // When there are no entities, we use old schema url merge behavior + if (result == null && entities.isEmpty()) { + return nextUrl; + } + if (!nextUrl.equals(result)) { + logger.info( + "Attempting to merge Resources with different schemaUrls. " + + "The resulting Resource will have no schemaUrl assigned. Schema 1: " + + baseUrl + + " Schema 2: " + + nextUrl); + return null; + } + return result; + } + + /** + * Merges "loose" attributes on resource, removing those which conflict with the set of entities. + * + * @param base loose attributes from base resource + * @param additional additional attributes to add to the resource. + * @param entities the set of entites on the resource. + * @return the new set of raw attributes for Resource and the set of conflicting entities that + * MUST NOT be reported on OTLP resource. + */ + @SuppressWarnings("unchecked") + static final RawAttributeMergeResult mergeRawAttributes( + Attributes base, Attributes additional, Collection entities) { + AttributesBuilder result = base.toBuilder(); + // We know attribute conflicts were handled perviously on the resource, so + // This needs to account for entity merge of new entities, and remove raw + // attributes that would have been removed with new entities. + result.removeIf(key -> hasAttributeKey(entities, key)); + // For every "raw" attribute on the other resource, we merge into the + // resource, but check for entity conflicts from previous entities. + ArrayList conflicts = new ArrayList<>(); + if (!additional.isEmpty()) { + additional.forEach( + (key, value) -> { + for (Entity e : entities) { + if (e.getId().asMap().keySet().contains(key) + || e.getDescription().asMap().keySet().contains(key)) { + // Remove the entity and push all attributes as raw, + // we have an override. + conflicts.add(e); + result.putAll(e.getId()).putAll(e.getDescription()); + } + } + result.put((AttributeKey) key, value); + }); + } + return RawAttributeMergeResult.create(result.build(), conflicts); + } + /** * Merges entities according to specification rules. * diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java new file mode 100644 index 00000000000..f1cc081b035 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.api.common.Attributes; +import java.util.Collection; +import javax.annotation.concurrent.Immutable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +@Immutable +@AutoValue +abstract class RawAttributeMergeResult { + /** Merged raw attributes. */ + abstract Attributes getAttributes(); + + /** + * Entities in conflict that should be removed from resource to avoid reporting invalid attribute + * sets in OTLP resource. + */ + abstract Collection getConflicts(); + + static final RawAttributeMergeResult create(Attributes attributes, Collection conflicts) { + return new AutoValue_RawAttributeMergeResult(attributes, conflicts); + } +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java new file mode 100644 index 00000000000..1f700d4c9e9 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.resources.ResourceBuilder; +import java.util.Collection; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * {@link Resource} represents a resource, which capture identifying information about the entities + * for which signals (stats or traces) are reported. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@Immutable +public interface ResourceWithEntity { + /** + * Returns the URL of the OpenTelemetry schema used by this resource. May be null. + * + * @return An OpenTelemetry schema URL. + * @since 1.4.0 + */ + @Nullable + String getSchemaUrl(); + + /** + * Returns a map of attributes that describe the resource, not associated with an entity. + * + * @return a map of attributes. + */ + Attributes getRawAttributes(); + + /** + * Returns a collectoion of associated entities. + * + * @return a collection of entities. + */ + Collection getEntities(); + + /** + * Returns a map of attributes that describe the resource. + * + *

Note: this includes all entity attribtues and raw attributes. + * + * @return a map of attributes. + */ + Attributes getAttributes(); + + /** + * Returns a new {@link ResourceBuilder} instance populated with the data of this {@link + * Resource}. + */ + ResourceWithEntityBuilder toBuilder(); + + // TODO - Merge +} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java new file mode 100644 index 00000000000..4e60617ea48 --- /dev/null +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.resources.internal; + +import io.opentelemetry.api.common.Attributes; +import java.util.Collection; + +/** + * A builder of {@link ResourceWithEntity} that allows to add key-value pairs and copy attributes + * from other {@link Attributes} or {@link ResourceWithEntity}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public interface ResourceWithEntityBuilder { + // TODO - Raw Attributes. + /** Appends a new entity on to the end of the list of entities. */ + ResourceWithEntityBuilder add(Entity e); + + /** Appends a new collection of entities on to the end of the list of entities. */ + ResourceWithEntityBuilder addAll(Collection entities); + + /** Create the {@link ResourceWithEntity} from this. */ + ResourceWithEntity build(); +} diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index b38aafd2585..d923edac68a 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import org.junit.jupiter.api.Test; /** Unit tests for {@link EntityUtil} */ @@ -160,4 +161,55 @@ void testMerge_entities_separate_types_and_schema() { .satisfiesExactlyInAnyOrder( a -> assertThat(a).hasType("a"), b -> assertThat(b).hasType("b")); } + + @Test + void testSchemaUrlMerge_no_entities_differentUrls() { + // If the we find conflicting schema URLs in resource we must drop schema url (set to null). + String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), "one", "two"); + assertThat(result).isNull(); + } + + @Test + void testSchemaUrlMerge_no_entities_base_null() { + // If the our resource had no schema url it abides by, we use the incoming schema url. + String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), null, "two"); + assertThat(result).isEqualTo("two"); + } + + @Test + void testSchemaUrlMerge_no_entities_next_null() { + // If the new resource had no schema url it abides by, we preserve ours. + // NOTE: this is by specification, but seems problematic if conflicts in merge + // cause violation of SchemaURL. + String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), "one", null); + assertThat(result).isEqualTo("one"); + } + + @Test + void testSchemaUrlMerge_entities_same_url() { + // If the new resource had no schema url it abides by, we preserve ours. + // NOTE: this is by specification, but seems problematic if conflicts in merge + // cause violation of SchemaURL. + String result = + EntityUtil.mergeResourceSchemaUrl( + Arrays.asList( + Entity.builder("t").setSchemaUrl("one").withId(id -> id.put("id", 1)).build()), + "one", + null); + assertThat(result).isEqualTo("one"); + } + + @Test + void testSchemaUrlMerge_entities_different_url() { + // When entities have conflciting schema urls, we cannot fill out resource schema url, + // no matter what. + String result = + EntityUtil.mergeResourceSchemaUrl( + Arrays.asList( + Entity.builder("t").setSchemaUrl("one").withId(id -> id.put("id", 1)).build(), + Entity.builder("t2").setSchemaUrl("two").withId(id -> id.put("id2", 1)).build()), + "one", + "one"); + assertThat(result).isEqualTo(null); + } } From f182ad2402c3fe1d1846c2892446bf1617f7fc06 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 13:53:41 -0400 Subject: [PATCH 05/42] Added test for merging raw attributes --- .../resources/internal/EntityUtilTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index d923edac68a..f63e7d2849c 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -8,6 +8,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.common.Attributes; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -212,4 +213,37 @@ void testSchemaUrlMerge_entities_different_url() { "one"); assertThat(result).isEqualTo(null); } + + @Test + void testRawAttributeMerge_no_entities() { + // When no entities are present all attributes are merged. + RawAttributeMergeResult result = + EntityUtil.mergeRawAttributes( + Attributes.builder().put("a", 1).put("b", 1).build(), + Attributes.builder().put("b", 2).put("c", 2).build(), + Collections.emptyList()); + assertThat(result.getConflicts()).isEmpty(); + assertThat(result.getAttributes()) + .hasSize(3) + .containsEntry("a", 1) + .containsEntry("b", 2) + .containsEntry("c", 2); + } + + @Test + void testRawAttributeMerge_entity_with_conflict() { + // When an entity conflicts with incoming raw attributes, we need to call out that conflict + // so resource merge logic can remove the entity from resource. + RawAttributeMergeResult result = + EntityUtil.mergeRawAttributes( + Attributes.builder().put("a", 1).put("b", 1).build(), + Attributes.builder().put("b", 2).put("c", 2).build(), + Arrays.asList(Entity.builder("c").withId(id -> id.put("c", 1)).build())); + assertThat(result.getConflicts()).satisfiesExactly(e -> assertThat(e).hasType("c")); + assertThat(result.getAttributes()) + .hasSize(3) + .containsEntry("a", 1) + .containsEntry("b", 2) + .containsEntry("c", 2); + } } From 30c578a26c489a1dceae15cb7bb1c6a2e683d29d Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 14:01:04 -0400 Subject: [PATCH 06/42] Cleanups and reverting silly changes --- .../prometheus/Otel2PrometheusConverter.java | 27 +++++++++---------- .../prometheus/CollectorIntegrationTest.java | 8 +++--- .../sdk/testing/assertj/EntityAssert.java | 4 +-- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java index d1b83b092d3..afa04a81394 100644 --- a/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java +++ b/exporters/prometheus/src/main/java/io/opentelemetry/exporter/prometheus/Otel2PrometheusConverter.java @@ -125,8 +125,7 @@ MetricSnapshots convert(@Nullable Collection metricDataCollection) { if (resource == null) { resource = metricData.getResource(); } - if (otelScopeEnabled - && !metricData.getInstrumentationScopeInfo().getDescription().isEmpty()) { + if (otelScopeEnabled && !metricData.getInstrumentationScopeInfo().getAttributes().isEmpty()) { scopes.add(metricData.getInstrumentationScopeInfo()); } } @@ -210,7 +209,7 @@ private GaugeSnapshot convertLongGauge( data.add( new GaugeDataPointSnapshot( (double) longData.getValue(), - convertAttributes(resource, scope, longData.getDescription()), + convertAttributes(resource, scope, longData.getAttributes()), convertLongExemplar(longData.getExemplars()))); } return new GaugeSnapshot(metadata, data); @@ -226,7 +225,7 @@ private CounterSnapshot convertLongCounter( data.add( new CounterDataPointSnapshot( (double) longData.getValue(), - convertAttributes(resource, scope, longData.getDescription()), + convertAttributes(resource, scope, longData.getAttributes()), convertLongExemplar(longData.getExemplars()), longData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -243,7 +242,7 @@ private GaugeSnapshot convertDoubleGauge( data.add( new GaugeDataPointSnapshot( doubleData.getValue(), - convertAttributes(resource, scope, doubleData.getDescription()), + convertAttributes(resource, scope, doubleData.getAttributes()), convertDoubleExemplar(doubleData.getExemplars()))); } return new GaugeSnapshot(metadata, data); @@ -259,7 +258,7 @@ private CounterSnapshot convertDoubleCounter( data.add( new CounterDataPointSnapshot( doubleData.getValue(), - convertAttributes(resource, scope, doubleData.getDescription()), + convertAttributes(resource, scope, doubleData.getAttributes()), convertDoubleExemplar(doubleData.getExemplars()), doubleData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -280,7 +279,7 @@ private HistogramSnapshot convertHistogram( new HistogramDataPointSnapshot( ClassicHistogramBuckets.of(boundaries, histogramData.getCounts()), histogramData.getSum(), - convertAttributes(resource, scope, histogramData.getDescription()), + convertAttributes(resource, scope, histogramData.getAttributes()), convertDoubleExemplars(histogramData.getExemplars()), histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -302,7 +301,7 @@ private HistogramSnapshot convertExponentialHistogram( "Dropping histogram " + metadata.getName() + " with attributes " - + histogramData.getDescription() + + histogramData.getAttributes() + " because it has scale < -4 which is unsupported in Prometheus"); return null; } @@ -316,7 +315,7 @@ private HistogramSnapshot convertExponentialHistogram( convertExponentialHistogramBuckets(histogramData.getPositiveBuckets(), scaleDown), convertExponentialHistogramBuckets(histogramData.getNegativeBuckets(), scaleDown), histogramData.getSum(), - convertAttributes(resource, scope, histogramData.getDescription()), + convertAttributes(resource, scope, histogramData.getAttributes()), convertDoubleExemplars(histogramData.getExemplars()), histogramData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -360,7 +359,7 @@ private SummarySnapshot convertSummary( summaryData.getCount(), summaryData.getSum(), convertQuantiles(summaryData.getValues()), - convertAttributes(resource, scope, summaryData.getDescription()), + convertAttributes(resource, scope, summaryData.getAttributes()), Exemplars.EMPTY, // Exemplars for Summaries not implemented yet. summaryData.getStartEpochNanos() / NANOS_PER_MILLISECOND)); } @@ -438,7 +437,7 @@ private InfoSnapshot makeTargetInfo(Resource resource) { convertAttributes( null, // resource attributes are only copied for point's attributes null, // scope attributes are only needed for point's attributes - resource.getDescription())))); + resource.getAttributes())))); } private InfoSnapshot makeScopeInfo(Set scopes) { @@ -449,7 +448,7 @@ private InfoSnapshot makeScopeInfo(Set scopes) { convertAttributes( null, // resource attributes are only copied for point's attributes scope, - scope.getDescription()))); + scope.getAttributes()))); } return new InfoSnapshot(new MetricMetadata("otel_scope"), prometheusScopeInfos); } @@ -504,7 +503,7 @@ private Labels convertAttributes( } if (resource != null) { - Attributes resourceAttributes = resource.getDescription(); + Attributes resourceAttributes = resource.getAttributes(); for (AttributeKey attributeKey : allowedAttributeKeys) { Object attributeValue = resourceAttributes.get(attributeKey); if (attributeValue != null) { @@ -537,7 +536,7 @@ private List> filterAllowedResourceAttributeKeys(@Nullable Resou List> allowedAttributeKeys = resourceAttributesToAllowedKeysCache.computeIfAbsent( - resource.getDescription(), + resource.getAttributes(), resourceAttributes -> resourceAttributes.asMap().keySet().stream() .filter(o -> allowedResourceAttributesFilter.test(o.getKey())) diff --git a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java index bcfe6067ee7..3e3ee1774df 100644 --- a/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java +++ b/exporters/prometheus/src/test/java/io/opentelemetry/exporter/prometheus/CollectorIntegrationTest.java @@ -140,19 +140,19 @@ void endToEnd() { // Resource attributes from the metric SDK resource translated to target_info stringKeyValue( "service_name", - Objects.requireNonNull(resource.getDescription().get(stringKey("service.name")))), + Objects.requireNonNull(resource.getAttributes().get(stringKey("service.name")))), stringKeyValue( "telemetry_sdk_name", Objects.requireNonNull( - resource.getDescription().get(stringKey("telemetry.sdk.name")))), + resource.getAttributes().get(stringKey("telemetry.sdk.name")))), stringKeyValue( "telemetry_sdk_language", Objects.requireNonNull( - resource.getDescription().get(stringKey("telemetry.sdk.language")))), + resource.getAttributes().get(stringKey("telemetry.sdk.language")))), stringKeyValue( "telemetry_sdk_version", Objects.requireNonNull( - resource.getDescription().get(stringKey("telemetry.sdk.version"))))); + resource.getAttributes().get(stringKey("telemetry.sdk.version"))))); assertThat(resourceMetrics.getScopeMetricsCount()).isEqualTo(2); ScopeMetrics testScopeMetrics = diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java index 3dd0789847e..149f8736d9f 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java @@ -25,13 +25,13 @@ public EntityAssert hasType(String entityType) { return this; } - /** Asserts that the entity id satisfies the given asserts */ + /** Asserts that the entity id satisfies the given asserts. */ public EntityAssert hasIdSatisfying(ThrowingConsumer asserts) { asserts.accept(actual.getId()); return this; } - /** Asserts that the entity description satisfies the given asserts */ + /** Asserts that the entity description satisfies the given asserts. */ public EntityAssert hasDescriptionSatisfying(ThrowingConsumer asserts) { asserts.accept(actual.getDescription()); return this; From 01a9383a3098fe6cf625b34544b4bbdafa09f265 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 14:25:28 -0400 Subject: [PATCH 07/42] fix minor style issue --- .../io/opentelemetry/sdk/resources/internal/EntityUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index f63e7d2849c..9033acacca3 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -14,7 +14,7 @@ import java.util.Collections; import org.junit.jupiter.api.Test; -/** Unit tests for {@link EntityUtil} */ +/** Unit tests for {@link EntityUtil}. */ class EntityUtilTest { @Test void testMerge_entities_same_types_and_id() { From fb08d7d96f1adc7ae5df4e9b689232061d9e9166 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 22:06:39 -0400 Subject: [PATCH 08/42] Actually wire entities into resource - Remove ResourceWithEntity, no way to keep bincompat that way - Update toString tests to be a lot more forgiving, but preserve intent --- .../internal/otlp/ResourceMarshaler.java | 5 +- .../DeclarativeConfigurationCreateTest.java | 2 +- .../sdk/OpenTelemetrySdkTest.java | 35 ++------ .../opentelemetry/sdk/resources/Resource.java | 80 ++++++++++++------- .../sdk/resources/ResourceBuilder.java | 36 ++++++++- .../sdk/resources/internal/EntityUtil.java | 30 ++++++- .../internal/ResourceWithEntity.java | 62 -------------- .../internal/ResourceWithEntityBuilder.java | 28 ------- .../sdk/resources/internal/SdkEntity.java | 2 +- .../resources/internal/SdkEntityBuilder.java | 2 +- .../sdk/resources/ResourceTest.java | 19 +++++ .../sdk/logs/SdkLoggerProviderTest.java | 16 ++-- .../sdk/trace/SdkSpanBuilderTest.java | 11 +-- 13 files changed, 159 insertions(+), 169 deletions(-) delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java index f3e6bcb9659..84b6af6d008 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java @@ -38,8 +38,9 @@ public static ResourceMarshaler create(io.opentelemetry.sdk.resources.Resource r RealResourceMarshaler realMarshaler = new RealResourceMarshaler( KeyValueMarshaler.createForAttributes(resource.getAttributes()), - // TODO(jsuereth): This will support EntityRef in the future. - new EntityRefMarshaler[] {}); + resource.getEntities().stream() + .map(EntityRefMarshaler::createForEntity) + .toArray(MarshalerWithSize[]::new)); ByteArrayOutputStream binaryBos = new ByteArrayOutputStream(realMarshaler.getBinarySerializedSize()); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java index b72c0bc8ebe..b71a6e7fcc5 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java @@ -165,7 +165,7 @@ void create_ModelCustomizer() { DeclarativeConfigurationCreateTest.class.getClassLoader())); assertThat(sdk.toString()) .contains( - "resource=Resource{schemaUrl=null, attributes={" + "resource=Resource{schemaUrl=null, rawAttributes={" + "color=\"blue\", " + "foo=\"bar\", " + "service.name=\"unknown_service:java\", " diff --git a/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java b/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java index 8fb5eafdf47..c6a48a0dbad 100644 --- a/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java +++ b/sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java @@ -406,34 +406,13 @@ void stringRepresentation() { .setPropagators(ContextPropagators.create(propagator)) .build(); + // Test that toString delegates to underlying classes, and make sure their toString is also + // nice. assertThat(sdk.toString()) - .isEqualTo( - "OpenTelemetrySdk{" - + "tracerProvider=SdkTracerProvider{" - + "clock=SystemClock{}, " - + "idGenerator=RandomIdGenerator{}, " - + "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, " - + "spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647}, " - + "sampler=ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler,remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler,localParentNotSampled:AlwaysOffSampler}, " - + "spanProcessor=SimpleSpanProcessor{spanExporter=MultiSpanExporter{spanExporters=[MockSpanExporter{}, MockSpanExporter{}]}, exportUnsampledSpans=false}, " - + "tracerConfigurator=ScopeConfiguratorImpl{conditions=[]}" - + "}, " - + "meterProvider=SdkMeterProvider{" - + "clock=SystemClock{}, " - + "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, " - + "metricReaders=[PeriodicMetricReader{exporter=MockMetricExporter{}, intervalNanos=60000000000}], " - + "metricProducers=[], " - + "views=[RegisteredView{instrumentSelector=InstrumentSelector{instrumentName=instrument}, view=View{name=new-instrument, aggregation=DefaultAggregation, attributesProcessor=NoopAttributesProcessor{}, cardinalityLimit=2000}}], " - + "meterConfigurator=ScopeConfiguratorImpl{conditions=[]}" - + "}, " - + "loggerProvider=SdkLoggerProvider{" - + "clock=SystemClock{}, " - + "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, " - + "logLimits=LogLimits{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647}, " - + "logRecordProcessor=SimpleLogRecordProcessor{logRecordExporter=MultiLogRecordExporter{logRecordExporters=[MockLogRecordExporter{}, MockLogRecordExporter{}]}}, " - + "loggerConfigurator=ScopeConfiguratorImpl{conditions=[]}" - + "}, " - + "propagators=DefaultContextPropagators{textMapPropagator=MockTextMapPropagator{}}" - + "}"); + .matches("OpenTelemetrySdk\\{.*}") + .matches("OpenTelemetrySdk\\{tracerProvider=SdkTracerProvider\\{.*}.*}") + .matches("OpenTelemetrySdk\\{.*, meterProvider=SdkMeterProvider\\{.*}.*}") + .matches("OpenTelemetrySdk\\{.*, loggerProvider=SdkLoggerProvider\\{.*}.*}") + .matches("OpenTelemetrySdk\\{.*, propagators=DefaultContextPropagators\\{.*}}"); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java index 22e1ec98913..4a02d7c1c7e 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java @@ -11,8 +11,11 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.common.internal.OtelVersion; import io.opentelemetry.sdk.resources.internal.AttributeCheckUtil; +import io.opentelemetry.sdk.resources.internal.Entity; +import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.Collection; +import java.util.Collections; import java.util.Objects; -import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -23,8 +26,6 @@ @Immutable @AutoValue public abstract class Resource { - private static final Logger logger = Logger.getLogger(Resource.class.getName()); - private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); private static final AttributeKey TELEMETRY_SDK_LANGUAGE = AttributeKey.stringKey("telemetry.sdk.language"); @@ -100,8 +101,24 @@ public static Resource create(Attributes attributes) { * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. */ public static Resource create(Attributes attributes, @Nullable String schemaUrl) { + return create(attributes, schemaUrl, Collections.emptyList()); + } + + /** + * Returns a {@link Resource}. + * + * @param attributes a map of {@link Attributes} that describe the resource. + * @param schemaUrl The URL of the OpenTelemetry schema used to create this Resource. + * @param entities The set of detected {@link Entity}s that participate in this resource. + * @return a {@code Resource}. + * @throws NullPointerException if {@code attributes} is null. + * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable + * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. + */ + public static Resource create( + Attributes attributes, @Nullable String schemaUrl, Collection entities) { AttributeCheckUtil.checkAttributes(Objects.requireNonNull(attributes, "attributes")); - return new AutoValue_Resource(schemaUrl, attributes); + return new AutoValue_Resource(schemaUrl, attributes, entities); } /** @@ -113,12 +130,38 @@ public static Resource create(Attributes attributes, @Nullable String schemaUrl) @Nullable public abstract String getSchemaUrl(); + /** + * Returns a map of attributes that describe the resource, not associated with entites. + * + * @return a map of attributes. + */ + public abstract Attributes getRawAttributes(); + + /** + * Returns a collectoion of associated entities. + * + * @return a collection of entities. + */ + public abstract Collection getEntities(); + /** * Returns a map of attributes that describe the resource. * * @return a map of attributes. */ - public abstract Attributes getAttributes(); + // @Memoized - This breaks nullaway. + public Attributes getAttributes() { + AttributesBuilder result = Attributes.builder(); + getEntities() + .forEach( + e -> { + result.putAll(e.getId()); + result.putAll(e.getDescription()); + }); + // In merge rules, raw comes last, so we return these last. + result.putAll(getRawAttributes()); + return result.build(); + } /** * Returns the value for a given resource attribute key. @@ -138,32 +181,7 @@ public T getAttribute(AttributeKey key) { * @return the newly merged {@code Resource}. */ public Resource merge(@Nullable Resource other) { - if (other == null || other == EMPTY) { - return this; - } - - AttributesBuilder attrBuilder = Attributes.builder(); - attrBuilder.putAll(this.getAttributes()); - attrBuilder.putAll(other.getAttributes()); - - if (other.getSchemaUrl() == null) { - return create(attrBuilder.build(), getSchemaUrl()); - } - if (getSchemaUrl() == null) { - return create(attrBuilder.build(), other.getSchemaUrl()); - } - if (!other.getSchemaUrl().equals(getSchemaUrl())) { - logger.info( - "Attempting to merge Resources with different schemaUrls. " - + "The resulting Resource will have no schemaUrl assigned. Schema 1: " - + getSchemaUrl() - + " Schema 2: " - + other.getSchemaUrl()); - // currently, behavior is undefined if schema URLs don't match. In the future, we may - // apply schema transformations if possible. - return create(attrBuilder.build(), null); - } - return create(attrBuilder.build(), getSchemaUrl()); + return EntityUtil.merge(this, other); } /** diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java index 9963eeaf541..d9bf901a404 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java @@ -8,7 +8,14 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.resources.internal.Entity; +import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nullable; /** @@ -20,6 +27,7 @@ public class ResourceBuilder { private final AttributesBuilder attributesBuilder = Attributes.builder(); + private final List entities = new ArrayList<>(); @Nullable private String schemaUrl; /** @@ -194,6 +202,32 @@ public ResourceBuilder setSchemaUrl(String schemaUrl) { /** Create the {@link Resource} from this. */ public Resource build() { - return Resource.create(attributesBuilder.build(), schemaUrl); + // What checks should we do on "real" resource here? + // Derive schemaUrl from entitiy, if able. + if (schemaUrl == null) { + Set entitySchemas = + entities.stream().map(Entity::getSchemaUrl).collect(Collectors.toSet()); + if (entitySchemas.size() == 1) { + // Updated Entities use same schema, we can preserve it. + schemaUrl = entitySchemas.iterator().next(); + } + } + + // TODO - here we deal with conflicts between entities and raw attributes. + // When adding an entity, we remove any raw attributes it may conflict with. + this.attributesBuilder.removeIf(key -> EntityUtil.hasAttributeKey(this.entities, key)); + return Resource.create(attributesBuilder.build(), schemaUrl, entities); + } + + /** Appends a new entity on to the end of the list of entities. */ + public ResourceBuilder add(Entity e) { + this.entities.add(e); + return this; + } + + /** Appends a new collection of entities on to the end of the list of entities. */ + public ResourceBuilder addAll(Collection entities) { + this.entities.addAll(entities); + return this; } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index 1a2e50e52a0..18a3e8977aa 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.resources.Resource; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -29,7 +30,8 @@ public final class EntityUtil { private EntityUtil() {} /** Returns true if any entity in the collection has the attribute key, in id or description. */ - static final boolean hasAttributeKey(Collection entities, AttributeKey key) { + public static final boolean hasAttributeKey( + Collection entities, AttributeKey key) { return entities.stream() .anyMatch( e -> e.getId().asMap().containsKey(key) || e.getDescription().asMap().containsKey(key)); @@ -178,4 +180,30 @@ public static final Collection mergeEntities( } return entities.values(); } + + /** + * Returns a new, merged {@link Resource} by merging the {@code base} {@code Resource} with the + * {@code next} {@code Resource}. In case of a collision, the "next" {@code Resource} takes + * precedence. + * + * @param base the {@code Resource} into which we merge new values. + * @param next the {@code Resource} that will be merged with {@code base}. + * @return the newly merged {@code Resource}. + */ + public static Resource merge(Resource base, @Nullable Resource next) { + if (next == null || next == Resource.empty()) { + return base; + } + // Merge Algorithm from + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/entities/0264-resource-and-entities.md#entity-merging-and-resource + Collection entities = EntityUtil.mergeEntities(base.getEntities(), next.getEntities()); + RawAttributeMergeResult attributeResult = + EntityUtil.mergeRawAttributes(base.getRawAttributes(), next.getRawAttributes(), entities); + // Remove entiites that are conflicting with raw attributes, and therefore in an unknown state. + entities.removeAll(attributeResult.getConflicts()); + // Now figure out schema url for overall resource. + String schemaUrl = + EntityUtil.mergeResourceSchemaUrl(entities, base.getSchemaUrl(), next.getSchemaUrl()); + return Resource.create(attributeResult.getAttributes(), schemaUrl, entities); + } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java deleted file mode 100644 index 1f700d4c9e9..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.resources.ResourceBuilder; -import java.util.Collection; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -/** - * {@link Resource} represents a resource, which capture identifying information about the entities - * for which signals (stats or traces) are reported. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -@Immutable -public interface ResourceWithEntity { - /** - * Returns the URL of the OpenTelemetry schema used by this resource. May be null. - * - * @return An OpenTelemetry schema URL. - * @since 1.4.0 - */ - @Nullable - String getSchemaUrl(); - - /** - * Returns a map of attributes that describe the resource, not associated with an entity. - * - * @return a map of attributes. - */ - Attributes getRawAttributes(); - - /** - * Returns a collectoion of associated entities. - * - * @return a collection of entities. - */ - Collection getEntities(); - - /** - * Returns a map of attributes that describe the resource. - * - *

Note: this includes all entity attribtues and raw attributes. - * - * @return a map of attributes. - */ - Attributes getAttributes(); - - /** - * Returns a new {@link ResourceBuilder} instance populated with the data of this {@link - * Resource}. - */ - ResourceWithEntityBuilder toBuilder(); - - // TODO - Merge -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java deleted file mode 100644 index 4e60617ea48..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.Attributes; -import java.util.Collection; - -/** - * A builder of {@link ResourceWithEntity} that allows to add key-value pairs and copy attributes - * from other {@link Attributes} or {@link ResourceWithEntity}. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public interface ResourceWithEntityBuilder { - // TODO - Raw Attributes. - /** Appends a new entity on to the end of the list of entities. */ - ResourceWithEntityBuilder add(Entity e); - - /** Appends a new collection of entities on to the end of the list of entities. */ - ResourceWithEntityBuilder addAll(Collection entities); - - /** Create the {@link ResourceWithEntity} from this. */ - ResourceWithEntity build(); -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java index a81a37c6601..6a5b31e31d6 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java @@ -18,7 +18,7 @@ */ @Immutable @AutoValue -public abstract class SdkEntity implements Entity { +abstract class SdkEntity implements Entity { /** * Returns a {@link Entity}. * diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java index 3d235c0b316..657bf3f5398 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java @@ -17,7 +17,7 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public final class SdkEntityBuilder implements EntityBuilder { +final class SdkEntityBuilder implements EntityBuilder { private final String entityType; private final AttributesBuilder descriptionBuilder; private final AttributesBuilder idBuilder; diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java index 1654a001a96..8cbc843057b 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java @@ -13,6 +13,7 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -21,6 +22,7 @@ import io.opentelemetry.api.common.AttributeType; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.resources.internal.Entity; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -234,6 +236,23 @@ void testMergeResources_Resource2_Null() { assertThat(resource.getAttributes()).isEqualTo(expectedAttributes); } + @Test + void testMergeResources_entities_separate_types_and_schema() { + Resource resource1 = + Resource.builder() + .add(Entity.builder("a").setSchemaUrl("one").withId(id -> id.put("a.id", "a")).build()) + .build(); + Resource resource2 = + Resource.builder() + .add(Entity.builder("b").setSchemaUrl("two").withId(id -> id.put("b.id", "b")).build()) + .build(); + Resource merged = resource1.merge(resource2); + assertThat(merged.getSchemaUrl()).isNull(); + assertThat(merged.getEntities()).hasSize(2); + assertThat(merged.getAttributes()).containsEntry("a.id", "a"); + assertThat(merged.getAttributes()).containsEntry("b.id", "b"); + } + @Test void testDefaultResources() { Resource resource = Resource.getDefault(); diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java index c6f0b2885ec..2f580d83e07 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java @@ -343,15 +343,15 @@ void close() { @Test void toString_Valid() { when(logRecordProcessor.toString()).thenReturn("MockLogRecordProcessor"); + // Check that components of Logger are to-stringed. assertThat(sdkLoggerProvider.toString()) - .isEqualTo( - "SdkLoggerProvider{" - + "clock=SystemClock{}, " - + "resource=Resource{schemaUrl=null, attributes={key=\"value\"}}, " - + "logLimits=LogLimits{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647}, " - + "logRecordProcessor=MockLogRecordProcessor, " - + "loggerConfigurator=ScopeConfiguratorImpl{conditions=[]}" - + "}"); + .matches("SdkLoggerProvider\\{.*}") + .matches(".*clock=SystemClock\\{}.*") + .matches(".*resource=Resource\\{.*}.*") + .matches( + ".*logLimits=LogLimits\\{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647},.*") + .matches(".*logRecordProcessor=MockLogRecordProcessor, .*") + .matches(".*loggerConfigurator=ScopeConfiguratorImpl\\{conditions=\\[]}.*"); } private static ScopeConfigurator flipConfigurator(boolean enabled) { diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index 9c9ecdc7e9c..aa81ce1e426 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -977,8 +977,12 @@ void spanDataToString() { span.setAttribute("http.url", "https://opentelemetry.io"); span.setStatus(StatusCode.ERROR, "error"); span.end(); - assertThat(span.toSpanData().toString()) + .matches("SpanData\\{.*}") + .matches(".*traceId=[0-9a-f]{32}, .*") + .matches(".*spanId=[0-9a-f]{16}, .*") + .matches(".*traceFlags=01, .*") + .matches(".*traceFlags=01, .*") .matches( "SpanData\\{spanContext=ImmutableSpanContext\\{" + "traceId=[0-9a-f]{32}, " @@ -990,10 +994,7 @@ void spanDataToString() { + "spanId=0000000000000000, " + "traceFlags=00, " + "traceState=ArrayBasedTraceState\\{entries=\\[]}, remote=false, valid=false}, " - + "resource=Resource\\{schemaUrl=null, " - + "attributes=\\{service.name=\"unknown_service:java\", " - + "telemetry.sdk.language=\"java\", telemetry.sdk.name=\"opentelemetry\", " - + "telemetry.sdk.version=\"\\d+.\\d+.\\d+(-rc.\\d+)?(-SNAPSHOT)?\"}}, " + + "resource=Resource\\{.*}, " + "instrumentationScopeInfo=InstrumentationScopeInfo\\{" + "name=SpanBuilderSdkTest, version=null, schemaUrl=null, attributes=\\{}}, " + "name=span_name, " From 1f00dcb1c6e9fab9cca5a36a818d04bb2723ceb8 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 22:17:50 -0400 Subject: [PATCH 09/42] Fix typo --- .../io/opentelemetry/sdk/resources/internal/EntityUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index 9033acacca3..cb05ddffe77 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -202,7 +202,7 @@ void testSchemaUrlMerge_entities_same_url() { @Test void testSchemaUrlMerge_entities_different_url() { - // When entities have conflciting schema urls, we cannot fill out resource schema url, + // When entities have conflicting schema urls, we cannot fill out resource schema url, // no matter what. String result = EntityUtil.mergeResourceSchemaUrl( From cfd444b4309648c565b7cdb51b506d89647bbee1 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 20 Jun 2025 18:12:47 -0400 Subject: [PATCH 10/42] Remove all publicly accessible references to Entity from stable packages --- .../internal/otlp/ResourceMarshaler.java | 3 +- .../opentelemetry/sdk/resources/Resource.java | 4 +- .../sdk/resources/ResourceBuilder.java | 4 +- .../sdk/resources/internal/EntityUtil.java | 90 ++++++++++++++++++- 4 files changed, 94 insertions(+), 7 deletions(-) diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java index 84b6af6d008..8be7df10099 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java @@ -10,6 +10,7 @@ import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize; import io.opentelemetry.exporter.internal.marshal.Serializer; import io.opentelemetry.proto.resource.v1.internal.Resource; +import io.opentelemetry.sdk.resources.internal.EntityUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -38,7 +39,7 @@ public static ResourceMarshaler create(io.opentelemetry.sdk.resources.Resource r RealResourceMarshaler realMarshaler = new RealResourceMarshaler( KeyValueMarshaler.createForAttributes(resource.getAttributes()), - resource.getEntities().stream() + EntityUtil.getEntities(resource).stream() .map(EntityRefMarshaler::createForEntity) .toArray(MarshalerWithSize[]::new)); diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java index 4a02d7c1c7e..50fed603b94 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java @@ -135,14 +135,14 @@ public static Resource create( * * @return a map of attributes. */ - public abstract Attributes getRawAttributes(); + abstract Attributes getRawAttributes(); /** * Returns a collectoion of associated entities. * * @return a collection of entities. */ - public abstract Collection getEntities(); + abstract Collection getEntities(); /** * Returns a map of attributes that describe the resource. diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java index d9bf901a404..f2abb8d8b20 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java @@ -220,13 +220,13 @@ public Resource build() { } /** Appends a new entity on to the end of the list of entities. */ - public ResourceBuilder add(Entity e) { + ResourceBuilder add(Entity e) { this.entities.add(e); return this; } /** Appends a new collection of entities on to the end of the list of entities. */ - public ResourceBuilder addAll(Collection entities) { + ResourceBuilder addAll(Collection entities) { this.entities.addAll(entities); return this; } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index 18a3e8977aa..0698e242fac 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -9,11 +9,16 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -29,6 +34,87 @@ public final class EntityUtil { private EntityUtil() {} + /** Appends a new entity on to the end of the list of entities. */ + public static final ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { + try { + Method method = Resource.class.getDeclaredMethod("add", Entity.class); + if (method != null) { + method.setAccessible(true); + method.invoke(rb, e); + } + } catch (NoSuchMethodException nme) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); + } catch (IllegalAccessException iae) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); + } catch (InvocationTargetException ite) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } + return rb; + } + + /** Appends a new collection of entities on to the end of the list of entities. */ + public static final ResourceBuilder addAllEntity(ResourceBuilder rb, Collection e) { + try { + Method method = Resource.class.getDeclaredMethod("addAll", Collection.class); + if (method != null) { + method.setAccessible(true); + method.invoke(rb, e); + } + } catch (NoSuchMethodException nme) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); + } catch (IllegalAccessException iae) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); + } catch (InvocationTargetException ite) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } + return rb; + } + + /** + * Returns a collectoion of associated entities. + * + * @return a collection of entities. + */ + @SuppressWarnings("unchecked") + public static final Collection getEntities(Resource r) { + try { + Method method = Resource.class.getDeclaredMethod("getEntities"); + if (method != null) { + method.setAccessible(true); + return (Collection) method.invoke(r); + } + } catch (NoSuchMethodException nme) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); + } catch (IllegalAccessException iae) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); + } catch (InvocationTargetException ite) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } + return Collections.emptyList(); + } + + /** + * Returns a map of attributes that describe the resource, not associated with entites. + * + * @return a map of attributes. + */ + public static final Attributes getRawAttributes(Resource r) { + try { + Method method = Resource.class.getDeclaredMethod("getRawAttributes"); + if (method != null) { + method.setAccessible(true); + return (Attributes) method.invoke(r); + } + } catch (NoSuchMethodException nme) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); + } catch (IllegalAccessException iae) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); + } catch (InvocationTargetException ite) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } + return Attributes.empty(); + } + /** Returns true if any entity in the collection has the attribute key, in id or description. */ public static final boolean hasAttributeKey( Collection entities, AttributeKey key) { @@ -196,9 +282,9 @@ public static Resource merge(Resource base, @Nullable Resource next) { } // Merge Algorithm from // https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/entities/0264-resource-and-entities.md#entity-merging-and-resource - Collection entities = EntityUtil.mergeEntities(base.getEntities(), next.getEntities()); + Collection entities = EntityUtil.mergeEntities(getEntities(base), getEntities(next)); RawAttributeMergeResult attributeResult = - EntityUtil.mergeRawAttributes(base.getRawAttributes(), next.getRawAttributes(), entities); + EntityUtil.mergeRawAttributes(getRawAttributes(base), getRawAttributes(next), entities); // Remove entiites that are conflicting with raw attributes, and therefore in an unknown state. entities.removeAll(attributeResult.getConflicts()); // Now figure out schema url for overall resource. From 0e0bc4e009af3e97867841a1518fdf8a75d8b26f Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 20 Jun 2025 19:17:38 -0400 Subject: [PATCH 11/42] Add incubating EntityProvider API for specification work --- .../extension/incubator/entities/Entity.java | 71 +++++++++++++++++++ .../incubator/entities/EntityBuilder.java | 43 +++++++++++ .../incubator/entities/EntityDetector.java | 22 ++++++ .../incubator/entities/EntityProvider.java | 29 ++++++++ .../entities/EntityProviderBuilder.java | 32 +++++++++ .../incubator/entities/PassthroughEntity.java | 52 ++++++++++++++ .../entities/PassthroughEntityBuilder.java | 40 +++++++++++ .../incubator/entities/SdkEntityProvider.java | 26 +++++++ .../entities/SdkEntityProviderBuilder.java | 66 +++++++++++++++++ .../entities/detectors/ServiceDetector.java | 58 +++++++++++++++ .../detectors/TelemetrySdkDetector.java | 49 +++++++++++++ .../entities/TestEntityProvider.java | 32 +++++++++ 12 files changed, 520 insertions(+) create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java new file mode 100644 index 00000000000..63fee4c56ac --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.common.Attributes; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Entity represents an object of interest associated with produced telemetry: traces, metrics or + * logs. + * + *

For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service + * entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we + * want to associate metrics with in this case. + * + *

Entities may be also associated with produced telemetry indirectly. For example a service that + * produces telemetry is also related with a process in which the service runs, so we say that the + * Service entity is related to the Process entity. The process normally also runs on a host, so we + * say that the Process entity is related to the Host entity. + */ +@Immutable +public interface Entity { + /** + * Returns the entity type string of this entity. Must not be null. + * + * @return the entity type. + */ + String getType(); + + /** + * Returns a map of attributes that identify the entity. + * + * @return a map of attributes. + */ + Attributes getId(); + + /** + * Returns a map of attributes that describe the entity. + * + * @return a map of attributes. + */ + Attributes getDescription(); + + /** + * Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity + * does not abide by schema conventions (i.e. is custom). + * + * @return An OpenTelemetry schema URL. + * @since 1.4.0 + */ + @Nullable + String getSchemaUrl(); + + /** + * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. + */ + EntityBuilder toBuilder(); + + /** + * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. + * + * @param entityType the entity type string of this entity. + */ + public static EntityBuilder builder(String entityType) { + return PassthroughEntity.builder(entityType); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java new file mode 100644 index 00000000000..d04b287af2b --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.function.Consumer; + +/** + * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as + * well as type and schema_url. + */ +public interface EntityBuilder { + /** + * Assign an OpenTelemetry schema URL to the resulting Entity. + * + * @param schemaUrl The URL of the OpenTelemetry schema being used to create this Entity. + * @return this + */ + EntityBuilder setSchemaUrl(String schemaUrl); + + /** + * Modify the descriptive attributes of this Entity. + * + * @param f A thunk which manipulates descriptive attributes. + * @return this + */ + EntityBuilder withDescription(Consumer f); + + /** + * Modify the identifying attributes of this Entity. + * + * @param f A thunk which manipulates identifying attributes. + * @return this + */ + EntityBuilder withId(Consumer f); + + /** Create the {@link Entity} from this. */ + Entity build(); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java new file mode 100644 index 00000000000..5565e89aa82 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import java.util.Collection; + +/** + * The Entity detector in the SDK is responsible for detecting possible entities that could identify + * the SDK (called "associated entities"). For Example, if the SDK is running in a kubernetes pod, + * it may provide an Entity for that pod. + */ +public interface EntityDetector { + /** + * Discovers {@link Entity} and their current attributes. + * + * @return a list of discovered entities. + */ + public Collection detect(); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java new file mode 100644 index 00000000000..4abc0a54a64 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.resources.Resource; + +/** + * A provider of {@link Resource} for this SDK. + * + *

{@code EntityProvider} is responsible for: + * + *

- Detecting Entities using registered detectors. - Providing thread-safe access to the current + * resource. + * + *

The future of this class may include the ability to add/remove {@link Entity} objects or + * re-run detectors. + */ +public interface EntityProvider { + /** the current {@link Resource} detected. */ + public Resource getResource(); + + /** A builder of {@link EntityProvider}. */ + public static EntityProviderBuilder builder() { + return new SdkEntityProviderBuilder(); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java new file mode 100644 index 00000000000..c2173da7dce --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.resources.Resource; + +/** Builder of {@link EntityProvider}. */ +public interface EntityProviderBuilder { + /** Adds an entity detector, which will detect {@link Entity}s to place in the resource. */ + EntityProviderBuilder addDetector(EntityDetector detector); + + /** + * Adds a discovered resource to include in resolving the SDK's resource. + * + * @deprecated Use {@link #addDetector(EntityDetector)}. + */ + @Deprecated + EntityProviderBuilder addDetectedResource(Resource resource); + + /** Sets whether or not default entity detectors will be included. */ + EntityProviderBuilder includeDefaults(boolean include); + + /** + * Returns the SDK entity provider which uses these detectors. + * + * @return the EntityProvider. + */ + EntityProvider build(); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java new file mode 100644 index 00000000000..d0dcb67a709 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.common.Attributes; +import javax.annotation.Nullable; + +final class PassthroughEntity implements Entity { + private final io.opentelemetry.sdk.resources.internal.Entity entity; + + PassthroughEntity(io.opentelemetry.sdk.resources.internal.Entity entity) { + this.entity = entity; + } + + io.opentelemetry.sdk.resources.internal.Entity getPassthrough() { + return entity; + } + + @Override + public String getType() { + return entity.getType(); + } + + @Override + public Attributes getId() { + return entity.getId(); + } + + @Override + public Attributes getDescription() { + return entity.getDescription(); + } + + @Override + @Nullable + public String getSchemaUrl() { + return entity.getSchemaUrl(); + } + + @Override + public EntityBuilder toBuilder() { + return new PassthroughEntityBuilder(entity.toBuilder()); + } + + static EntityBuilder builder(String entityType) { + return new PassthroughEntityBuilder( + io.opentelemetry.sdk.resources.internal.Entity.builder(entityType)); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java new file mode 100644 index 00000000000..21617121fc3 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.function.Consumer; + +final class PassthroughEntityBuilder implements EntityBuilder { + private final io.opentelemetry.sdk.resources.internal.EntityBuilder builder; + + PassthroughEntityBuilder(io.opentelemetry.sdk.resources.internal.EntityBuilder builder) { + this.builder = builder; + } + + @Override + public EntityBuilder setSchemaUrl(String schemaUrl) { + builder.setSchemaUrl(schemaUrl); + return this; + } + + @Override + public EntityBuilder withDescription(Consumer f) { + builder.withDescription(f); + return this; + } + + @Override + public EntityBuilder withId(Consumer f) { + builder.withId(f); + return this; + } + + @Override + public Entity build() { + return new PassthroughEntity(builder.build()); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java new file mode 100644 index 00000000000..aece8f36897 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.resources.Resource; + +/** + * Instance of {@link EntityProvider}. + * + *

This class doesn't do much now, but will expand in responsibilities. + */ +class SdkEntityProvider implements EntityProvider { + private final Resource resource; + + SdkEntityProvider(Resource resource) { + this.resource = resource; + } + + @Override + public Resource getResource() { + return resource; + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java new file mode 100644 index 00000000000..c8c6d961599 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.extension.incubator.entities.detectors.ServiceDetector; +import io.opentelemetry.sdk.extension.incubator.entities.detectors.TelemetrySdkDetector; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +class SdkEntityProviderBuilder implements EntityProviderBuilder { + private final List entityDetectors = new ArrayList<>(); + private final List detectedResources = new ArrayList<>(); + private boolean includeDefaults = true; + + @Override + public EntityProviderBuilder addDetector(EntityDetector detector) { + this.entityDetectors.add(detector); + return this; + } + + @Override + @Deprecated + public EntityProviderBuilder addDetectedResource(Resource resource) { + this.detectedResources.add(resource); + return this; + } + + private final Resource mergeDetectedAndRaw() { + if (includeDefaults) { + entityDetectors.add(new ServiceDetector()); + entityDetectors.add(new TelemetrySdkDetector()); + } + Resource result = Resource.empty(); + for (EntityDetector detector : entityDetectors) { + result = + result.merge( + EntityUtil.addAllEntity( + Resource.builder(), + detector.detect().stream() + .map(e -> ((PassthroughEntity) e).getPassthrough()) + .collect(Collectors.toList())) + .build()); + } + for (Resource next : detectedResources) { + result = result.merge(next); + } + return result; + } + + @Override + public EntityProvider build() { + return new SdkEntityProvider(mergeDetectedAndRaw()); + } + + @Override + public EntityProviderBuilder includeDefaults(boolean include) { + this.includeDefaults = include; + return this; + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java new file mode 100644 index 00000000000..1c359a0e845 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities.detectors; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.extension.incubator.entities.Entity; +import io.opentelemetry.sdk.extension.incubator.entities.EntityDetector; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +/** + * Detection for {@code service} entity. + * + *

See: service + * entity + */ +public final class ServiceDetector implements EntityDetector { + private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.34.0"; + private static final String ENTITY_TYPE = "service"; + private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); + private static final AttributeKey SERVICE_INSTANCE_ID = + AttributeKey.stringKey("service.instance.id"); + private static final UUID FALLBACK_INSTANCE_ID = UUID.randomUUID(); + + private static String getServiceName() { + return System.getenv().getOrDefault("OTEL_SERVICE_NAME", "unknown_service:java"); + } + + private static String getServiceInstanceId() { + // TODO - no way for users to specify a non-default. + return FALLBACK_INSTANCE_ID.toString(); + } + + @Override + public Collection detect() { + return Collections.singletonList( + Entity.builder(ENTITY_TYPE) + .setSchemaUrl(SCHEMA_URL) + .withId( + id -> { + // Note: Identifying attributes MUST be provided together. + id.put(SERVICE_NAME, getServiceName()) + .put(SERVICE_INSTANCE_ID, getServiceInstanceId()); + }) + // No specified way to take these in. + // .withDescriptive( + // builder -> { + // if (!StringUtils.isNullOrEmpty(getVersion())) { + // builder.put(SERVICE_VERSION, getVersion()); + // } + // }) + .build()); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java new file mode 100644 index 00000000000..4ebc51037b9 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities.detectors; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.common.internal.OtelVersion; +import io.opentelemetry.sdk.extension.incubator.entities.Entity; +import io.opentelemetry.sdk.extension.incubator.entities.EntityDetector; +import java.util.Collection; +import java.util.Collections; + +/** + * Detection for {@code telemetry.sdk} entity. + * + *

See: teleemtry.sdk + * entity + */ +public class TelemetrySdkDetector implements EntityDetector { + private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.28.0"; + private static final String ENTITY_TYPE = "telemetry.sdk"; + private static final AttributeKey TELEMETRY_SDK_LANGUAGE = + AttributeKey.stringKey("telemetry.sdk.language"); + private static final AttributeKey TELEMETRY_SDK_NAME = + AttributeKey.stringKey("telemetry.sdk.name"); + private static final AttributeKey TELEMETRY_SDK_VERSION = + AttributeKey.stringKey("telemetry.sdk.version"); + private static final Entity TELEMETRY_SDK; + + static { + TELEMETRY_SDK = + Entity.builder(ENTITY_TYPE) + .setSchemaUrl(SCHEMA_URL) + .withId( + id -> { + id.put(TELEMETRY_SDK_NAME, "opentelemetry").put(TELEMETRY_SDK_LANGUAGE, "java"); + }) + .withDescription(desc -> desc.put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION)) + .build(); + } + + @Override + public Collection detect() { + return Collections.singletonList(TELEMETRY_SDK); + } +} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java new file mode 100644 index 00000000000..810a64e678a --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.resources.internal.EntityUtil; +import org.junit.jupiter.api.Test; + +class TestEntityProvider { + @Test + void defaults_includeServiceAndSdk() { + EntityProvider provider = EntityProvider.builder().includeDefaults(true).build(); + + assertThat(provider.getResource().getAttributes()) + .containsKey("service.name") + .containsKey("service.instance.id") + .containsKey("telemetry.sdk.language") + .containsKey("telemetry.sdk.name") + .containsKey("telemetry.sdk.version"); + assertThat(provider.getResource().getSchemaUrl()) + .isEqualTo("https://opentelemetry.io/schemas/1.28.0"); + + assertThat(EntityUtil.getEntities(provider.getResource())) + .satisfiesExactlyInAnyOrder( + e -> assertThat(e).hasType("service"), e -> assertThat(e).hasType("telemetry.sdk")); + } +} From c8bfbd4faf9037a284e0af0c73ad47589f283ff7 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 21 Jun 2025 07:00:23 -0400 Subject: [PATCH 12/42] Fix refactoring that broke reflection. - fix reflective method lookup - also add unit test in the right project. --- .../sdk/resources/internal/EntityUtil.java | 4 +-- .../resources/internal/EntityUtilTest.java | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index 0698e242fac..f1472e63fe7 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -37,7 +37,7 @@ private EntityUtil() {} /** Appends a new entity on to the end of the list of entities. */ public static final ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { try { - Method method = Resource.class.getDeclaredMethod("add", Entity.class); + Method method = ResourceBuilder.class.getDeclaredMethod("add", Entity.class); if (method != null) { method.setAccessible(true); method.invoke(rb, e); @@ -55,7 +55,7 @@ public static final ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { /** Appends a new collection of entities on to the end of the list of entities. */ public static final ResourceBuilder addAllEntity(ResourceBuilder rb, Collection e) { try { - Method method = Resource.class.getDeclaredMethod("addAll", Collection.class); + Method method = ResourceBuilder.class.getDeclaredMethod("addAll", Collection.class); if (method != null) { method.setAccessible(true); method.invoke(rb, e); diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index cb05ddffe77..f9e9eca64d8 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.resources.Resource; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -246,4 +247,28 @@ void testRawAttributeMerge_entity_with_conflict() { .containsEntry("b", 2) .containsEntry("c", 2); } + + @Test + void testAddEntity_reflection() { + Resource result = + EntityUtil.addEntity( + Resource.builder(), Entity.builder("a").withId(id -> id.put("a", 1)).build()) + .build(); + assertThat(EntityUtil.getEntities(result)) + .satisfiesExactlyInAnyOrder(e -> assertThat(e).hasType("a")); + } + + @Test + void testAddAllEntity_reflection() { + Resource result = + EntityUtil.addAllEntity( + Resource.builder(), + Arrays.asList( + Entity.builder("a").withId(id -> id.put("a", 1)).build(), + Entity.builder("b").withId(id -> id.put("b", 1)).build())) + .build(); + assertThat(EntityUtil.getEntities(result)) + .satisfiesExactlyInAnyOrder( + e -> assertThat(e).hasType("a"), e -> assertThat(e).hasType("b")); + } } From e93fcebd00d74612c2c6b72ddc5f404c6f22a621 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 21 Jun 2025 08:45:50 -0400 Subject: [PATCH 13/42] Fix schema url and test, now that we proved it works --- .../incubator/entities/detectors/TelemetrySdkDetector.java | 2 +- .../sdk/extension/incubator/entities/TestEntityProvider.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java index 4ebc51037b9..bf4273ecf58 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java @@ -20,7 +20,7 @@ * entity */ public class TelemetrySdkDetector implements EntityDetector { - private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.28.0"; + private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.34.0"; private static final String ENTITY_TYPE = "telemetry.sdk"; private static final AttributeKey TELEMETRY_SDK_LANGUAGE = AttributeKey.stringKey("telemetry.sdk.language"); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java index 810a64e678a..f26b93137df 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java @@ -23,7 +23,7 @@ void defaults_includeServiceAndSdk() { .containsKey("telemetry.sdk.name") .containsKey("telemetry.sdk.version"); assertThat(provider.getResource().getSchemaUrl()) - .isEqualTo("https://opentelemetry.io/schemas/1.28.0"); + .isEqualTo("https://opentelemetry.io/schemas/1.34.0"); assertThat(EntityUtil.getEntities(provider.getResource())) .satisfiesExactlyInAnyOrder( From 96218f76148e84fa3eba0d059aed476ee4f838a2 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 5 Jul 2025 15:01:30 -0400 Subject: [PATCH 14/42] Initial cut at moving Entity SDK to have correpsonding API. - Creates an API matching EntityProvider OTEP - Updates EntityDetector to be ResourceDetector - Creates synchronous "update resource with this entity" method. - Adds wiring for incubating API + incubating SDK Note: Still have some thoughts and ideas about a formal API, this may still change. --- .../api}/incubator/entities/Entity.java | 11 +-- .../incubator/entities/EntityBuilder.java | 6 +- .../entities/ExtendedOpenTelemetry.java | 21 +++++ .../api/incubator/entities/NoopEntity.java | 36 +++++++++ .../incubator/entities/NoopEntityBuilder.java | 31 ++++++++ .../api/incubator/entities/NoopResource.java | 24 ++++++ .../entities/NoopResourceProvider.java | 14 ++++ .../api/incubator/entities/Resource.java | 35 +++++++++ .../incubator/entities/ResourceProvider.java | 25 ++++++ .../incubator/entities/EntityDetector.java | 22 ------ .../incubator/entities/EntityProvider.java | 29 ------- .../entities/EntityProviderBuilder.java | 32 -------- .../incubator/entities/PassthroughEntity.java | 2 + .../entities/PassthroughEntityBuilder.java | 2 + .../incubator/entities/ResourceDetector.java | 22 ++++++ .../incubator/entities/SdkEntityProvider.java | 26 ------- .../entities/SdkEntityProviderBuilder.java | 66 ---------------- .../incubator/entities/SdkResource.java | 78 +++++++++++++++++++ .../entities/SdkResourceProvider.java | 27 +++++++ .../entities/SdkResourceProviderBuilder.java | 51 ++++++++++++ .../entities/detectors/ServiceDetector.java | 16 ++-- .../detectors/TelemetrySdkDetector.java | 24 +++--- ...rovider.java => TestResourceProvider.java} | 10 +-- 23 files changed, 394 insertions(+), 216 deletions(-) rename {sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension => api/incubator/src/main/java/io/opentelemetry/api}/incubator/entities/Entity.java (84%) rename {sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension => api/incubator/src/main/java/io/opentelemetry/api}/incubator/entities/EntityBuilder.java (83%) create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java rename sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/{TestEntityProvider.java => TestResourceProvider.java} (73%) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java similarity index 84% rename from sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java index 63fee4c56ac..e594a3175d2 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/Entity.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.extension.incubator.entities; +package io.opentelemetry.api.incubator.entities; import io.opentelemetry.api.common.Attributes; import javax.annotation.Nullable; @@ -59,13 +59,4 @@ public interface Entity { * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. */ EntityBuilder toBuilder(); - - /** - * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. - * - * @param entityType the entity type string of this entity. - */ - public static EntityBuilder builder(String entityType) { - return PassthroughEntity.builder(entityType); - } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java similarity index 83% rename from sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java index d04b287af2b..546c1f68d0d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.sdk.extension.incubator.entities; +package io.opentelemetry.api.incubator.entities; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; @@ -25,7 +25,7 @@ public interface EntityBuilder { /** * Modify the descriptive attributes of this Entity. * - * @param f A thunk which manipulates descriptive attributes. + * @param f A {@link Consumer} which builds the descriptive attributes. * @return this */ EntityBuilder withDescription(Consumer f); @@ -33,7 +33,7 @@ public interface EntityBuilder { /** * Modify the identifying attributes of this Entity. * - * @param f A thunk which manipulates identifying attributes. + * @param f A {@link Consumer} which builds the identifying attributes. * @return this */ EntityBuilder withId(Consumer f); diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java new file mode 100644 index 00000000000..d558bc8e146 --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +import io.opentelemetry.api.OpenTelemetry; + +/** Extension to {@link OpenTelemetry} that adds {@link ResourceProvider}. */ +public interface ExtendedOpenTelemetry extends OpenTelemetry { + /** Returns the {@link ResourceProvider} for this {@link OpenTelemetry}. */ + default ResourceProvider getResourceProvider() { + return ResourceProvider.noop(); + } + + /** Returns the {@link Resource} that telemetry from this {@link OpenTelemetry} uses. */ + default Resource getResource() { + return getResourceProvider().getResource(); + } +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java new file mode 100644 index 00000000000..3155b5bf4dc --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +import io.opentelemetry.api.common.Attributes; + +final class NoopEntity implements Entity { + + @Override + public String getType() { + return ""; + } + + @Override + public Attributes getId() { + return Attributes.empty(); + } + + @Override + public Attributes getDescription() { + return Attributes.empty(); + } + + @Override + public String getSchemaUrl() { + return ""; + } + + @Override + public EntityBuilder toBuilder() { + return new NoopEntityBuilder(); + } +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java new file mode 100644 index 00000000000..274237a3b71 --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +import io.opentelemetry.api.common.AttributesBuilder; +import java.util.function.Consumer; + +final class NoopEntityBuilder implements EntityBuilder { + @Override + public EntityBuilder setSchemaUrl(String schemaUrl) { + return this; + } + + @Override + public EntityBuilder withDescription(Consumer f) { + return this; + } + + @Override + public EntityBuilder withId(Consumer f) { + return this; + } + + @Override + public Entity build() { + return new NoopEntity(); + } +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java new file mode 100644 index 00000000000..d5d30f2464d --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +final class NoopResource implements Resource { + + @Override + public boolean addOrUpdate(Entity e) { + return false; + } + + @Override + public boolean removeEntity(Entity e) { + return false; + } + + @Override + public EntityBuilder createEntity(String entityType) { + return new NoopEntityBuilder(); + } +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java new file mode 100644 index 00000000000..ff8c1f03d6f --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +final class NoopResourceProvider implements ResourceProvider { + + @Override + public Resource getResource() { + return new NoopResource(); + } +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java new file mode 100644 index 00000000000..cf23d63a213 --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +/** The active resource for which Telemetry is being generated. */ +public interface Resource { + /** + * Adds an {@link Entity} to this resource. + * + *

If the entity already exists, this updates the description. + * + * @param e The entity + * @return true if the entity was added or updated, false if there was a conflict. + */ + public boolean addOrUpdate(Entity e); + + /** + * Removes an {@link Entity} from this resource. + * + * @param e The entity + * @return true if entity was found and removed. + */ + public boolean removeEntity(Entity e); + + /** + * Returns a builder that can construct an {@link Entity}. + * + * @param entityType The type of the entity. + * @return A builder that can construct an entity. + */ + public EntityBuilder createEntity(String entityType); +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java new file mode 100644 index 00000000000..937d1342ecd --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +/** + * A registry for interacting with {@link Resource}s. The name Provider is for consistency + * with other languages and it is NOT loaded using reflection. + * + * @see Resource + */ +public interface ResourceProvider { + /** + * Returns a no-op {@link ResourceProvider} which only creates no-op {@link Resource}s which do + * not record nor are emitted. + */ + static ResourceProvider noop() { + return new NoopResourceProvider(); + } + + /** Returns the active {@link Resource} for which Telemetry is reported. */ + Resource getResource(); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java deleted file mode 100644 index 5565e89aa82..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityDetector.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import java.util.Collection; - -/** - * The Entity detector in the SDK is responsible for detecting possible entities that could identify - * the SDK (called "associated entities"). For Example, if the SDK is running in a kubernetes pod, - * it may provide an Entity for that pod. - */ -public interface EntityDetector { - /** - * Discovers {@link Entity} and their current attributes. - * - * @return a list of discovered entities. - */ - public Collection detect(); -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java deleted file mode 100644 index 4abc0a54a64..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.sdk.resources.Resource; - -/** - * A provider of {@link Resource} for this SDK. - * - *

{@code EntityProvider} is responsible for: - * - *

- Detecting Entities using registered detectors. - Providing thread-safe access to the current - * resource. - * - *

The future of this class may include the ability to add/remove {@link Entity} objects or - * re-run detectors. - */ -public interface EntityProvider { - /** the current {@link Resource} detected. */ - public Resource getResource(); - - /** A builder of {@link EntityProvider}. */ - public static EntityProviderBuilder builder() { - return new SdkEntityProviderBuilder(); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java deleted file mode 100644 index c2173da7dce..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityProviderBuilder.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.sdk.resources.Resource; - -/** Builder of {@link EntityProvider}. */ -public interface EntityProviderBuilder { - /** Adds an entity detector, which will detect {@link Entity}s to place in the resource. */ - EntityProviderBuilder addDetector(EntityDetector detector); - - /** - * Adds a discovered resource to include in resolving the SDK's resource. - * - * @deprecated Use {@link #addDetector(EntityDetector)}. - */ - @Deprecated - EntityProviderBuilder addDetectedResource(Resource resource); - - /** Sets whether or not default entity detectors will be included. */ - EntityProviderBuilder includeDefaults(boolean include); - - /** - * Returns the SDK entity provider which uses these detectors. - * - * @return the EntityProvider. - */ - EntityProvider build(); -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java index d0dcb67a709..b66458f27a9 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java @@ -6,6 +6,8 @@ package io.opentelemetry.sdk.extension.incubator.entities; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.entities.Entity; +import io.opentelemetry.api.incubator.entities.EntityBuilder; import javax.annotation.Nullable; final class PassthroughEntity implements Entity { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java index 21617121fc3..544cdb6f561 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java @@ -6,6 +6,8 @@ package io.opentelemetry.sdk.extension.incubator.entities; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.incubator.entities.Entity; +import io.opentelemetry.api.incubator.entities.EntityBuilder; import java.util.function.Consumer; final class PassthroughEntityBuilder implements EntityBuilder { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java new file mode 100644 index 00000000000..c5694d86b33 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.incubator.entities.Resource; + +/** + * The Resource detector in the SDK is responsible for detecting possible entities that could + * identify the SDK (called "associated entities"). For Example, if the SDK is running in a + * kubernetes pod, it may provide an Entity for that pod. + */ +public interface ResourceDetector { + /** + * Configures a resource with detected entities. + * + * @param resource The resource to detect entities on. + */ + public void configure(Resource resource); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java deleted file mode 100644 index aece8f36897..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.sdk.resources.Resource; - -/** - * Instance of {@link EntityProvider}. - * - *

This class doesn't do much now, but will expand in responsibilities. - */ -class SdkEntityProvider implements EntityProvider { - private final Resource resource; - - SdkEntityProvider(Resource resource) { - this.resource = resource; - } - - @Override - public Resource getResource() { - return resource; - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java deleted file mode 100644 index c8c6d961599..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.sdk.extension.incubator.entities.detectors.ServiceDetector; -import io.opentelemetry.sdk.extension.incubator.entities.detectors.TelemetrySdkDetector; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.internal.EntityUtil; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -class SdkEntityProviderBuilder implements EntityProviderBuilder { - private final List entityDetectors = new ArrayList<>(); - private final List detectedResources = new ArrayList<>(); - private boolean includeDefaults = true; - - @Override - public EntityProviderBuilder addDetector(EntityDetector detector) { - this.entityDetectors.add(detector); - return this; - } - - @Override - @Deprecated - public EntityProviderBuilder addDetectedResource(Resource resource) { - this.detectedResources.add(resource); - return this; - } - - private final Resource mergeDetectedAndRaw() { - if (includeDefaults) { - entityDetectors.add(new ServiceDetector()); - entityDetectors.add(new TelemetrySdkDetector()); - } - Resource result = Resource.empty(); - for (EntityDetector detector : entityDetectors) { - result = - result.merge( - EntityUtil.addAllEntity( - Resource.builder(), - detector.detect().stream() - .map(e -> ((PassthroughEntity) e).getPassthrough()) - .collect(Collectors.toList())) - .build()); - } - for (Resource next : detectedResources) { - result = result.merge(next); - } - return result; - } - - @Override - public EntityProvider build() { - return new SdkEntityProvider(mergeDetectedAndRaw()); - } - - @Override - public EntityProviderBuilder includeDefaults(boolean include) { - this.includeDefaults = include; - return this; - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java new file mode 100644 index 00000000000..fede1427878 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.incubator.entities.Entity; +import io.opentelemetry.api.incubator.entities.EntityBuilder; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; +import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicReference; + +final class SdkResource implements io.opentelemetry.api.incubator.entities.Resource { + + private final AtomicReference resource = new AtomicReference<>(Resource.empty()); + + private final Object writeLock = new Object(); + + /** Returns the currently active resource. */ + public Resource getResource() { + Resource result = resource.get(); + // We do this to make NullAway happy. + if (result == null) { + throw new IllegalStateException("SdkResource should never have null resource"); + } + return result; + } + + @Override + public boolean addOrUpdate(Entity e) { + synchronized (writeLock) { + Resource current = getResource(); + Resource next = + EntityUtil.addEntity(Resource.builder(), ((PassthroughEntity) e).getPassthrough()) + .build(); + Resource result = current.merge(next); + // We rely on a volatile read to force this to be written. + resource.lazySet(result); + return EntityUtil.getEntities(result).stream() + .anyMatch(r -> r.getType().equals(e.getType()) && r.getId().equals(e.getId())); + } + } + + @Override + public boolean removeEntity(Entity e) { + synchronized (writeLock) { + Resource current = getResource(); + Collection previousEntities = + EntityUtil.getEntities(current); + Collection currentEntities = + new ArrayList<>(previousEntities); + boolean result = currentEntities.removeIf(c -> c.getType().equals(e.getType())); + ResourceBuilder rb = Resource.builder(); + EntityUtil.addAllEntity(rb, currentEntities); + rb.putAll( + current.getAttributes().toBuilder() + .removeIf( + key -> + previousEntities.stream() + .anyMatch( + pe -> + pe.getId().asMap().containsKey(key) + || pe.getDescription().asMap().containsKey(key))) + .build()); + resource.lazySet(rb.build()); + return result; + } + } + + @Override + public EntityBuilder createEntity(String entityType) { + return PassthroughEntity.builder(entityType); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java new file mode 100644 index 00000000000..da7631f95b4 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.api.incubator.entities.ResourceProvider; + +/** The SDK implementation of {@link ResourceProvider}. */ +public final class SdkResourceProvider implements ResourceProvider { + private final SdkResource resource = new SdkResource(); + + @Override + public Resource getResource() { + return resource; + } + + public io.opentelemetry.sdk.resources.Resource getSdkResource() { + return resource.getResource(); + } + + public static SdkResourceProviderBuilder builder() { + return new SdkResourceProviderBuilder(); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java new file mode 100644 index 00000000000..323e049b54c --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.extension.incubator.entities.detectors.ServiceDetector; +import io.opentelemetry.sdk.extension.incubator.entities.detectors.TelemetrySdkDetector; +import java.util.ArrayList; +import java.util.List; + +/** A builder for {@link SdResourceProvider}. */ +public final class SdkResourceProviderBuilder { + private final List detectors = new ArrayList<>(); + private boolean includeDefaults = true; + + /** + * Adds a {@link ResourceDetector} that will be run when constructing this provider. + * + * @param detector The resource detector. + * @return this + */ + public SdkResourceProviderBuilder addDetector(ResourceDetector detector) { + this.detectors.add(detector); + return this; + } + + /** + * Configure whether to include SDK default resoruce detection. + * + * @param include true if defaults should be used. + * @return this + */ + public SdkResourceProviderBuilder includeDefaults(boolean include) { + this.includeDefaults = include; + return this; + } + + public SdkResourceProvider build() { + // TODO - have defaults in the front? + if (includeDefaults) { + detectors.add(new ServiceDetector()); + detectors.add(new TelemetrySdkDetector()); + } + SdkResourceProvider result = new SdkResourceProvider(); + // TODO - Should we move these onto the resource provider? + detectors.forEach(d -> d.configure(result.getResource())); + return result; + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java index 1c359a0e845..f7112fd2e01 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java @@ -6,10 +6,8 @@ package io.opentelemetry.sdk.extension.incubator.entities.detectors; import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.sdk.extension.incubator.entities.Entity; -import io.opentelemetry.sdk.extension.incubator.entities.EntityDetector; -import java.util.Collection; -import java.util.Collections; +import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; import java.util.UUID; /** @@ -18,7 +16,7 @@ *

See: service * entity */ -public final class ServiceDetector implements EntityDetector { +public final class ServiceDetector implements ResourceDetector { private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.34.0"; private static final String ENTITY_TYPE = "service"; private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); @@ -36,9 +34,11 @@ private static String getServiceInstanceId() { } @Override - public Collection detect() { - return Collections.singletonList( - Entity.builder(ENTITY_TYPE) + public void configure(Resource resource) { + // We only run on startup. + resource.addOrUpdate( + resource + .createEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) .withId( id -> { diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java index bf4273ecf58..0ad2b3e965d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java @@ -6,11 +6,9 @@ package io.opentelemetry.sdk.extension.incubator.entities.detectors; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.incubator.entities.Resource; import io.opentelemetry.sdk.common.internal.OtelVersion; -import io.opentelemetry.sdk.extension.incubator.entities.Entity; -import io.opentelemetry.sdk.extension.incubator.entities.EntityDetector; -import java.util.Collection; -import java.util.Collections; +import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; /** * Detection for {@code telemetry.sdk} entity. @@ -19,7 +17,7 @@ * href="https://opentelemetry.io/docs/specs/semconv/resource/#telemetry-sdk">teleemtry.sdk * entity */ -public class TelemetrySdkDetector implements EntityDetector { +public final class TelemetrySdkDetector implements ResourceDetector { private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.34.0"; private static final String ENTITY_TYPE = "telemetry.sdk"; private static final AttributeKey TELEMETRY_SDK_LANGUAGE = @@ -28,22 +26,18 @@ public class TelemetrySdkDetector implements EntityDetector { AttributeKey.stringKey("telemetry.sdk.name"); private static final AttributeKey TELEMETRY_SDK_VERSION = AttributeKey.stringKey("telemetry.sdk.version"); - private static final Entity TELEMETRY_SDK; - static { - TELEMETRY_SDK = - Entity.builder(ENTITY_TYPE) + @Override + public void configure(Resource resource) { + resource.addOrUpdate( + resource + .createEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) .withId( id -> { id.put(TELEMETRY_SDK_NAME, "opentelemetry").put(TELEMETRY_SDK_LANGUAGE, "java"); }) .withDescription(desc -> desc.put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION)) - .build(); - } - - @Override - public Collection detect() { - return Collections.singletonList(TELEMETRY_SDK); + .build()); } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java similarity index 73% rename from sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java rename to sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java index f26b93137df..69665e64bf6 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java @@ -11,21 +11,21 @@ import io.opentelemetry.sdk.resources.internal.EntityUtil; import org.junit.jupiter.api.Test; -class TestEntityProvider { +class TestResourceProvider { @Test void defaults_includeServiceAndSdk() { - EntityProvider provider = EntityProvider.builder().includeDefaults(true).build(); + SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(true).build(); - assertThat(provider.getResource().getAttributes()) + assertThat(provider.getSdkResource().getAttributes()) .containsKey("service.name") .containsKey("service.instance.id") .containsKey("telemetry.sdk.language") .containsKey("telemetry.sdk.name") .containsKey("telemetry.sdk.version"); - assertThat(provider.getResource().getSchemaUrl()) + assertThat(provider.getSdkResource().getSchemaUrl()) .isEqualTo("https://opentelemetry.io/schemas/1.34.0"); - assertThat(EntityUtil.getEntities(provider.getResource())) + assertThat(EntityUtil.getEntities(provider.getSdkResource())) .satisfiesExactlyInAnyOrder( e -> assertThat(e).hasType("service"), e -> assertThat(e).hasType("telemetry.sdk")); } From 11477332c531f890dae7d4f6d44024f6e84559bb Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 5 Jul 2025 15:18:22 -0400 Subject: [PATCH 15/42] Further simplify the API and SDK for entities - Remove `Entity` and only expose `EntityBuilder` similar to how we do `LogRecord`s. - Have methods on `Resource` which determine if you're attaching the entity, simplify "remove" or just ignore for now. --- .../api/incubator/entities/Entity.java | 62 ------------------- .../api/incubator/entities/EntityBuilder.java | 20 ++++-- .../api/incubator/entities/NoopEntity.java | 36 ----------- .../incubator/entities/NoopEntityBuilder.java | 4 +- .../api/incubator/entities/NoopResource.java | 9 +-- .../api/incubator/entities/Resource.java | 20 ++---- .../incubator/entities/PassthroughEntity.java | 54 ---------------- ...tityBuilder.java => SdkEntityBuilder.java} | 14 +++-- .../incubator/entities/SdkResource.java | 49 +++------------ .../entities/detectors/ServiceDetector.java | 35 +++++------ .../detectors/TelemetrySdkDetector.java | 19 +++--- 11 files changed, 67 insertions(+), 255 deletions(-) delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java rename sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/{PassthroughEntityBuilder.java => SdkEntityBuilder.java} (68%) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java deleted file mode 100644 index e594a3175d2..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Entity.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -/** - * Entity represents an object of interest associated with produced telemetry: traces, metrics or - * logs. - * - *

For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service - * entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we - * want to associate metrics with in this case. - * - *

Entities may be also associated with produced telemetry indirectly. For example a service that - * produces telemetry is also related with a process in which the service runs, so we say that the - * Service entity is related to the Process entity. The process normally also runs on a host, so we - * say that the Process entity is related to the Host entity. - */ -@Immutable -public interface Entity { - /** - * Returns the entity type string of this entity. Must not be null. - * - * @return the entity type. - */ - String getType(); - - /** - * Returns a map of attributes that identify the entity. - * - * @return a map of attributes. - */ - Attributes getId(); - - /** - * Returns a map of attributes that describe the entity. - * - * @return a map of attributes. - */ - Attributes getDescription(); - - /** - * Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity - * does not abide by schema conventions (i.e. is custom). - * - * @return An OpenTelemetry schema URL. - * @since 1.4.0 - */ - @Nullable - String getSchemaUrl(); - - /** - * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. - */ - EntityBuilder toBuilder(); -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java index 546c1f68d0d..62e4fd76783 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java @@ -10,8 +10,20 @@ import java.util.function.Consumer; /** - * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as - * well as type and schema_url. + * A builder of an Entity that allows to add identifying or descriptive {@link Attributes}, as well + * as type and schema_url. + * + *

Entity represents an object of interest associated with produced telemetry: traces, metrics or + * logs. + * + *

For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service + * entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we + * want to associate metrics with in this case. + * + *

Entities may be also associated with produced telemetry indirectly. For example a service that + * produces telemetry is also related with a process in which the service runs, so we say that the + * Service entity is related to the Process entity. The process normally also runs on a host, so we + * say that the Process entity is related to the Host entity. */ public interface EntityBuilder { /** @@ -38,6 +50,6 @@ public interface EntityBuilder { */ EntityBuilder withId(Consumer f); - /** Create the {@link Entity} from this. */ - Entity build(); + /** Emits the current entity. */ + void emit(); } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java deleted file mode 100644 index 3155b5bf4dc..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntity.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -import io.opentelemetry.api.common.Attributes; - -final class NoopEntity implements Entity { - - @Override - public String getType() { - return ""; - } - - @Override - public Attributes getId() { - return Attributes.empty(); - } - - @Override - public Attributes getDescription() { - return Attributes.empty(); - } - - @Override - public String getSchemaUrl() { - return ""; - } - - @Override - public EntityBuilder toBuilder() { - return new NoopEntityBuilder(); - } -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java index 274237a3b71..1d7cff08cb0 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java @@ -25,7 +25,5 @@ public EntityBuilder withId(Consumer f) { } @Override - public Entity build() { - return new NoopEntity(); - } + public void emit() {} } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java index d5d30f2464d..339c45d25fe 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java @@ -8,17 +8,12 @@ final class NoopResource implements Resource { @Override - public boolean addOrUpdate(Entity e) { + public boolean removeEntity(String entityType) { return false; } @Override - public boolean removeEntity(Entity e) { - return false; - } - - @Override - public EntityBuilder createEntity(String entityType) { + public EntityBuilder attachEntity(String entityType) { return new NoopEntityBuilder(); } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java index cf23d63a213..382b213c3c1 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java @@ -7,29 +7,19 @@ /** The active resource for which Telemetry is being generated. */ public interface Resource { - /** - * Adds an {@link Entity} to this resource. - * - *

If the entity already exists, this updates the description. - * - * @param e The entity - * @return true if the entity was added or updated, false if there was a conflict. - */ - public boolean addOrUpdate(Entity e); - /** * Removes an {@link Entity} from this resource. * - * @param e The entity + * @param entityType the type of entity to remove. * @return true if entity was found and removed. */ - public boolean removeEntity(Entity e); + public boolean removeEntity(String entityType); /** - * Returns a builder that can construct an {@link Entity}. + * Attaches an entity to the current {@link Resource}. * * @param entityType The type of the entity. - * @return A builder that can construct an entity. + * @return A builder that can construct an {@link Entity}. */ - public EntityBuilder createEntity(String entityType); + public EntityBuilder attachEntity(String entityType); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java deleted file mode 100644 index b66458f27a9..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.entities.Entity; -import io.opentelemetry.api.incubator.entities.EntityBuilder; -import javax.annotation.Nullable; - -final class PassthroughEntity implements Entity { - private final io.opentelemetry.sdk.resources.internal.Entity entity; - - PassthroughEntity(io.opentelemetry.sdk.resources.internal.Entity entity) { - this.entity = entity; - } - - io.opentelemetry.sdk.resources.internal.Entity getPassthrough() { - return entity; - } - - @Override - public String getType() { - return entity.getType(); - } - - @Override - public Attributes getId() { - return entity.getId(); - } - - @Override - public Attributes getDescription() { - return entity.getDescription(); - } - - @Override - @Nullable - public String getSchemaUrl() { - return entity.getSchemaUrl(); - } - - @Override - public EntityBuilder toBuilder() { - return new PassthroughEntityBuilder(entity.toBuilder()); - } - - static EntityBuilder builder(String entityType) { - return new PassthroughEntityBuilder( - io.opentelemetry.sdk.resources.internal.Entity.builder(entityType)); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java similarity index 68% rename from sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java rename to sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java index 544cdb6f561..27770a9bdb2 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/PassthroughEntityBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java @@ -6,15 +6,17 @@ package io.opentelemetry.sdk.extension.incubator.entities; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.api.incubator.entities.Entity; import io.opentelemetry.api.incubator.entities.EntityBuilder; +import io.opentelemetry.sdk.resources.internal.Entity; import java.util.function.Consumer; -final class PassthroughEntityBuilder implements EntityBuilder { +final class SdkEntityBuilder implements EntityBuilder { private final io.opentelemetry.sdk.resources.internal.EntityBuilder builder; + private final Consumer emitter; - PassthroughEntityBuilder(io.opentelemetry.sdk.resources.internal.EntityBuilder builder) { - this.builder = builder; + SdkEntityBuilder(String entityType, Consumer emitter) { + this.builder = Entity.builder(entityType); + this.emitter = emitter; } @Override @@ -36,7 +38,7 @@ public EntityBuilder withId(Consumer f) { } @Override - public Entity build() { - return new PassthroughEntity(builder.build()); + public void emit() { + emitter.accept(builder.build()); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java index fede1427878..c3ac2f5ac3b 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java @@ -5,19 +5,15 @@ package io.opentelemetry.sdk.extension.incubator.entities; -import io.opentelemetry.api.incubator.entities.Entity; import io.opentelemetry.api.incubator.entities.EntityBuilder; import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.ResourceBuilder; +import io.opentelemetry.sdk.resources.internal.Entity; import io.opentelemetry.sdk.resources.internal.EntityUtil; -import java.util.ArrayList; -import java.util.Collection; import java.util.concurrent.atomic.AtomicReference; final class SdkResource implements io.opentelemetry.api.incubator.entities.Resource { private final AtomicReference resource = new AtomicReference<>(Resource.empty()); - private final Object writeLock = new Object(); /** Returns the currently active resource. */ @@ -31,48 +27,21 @@ public Resource getResource() { } @Override - public boolean addOrUpdate(Entity e) { - synchronized (writeLock) { - Resource current = getResource(); - Resource next = - EntityUtil.addEntity(Resource.builder(), ((PassthroughEntity) e).getPassthrough()) - .build(); - Resource result = current.merge(next); - // We rely on a volatile read to force this to be written. - resource.lazySet(result); - return EntityUtil.getEntities(result).stream() - .anyMatch(r -> r.getType().equals(e.getType()) && r.getId().equals(e.getId())); - } + public boolean removeEntity(String entityType) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'removeEntity'"); } - @Override - public boolean removeEntity(Entity e) { + void attachEntityOnEmit(Entity e) { synchronized (writeLock) { Resource current = getResource(); - Collection previousEntities = - EntityUtil.getEntities(current); - Collection currentEntities = - new ArrayList<>(previousEntities); - boolean result = currentEntities.removeIf(c -> c.getType().equals(e.getType())); - ResourceBuilder rb = Resource.builder(); - EntityUtil.addAllEntity(rb, currentEntities); - rb.putAll( - current.getAttributes().toBuilder() - .removeIf( - key -> - previousEntities.stream() - .anyMatch( - pe -> - pe.getId().asMap().containsKey(key) - || pe.getDescription().asMap().containsKey(key))) - .build()); - resource.lazySet(rb.build()); - return result; + Resource next = EntityUtil.addEntity(Resource.builder(), e).build(); + resource.lazySet(current.merge(next)); } } @Override - public EntityBuilder createEntity(String entityType) { - return PassthroughEntity.builder(entityType); + public EntityBuilder attachEntity(String entityType) { + return new SdkEntityBuilder(entityType, this::attachEntityOnEmit); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java index f7112fd2e01..5cc96be88f6 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java @@ -36,23 +36,22 @@ private static String getServiceInstanceId() { @Override public void configure(Resource resource) { // We only run on startup. - resource.addOrUpdate( - resource - .createEntity(ENTITY_TYPE) - .setSchemaUrl(SCHEMA_URL) - .withId( - id -> { - // Note: Identifying attributes MUST be provided together. - id.put(SERVICE_NAME, getServiceName()) - .put(SERVICE_INSTANCE_ID, getServiceInstanceId()); - }) - // No specified way to take these in. - // .withDescriptive( - // builder -> { - // if (!StringUtils.isNullOrEmpty(getVersion())) { - // builder.put(SERVICE_VERSION, getVersion()); - // } - // }) - .build()); + resource + .attachEntity(ENTITY_TYPE) + .setSchemaUrl(SCHEMA_URL) + .withId( + id -> { + // Note: Identifying attributes MUST be provided together. + id.put(SERVICE_NAME, getServiceName()) + .put(SERVICE_INSTANCE_ID, getServiceInstanceId()); + }) + // No specified way to take these in. + // .withDescriptive( + // builder -> { + // if (!StringUtils.isNullOrEmpty(getVersion())) { + // builder.put(SERVICE_VERSION, getVersion()); + // } + // }) + .emit(); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java index 0ad2b3e965d..7dad1b7a267 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java @@ -29,15 +29,14 @@ public final class TelemetrySdkDetector implements ResourceDetector { @Override public void configure(Resource resource) { - resource.addOrUpdate( - resource - .createEntity(ENTITY_TYPE) - .setSchemaUrl(SCHEMA_URL) - .withId( - id -> { - id.put(TELEMETRY_SDK_NAME, "opentelemetry").put(TELEMETRY_SDK_LANGUAGE, "java"); - }) - .withDescription(desc -> desc.put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION)) - .build()); + resource + .attachEntity(ENTITY_TYPE) + .setSchemaUrl(SCHEMA_URL) + .withId( + id -> { + id.put(TELEMETRY_SDK_NAME, "opentelemetry").put(TELEMETRY_SDK_LANGUAGE, "java"); + }) + .withDescription(desc -> desc.put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION)) + .emit(); } } From 1807b62d039e0100ad11cce39fd01b92f4303bab Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sun, 6 Jul 2025 10:01:29 -0400 Subject: [PATCH 16/42] Wire ResourceProvider in end-to-end test with new incubating ExtendedOpenTelemetry API. - Update shared state in SDK to use Supplier instead of Resource - Add helper utils to expose private methods to supply the supplier - Create new ExtendedOpenTelemetry* API/SDK and end-to-end test. --- .../incubator/ExtendedOpenTelemetrySdk.java | 29 +++ .../ExtendedOpenTelemetrySdkBuilder.java | 117 +++++++++ .../ObfuscatedExtendedOpenTelemerySdk.java | 231 ++++++++++++++++++ .../entities/SdkResourceProvider.java | 5 + .../TestExtendedOpenTelemetrySdk.java | 69 ++++++ .../io/opentelemetry/sdk/IncubatingUtil.java | 22 ++ .../sdk/logs/LoggerSharedState.java | 11 +- .../sdk/logs/SdkLoggerProvider.java | 2 +- .../sdk/logs/SdkLoggerProviderBuilder.java | 25 +- .../logs/internal/SdkLoggerProviderUtil.java | 22 ++ .../sdk/logs/LoggerSharedStateTest.java | 2 +- .../logs/SdkLoggerProviderBuilderTest.java | 18 ++ .../sdk/metrics/SdkMeterProvider.java | 3 +- .../sdk/metrics/SdkMeterProviderBuilder.java | 26 +- .../internal/SdkMeterProviderUtil.java | 22 ++ .../state/MeterProviderSharedState.java | 15 +- .../sdk/metrics/InstrumentBuilderTest.java | 2 +- .../sdk/trace/SdkTracerProvider.java | 4 +- .../sdk/trace/SdkTracerProviderBuilder.java | 25 +- .../sdk/trace/TracerSharedState.java | 11 +- .../trace/internal/SdkTracerProviderUtil.java | 20 ++ 21 files changed, 659 insertions(+), 22 deletions(-) create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java create mode 100644 sdk/all/src/main/java/io/opentelemetry/sdk/IncubatingUtil.java diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java new file mode 100644 index 00000000000..67c2abe2bfb --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator; + +import io.opentelemetry.api.incubator.entities.ExtendedOpenTelemetry; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.io.Closeable; + +/** A new interface for creating OpenTelemetrySdk that supports {@link ResourceProvider}. */ +public interface ExtendedOpenTelemetrySdk extends ExtendedOpenTelemetry, Closeable { + /** + * Shutdown the SDK. Calls {@link SdkTracerProvider#shutdown()}, {@link + * SdkMeterProvider#shutdown()}, and {@link SdkLoggerProvider#shutdown()}. + * + * @return a {@link CompletableResultCode} which completes when all providers are shutdown + */ + CompletableResultCode shutdown(); + + /** Returns a builder for {@link ExtendedOpenTelemetrySdk}. */ + static ExtendedOpenTelemetrySdkBuilder builder() { + return new ExtendedOpenTelemetrySdkBuilder(); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java new file mode 100644 index 00000000000..88573c4164c --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.extension.incubator.entities.SdkResourceProvider; +import io.opentelemetry.sdk.extension.incubator.entities.SdkResourceProviderBuilder; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; +import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; +import java.util.function.Consumer; + +/** A new interface for creating OpenTelemetrySdk that supports {@link ResourceProvider}. */ +public final class ExtendedOpenTelemetrySdkBuilder { + private ContextPropagators propagators = ContextPropagators.noop(); + private final SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); + private final SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); + private final SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); + private final SdkResourceProviderBuilder resourceProviderBuilder = SdkResourceProvider.builder(); + + /** Sets the {@link ContextPropagators} to use. */ + public ExtendedOpenTelemetrySdkBuilder setPropagators(ContextPropagators propagators) { + this.propagators = propagators; + return this; + } + + /** + * Applies a consumer callback to configure the TracerProvider being built for this OpenTelemetry. + * + * @param configurator A callback fleshing out tracers. + * @return this + */ + public ExtendedOpenTelemetrySdkBuilder withTracerProvider( + Consumer configurator) { + configurator.accept(this.tracerProviderBuilder); + return this; + } + + /** + * Applies a consumer callback to configure the MeterProvider being built for this OpenTelemetry. + * + * @param configurator A callback fleshing out meters. + * @return this + */ + public ExtendedOpenTelemetrySdkBuilder withMeterProvider( + Consumer configurator) { + configurator.accept(this.meterProviderBuilder); + return this; + } + + /** + * Applies a consumer callback to configure the LoggerProvider being built for this OpenTelemetry. + * + * @param configurator A callback fleshing out meters. + * @return this + */ + public ExtendedOpenTelemetrySdkBuilder withLoggerProvider( + Consumer configurator) { + configurator.accept(this.loggerProviderBuilder); + return this; + } + + /** + * Returns a new {@link OpenTelemetrySdk} built with the configuration of this {@link + * OpenTelemetrySdkBuilder}. This SDK is not registered as the global {@link + * io.opentelemetry.api.OpenTelemetry}. It is recommended that you register one SDK using {@link + * OpenTelemetrySdkBuilder#buildAndRegisterGlobal()} for use by instrumentation that requires + * access to a global instance of {@link io.opentelemetry.api.OpenTelemetry}. + * + * @see GlobalOpenTelemetry + */ + public ExtendedOpenTelemetrySdk build() { + SdkResourceProvider resourceProvider = resourceProviderBuilder.build(); + SdkTracerProvider tracerProvider = + SdkTracerProviderUtil.setResourceSupplier( + tracerProviderBuilder, resourceProvider::getSdkResource) + .build(); + SdkMeterProvider meterProvider = + SdkMeterProviderUtil.setResourceSupplier( + meterProviderBuilder, resourceProvider::getSdkResource) + .build(); + SdkLoggerProvider loggerProvider = + SdkLoggerProviderUtil.setResourceSupplier( + loggerProviderBuilder, resourceProvider::getSdkResource) + .build(); + return new ObfuscatedExtendedOpenTelemerySdk( + resourceProvider, tracerProvider, meterProvider, loggerProvider, propagators); + } + + /** + * Returns a new {@link OpenTelemetrySdk} built with the configuration of this {@link + * OpenTelemetrySdkBuilder} and registers it as the global {@link + * io.opentelemetry.api.OpenTelemetry}. An exception will be thrown if this method is attempted to + * be called multiple times in the lifecycle of an application - ensure you have only one SDK for + * use as the global instance. If you need to configure multiple SDKs for tests, use {@link + * GlobalOpenTelemetry#resetForTest()} between them. + * + * @see GlobalOpenTelemetry + */ + public ExtendedOpenTelemetrySdk buildAndRegisterGlobal() { + ExtendedOpenTelemetrySdk sdk = build(); + GlobalOpenTelemetry.set(sdk); + return sdk; + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java new file mode 100644 index 00000000000..87cdffc0db4 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java @@ -0,0 +1,231 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator; + +import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.api.incubator.entities.ResourceProvider; +import io.opentelemetry.api.logs.LoggerBuilder; +import io.opentelemetry.api.logs.LoggerProvider; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.extension.incubator.entities.SdkResourceProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; +import javax.annotation.concurrent.ThreadSafe; + +/** The SDK implementation of {@link ExtendedOpenTelemetrySdk}. */ +final class ObfuscatedExtendedOpenTelemerySdk implements ExtendedOpenTelemetrySdk { + + private static final Logger LOGGER = + Logger.getLogger(ObfuscatedExtendedOpenTelemerySdk.class.getName()); + private final AtomicBoolean isShutdown = new AtomicBoolean(false); + private final ObfuscatedTracerProvider tracerProvider; + private final ObfuscatedMeterProvider meterProvider; + private final ObfuscatedLoggerProvider loggerProvider; + private final ObfuscatedResourceProvider resourceProvider; + private final ContextPropagators propagators; + + ObfuscatedExtendedOpenTelemerySdk( + SdkResourceProvider resourceProvider, + SdkTracerProvider tracerProvider, + SdkMeterProvider meterProvider, + SdkLoggerProvider loggerProvider, + ContextPropagators propagators) { + this.resourceProvider = new ObfuscatedResourceProvider(resourceProvider); + this.tracerProvider = new ObfuscatedTracerProvider(tracerProvider); + this.meterProvider = new ObfuscatedMeterProvider(meterProvider); + this.loggerProvider = new ObfuscatedLoggerProvider(loggerProvider); + this.propagators = propagators; + } + + @Override + public CompletableResultCode shutdown() { + if (!isShutdown.compareAndSet(false, true)) { + LOGGER.info("Multiple shutdown calls"); + return CompletableResultCode.ofSuccess(); + } + List results = new ArrayList<>(); + results.add(tracerProvider.unobfuscate().shutdown()); + results.add(meterProvider.unobfuscate().shutdown()); + results.add(loggerProvider.unobfuscate().shutdown()); + return CompletableResultCode.ofAll(results); + } + + @Override + public void close() { + shutdown().join(10, TimeUnit.SECONDS); + } + + @Override + public TracerProvider getTracerProvider() { + return tracerProvider; + } + + @Override + public MeterProvider getMeterProvider() { + return meterProvider; + } + + @Override + public LoggerProvider getLogsBridge() { + return loggerProvider; + } + + @Override + public ResourceProvider getResourceProvider() { + return resourceProvider; + } + + @Override + public ContextPropagators getPropagators() { + return propagators; + } + + @Override + public String toString() { + return "ExtendedOpenTelemetrySdk{" + + "resourceProivder=" + + resourceProvider.unobfuscate() + + ", tracerProvider=" + + tracerProvider.unobfuscate() + + ", meterProvider=" + + meterProvider.unobfuscate() + + ", loggerProvider=" + + loggerProvider.unobfuscate() + + ", propagators=" + + propagators + + "}"; + } + + /** + * This class allows the SDK to unobfuscate an obfuscated static global provider. + * + *

Static global providers are obfuscated when they are returned from the API to prevent users + * from casting them to their SDK specific implementation. For example, we do not want users to + * use patterns like {@code (SdkTracerProvider) openTelemetry.getTracerProvider()}. + */ + @ThreadSafe + // Visible for testing + static class ObfuscatedTracerProvider implements TracerProvider { + + private final SdkTracerProvider delegate; + + ObfuscatedTracerProvider(SdkTracerProvider delegate) { + this.delegate = delegate; + } + + @Override + public Tracer get(String instrumentationScopeName) { + return delegate.get(instrumentationScopeName); + } + + @Override + public Tracer get(String instrumentationScopeName, String instrumentationScopeVersion) { + return delegate.get(instrumentationScopeName, instrumentationScopeVersion); + } + + @Override + public TracerBuilder tracerBuilder(String instrumentationScopeName) { + return delegate.tracerBuilder(instrumentationScopeName); + } + + public SdkTracerProvider unobfuscate() { + return delegate; + } + } + + /** + * This class allows the SDK to unobfuscate an obfuscated static global provider. + * + *

Static global providers are obfuscated when they are returned from the API to prevent users + * from casting them to their SDK specific implementation. For example, we do not want users to + * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. + */ + @ThreadSafe + // Visible for testing + static class ObfuscatedMeterProvider implements MeterProvider { + + private final SdkMeterProvider delegate; + + ObfuscatedMeterProvider(SdkMeterProvider delegate) { + this.delegate = delegate; + } + + @Override + public MeterBuilder meterBuilder(String instrumentationScopeName) { + return delegate.meterBuilder(instrumentationScopeName); + } + + public SdkMeterProvider unobfuscate() { + return delegate; + } + } + + /** + * This class allows the SDK to unobfuscate an obfuscated static global provider. + * + *

Static global providers are obfuscated when they are returned from the API to prevent users + * from casting them to their SDK specific implementation. For example, we do not want users to + * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. + */ + @ThreadSafe + // Visible for testing + static class ObfuscatedLoggerProvider implements LoggerProvider { + + private final SdkLoggerProvider delegate; + + ObfuscatedLoggerProvider(SdkLoggerProvider delegate) { + this.delegate = delegate; + } + + @Override + public LoggerBuilder loggerBuilder(String instrumentationScopeName) { + return delegate.loggerBuilder(instrumentationScopeName); + } + + public SdkLoggerProvider unobfuscate() { + return delegate; + } + } + + /** + * This class allows the SDK to unobfuscate an obfuscated static global provider. + * + *

Static global providers are obfuscated when they are returned from the API to prevent users + * from casting them to their SDK specific implementation. For example, we do not want users to + * use patterns like {@code (SdkResourceProvider) openTelemetry.getResourceProvider()}. + */ + @ThreadSafe + // Visible for testing + static class ObfuscatedResourceProvider implements ResourceProvider { + + private final SdkResourceProvider delegate; + + ObfuscatedResourceProvider(SdkResourceProvider delegate) { + this.delegate = delegate; + } + + @Override + public Resource getResource() { + return delegate.getResource(); + } + + public SdkResourceProvider unobfuscate() { + return delegate; + } + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java index da7631f95b4..a748a61b2b2 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java @@ -24,4 +24,9 @@ public io.opentelemetry.sdk.resources.Resource getSdkResource() { public static SdkResourceProviderBuilder builder() { return new SdkResourceProviderBuilder(); } + + @Override + public String toString() { + return "SdkResourceProvider{}"; + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java new file mode 100644 index 00000000000..6b83956ab58 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.Test; + +class TestExtendedOpenTelemetrySdk { + private final InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.create(); + + @Test + void endToEnd() { + ExtendedOpenTelemetrySdk otel = + ExtendedOpenTelemetrySdk.builder() + .withMeterProvider(builder -> builder.registerMetricReader(sdkMeterReader)) + .build(); + // Generate our first entity. + otel.getResourceProvider() + .getResource() + .attachEntity("test") + .withId(id -> id.put("test.id", 1)) + .emit(); + // Write a metric. + Meter meter = otel.getMeterProvider().get("test.scope"); + LongCounter counter = meter.counterBuilder("testCounter").build(); + counter.add(1, Attributes.empty()); + + // Verify we see the entity and the metric. + assertThat(sdkMeterReader.collectAllMetrics()) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("testCounter") + .hasResourceSatisfying( + resource -> + resource.hasAttributesSatisfying( + attributes -> + assertThat(attributes) + .containsEntry("test.id", 1)))); + + // Now update the resource and check the point. + otel.getResourceProvider() + .getResource() + .attachEntity("test2") + .withId(id -> id.put("test2.id", 1)) + .emit(); + // Verify we see the new entity and the metric. + assertThat(sdkMeterReader.collectAllMetrics()) + .anySatisfy( + metric -> + assertThat(metric) + .hasName("testCounter") + .hasResourceSatisfying( + resource -> + resource.hasAttributesSatisfying( + attributes -> + assertThat(attributes) + .containsEntry("test.id", 1) + .containsEntry("test2.id", 1)))); + } +} diff --git a/sdk/all/src/main/java/io/opentelemetry/sdk/IncubatingUtil.java b/sdk/all/src/main/java/io/opentelemetry/sdk/IncubatingUtil.java new file mode 100644 index 00000000000..0ada6e9f55d --- /dev/null +++ b/sdk/all/src/main/java/io/opentelemetry/sdk/IncubatingUtil.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk; + +import javax.annotation.Nullable; + +/** + * Utilities for interacting with {@code io.opentelemetry:opentelemetry-api-incubator}, which is not + * guaranteed to be present on the classpath. For all methods, callers MUST first separately + * reflectively confirm that the incubator is available on the classpath. + */ +final class IncubatingUtil { + private IncubatingUtil() {} + + @Nullable + static OpenTelemetrySdk createExtendedOpenTelemetrySdk() { + return null; + } +} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java index 5b40f897a32..5f2531f6ed0 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java @@ -18,7 +18,7 @@ */ final class LoggerSharedState { private final Object lock = new Object(); - private final Resource resource; + private final Supplier resourceSupplier; private final Supplier logLimitsSupplier; private final LogRecordProcessor logRecordProcessor; private final Clock clock; @@ -26,20 +26,21 @@ final class LoggerSharedState { @Nullable private volatile CompletableResultCode shutdownResult = null; LoggerSharedState( - Resource resource, + Supplier resourceSupplier, Supplier logLimitsSupplier, LogRecordProcessor logRecordProcessor, Clock clock, ExceptionAttributeResolver exceptionAttributeResolver) { - this.resource = resource; + this.resourceSupplier = resourceSupplier; this.logLimitsSupplier = logLimitsSupplier; this.logRecordProcessor = logRecordProcessor; this.clock = clock; this.exceptionAttributeResolver = exceptionAttributeResolver; } - Resource getResource() { - return resource; + // This is used in a test, and must be public for it. + public Resource getResource() { + return resourceSupplier.get(); } LogLimits getLogLimits() { diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java index 54825caf21e..aa4f4d63b79 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java @@ -53,7 +53,7 @@ public static SdkLoggerProviderBuilder builder() { } SdkLoggerProvider( - Resource resource, + Supplier resource, Supplier logLimitsSupplier, List processors, Clock clock, diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java index 41ce7d44980..f2c864f08cd 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java @@ -24,6 +24,7 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; /** * Builder class for {@link SdkLoggerProvider} instances. @@ -33,6 +34,7 @@ public final class SdkLoggerProviderBuilder { private final List logRecordProcessors = new ArrayList<>(); + @Nullable private Supplier resourceSupplier = null; private Resource resource = Resource.getDefault(); private Supplier logLimitsSupplier = LogLimits::getDefault; private Clock clock = Clock.getDefault(); @@ -68,6 +70,23 @@ public SdkLoggerProviderBuilder addResource(Resource resource) { return this; } + /** + * Registers a supplier of {@link Resource}. + * + *

This will override any {@link #addResource(Resource)} or {@link #setResource(Resource)} + * calls with the current supplier. + * + *

This method is experimental so not public. You may reflectively call it using {@link + * SdkLoggerProviderUtil#setResourceSupplier(SdkLoggerProviderBuilder, Supplier)}. + * + * @param supplier The supplier of {@link Resource}. + * @since 1.X.0 + */ + SdkLoggerProviderBuilder setResourceSupplier(Supplier supplier) { + this.resourceSupplier = supplier; + return this; + } + /** * Assign a {@link Supplier} of {@link LogLimits}. {@link LogLimits} will be retrieved each time a * {@link Logger#logRecordBuilder()} is called. @@ -192,8 +211,12 @@ SdkLoggerProviderBuilder setExceptionAttributeResolver( * @return an instance configured with the provided options */ public SdkLoggerProvider build() { + Supplier resolvedSupplier = () -> this.resource; + if (resourceSupplier != null) { + resolvedSupplier = this.resourceSupplier; + } return new SdkLoggerProvider( - resource, + resolvedSupplier, logLimitsSupplier, logRecordProcessors, clock, diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java index 417601bbad4..1b1848ffa55 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java @@ -10,9 +10,11 @@ import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.resources.Resource; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; +import java.util.function.Supplier; /** * A collection of methods that allow use of experimental features prior to availability in public @@ -90,4 +92,24 @@ public static void setExceptionAttributeResolver( "Error calling setExceptionAttributeResolver on SdkLoggerProviderBuilder", e); } } + + /** + * Reflectively assign the {@link Supplier} of {@link Resource} to the {@link + * SdkLoggerProviderBuilder}. + * + * @param sdkLoggerProvider the builder + */ + public static SdkLoggerProviderBuilder setResourceSupplier( + SdkLoggerProviderBuilder sdkLoggerProvider, Supplier resourceSupplier) { + try { + Method method = + SdkLoggerProviderBuilder.class.getDeclaredMethod("setResourceSupplier", Supplier.class); + method.setAccessible(true); + method.invoke(sdkLoggerProvider, resourceSupplier); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Error calling setLoggerConfigurator on SdkLoggerProvider", e); + } + return sdkLoggerProvider; + } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java index 7aa4f02aa0a..df059ffb306 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java @@ -25,7 +25,7 @@ void shutdown() { when(logRecordProcessor.shutdown()).thenReturn(code); LoggerSharedState state = new LoggerSharedState( - Resource.empty(), + () -> Resource.empty(), LogLimits::getDefault, logRecordProcessor, Clock.getDefault(), diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java index 5c49d35e5ca..ae1adea9aa7 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java @@ -24,8 +24,26 @@ void addResource() { SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder().addResource(customResource).build(); + // We should find a less invasive way to verify this. assertThat(sdkLoggerProvider) .extracting("sharedState") .hasFieldOrPropertyWithValue("resource", Resource.getDefault().merge(customResource)); } + + @Test + void setResourceSupplier() { + Resource customResource = + Resource.create( + Attributes.of( + AttributeKey.stringKey("custom_attribute_key"), "custom_attribute_value")); + + SdkLoggerProvider sdkLoggerProvider = + SdkLoggerProvider.builder().setResourceSupplier(() -> customResource).build(); + + // We should find a less invasive way to verify this. + assertThat(sdkLoggerProvider) + .extracting("sharedState") + // Validate the default resource values are NO Longer here when a supplier takes over. + .hasFieldOrPropertyWithValue("resource", customResource); + } } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java index 30ae0b1da5a..15199027b3b 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import java.util.logging.Logger; /** @@ -65,7 +66,7 @@ public static SdkMeterProviderBuilder builder() { IdentityHashMap metricReaders, List metricProducers, Clock clock, - Resource resource, + Supplier resource, ExemplarFilter exemplarFilter, ScopeConfigurator meterConfigurator) { long startEpochNanos = clock.now(); diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java index 90d0c7a8fef..77a4bce64a7 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Objects; import java.util.function.Predicate; +import java.util.function.Supplier; +import javax.annotation.Nullable; /** * Builder class for the {@link SdkMeterProvider}. @@ -39,6 +41,7 @@ public final class SdkMeterProviderBuilder { private static final ExemplarFilter DEFAULT_EXEMPLAR_FILTER = ExemplarFilter.traceBased(); private Clock clock = Clock.getDefault(); + @Nullable private Supplier resourceSupplier = null; private Resource resource = Resource.getDefault(); private final IdentityHashMap metricReaders = new IdentityHashMap<>(); @@ -80,6 +83,23 @@ public SdkMeterProviderBuilder addResource(Resource resource) { return this; } + /** + * Registers a supplier of {@link Resource}. + * + *

This will override any {@link #addResource(Resource)} or {@link #setResource(Resource)} + * calls with the current supplier. + * + *

This method is experimental so not public. You may reflectively call it using {@link + * SdkMeterProviderUtil#setResourceSupplier(SdkMeterProviderBuilder, Supplier)}. + * + * @param supplier The supplier of {@link Resource}. + * @since 1.X.0 + */ + SdkMeterProviderBuilder setResourceSupplier(Supplier supplier) { + this.resourceSupplier = supplier; + return this; + } + /** * Assign an {@link ExemplarFilter} for all metrics created by Meters. * @@ -200,12 +220,16 @@ SdkMeterProviderBuilder addMeterConfiguratorCondition( /** Returns an {@link SdkMeterProvider} built with the configuration of this builder. */ public SdkMeterProvider build() { + Supplier resolvedSupplier = () -> this.resource; + if (resourceSupplier != null) { + resolvedSupplier = this.resourceSupplier; + } return new SdkMeterProvider( registeredViews, metricReaders, metricProducers, clock, - resource, + resolvedSupplier, exemplarFilter, meterConfiguratorBuilder.build()); } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java index 9fc690366ed..0af2c8f63b5 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java @@ -13,9 +13,11 @@ import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor; import io.opentelemetry.sdk.metrics.internal.view.StringPredicates; +import io.opentelemetry.sdk.resources.Resource; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; +import java.util.function.Supplier; /** * A collection of methods that allow use of experimental features prior to availability in public @@ -49,6 +51,26 @@ public static SdkMeterProviderBuilder setExemplarFilter( return sdkMeterProviderBuilder; } + /** + * Reflectively assign the {@link Supplier} of {@link Resource} to the {@link + * SdkMeterProviderBuilder}. + * + * @param sdkMeterProviderBuilder the builder + */ + public static SdkMeterProviderBuilder setResourceSupplier( + SdkMeterProviderBuilder sdkMeterProviderBuilder, Supplier supplier) { + try { + Method method = + SdkMeterProviderBuilder.class.getDeclaredMethod("setResourceSupplier", Supplier.class); + method.setAccessible(true); + method.invoke(sdkMeterProviderBuilder, supplier); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Error calling setResourceSupplier on SdkMeterProviderBuilder", e); + } + return sdkMeterProviderBuilder; + } + /** Reflectively set the {@link ScopeConfigurator} to the {@link SdkMeterProviderBuilder}. */ public static SdkMeterProviderBuilder setMeterConfigurator( SdkMeterProviderBuilder sdkMeterProviderBuilder, diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java index aaf932d9202..8265bf415bd 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java @@ -10,6 +10,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; import io.opentelemetry.sdk.resources.Resource; +import java.util.function.Supplier; import javax.annotation.concurrent.Immutable; /** @@ -23,9 +24,13 @@ public abstract class MeterProviderSharedState { public static MeterProviderSharedState create( - Clock clock, Resource resource, ExemplarFilter exemplarFilter, long startEpochNanos) { + Clock clock, + Supplier resourceSupplier, + ExemplarFilter exemplarFilter, + long startEpochNanos) { MeterProviderSharedState sharedState = - new AutoValue_MeterProviderSharedState(clock, resource, startEpochNanos, exemplarFilter); + new AutoValue_MeterProviderSharedState( + clock, resourceSupplier, startEpochNanos, exemplarFilter); return sharedState; } @@ -35,7 +40,11 @@ public static MeterProviderSharedState create( public abstract Clock getClock(); /** Returns the {@link Resource} to attach telemetry to. */ - public abstract Resource getResource(); + public Resource getResource() { + return getResourceSupplier().get(); + } + + abstract Supplier getResourceSupplier(); /** Returns the timestamp when the {@link SdkMeterProvider} was started, in epoch nanos. */ public abstract long getStartEpochNanos(); diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java index de1a351e619..3f4c8cdf048 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java @@ -21,7 +21,7 @@ class InstrumentBuilderTest { public static final MeterProviderSharedState PROVIDER_SHARED_STATE = MeterProviderSharedState.create( - TestClock.create(), Resource.getDefault(), ExemplarFilter.alwaysOff(), 0); + TestClock.create(), () -> Resource.getDefault(), ExemplarFilter.alwaysOff(), 0); static final InstrumentationScopeInfo SCOPE = InstrumentationScopeInfo.create("scope-name"); public static final SdkMeter SDK_METER = new SdkMeter( diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index f39ce565731..9b74070c191 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -49,7 +49,7 @@ public static SdkTracerProviderBuilder builder() { SdkTracerProvider( Clock clock, IdGenerator idsGenerator, - Resource resource, + Supplier resourceSupplier, Supplier spanLimitsSupplier, Sampler sampler, List spanProcessors, @@ -59,7 +59,7 @@ public static SdkTracerProviderBuilder builder() { new TracerSharedState( clock, idsGenerator, - resource, + resourceSupplier, spanLimitsSupplier, sampler, spanProcessors, diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index 71f589d15b6..ad15e196ce5 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; /** Builder of {@link SdkTracerProvider}. */ public final class SdkTracerProviderBuilder { @@ -32,6 +33,7 @@ public final class SdkTracerProviderBuilder { private Clock clock = Clock.getDefault(); private IdGenerator idsGenerator = IdGenerator.random(); private Resource resource = Resource.getDefault(); + @Nullable private Supplier resourceSupplier = null; private Supplier spanLimitsSupplier = SpanLimits::getDefault; private Sampler sampler = DEFAULT_SAMPLER; private ScopeConfiguratorBuilder tracerConfiguratorBuilder = @@ -95,6 +97,23 @@ public SdkTracerProviderBuilder addResource(Resource resource) { return this; } + /** + * Registers a supplier of {@link Resource}. + * + *

This will override any {@link #addResource(Resource)} or {@link #setResource(Resource)} + * calls with the current supplier. + * + *

This method is experimental so not public. You may reflectively call it using {@link + * SdkTracerProviderUtil#setResourceSupplier(SdkTracerProviderBuilder, Supplier)}. + * + * @param supplier The supplier of {@link Resource}. + * @since 1.X.0 + */ + SdkTracerProviderBuilder setResourceSupplier(Supplier supplier) { + this.resourceSupplier = supplier; + return this; + } + /** * Assign an initial {@link SpanLimits} that should be used with this SDK. * @@ -238,10 +257,14 @@ SdkTracerProviderBuilder setExceptionAttributeResolver( * @return The instance. */ public SdkTracerProvider build() { + Supplier resolvedSupplier = () -> resource; + if (resourceSupplier != null) { + resolvedSupplier = resourceSupplier; + } return new SdkTracerProvider( clock, idsGenerator, - resource, + resolvedSupplier, spanLimitsSupplier, sampler, spanProcessors, diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index 74d43076b97..f2bd64b946c 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -22,7 +22,7 @@ final class TracerSharedState { private final IdGenerator idGenerator; // tracks whether it is safe to skip id validation on ids from the above generator private final boolean idGeneratorSafeToSkipIdValidation; - private final Resource resource; + private final Supplier resourceSupplier; private final Supplier spanLimitsSupplier; private final Sampler sampler; @@ -34,7 +34,7 @@ final class TracerSharedState { TracerSharedState( Clock clock, IdGenerator idGenerator, - Resource resource, + Supplier resourceSupplier, Supplier spanLimitsSupplier, Sampler sampler, List spanProcessors, @@ -42,7 +42,7 @@ final class TracerSharedState { this.clock = clock; this.idGenerator = idGenerator; this.idGeneratorSafeToSkipIdValidation = idGenerator instanceof RandomIdGenerator; - this.resource = resource; + this.resourceSupplier = resourceSupplier; this.spanLimitsSupplier = spanLimitsSupplier; this.sampler = sampler; this.activeSpanProcessor = SpanProcessor.composite(spanProcessors); @@ -61,8 +61,9 @@ boolean isIdGeneratorSafeToSkipIdValidation() { return idGeneratorSafeToSkipIdValidation; } - Resource getResource() { - return resource; + // Needed for tests. + public Resource getResource() { + return resourceSupplier.get(); } /** Returns the current {@link SpanLimits}. */ diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java index f9ca8fafc56..1fc4ccb2eb1 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java @@ -8,11 +8,13 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ScopeConfigurator; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; +import java.util.function.Supplier; /** * A collection of methods that allow use of experimental features prior to availability in public @@ -89,4 +91,22 @@ public static void setExceptionAttributeResolver( "Error calling setExceptionAttributeResolver on SdkTracerProviderBuilder", e); } } + + /** + * Reflectively set the {@link Supplier} of {@link Resource} to the {@link + * SdkTracerProviderBuilder}. + */ + public static SdkTracerProviderBuilder setResourceSupplier( + SdkTracerProviderBuilder sdkTracerProviderBuilder, Supplier supplier) { + try { + Method method = + SdkTracerProviderBuilder.class.getDeclaredMethod("setResourceSupplier", Supplier.class); + method.setAccessible(true); + method.invoke(sdkTracerProviderBuilder, supplier); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException( + "Error calling setTracerConfigurator on SdkTracerProvider", e); + } + return sdkTracerProviderBuilder; + } } From 00829e79b29dfd7449e7ca69f0be5c6ac64a87ef Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sun, 6 Jul 2025 10:10:22 -0400 Subject: [PATCH 17/42] Fix javadoc issues --- .../io/opentelemetry/api/incubator/entities/Resource.java | 4 ++-- .../sdk/extension/incubator/ExtendedOpenTelemetrySdk.java | 1 + .../extension/incubator/ExtendedOpenTelemetrySdkBuilder.java | 1 + .../incubator/entities/SdkResourceProviderBuilder.java | 2 +- .../sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java | 4 +--- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java index 382b213c3c1..e9287986dd7 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java @@ -8,7 +8,7 @@ /** The active resource for which Telemetry is being generated. */ public interface Resource { /** - * Removes an {@link Entity} from this resource. + * Removes an entity from this resource. * * @param entityType the type of entity to remove. * @return true if entity was found and removed. @@ -19,7 +19,7 @@ public interface Resource { * Attaches an entity to the current {@link Resource}. * * @param entityType The type of the entity. - * @return A builder that can construct an {@link Entity}. + * @return A builder that can construct an entity. */ public EntityBuilder attachEntity(String entityType); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java index 67c2abe2bfb..f060e324481 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.incubator.entities.ExtendedOpenTelemetry; +import io.opentelemetry.api.incubator.entities.ResourceProvider; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java index 88573c4164c..2f12d2d8cd5 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.incubator.entities.ResourceProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java index 323e049b54c..122f2813f3e 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.List; -/** A builder for {@link SdResourceProvider}. */ +/** A builder for {@link SdkResourceProvider}. */ public final class SdkResourceProviderBuilder { private final List detectors = new ArrayList<>(); private boolean includeDefaults = true; diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java index 6b83956ab58..1916168e8d6 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java @@ -42,9 +42,7 @@ void endToEnd() { .hasResourceSatisfying( resource -> resource.hasAttributesSatisfying( - attributes -> - assertThat(attributes) - .containsEntry("test.id", 1)))); + attributes -> assertThat(attributes).containsEntry("test.id", 1)))); // Now update the resource and check the point. otel.getResourceProvider() From ecd9c7bcf9affcf51d29f8a730b764570d80279c Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 09:52:03 -0400 Subject: [PATCH 18/42] Update exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com> --- .../exporter/internal/otlp/EntityRefMarshaler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java index 50809f9cfb7..968a753f3e2 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java @@ -16,7 +16,7 @@ import javax.annotation.Nullable; /** - * A Marshaler of {@link io.opentelemetry.sdk.resources.Entity}. + * A Marshaler of {@link io.opentelemetry.sdk.resources.internal.Entity}. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. From 802c056d54d868ec4fb0573ff24abaef2ad8ac2f Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 09:52:43 -0400 Subject: [PATCH 19/42] Update sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java Co-authored-by: jack-berg <34418638+jack-berg@users.noreply.github.com> --- .../java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java index f2c864f08cd..9f8a6f9f682 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java @@ -80,7 +80,6 @@ public SdkLoggerProviderBuilder addResource(Resource resource) { * SdkLoggerProviderUtil#setResourceSupplier(SdkLoggerProviderBuilder, Supplier)}. * * @param supplier The supplier of {@link Resource}. - * @since 1.X.0 */ SdkLoggerProviderBuilder setResourceSupplier(Supplier supplier) { this.resourceSupplier = supplier; From fed17544c7ef648aae6230abc5eaac9f36a5175e Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 12:00:05 -0400 Subject: [PATCH 20/42] Fixes from review. - Hide more methods (no incubating methods in public APIs that are not internal) - Update builder API to use attributes directly - Use static instances in Noop API --- .../{entities => }/ExtendedOpenTelemetry.java | 4 +- .../api/incubator/entities/EntityBuilder.java | 10 +-- .../incubator/entities/NoopEntityBuilder.java | 10 ++- .../api/incubator/entities/NoopResource.java | 4 +- .../entities/NoopResourceProvider.java | 4 +- .../incubator/entities/ResourceProvider.java | 2 +- .../internal/otlp/EntityRefMarshaler.java | 4 +- .../internal/otlp/EntityRefMarshalerTest.java | 5 +- .../incubator/ExtendedOpenTelemetrySdk.java | 2 +- .../incubator/entities/SdkEntityBuilder.java | 10 +-- .../entities/detectors/ServiceDetector.java | 19 ++--- .../detectors/TelemetrySdkDetector.java | 11 ++- .../TestExtendedOpenTelemetrySdk.java | 5 +- .../opentelemetry/sdk/resources/Resource.java | 2 +- .../sdk/resources/internal/EntityBuilder.java | 10 +-- .../sdk/resources/internal/EntityUtil.java | 59 ++++++++++--- .../resources/internal/SdkEntityBuilder.java | 24 +++--- .../sdk/resources/ResourceTest.java | 12 ++- .../resources/internal/EntityUtilTest.java | 83 +++++++------------ 19 files changed, 155 insertions(+), 125 deletions(-) rename api/incubator/src/main/java/io/opentelemetry/api/incubator/{entities => }/ExtendedOpenTelemetry.java (79%) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java similarity index 79% rename from api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java index d558bc8e146..674bdd87701 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ExtendedOpenTelemetry.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java @@ -3,9 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.api.incubator.entities; +package io.opentelemetry.api.incubator; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.api.incubator.entities.ResourceProvider; /** Extension to {@link OpenTelemetry} that adds {@link ResourceProvider}. */ public interface ExtendedOpenTelemetry extends OpenTelemetry { diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java index 62e4fd76783..9795ba2f724 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java @@ -6,8 +6,6 @@ package io.opentelemetry.api.incubator.entities; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import java.util.function.Consumer; /** * A builder of an Entity that allows to add identifying or descriptive {@link Attributes}, as well @@ -37,18 +35,18 @@ public interface EntityBuilder { /** * Modify the descriptive attributes of this Entity. * - * @param f A {@link Consumer} which builds the descriptive attributes. + * @param description The {@link Attributes} which describe this Entity. * @return this */ - EntityBuilder withDescription(Consumer f); + EntityBuilder withDescription(Attributes description); /** * Modify the identifying attributes of this Entity. * - * @param f A {@link Consumer} which builds the identifying attributes. + * @param id The {@link Attributes} which identify this Entity. * @return this */ - EntityBuilder withId(Consumer f); + EntityBuilder withId(Attributes id); /** Emits the current entity. */ void emit(); diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java index 1d7cff08cb0..59474533a98 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java @@ -5,22 +5,24 @@ package io.opentelemetry.api.incubator.entities; -import io.opentelemetry.api.common.AttributesBuilder; -import java.util.function.Consumer; +import io.opentelemetry.api.common.Attributes; final class NoopEntityBuilder implements EntityBuilder { + + static final EntityBuilder INSTANCE = new NoopEntityBuilder(); + @Override public EntityBuilder setSchemaUrl(String schemaUrl) { return this; } @Override - public EntityBuilder withDescription(Consumer f) { + public EntityBuilder withDescription(Attributes description) { return this; } @Override - public EntityBuilder withId(Consumer f) { + public EntityBuilder withId(Attributes id) { return this; } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java index 339c45d25fe..a209c2de3dd 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java @@ -7,6 +7,8 @@ final class NoopResource implements Resource { + static final Resource INSTANCE = new NoopResource(); + @Override public boolean removeEntity(String entityType) { return false; @@ -14,6 +16,6 @@ public boolean removeEntity(String entityType) { @Override public EntityBuilder attachEntity(String entityType) { - return new NoopEntityBuilder(); + return NoopEntityBuilder.INSTANCE; } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java index ff8c1f03d6f..84d3860a4b2 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java @@ -7,8 +7,10 @@ final class NoopResourceProvider implements ResourceProvider { + static final ResourceProvider INSTANCE = new NoopResourceProvider(); + @Override public Resource getResource() { - return new NoopResource(); + return NoopResource.INSTANCE; } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java index 937d1342ecd..2ac9c985f53 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java @@ -17,7 +17,7 @@ public interface ResourceProvider { * not record nor are emitted. */ static ResourceProvider noop() { - return new NoopResourceProvider(); + return NoopResourceProvider.INSTANCE; } /** Returns the active {@link Resource} for which Telemetry is reported. */ diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java index 968a753f3e2..e77ab7f8308 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java @@ -21,7 +21,7 @@ *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ -public final class EntityRefMarshaler extends MarshalerWithSize { +final class EntityRefMarshaler extends MarshalerWithSize { @Nullable private final byte[] schemaUrlUtf8; private final byte[] typeUtf8; private final byte[][] idKeysUtf8; @@ -38,7 +38,7 @@ protected void writeTo(Serializer output) throws IOException { } /** Consttructs an entity reference marshaler from a full entity. */ - public static EntityRefMarshaler createForEntity(Entity e) { + static EntityRefMarshaler createForEntity(Entity e) { byte[] schemaUrlUtf8 = null; if (!StringUtils.isNullOrEmpty(e.getSchemaUrl())) { schemaUrlUtf8 = e.getSchemaUrl().getBytes(StandardCharsets.UTF_8); diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java index eea9cd494d4..b5e8cad39f5 100644 --- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java +++ b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java @@ -10,6 +10,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import com.google.protobuf.util.JsonFormat; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.exporter.internal.marshal.Marshaler; import io.opentelemetry.proto.common.v1.EntityRef; import io.opentelemetry.sdk.resources.internal.Entity; @@ -25,8 +26,8 @@ void toEntityRefs() { Entity e = Entity.builder("test") .setSchemaUrl("test-url") - .withDescription(attr -> attr.put("desc.key", "desc.value")) - .withId(attr -> attr.put("id.key", "id.value")) + .withDescription(Attributes.builder().put("desc.key", "desc.value").build()) + .withId(Attributes.builder().put("id.key", "id.value").build()) .build(); EntityRef proto = parse(EntityRef.getDefaultInstance(), EntityRefMarshaler.createForEntity(e)); assertThat(proto.getType()).isEqualTo("test"); diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java index f060e324481..66673dca7f7 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.extension.incubator; -import io.opentelemetry.api.incubator.entities.ExtendedOpenTelemetry; +import io.opentelemetry.api.incubator.ExtendedOpenTelemetry; import io.opentelemetry.api.incubator.entities.ResourceProvider; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java index 27770a9bdb2..3cf68e2f56c 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.extension.incubator.entities; -import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.entities.EntityBuilder; import io.opentelemetry.sdk.resources.internal.Entity; import java.util.function.Consumer; @@ -26,14 +26,14 @@ public EntityBuilder setSchemaUrl(String schemaUrl) { } @Override - public EntityBuilder withDescription(Consumer f) { - builder.withDescription(f); + public EntityBuilder withDescription(Attributes description) { + builder.withDescription(description); return this; } @Override - public EntityBuilder withId(Consumer f) { - builder.withId(f); + public EntityBuilder withId(Attributes id) { + builder.withId(id); return this; } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java index 5cc96be88f6..33607fb6d26 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator.entities.detectors; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.entities.Resource; import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; import java.util.UUID; @@ -40,18 +41,12 @@ public void configure(Resource resource) { .attachEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) .withId( - id -> { - // Note: Identifying attributes MUST be provided together. - id.put(SERVICE_NAME, getServiceName()) - .put(SERVICE_INSTANCE_ID, getServiceInstanceId()); - }) - // No specified way to take these in. - // .withDescriptive( - // builder -> { - // if (!StringUtils.isNullOrEmpty(getVersion())) { - // builder.put(SERVICE_VERSION, getVersion()); - // } - // }) + // Note: Identifying attributes MUST be provided together. + Attributes.builder() + .put(SERVICE_NAME, getServiceName()) + .put(SERVICE_INSTANCE_ID, getServiceInstanceId()) + .build()) + // TODO - Need to figure out version .emit(); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java index 7dad1b7a267..dec7eb69dee 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator.entities.detectors; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.entities.Resource; import io.opentelemetry.sdk.common.internal.OtelVersion; import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; @@ -33,10 +34,12 @@ public void configure(Resource resource) { .attachEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) .withId( - id -> { - id.put(TELEMETRY_SDK_NAME, "opentelemetry").put(TELEMETRY_SDK_LANGUAGE, "java"); - }) - .withDescription(desc -> desc.put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION)) + Attributes.builder() + .put(TELEMETRY_SDK_NAME, "opentelemetry") + .put(TELEMETRY_SDK_LANGUAGE, "java") + .build()) + .withDescription( + Attributes.builder().put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION).build()) .emit(); } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java index 1916168e8d6..8c7f69fde27 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongCounter; @@ -26,7 +27,7 @@ void endToEnd() { otel.getResourceProvider() .getResource() .attachEntity("test") - .withId(id -> id.put("test.id", 1)) + .withId(Attributes.builder().put("test.id", 1).build()) .emit(); // Write a metric. Meter meter = otel.getMeterProvider().get("test.scope"); @@ -48,7 +49,7 @@ void endToEnd() { otel.getResourceProvider() .getResource() .attachEntity("test2") - .withId(id -> id.put("test2.id", 1)) + .withId(Attributes.builder().put("test2.id", 1).build()) .emit(); // Verify we see the new entity and the metric. assertThat(sdkMeterReader.collectAllMetrics()) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java index 50fed603b94..e72e5db90a4 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java @@ -115,7 +115,7 @@ public static Resource create(Attributes attributes, @Nullable String schemaUrl) * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. */ - public static Resource create( + static Resource create( Attributes attributes, @Nullable String schemaUrl, Collection entities) { AttributeCheckUtil.checkAttributes(Objects.requireNonNull(attributes, "attributes")); return new AutoValue_Resource(schemaUrl, attributes, entities); diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java index fbb4cc40dca..bb2b21c998b 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java @@ -6,8 +6,6 @@ package io.opentelemetry.sdk.resources.internal; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import java.util.function.Consumer; /** * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as @@ -28,18 +26,18 @@ public interface EntityBuilder { /** * Modify the descriptive attributes of this Entity. * - * @param f A thunk which manipulates descriptive attributes. + * @param description The attributes that describe the Entity. * @return this */ - EntityBuilder withDescription(Consumer f); + EntityBuilder withDescription(Attributes description); /** * Modify the identifying attributes of this Entity. * - * @param f A thunk which manipulates identifying attributes. + * @param id The identifying attributes. * @return this */ - EntityBuilder withId(Consumer f); + EntityBuilder withId(Attributes id); /** Create the {@link Entity} from this. */ Entity build(); diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index f1472e63fe7..b62d10ccba2 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -34,6 +34,50 @@ public final class EntityUtil { private EntityUtil() {} + /** + * Constructs a new {@link Resource} with Entity support. + * + * @param entities The set of entities the resource needs. + * @return A constructed resource. + */ + public static final Resource createResource(Collection entities) { + return createResourceRaw( + Attributes.empty(), EntityUtil.mergeResourceSchemaUrl(entities, null, null), entities); + } + + /** + * Constructs a new {@link Resource} with Entity support. + * + * @param attributes The raw attributes for the resource. + * @param schemaUrl The schema url for the resource. + * @param entities The set of entities the resource needs. + * @return A constructed resource. + */ + static final Resource createResourceRaw( + Attributes attributes, @Nullable String schemaUrl, Collection entities) { + try { + Method method = + Resource.class.getDeclaredMethod( + "create", Attributes.class, String.class, Collection.class); + if (method != null) { + method.setAccessible(true); + Object result = method.invoke(null, attributes, schemaUrl, entities); + if (result instanceof Resource) { + return (Resource) result; + } + } + } catch (NoSuchMethodException nme) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); + } catch (IllegalAccessException iae) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); + } catch (InvocationTargetException ite) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } + // Fall back to non-entity behavior? + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource"); + return Resource.empty(); + } + /** Appends a new entity on to the end of the list of entities. */ public static final ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { try { @@ -250,15 +294,10 @@ public static final Collection mergeEntities( Entity next = old.toBuilder() .withDescription( - builder -> { - // Clean existing attributes. - builder.removeIf(ignore -> true); - // For attributes, last one wins. - // To ensure the previous attributes override, - // we write them second. - builder.putAll(e.getDescription()); - builder.putAll(old.getDescription()); - }) + Attributes.builder() + .putAll(e.getDescription()) + .putAll(old.getDescription()) + .build()) .build(); entities.put(next.getType(), next); } @@ -290,6 +329,6 @@ public static Resource merge(Resource base, @Nullable Resource next) { // Now figure out schema url for overall resource. String schemaUrl = EntityUtil.mergeResourceSchemaUrl(entities, base.getSchemaUrl(), next.getSchemaUrl()); - return Resource.create(attributeResult.getAttributes(), schemaUrl, entities); + return createResourceRaw(attributeResult.getAttributes(), schemaUrl, entities); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java index 657bf3f5398..896f84a9b11 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java @@ -6,8 +6,6 @@ package io.opentelemetry.sdk.resources.internal; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import java.util.function.Consumer; import javax.annotation.Nullable; /** @@ -19,21 +17,21 @@ */ final class SdkEntityBuilder implements EntityBuilder { private final String entityType; - private final AttributesBuilder descriptionBuilder; - private final AttributesBuilder idBuilder; + private Attributes description; + private Attributes id; @Nullable private String schemaUrl; SdkEntityBuilder(String entityType) { this.entityType = entityType; - this.descriptionBuilder = Attributes.builder(); - this.idBuilder = Attributes.builder(); + this.description = Attributes.empty(); + this.id = Attributes.empty(); } SdkEntityBuilder(Entity seed) { this.entityType = seed.getType(); this.schemaUrl = seed.getSchemaUrl(); - this.idBuilder = seed.getId().toBuilder(); - this.descriptionBuilder = seed.getDescription().toBuilder(); + this.id = seed.getId(); + this.description = seed.getDescription(); } @Override @@ -43,19 +41,19 @@ public EntityBuilder setSchemaUrl(String schemaUrl) { } @Override - public EntityBuilder withDescription(Consumer f) { - f.accept(this.descriptionBuilder); + public EntityBuilder withDescription(Attributes description) { + this.description = description; return this; } @Override - public EntityBuilder withId(Consumer f) { - f.accept(this.idBuilder); + public EntityBuilder withId(Attributes id) { + this.id = id; return this; } @Override public Entity build() { - return SdkEntity.create(entityType, idBuilder.build(), descriptionBuilder.build(), schemaUrl); + return SdkEntity.create(entityType, id, description, schemaUrl); } } diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java index 8cbc843057b..6e170226ed5 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java @@ -240,11 +240,19 @@ void testMergeResources_Resource2_Null() { void testMergeResources_entities_separate_types_and_schema() { Resource resource1 = Resource.builder() - .add(Entity.builder("a").setSchemaUrl("one").withId(id -> id.put("a.id", "a")).build()) + .add( + Entity.builder("a") + .setSchemaUrl("one") + .withId(Attributes.builder().put("a.id", "a").build()) + .build()) .build(); Resource resource2 = Resource.builder() - .add(Entity.builder("b").setSchemaUrl("two").withId(id -> id.put("b.id", "b")).build()) + .add( + Entity.builder("b") + .setSchemaUrl("two") + .withId(Attributes.builder().put("b.id", "b").build()) + .build()) .build(); Resource merged = resource1.merge(resource2); assertThat(merged.getSchemaUrl()).isNull(); diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index f9e9eca64d8..2732bbf35fd 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -23,21 +23,15 @@ void testMerge_entities_same_types_and_id() { Arrays.asList( Entity.builder("a") .setSchemaUrl("one") - .withId(id -> id.put("a.id", "a")) - .withDescription(builder -> builder.put("a.desc1", "a")) + .withId(Attributes.builder().put("a.id", "a").build()) + .withDescription(Attributes.builder().put("a.desc1", "a").build()) .build()); Collection added = Arrays.asList( Entity.builder("a") .setSchemaUrl("one") - .withId( - builder -> { - builder.put("a.id", "a"); - }) - .withDescription( - builder -> { - builder.put("a.desc2", "b"); - }) + .withId(Attributes.builder().put("a.id", "a").build()) + .withDescription(Attributes.builder().put("a.desc2", "b").build()) .build()); Collection merged = EntityUtil.mergeEntities(base, added); assertThat(merged).hasSize(1); @@ -61,21 +55,15 @@ void testMerge_entities_same_types_and_id_different_schema() { Arrays.asList( Entity.builder("a") .setSchemaUrl("one") - .withId(id -> id.put("a.id", "a")) - .withDescription(builder -> builder.put("a.desc1", "a")) + .withId(Attributes.builder().put("a.id", "a").build()) + .withDescription(Attributes.builder().put("a.desc1", "a").build()) .build()); Collection added = Arrays.asList( Entity.builder("a") .setSchemaUrl("two") - .withId( - builder -> { - builder.put("a.id", "a"); - }) - .withDescription( - builder -> { - builder.put("a.desc2", "b"); - }) + .withId(Attributes.builder().put("a.id", "a").build()) + .withDescription(Attributes.builder().put("a.desc2", "b").build()) .build()); Collection merged = EntityUtil.mergeEntities(base, added); assertThat(merged).hasSize(1); @@ -100,27 +88,15 @@ void testMerge_entities_same_types_different_id() { Arrays.asList( Entity.builder("a") .setSchemaUrl("one") - .withId( - builder -> { - builder.put("a.id", "a"); - }) - .withDescription( - builder -> { - builder.put("a.desc1", "a"); - }) + .withId(Attributes.builder().put("a.id", "a").build()) + .withDescription(Attributes.builder().put("a.desc1", "a").build()) .build()); Collection added = Arrays.asList( Entity.builder("a") .setSchemaUrl("one") - .withId( - builder -> { - builder.put("a.id", "b"); - }) - .withDescription( - builder -> { - builder.put("a.desc2", "b"); - }) + .withId(Attributes.builder().put("a.id", "b").build()) + .withDescription(Attributes.builder().put("a.desc2", "b").build()) .build()); Collection merged = EntityUtil.mergeEntities(base, added); assertThat(merged).hasSize(1); @@ -143,19 +119,13 @@ void testMerge_entities_separate_types_and_schema() { Arrays.asList( Entity.builder("a") .setSchemaUrl("one") - .withId( - builder -> { - builder.put("a.id", "a"); - }) + .withId(Attributes.builder().put("a.id", "a").build()) .build()); Collection added = Arrays.asList( Entity.builder("b") .setSchemaUrl("two") - .withId( - builder -> { - builder.put("b.id", "b"); - }) + .withId(Attributes.builder().put("b.id", "b").build()) .build()); Collection merged = EntityUtil.mergeEntities(base, added); // Make sure we keep both entities when no conflict. @@ -195,7 +165,10 @@ void testSchemaUrlMerge_entities_same_url() { String result = EntityUtil.mergeResourceSchemaUrl( Arrays.asList( - Entity.builder("t").setSchemaUrl("one").withId(id -> id.put("id", 1)).build()), + Entity.builder("t") + .setSchemaUrl("one") + .withId(Attributes.builder().put("id", 1).build()) + .build()), "one", null); assertThat(result).isEqualTo("one"); @@ -208,8 +181,14 @@ void testSchemaUrlMerge_entities_different_url() { String result = EntityUtil.mergeResourceSchemaUrl( Arrays.asList( - Entity.builder("t").setSchemaUrl("one").withId(id -> id.put("id", 1)).build(), - Entity.builder("t2").setSchemaUrl("two").withId(id -> id.put("id2", 1)).build()), + Entity.builder("t") + .setSchemaUrl("one") + .withId(Attributes.builder().put("id", 1).build()) + .build(), + Entity.builder("t2") + .setSchemaUrl("two") + .withId(Attributes.builder().put("id2", 1).build()) + .build()), "one", "one"); assertThat(result).isEqualTo(null); @@ -239,7 +218,8 @@ void testRawAttributeMerge_entity_with_conflict() { EntityUtil.mergeRawAttributes( Attributes.builder().put("a", 1).put("b", 1).build(), Attributes.builder().put("b", 2).put("c", 2).build(), - Arrays.asList(Entity.builder("c").withId(id -> id.put("c", 1)).build())); + Arrays.asList( + Entity.builder("c").withId(Attributes.builder().put("c", 1).build()).build())); assertThat(result.getConflicts()).satisfiesExactly(e -> assertThat(e).hasType("c")); assertThat(result.getAttributes()) .hasSize(3) @@ -252,7 +232,8 @@ void testRawAttributeMerge_entity_with_conflict() { void testAddEntity_reflection() { Resource result = EntityUtil.addEntity( - Resource.builder(), Entity.builder("a").withId(id -> id.put("a", 1)).build()) + Resource.builder(), + Entity.builder("a").withId(Attributes.builder().put("a", 1).build()).build()) .build(); assertThat(EntityUtil.getEntities(result)) .satisfiesExactlyInAnyOrder(e -> assertThat(e).hasType("a")); @@ -264,8 +245,8 @@ void testAddAllEntity_reflection() { EntityUtil.addAllEntity( Resource.builder(), Arrays.asList( - Entity.builder("a").withId(id -> id.put("a", 1)).build(), - Entity.builder("b").withId(id -> id.put("b", 1)).build())) + Entity.builder("a").withId(Attributes.builder().put("a", 1).build()).build(), + Entity.builder("b").withId(Attributes.builder().put("b", 1).build()).build())) .build(); assertThat(EntityUtil.getEntities(result)) .satisfiesExactlyInAnyOrder( From 6924bf5ce1cffbb25fcc5b3add5b8868e4396c4b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 12:44:57 -0400 Subject: [PATCH 21/42] Remove methods we don't need yet. --- .../opentelemetry/api/incubator/ExtendedOpenTelemetry.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java index 674bdd87701..3fceb315231 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java @@ -6,7 +6,6 @@ package io.opentelemetry.api.incubator; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.incubator.entities.Resource; import io.opentelemetry.api.incubator.entities.ResourceProvider; /** Extension to {@link OpenTelemetry} that adds {@link ResourceProvider}. */ @@ -15,9 +14,4 @@ public interface ExtendedOpenTelemetry extends OpenTelemetry { default ResourceProvider getResourceProvider() { return ResourceProvider.noop(); } - - /** Returns the {@link Resource} that telemetry from this {@link OpenTelemetry} uses. */ - default Resource getResource() { - return getResourceProvider().getResource(); - } } From fe34bb1d040669fe9ef6576a40e4e830cb34507f Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 12:46:29 -0400 Subject: [PATCH 22/42] Remove redundant visibility. --- .../io/opentelemetry/api/incubator/entities/Resource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java index e9287986dd7..64490fc1d84 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java @@ -13,7 +13,7 @@ public interface Resource { * @param entityType the type of entity to remove. * @return true if entity was found and removed. */ - public boolean removeEntity(String entityType); + boolean removeEntity(String entityType); /** * Attaches an entity to the current {@link Resource}. @@ -21,5 +21,5 @@ public interface Resource { * @param entityType The type of the entity. * @return A builder that can construct an entity. */ - public EntityBuilder attachEntity(String entityType); + EntityBuilder attachEntity(String entityType); } From 552dea2528d44d3fed0cd5316111f7363d0dc991 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 13:04:00 -0400 Subject: [PATCH 23/42] More cleanups from review. --- .../opentelemetry/sdk/resources/internal/SdkEntityBuilder.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java index 896f84a9b11..2a425f5a0d3 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java @@ -22,6 +22,7 @@ final class SdkEntityBuilder implements EntityBuilder { @Nullable private String schemaUrl; SdkEntityBuilder(String entityType) { + AttributeCheckUtil.isValid(entityType); this.entityType = entityType; this.description = Attributes.empty(); this.id = Attributes.empty(); @@ -42,12 +43,14 @@ public EntityBuilder setSchemaUrl(String schemaUrl) { @Override public EntityBuilder withDescription(Attributes description) { + AttributeCheckUtil.checkAttributes(description); this.description = description; return this; } @Override public EntityBuilder withId(Attributes id) { + AttributeCheckUtil.checkAttributes(id); this.id = id; return this; } From 92848a45f44ede1ab869d93363dc6a8df1730dc4 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 16:13:27 -0400 Subject: [PATCH 24/42] Simplify prototype of SdkResource. --- .../incubator/entities/SdkResource.java | 51 +++++++++++++++++-- .../sdk/resources/internal/EntityUtil.java | 22 ++++---- .../sdk/resources/internal/SdkEntity.java | 6 --- .../sdk/logs/LoggerSharedState.java | 3 +- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java index c3ac2f5ac3b..202b80e0035 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java @@ -6,16 +6,28 @@ package io.opentelemetry.sdk.extension.incubator.entities; import io.opentelemetry.api.incubator.entities.EntityBuilder; +import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.internal.Entity; import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; final class SdkResource implements io.opentelemetry.api.incubator.entities.Resource { + // The currently advertised Resource to other SDK providers. private final AtomicReference resource = new AtomicReference<>(Resource.empty()); private final Object writeLock = new Object(); + // Our internal storage of registered entities. + @GuardedBy("writeLock") + private final ArrayList entities = new ArrayList<>(); + + private static final Logger logger = Logger.getLogger(SdkResource.class.getName()); + /** Returns the currently active resource. */ public Resource getResource() { Resource result = resource.get(); @@ -32,11 +44,44 @@ public boolean removeEntity(String entityType) { throw new UnsupportedOperationException("Unimplemented method 'removeEntity'"); } + private static boolean hasSameSchemaUrl(Entity lhs, Entity rhs) { + if (lhs.getSchemaUrl() != null) { + return lhs.getSchemaUrl().equals(rhs.getSchemaUrl()); + } + return rhs.getSchemaUrl() == null; + } + void attachEntityOnEmit(Entity e) { synchronized (writeLock) { - Resource current = getResource(); - Resource next = EntityUtil.addEntity(Resource.builder(), e).build(); - resource.lazySet(current.merge(next)); + @Nullable Entity conflict = null; + for (Entity existing : entities) { + if (existing.getType().equals(e.getType())) { + conflict = existing; + } + } + + if (conflict != null) { + if (hasSameSchemaUrl(conflict, e) && conflict.getId().equals(e.getId())) { + // We can merge descriptive attributes. + entities.remove(conflict); + io.opentelemetry.sdk.resources.internal.EntityBuilder newEntity = + Entity.builder(conflict.getType()) + .withId(conflict.getId()) + .withDescription( + conflict.getDescription().toBuilder().putAll(e.getDescription()).build()); + if (conflict.getSchemaUrl() != null) { + newEntity.setSchemaUrl(conflict.getSchemaUrl()); + } + entities.add(newEntity.build()); + } else { + // TODO - use ThrottlingLogger? + logger.log(Level.WARNING, "Ignoring new entity, conflicts with existing: ", e); + } + } else { + entities.add(e); + } + + resource.lazySet(EntityUtil.createResource(entities)); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index b62d10ccba2..c028f11ea40 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -40,7 +40,7 @@ private EntityUtil() {} * @param entities The set of entities the resource needs. * @return A constructed resource. */ - public static final Resource createResource(Collection entities) { + public static Resource createResource(Collection entities) { return createResourceRaw( Attributes.empty(), EntityUtil.mergeResourceSchemaUrl(entities, null, null), entities); } @@ -53,7 +53,7 @@ public static final Resource createResource(Collection entities) { * @param entities The set of entities the resource needs. * @return A constructed resource. */ - static final Resource createResourceRaw( + static Resource createResourceRaw( Attributes attributes, @Nullable String schemaUrl, Collection entities) { try { Method method = @@ -79,7 +79,7 @@ static final Resource createResourceRaw( } /** Appends a new entity on to the end of the list of entities. */ - public static final ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { + public static ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { try { Method method = ResourceBuilder.class.getDeclaredMethod("add", Entity.class); if (method != null) { @@ -97,7 +97,7 @@ public static final ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { } /** Appends a new collection of entities on to the end of the list of entities. */ - public static final ResourceBuilder addAllEntity(ResourceBuilder rb, Collection e) { + public static ResourceBuilder addAllEntity(ResourceBuilder rb, Collection e) { try { Method method = ResourceBuilder.class.getDeclaredMethod("addAll", Collection.class); if (method != null) { @@ -120,7 +120,7 @@ public static final ResourceBuilder addAllEntity(ResourceBuilder rb, Collection< * @return a collection of entities. */ @SuppressWarnings("unchecked") - public static final Collection getEntities(Resource r) { + public static Collection getEntities(Resource r) { try { Method method = Resource.class.getDeclaredMethod("getEntities"); if (method != null) { @@ -142,7 +142,7 @@ public static final Collection getEntities(Resource r) { * * @return a map of attributes. */ - public static final Attributes getRawAttributes(Resource r) { + public static Attributes getRawAttributes(Resource r) { try { Method method = Resource.class.getDeclaredMethod("getRawAttributes"); if (method != null) { @@ -160,8 +160,7 @@ public static final Attributes getRawAttributes(Resource r) { } /** Returns true if any entity in the collection has the attribute key, in id or description. */ - public static final boolean hasAttributeKey( - Collection entities, AttributeKey key) { + public static boolean hasAttributeKey(Collection entities, AttributeKey key) { return entities.stream() .anyMatch( e -> e.getId().asMap().containsKey(key) || e.getDescription().asMap().containsKey(key)); @@ -169,7 +168,7 @@ public static final boolean hasAttributeKey( /** Decides on a final SchemaURL for OTLP Resource based on entities chosen. */ @Nullable - static final String mergeResourceSchemaUrl( + static String mergeResourceSchemaUrl( Collection entities, @Nullable String baseUrl, @Nullable String nextUrl) { // Check if entities all share the same URL. Set entitySchemas = @@ -228,8 +227,7 @@ static final RawAttributeMergeResult mergeRawAttributes( additional.forEach( (key, value) -> { for (Entity e : entities) { - if (e.getId().asMap().keySet().contains(key) - || e.getDescription().asMap().keySet().contains(key)) { + if (e.getId().get(key) != null || e.getDescription().get(key) != null) { // Remove the entity and push all attributes as raw, // we have an override. conflicts.add(e); @@ -249,7 +247,7 @@ static final RawAttributeMergeResult mergeRawAttributes( * @param additional Additional entities to merge with base set. * @return A new set of entities with no duplicate types. */ - public static final Collection mergeEntities( + public static Collection mergeEntities( Collection base, Collection additional) { if (base.isEmpty()) { return additional; diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java index 6a5b31e31d6..2ddf17dde33 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java @@ -26,15 +26,9 @@ abstract class SdkEntity implements Entity { * @param id a map of attributes that identify the entity. * @param description a map of attributes that describe the entity. * @return a {@code Entity}. - * @throws NullPointerException if {@code id} or {@code description} is null. - * @throws IllegalArgumentException if entityType string, attribute key or attribute value is not - * a valid printable ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. */ static final Entity create( String entityType, Attributes id, Attributes description, @Nullable String schemaUrl) { - AttributeCheckUtil.isValid(entityType); - AttributeCheckUtil.checkAttributes(id); - AttributeCheckUtil.checkAttributes(description); return new AutoValue_SdkEntity(entityType, id, description, schemaUrl); } diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java index 5f2531f6ed0..232822a5f20 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java @@ -38,8 +38,7 @@ final class LoggerSharedState { this.exceptionAttributeResolver = exceptionAttributeResolver; } - // This is used in a test, and must be public for it. - public Resource getResource() { + Resource getResource() { return resourceSupplier.get(); } From fa6c704aca009dddc6517727308a5b07df0ab674 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 16:17:42 -0400 Subject: [PATCH 25/42] Fix typo. --- .../io/opentelemetry/sdk/resources/internal/EntityUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index c028f11ea40..62bc2c677ed 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -322,7 +322,7 @@ public static Resource merge(Resource base, @Nullable Resource next) { Collection entities = EntityUtil.mergeEntities(getEntities(base), getEntities(next)); RawAttributeMergeResult attributeResult = EntityUtil.mergeRawAttributes(getRawAttributes(base), getRawAttributes(next), entities); - // Remove entiites that are conflicting with raw attributes, and therefore in an unknown state. + // Remove entities that are conflicting with raw attributes, and therefore in an unknown state. entities.removeAll(attributeResult.getConflicts()); // Now figure out schema url for overall resource. String schemaUrl = From 698b753a27fb1364ef5fec683e989b4f5e644aac Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 16:43:19 -0400 Subject: [PATCH 26/42] More cleanups from review. --- .../extension/incubator/entities/SdkResource.java | 2 +- .../sdk/resources/internal/EntityUtil.java | 3 +-- .../sdk/logs/SdkLoggerProviderBuilderTest.java | 14 ++++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java index 202b80e0035..4c93e99533d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java @@ -75,7 +75,7 @@ void attachEntityOnEmit(Entity e) { entities.add(newEntity.build()); } else { // TODO - use ThrottlingLogger? - logger.log(Level.WARNING, "Ignoring new entity, conflicts with existing: ", e); + logger.log(Level.INFO, "Ignoring new entity, conflicts with existing: ", e); } } else { entities.add(e); diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index 62bc2c677ed..fb147f25319 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -247,8 +247,7 @@ static final RawAttributeMergeResult mergeRawAttributes( * @param additional Additional entities to merge with base set. * @return A new set of entities with no duplicate types. */ - public static Collection mergeEntities( - Collection base, Collection additional) { + static Collection mergeEntities(Collection base, Collection additional) { if (base.isEmpty()) { return additional; } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java index ae1adea9aa7..4a4a4c17525 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java @@ -5,11 +5,13 @@ package io.opentelemetry.sdk.logs; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.resources.Resource; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; public class SdkLoggerProviderBuilderTest { @@ -24,10 +26,10 @@ void addResource() { SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder().addResource(customResource).build(); - // We should find a less invasive way to verify this. assertThat(sdkLoggerProvider) - .extracting("sharedState") - .hasFieldOrPropertyWithValue("resource", Resource.getDefault().merge(customResource)); + .extracting("sharedState", as(InstanceOfAssertFactories.type(LoggerSharedState.class))) + .extracting(LoggerSharedState::getResource) + .isEqualTo(Resource.getDefault().merge(customResource)); } @Test @@ -40,10 +42,10 @@ void setResourceSupplier() { SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder().setResourceSupplier(() -> customResource).build(); - // We should find a less invasive way to verify this. assertThat(sdkLoggerProvider) - .extracting("sharedState") + .extracting("sharedState", as(InstanceOfAssertFactories.type(LoggerSharedState.class))) + .extracting(LoggerSharedState::getResource) // Validate the default resource values are NO Longer here when a supplier takes over. - .hasFieldOrPropertyWithValue("resource", customResource); + .isEqualTo(customResource); } } From 612c2c036b39cecca0548bfcd65039b918d2b669 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 22:15:01 -0400 Subject: [PATCH 27/42] Add more tests for ResourceProvider. --- .../entities/TestResourceProvider.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java index 69665e64bf6..06c65d033f2 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java @@ -8,6 +8,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.resources.internal.EntityUtil; import org.junit.jupiter.api.Test; @@ -29,4 +30,97 @@ void defaults_includeServiceAndSdk() { .satisfiesExactlyInAnyOrder( e -> assertThat(e).hasType("service"), e -> assertThat(e).hasType("telemetry.sdk")); } + + @Test + void Resource_updatesDescription() { + SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .withDescription(Attributes.builder().put("one.desc", "desc").build()) + .emit(); + + assertThat(provider.getSdkResource().getAttributes()) + .hasSize(2) + .containsKey("one.id") + .containsKey("one.desc"); + } + + @Test + void Resource_ignoresNewIds() { + SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 2).build()) + .withDescription(Attributes.builder().put("one.desc", "desc").build()) + .emit(); + + assertThat(provider.getSdkResource().getAttributes()).hasSize(1).containsKey("one.id"); + } + + @Test + void Resource_ignoresNewSchemaUrl() { + SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("two") + .withId(Attributes.builder().put("one.id", 1).build()) + .withDescription(Attributes.builder().put("one.desc", "desc").build()) + .emit(); + + assertThat(provider.getSdkResource().getAttributes()).hasSize(1).containsKey("one.id"); + } + + @Test + void Resource_addsNewEntity() { + SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + + provider + .getResource() + .attachEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + + provider + .getResource() + .attachEntity("two") + .setSchemaUrl("two") + .withId(Attributes.builder().put("two.id", 2).build()) + .emit(); + + assertThat(provider.getSdkResource().getAttributes()) + .hasSize(2) + .containsKey("one.id") + .containsKey("two.id"); + } } From 41c92eea521d5114ff5c00fcea1142973fc42347 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 10:01:23 -0400 Subject: [PATCH 28/42] Fix stylecheck. --- .../sdk/extension/incubator/entities/TestResourceProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java index 06c65d033f2..c4c10201f5c 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java @@ -101,7 +101,7 @@ void Resource_ignoresNewSchemaUrl() { } @Test - void Resource_addsNewEntity() { + void resource_addsNewEntity() { SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); provider From 7a699525c21bbb45cacb39eea2f0a93706e858bf Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 10:17:55 -0400 Subject: [PATCH 29/42] More checkstyle fixes. --- .../extension/incubator/entities/TestResourceProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java index c4c10201f5c..f45baad8200 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java @@ -32,7 +32,7 @@ void defaults_includeServiceAndSdk() { } @Test - void Resource_updatesDescription() { + void resource_updatesDescription() { SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); provider @@ -57,7 +57,7 @@ void Resource_updatesDescription() { } @Test - void Resource_ignoresNewIds() { + void resource_ignoresNewIds() { SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); provider @@ -79,7 +79,7 @@ void Resource_ignoresNewIds() { } @Test - void Resource_ignoresNewSchemaUrl() { + void resource_ignoresNewSchemaUrl() { SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); provider From 30a0fe723d095c4c2d70511cac6f5ab47a501541 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 10:41:55 -0400 Subject: [PATCH 30/42] Rename prototype to more closely match OTEP 4316. --- .../api/incubator/ExtendedOpenTelemetry.java | 10 ++-- .../incubator/entities/EntityProvider.java | 40 +++++++++++++++ ...pResource.java => NoopEntityProvider.java} | 6 +-- .../entities/NoopResourceProvider.java | 16 ------ .../api/incubator/entities/Resource.java | 25 ---------- .../incubator/entities/ResourceProvider.java | 25 ---------- .../incubator/ExtendedOpenTelemetrySdk.java | 4 +- .../ExtendedOpenTelemetrySdkBuilder.java | 18 +++---- .../ObfuscatedExtendedOpenTelemerySdk.java | 39 ++++++++------- .../incubator/entities/EntityListener.java | 27 ++++++++++ .../incubator/entities/EntityState.java | 25 ++++++++++ .../incubator/entities/ResourceDetector.java | 8 +-- .../incubator/entities/SdkEntityProvider.java | 44 ++++++++++++++++ ...der.java => SdkEntityProviderBuilder.java} | 16 +++--- .../incubator/entities/SdkEntityState.java | 39 +++++++++++++++ .../entities/SdkResourceProvider.java | 32 ------------ ...SdkResource.java => SdkResourceState.java} | 16 +----- .../entities/detectors/ServiceDetector.java | 8 +-- .../detectors/TelemetrySdkDetector.java | 8 +-- .../TestExtendedOpenTelemetrySdk.java | 10 ++-- ...eProvider.java => TestEntityProvider.java} | 50 ++++++++----------- 21 files changed, 263 insertions(+), 203 deletions(-) create mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java rename api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/{NoopResource.java => NoopEntityProvider.java} (59%) delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java rename sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/{SdkResourceProviderBuilder.java => SdkEntityProviderBuilder.java} (70%) create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java rename sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/{SdkResource.java => SdkResourceState.java} (84%) rename sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/{TestResourceProvider.java => TestEntityProvider.java} (63%) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java index 3fceb315231..3021b24a2c4 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java @@ -6,12 +6,12 @@ package io.opentelemetry.api.incubator; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.incubator.entities.ResourceProvider; +import io.opentelemetry.api.incubator.entities.EntityProvider; -/** Extension to {@link OpenTelemetry} that adds {@link ResourceProvider}. */ +/** Extension to {@link OpenTelemetry} that adds {@link EntityProvider}. */ public interface ExtendedOpenTelemetry extends OpenTelemetry { - /** Returns the {@link ResourceProvider} for this {@link OpenTelemetry}. */ - default ResourceProvider getResourceProvider() { - return ResourceProvider.noop(); + /** Returns the {@link EntityProvider} for this {@link OpenTelemetry}. */ + default EntityProvider getEntityProvider() { + return EntityProvider.noop(); } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java new file mode 100644 index 00000000000..b538d39ed42 --- /dev/null +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.api.incubator.entities; + +/** + * A registry for interacting with {@link Resource}s. The name Provider is for consistency + * with other languages and it is NOT loaded using reflection. + * + * @see Resource + */ +public interface EntityProvider { + /** + * Returns a no-op {@link EntityProvider} which only creates no-op {@link Resource}s which do not + * record nor are emitted. + */ + static EntityProvider noop() { + return NoopEntityProvider.INSTANCE; + } + + /** + * Removes an entity from this resource. + * + * @param entityType the type of entity to remove. + * @return true if entity was found and removed. + */ + boolean removeEntity(String entityType); + + /** + * Attaches an entity to the current {@code Resource}. + * + *

This will only add new entities or update description of existing entities. + * + * @param entityType The type of the entity. + * @return A builder that can construct an entity. + */ + EntityBuilder attachOrUpdateEntity(String entityType); +} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java similarity index 59% rename from api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java rename to api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java index a209c2de3dd..08a60093531 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResource.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java @@ -5,9 +5,9 @@ package io.opentelemetry.api.incubator.entities; -final class NoopResource implements Resource { +final class NoopEntityProvider implements EntityProvider { - static final Resource INSTANCE = new NoopResource(); + static final EntityProvider INSTANCE = new NoopEntityProvider(); @Override public boolean removeEntity(String entityType) { @@ -15,7 +15,7 @@ public boolean removeEntity(String entityType) { } @Override - public EntityBuilder attachEntity(String entityType) { + public EntityBuilder attachOrUpdateEntity(String entityType) { return NoopEntityBuilder.INSTANCE; } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java deleted file mode 100644 index 84d3860a4b2..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopResourceProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -final class NoopResourceProvider implements ResourceProvider { - - static final ResourceProvider INSTANCE = new NoopResourceProvider(); - - @Override - public Resource getResource() { - return NoopResource.INSTANCE; - } -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java deleted file mode 100644 index 64490fc1d84..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/Resource.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -/** The active resource for which Telemetry is being generated. */ -public interface Resource { - /** - * Removes an entity from this resource. - * - * @param entityType the type of entity to remove. - * @return true if entity was found and removed. - */ - boolean removeEntity(String entityType); - - /** - * Attaches an entity to the current {@link Resource}. - * - * @param entityType The type of the entity. - * @return A builder that can construct an entity. - */ - EntityBuilder attachEntity(String entityType); -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java deleted file mode 100644 index 2ac9c985f53..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/ResourceProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -/** - * A registry for interacting with {@link Resource}s. The name Provider is for consistency - * with other languages and it is NOT loaded using reflection. - * - * @see Resource - */ -public interface ResourceProvider { - /** - * Returns a no-op {@link ResourceProvider} which only creates no-op {@link Resource}s which do - * not record nor are emitted. - */ - static ResourceProvider noop() { - return NoopResourceProvider.INSTANCE; - } - - /** Returns the active {@link Resource} for which Telemetry is reported. */ - Resource getResource(); -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java index 66673dca7f7..d4fdb19647b 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java @@ -6,14 +6,14 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.incubator.ExtendedOpenTelemetry; -import io.opentelemetry.api.incubator.entities.ResourceProvider; +import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.io.Closeable; -/** A new interface for creating OpenTelemetrySdk that supports {@link ResourceProvider}. */ +/** A new interface for creating OpenTelemetrySdk that supports {@link EntityProvider}. */ public interface ExtendedOpenTelemetrySdk extends ExtendedOpenTelemetry, Closeable { /** * Shutdown the SDK. Calls {@link SdkTracerProvider#shutdown()}, {@link diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java index 2f12d2d8cd5..4fd77ce12e1 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -6,12 +6,12 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.incubator.entities.ResourceProvider; +import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; -import io.opentelemetry.sdk.extension.incubator.entities.SdkResourceProvider; -import io.opentelemetry.sdk.extension.incubator.entities.SdkResourceProviderBuilder; +import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProvider; +import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProviderBuilder; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil; @@ -23,13 +23,13 @@ import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; import java.util.function.Consumer; -/** A new interface for creating OpenTelemetrySdk that supports {@link ResourceProvider}. */ +/** A new interface for creating OpenTelemetrySdk that supports {@link EntityProvider}. */ public final class ExtendedOpenTelemetrySdkBuilder { private ContextPropagators propagators = ContextPropagators.noop(); private final SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); private final SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); private final SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); - private final SdkResourceProviderBuilder resourceProviderBuilder = SdkResourceProvider.builder(); + private final SdkEntityProviderBuilder resourceProviderBuilder = SdkEntityProvider.builder(); /** Sets the {@link ContextPropagators} to use. */ public ExtendedOpenTelemetrySdkBuilder setPropagators(ContextPropagators propagators) { @@ -83,18 +83,18 @@ public ExtendedOpenTelemetrySdkBuilder withLoggerProvider( * @see GlobalOpenTelemetry */ public ExtendedOpenTelemetrySdk build() { - SdkResourceProvider resourceProvider = resourceProviderBuilder.build(); + SdkEntityProvider resourceProvider = resourceProviderBuilder.build(); SdkTracerProvider tracerProvider = SdkTracerProviderUtil.setResourceSupplier( - tracerProviderBuilder, resourceProvider::getSdkResource) + tracerProviderBuilder, resourceProvider::getResource) .build(); SdkMeterProvider meterProvider = SdkMeterProviderUtil.setResourceSupplier( - meterProviderBuilder, resourceProvider::getSdkResource) + meterProviderBuilder, resourceProvider::getResource) .build(); SdkLoggerProvider loggerProvider = SdkLoggerProviderUtil.setResourceSupplier( - loggerProviderBuilder, resourceProvider::getSdkResource) + loggerProviderBuilder, resourceProvider::getResource) .build(); return new ObfuscatedExtendedOpenTelemerySdk( resourceProvider, tracerProvider, meterProvider, loggerProvider, propagators); diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java index 87cdffc0db4..d868e0b26a5 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java @@ -5,8 +5,8 @@ package io.opentelemetry.sdk.extension.incubator; -import io.opentelemetry.api.incubator.entities.Resource; -import io.opentelemetry.api.incubator.entities.ResourceProvider; +import io.opentelemetry.api.incubator.entities.EntityBuilder; +import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.api.logs.LoggerBuilder; import io.opentelemetry.api.logs.LoggerProvider; import io.opentelemetry.api.metrics.MeterBuilder; @@ -16,7 +16,7 @@ import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.extension.incubator.entities.SdkResourceProvider; +import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProvider; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -36,16 +36,16 @@ final class ObfuscatedExtendedOpenTelemerySdk implements ExtendedOpenTelemetrySd private final ObfuscatedTracerProvider tracerProvider; private final ObfuscatedMeterProvider meterProvider; private final ObfuscatedLoggerProvider loggerProvider; - private final ObfuscatedResourceProvider resourceProvider; + private final ObfuscatedEntityProvider entityProvider; private final ContextPropagators propagators; ObfuscatedExtendedOpenTelemerySdk( - SdkResourceProvider resourceProvider, + SdkEntityProvider entityProvider, SdkTracerProvider tracerProvider, SdkMeterProvider meterProvider, SdkLoggerProvider loggerProvider, ContextPropagators propagators) { - this.resourceProvider = new ObfuscatedResourceProvider(resourceProvider); + this.entityProvider = new ObfuscatedEntityProvider(entityProvider); this.tracerProvider = new ObfuscatedTracerProvider(tracerProvider); this.meterProvider = new ObfuscatedMeterProvider(meterProvider); this.loggerProvider = new ObfuscatedLoggerProvider(loggerProvider); @@ -86,8 +86,8 @@ public LoggerProvider getLogsBridge() { } @Override - public ResourceProvider getResourceProvider() { - return resourceProvider; + public EntityProvider getEntityProvider() { + return entityProvider; } @Override @@ -98,8 +98,8 @@ public ContextPropagators getPropagators() { @Override public String toString() { return "ExtendedOpenTelemetrySdk{" - + "resourceProivder=" - + resourceProvider.unobfuscate() + + "entityProvider=" + + entityProvider.unobfuscate() + ", tracerProvider=" + tracerProvider.unobfuscate() + ", meterProvider=" @@ -211,21 +211,26 @@ public SdkLoggerProvider unobfuscate() { */ @ThreadSafe // Visible for testing - static class ObfuscatedResourceProvider implements ResourceProvider { + static class ObfuscatedEntityProvider implements EntityProvider { - private final SdkResourceProvider delegate; + private final SdkEntityProvider delegate; - ObfuscatedResourceProvider(SdkResourceProvider delegate) { + ObfuscatedEntityProvider(SdkEntityProvider delegate) { this.delegate = delegate; } + public SdkEntityProvider unobfuscate() { + return delegate; + } + @Override - public Resource getResource() { - return delegate.getResource(); + public boolean removeEntity(String entityType) { + return delegate.removeEntity(entityType); } - public SdkResourceProvider unobfuscate() { - return delegate; + @Override + public EntityBuilder attachOrUpdateEntity(String entityType) { + return delegate.attachOrUpdateEntity(entityType); } } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java new file mode 100644 index 00000000000..51fc35fd906 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.resources.Resource; + +/** A listener for changes in the EntityState of this SDK. */ +public interface EntityListener { + /** + * Called when an entity has been added or its state has changed. + * + * @param state The current state of the entity. + * @param resource The current state of the Resource. + */ + public void onEntityState(EntityState state, Resource resource); + + /** + * Called when an entity has been removed. + * + * @param state The current state of the removed entity. + * @param resource The current state of the Resource. + */ + public void onEntityDelete(EntityState state, Resource resource); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java new file mode 100644 index 00000000000..07ef93bf359 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.common.Attributes; +import javax.annotation.Nullable; + +/** The current state of an Entity. */ +public interface EntityState { + /** Returns the type of the Entity. */ + String getType(); + + /** Returns the schema_url of the Entity, or null. */ + @Nullable + String getSchemaUrl(); + + /** Returns the identity of the Entity. */ + Attributes getId(); + + /** Returns the description of the Entity. */ + Attributes getDescription(); +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java index c5694d86b33..a7163908655 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java @@ -5,7 +5,7 @@ package io.opentelemetry.sdk.extension.incubator.entities; -import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.api.incubator.entities.EntityProvider; /** * The Resource detector in the SDK is responsible for detecting possible entities that could @@ -14,9 +14,9 @@ */ public interface ResourceDetector { /** - * Configures a resource with detected entities. + * Reports detected entities. * - * @param resource The resource to detect entities on. + * @param provider The provider where entities are reported. */ - public void configure(Resource resource); + public void report(EntityProvider provider); } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java new file mode 100644 index 00000000000..07b891d5f33 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.incubator.entities.EntityBuilder; +import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.sdk.resources.Resource; + +/** The SDK implementation of {@link EntityProvider}. */ +public final class SdkEntityProvider implements EntityProvider { + private final SdkResourceState state = new SdkResourceState(); + + /** + * Obtains the current {@link Resource} for the SDK. + * + * @return the active {@link Resource} for this SDK. + */ + public Resource getResource() { + return state.getResource(); + } + + public static SdkEntityProviderBuilder builder() { + return new SdkEntityProviderBuilder(); + } + + @Override + public String toString() { + return "SdkResourceProvider{}"; + } + + @Override + public boolean removeEntity(String entityType) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'removeEntity'"); + } + + @Override + public EntityBuilder attachOrUpdateEntity(String entityType) { + return new SdkEntityBuilder(entityType, state::attachEntityOnEmit); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java similarity index 70% rename from sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java rename to sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java index 122f2813f3e..4a94185b3c4 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProviderBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java @@ -10,8 +10,8 @@ import java.util.ArrayList; import java.util.List; -/** A builder for {@link SdkResourceProvider}. */ -public final class SdkResourceProviderBuilder { +/** A builder for {@link SdkEntityProvider}. */ +public final class SdkEntityProviderBuilder { private final List detectors = new ArrayList<>(); private boolean includeDefaults = true; @@ -21,7 +21,7 @@ public final class SdkResourceProviderBuilder { * @param detector The resource detector. * @return this */ - public SdkResourceProviderBuilder addDetector(ResourceDetector detector) { + public SdkEntityProviderBuilder addDetector(ResourceDetector detector) { this.detectors.add(detector); return this; } @@ -32,20 +32,20 @@ public SdkResourceProviderBuilder addDetector(ResourceDetector detector) { * @param include true if defaults should be used. * @return this */ - public SdkResourceProviderBuilder includeDefaults(boolean include) { + public SdkEntityProviderBuilder includeDefaults(boolean include) { this.includeDefaults = include; return this; } - public SdkResourceProvider build() { + public SdkEntityProvider build() { // TODO - have defaults in the front? if (includeDefaults) { detectors.add(new ServiceDetector()); detectors.add(new TelemetrySdkDetector()); } - SdkResourceProvider result = new SdkResourceProvider(); - // TODO - Should we move these onto the resource provider? - detectors.forEach(d -> d.configure(result.getResource())); + SdkEntityProvider result = new SdkEntityProvider(); + // TODO - Should we move these onto the provider? + detectors.forEach(d -> d.report(result)); return result; } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java new file mode 100644 index 00000000000..de049edd46e --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.resources.internal.Entity; +import javax.annotation.Nullable; + +final class SdkEntityState implements EntityState { + private final Entity delegate; + + SdkEntityState(Entity delegate) { + this.delegate = delegate; + } + + @Override + public String getType() { + return delegate.getType(); + } + + @Override + @Nullable + public String getSchemaUrl() { + return delegate.getSchemaUrl(); + } + + @Override + public Attributes getId() { + return delegate.getId(); + } + + @Override + public Attributes getDescription() { + return delegate.getDescription(); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java deleted file mode 100644 index a748a61b2b2..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.incubator.entities.Resource; -import io.opentelemetry.api.incubator.entities.ResourceProvider; - -/** The SDK implementation of {@link ResourceProvider}. */ -public final class SdkResourceProvider implements ResourceProvider { - private final SdkResource resource = new SdkResource(); - - @Override - public Resource getResource() { - return resource; - } - - public io.opentelemetry.sdk.resources.Resource getSdkResource() { - return resource.getResource(); - } - - public static SdkResourceProviderBuilder builder() { - return new SdkResourceProviderBuilder(); - } - - @Override - public String toString() { - return "SdkResourceProvider{}"; - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java similarity index 84% rename from sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java rename to sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java index 4c93e99533d..922f3f9a6a1 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResource.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java @@ -5,7 +5,6 @@ package io.opentelemetry.sdk.extension.incubator.entities; -import io.opentelemetry.api.incubator.entities.EntityBuilder; import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.internal.Entity; @@ -16,7 +15,7 @@ import java.util.logging.Logger; import javax.annotation.Nullable; -final class SdkResource implements io.opentelemetry.api.incubator.entities.Resource { +final class SdkResourceState { // The currently advertised Resource to other SDK providers. private final AtomicReference resource = new AtomicReference<>(Resource.empty()); @@ -26,7 +25,7 @@ final class SdkResource implements io.opentelemetry.api.incubator.entities.Resou @GuardedBy("writeLock") private final ArrayList entities = new ArrayList<>(); - private static final Logger logger = Logger.getLogger(SdkResource.class.getName()); + private static final Logger logger = Logger.getLogger(SdkResourceState.class.getName()); /** Returns the currently active resource. */ public Resource getResource() { @@ -38,12 +37,6 @@ public Resource getResource() { return result; } - @Override - public boolean removeEntity(String entityType) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removeEntity'"); - } - private static boolean hasSameSchemaUrl(Entity lhs, Entity rhs) { if (lhs.getSchemaUrl() != null) { return lhs.getSchemaUrl().equals(rhs.getSchemaUrl()); @@ -84,9 +77,4 @@ void attachEntityOnEmit(Entity e) { resource.lazySet(EntityUtil.createResource(entities)); } } - - @Override - public EntityBuilder attachEntity(String entityType) { - return new SdkEntityBuilder(entityType, this::attachEntityOnEmit); - } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java index 33607fb6d26..a0acc9b5c38 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java @@ -7,7 +7,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; import java.util.UUID; @@ -35,10 +35,10 @@ private static String getServiceInstanceId() { } @Override - public void configure(Resource resource) { + public void report(EntityProvider provider) { // We only run on startup. - resource - .attachEntity(ENTITY_TYPE) + provider + .attachOrUpdateEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) .withId( // Note: Identifying attributes MUST be provided together. diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java index dec7eb69dee..6c68198f49e 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java @@ -7,7 +7,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.entities.Resource; +import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.sdk.common.internal.OtelVersion; import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; @@ -29,9 +29,9 @@ public final class TelemetrySdkDetector implements ResourceDetector { AttributeKey.stringKey("telemetry.sdk.version"); @Override - public void configure(Resource resource) { - resource - .attachEntity(ENTITY_TYPE) + public void report(EntityProvider provider) { + provider + .attachOrUpdateEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) .withId( Attributes.builder() diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java index 8c7f69fde27..80c8321ab2a 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java @@ -24,9 +24,8 @@ void endToEnd() { .withMeterProvider(builder -> builder.registerMetricReader(sdkMeterReader)) .build(); // Generate our first entity. - otel.getResourceProvider() - .getResource() - .attachEntity("test") + otel.getEntityProvider() + .attachOrUpdateEntity("test") .withId(Attributes.builder().put("test.id", 1).build()) .emit(); // Write a metric. @@ -46,9 +45,8 @@ void endToEnd() { attributes -> assertThat(attributes).containsEntry("test.id", 1)))); // Now update the resource and check the point. - otel.getResourceProvider() - .getResource() - .attachEntity("test2") + otel.getEntityProvider() + .attachOrUpdateEntity("test2") .withId(Attributes.builder().put("test2.id", 1).build()) .emit(); // Verify we see the new entity and the metric. diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java similarity index 63% rename from sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java rename to sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java index f45baad8200..a53581bc834 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestResourceProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java @@ -12,45 +12,43 @@ import io.opentelemetry.sdk.resources.internal.EntityUtil; import org.junit.jupiter.api.Test; -class TestResourceProvider { +class TestEntityProvider { @Test void defaults_includeServiceAndSdk() { - SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(true).build(); + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(true).build(); - assertThat(provider.getSdkResource().getAttributes()) + assertThat(provider.getResource().getAttributes()) .containsKey("service.name") .containsKey("service.instance.id") .containsKey("telemetry.sdk.language") .containsKey("telemetry.sdk.name") .containsKey("telemetry.sdk.version"); - assertThat(provider.getSdkResource().getSchemaUrl()) + assertThat(provider.getResource().getSchemaUrl()) .isEqualTo("https://opentelemetry.io/schemas/1.34.0"); - assertThat(EntityUtil.getEntities(provider.getSdkResource())) + assertThat(EntityUtil.getEntities(provider.getResource())) .satisfiesExactlyInAnyOrder( e -> assertThat(e).hasType("service"), e -> assertThat(e).hasType("telemetry.sdk")); } @Test void resource_updatesDescription() { - SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("one") .withId(Attributes.builder().put("one.id", 1).build()) .emit(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("one") .withId(Attributes.builder().put("one.id", 1).build()) .withDescription(Attributes.builder().put("one.desc", "desc").build()) .emit(); - assertThat(provider.getSdkResource().getAttributes()) + assertThat(provider.getResource().getAttributes()) .hasSize(2) .containsKey("one.id") .containsKey("one.desc"); @@ -58,67 +56,61 @@ void resource_updatesDescription() { @Test void resource_ignoresNewIds() { - SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("one") .withId(Attributes.builder().put("one.id", 1).build()) .emit(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("one") .withId(Attributes.builder().put("one.id", 2).build()) .withDescription(Attributes.builder().put("one.desc", "desc").build()) .emit(); - assertThat(provider.getSdkResource().getAttributes()).hasSize(1).containsKey("one.id"); + assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); } @Test void resource_ignoresNewSchemaUrl() { - SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("one") .withId(Attributes.builder().put("one.id", 1).build()) .emit(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("two") .withId(Attributes.builder().put("one.id", 1).build()) .withDescription(Attributes.builder().put("one.desc", "desc").build()) .emit(); - assertThat(provider.getSdkResource().getAttributes()).hasSize(1).containsKey("one.id"); + assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); } @Test void resource_addsNewEntity() { - SdkResourceProvider provider = SdkResourceProvider.builder().includeDefaults(false).build(); + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); provider - .getResource() - .attachEntity("one") + .attachOrUpdateEntity("one") .setSchemaUrl("one") .withId(Attributes.builder().put("one.id", 1).build()) .emit(); provider - .getResource() - .attachEntity("two") + .attachOrUpdateEntity("two") .setSchemaUrl("two") .withId(Attributes.builder().put("two.id", 2).build()) .emit(); - assertThat(provider.getSdkResource().getAttributes()) + assertThat(provider.getResource().getAttributes()) .hasSize(2) .containsKey("one.id") .containsKey("two.id"); From f917d9d216b36e8c79aaa291af268c2a56fb3416 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 13:31:58 -0400 Subject: [PATCH 31/42] Finish remove implementation and listeners. --- .../CurrentThreadExecutorService.java | 137 +++++++++++++++ .../incubator/entities/SdkEntityProvider.java | 33 +++- .../entities/SdkResourceSharedState.java | 161 ++++++++++++++++++ .../incubator/entities/SdkResourceState.java | 80 --------- .../entities/TestEntityProvider.java | 59 +++++++ 5 files changed, 386 insertions(+), 84 deletions(-) create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java new file mode 100644 index 00000000000..8718ecf1804 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java @@ -0,0 +1,137 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * An executor service that runs all jobs immediately on the current thread. + * + *

We use this so SDK users can determine how to isolate {@link EntityListener}s with the default + * being no isolation of events. + */ +final class CurrentThreadExecutorService implements ExecutorService { + private volatile boolean shutdown = false; + + @Override + public void execute(Runnable command) { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + command.run(); + } + + @Override + public List> invokeAll(Collection> tasks) { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + // Execute all tasks synchronously and collect their Futures + return tasks.stream().map(task -> submit(task)).collect(Collectors.toList()); + } + + @Override + public List> invokeAll( + Collection> tasks, long timeout, TimeUnit unit) { + return invokeAll(tasks); + } + + @Override + public T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + // Execute all tasks synchronously and return first success. + for (Callable task : tasks) { + try { + // We wrap the task in a `submit` call to get ExecutionExceptions. + return submit(task).get(); + } catch (ExecutionException e) { + // Ignore this error, and try the next one. + } + } + throw new ExecutionException("No tasks completed successfully", null); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + return invokeAny(tasks); + } + + @Override + public Future submit(Callable task) { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + FutureTask future = new FutureTask<>(task); + // Run in this thread. + future.run(); + return future; + } + + @Override + public Future submit(Runnable task) { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + FutureTask future = new FutureTask<>(task, null); + // Run in this thread. + future.run(); + return future; + } + + @Override + public Future submit(Runnable task, T result) { + if (shutdown) { + throw new RejectedExecutionException("ExecutorService is shut down"); + } + FutureTask future = new FutureTask<>(task, result); + // Run in this thread. + future.run(); + return future; + } + + @Override + public boolean isShutdown() { + return shutdown; + } + + @Override + public boolean isTerminated() { + return shutdown; + } + + @Override + public void shutdown() { + shutdown = true; + } + + @Override + public List shutdownNow() { + shutdown = true; + return Collections.emptyList(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return isTerminated(); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java index 07b891d5f33..aebd4782176 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java @@ -7,11 +7,16 @@ import io.opentelemetry.api.incubator.entities.EntityBuilder; import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.resources.Resource; +import java.util.concurrent.TimeUnit; /** The SDK implementation of {@link EntityProvider}. */ public final class SdkEntityProvider implements EntityProvider { - private final SdkResourceState state = new SdkResourceState(); + // TODO - Give control over listener execution model. + // For now, just run everything on the same thread as the entity-attach call. + private final SdkResourceSharedState state = + new SdkResourceSharedState(new CurrentThreadExecutorService()); /** * Obtains the current {@link Resource} for the SDK. @@ -22,6 +27,11 @@ public Resource getResource() { return state.getResource(); } + /** + * Creates a builder for SdkEntityProvider. + * + * @return The new builder. + */ public static SdkEntityProviderBuilder builder() { return new SdkEntityProviderBuilder(); } @@ -33,12 +43,27 @@ public String toString() { @Override public boolean removeEntity(String entityType) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'removeEntity'"); + return state.removeEntity(entityType); } @Override public EntityBuilder attachOrUpdateEntity(String entityType) { - return new SdkEntityBuilder(entityType, state::attachEntityOnEmit); + return new SdkEntityBuilder(entityType, state::addOrUpdateEntity); + } + + public void onChange(EntityListener listener) { + state.addListener(listener); + } + + /** + * Shutdown the provider. The resulting {@link CompletableResultCode} completes when all complete. + */ + public CompletableResultCode shutdown() { + return state.shutdown(); + } + + /** Close the provider. Calls {@link #shutdown()} and blocks waiting for it to complete. */ + public void close() { + shutdown().join(10, TimeUnit.SECONDS); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java new file mode 100644 index 00000000000..afb28929fb1 --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java @@ -0,0 +1,161 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.api.internal.GuardedBy; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.internal.ThrottlingLogger; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.internal.Entity; +import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * This class does all state and listener management for a {@link Resource} constructed of {@link + * Entity}s. + */ +final class SdkResourceSharedState { + + // The currently advertised Resource to other SDK providers. + private final AtomicReference resource = new AtomicReference<>(Resource.empty()); + private final Object writeLock = new Object(); + private final List listeners = new CopyOnWriteArrayList<>(); + private final ExecutorService listenerExecutor; + + // Our internal storage of registered entities. + @GuardedBy("writeLock") + private final ArrayList entities = new ArrayList<>(); + + private static final ThrottlingLogger logger = + new ThrottlingLogger(Logger.getLogger(SdkResourceSharedState.class.getName())); + + SdkResourceSharedState(ExecutorService listenerExecutor) { + this.listenerExecutor = listenerExecutor; + } + + /** + * Shutdown the provider. The resulting {@link CompletableResultCode} completes when all complete. + */ + CompletableResultCode shutdown() { + // TODO - Actually figure out how to wait for shutdown and deal with pending tasks. + listenerExecutor.shutdown(); + return CompletableResultCode.ofSuccess(); + } + + /** Returns the currently active resource. */ + public Resource getResource() { + Resource result = resource.get(); + // We do this to make NullAway happy. + if (result == null) { + throw new IllegalStateException("SdkResource should never have null resource"); + } + return result; + } + + private static boolean hasSameSchemaUrl(Entity lhs, Entity rhs) { + if (lhs.getSchemaUrl() != null) { + return lhs.getSchemaUrl().equals(rhs.getSchemaUrl()); + } + return rhs.getSchemaUrl() == null; + } + + /** + * Removes an entity by type and notifies listeners. + * + * @param entityType The entity type to remove. + */ + boolean removeEntity(String entityType) { + synchronized (writeLock) { + @Nullable Entity removed = null; + for (Entity existing : entities) { + if (existing.getType().equals(entityType)) { + removed = existing; + } + } + if (removed == null) { + return false; + } + entities.remove(removed); + Resource result = EntityUtil.createResource(entities); + resource.lazySet(result); + publishEntityDelete(new SdkEntityState(removed), result); + return true; + } + } + + /** + * Adds an entity and notifies listeners. + * + *

Note: This will not add an entity on conflict. This will update the description if the + * entity already exists. + * + * @param e The entity type to add. + */ + void addOrUpdateEntity(Entity e) { + synchronized (writeLock) { + @Nullable Entity conflict = null; + for (Entity existing : entities) { + if (existing.getType().equals(e.getType())) { + conflict = existing; + } + } + Entity newState = e; + if (conflict != null) { + if (hasSameSchemaUrl(conflict, e) && conflict.getId().equals(e.getId())) { + // We can merge descriptive attributes. + entities.remove(conflict); + io.opentelemetry.sdk.resources.internal.EntityBuilder newEntity = + Entity.builder(conflict.getType()) + .withId(conflict.getId()) + .withDescription( + conflict.getDescription().toBuilder().putAll(e.getDescription()).build()); + if (conflict.getSchemaUrl() != null) { + newEntity.setSchemaUrl(conflict.getSchemaUrl()); + } + newState = newEntity.build(); + entities.add(newState); + } else { + logger.log(Level.INFO, "Ignoring new entity, conflicts with existing: " + e); + return; + } + } else { + entities.add(e); + } + Resource result = EntityUtil.createResource(entities); + resource.lazySet(result); + publishEntityStateChange(new SdkEntityState(newState), result); + } + } + + @SuppressWarnings("FutureReturnValueIgnored") + private void publishEntityStateChange(EntityState state, Resource resource) { + for (EntityListener listener : listeners) { + // We isolate listener execution via executor, if configured. + // We ignore failures on futures to avoid having one listener block others. + listenerExecutor.submit(() -> listener.onEntityState(state, resource)); + } + } + + @SuppressWarnings("FutureReturnValueIgnored") + private void publishEntityDelete(EntityState deleted, Resource resource) { + for (EntityListener listener : listeners) { + // We isolate listener execution via executor, if configured. + // We ignore failures on futures to avoid having one listener block others. + listenerExecutor.submit(() -> listener.onEntityDelete(deleted, resource)); + } + } + + public void addListener(EntityListener listener) { + listeners.add(listener); + } +} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java deleted file mode 100644 index 922f3f9a6a1..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceState.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.internal.GuardedBy; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.internal.Entity; -import io.opentelemetry.sdk.resources.internal.EntityUtil; -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -final class SdkResourceState { - - // The currently advertised Resource to other SDK providers. - private final AtomicReference resource = new AtomicReference<>(Resource.empty()); - private final Object writeLock = new Object(); - - // Our internal storage of registered entities. - @GuardedBy("writeLock") - private final ArrayList entities = new ArrayList<>(); - - private static final Logger logger = Logger.getLogger(SdkResourceState.class.getName()); - - /** Returns the currently active resource. */ - public Resource getResource() { - Resource result = resource.get(); - // We do this to make NullAway happy. - if (result == null) { - throw new IllegalStateException("SdkResource should never have null resource"); - } - return result; - } - - private static boolean hasSameSchemaUrl(Entity lhs, Entity rhs) { - if (lhs.getSchemaUrl() != null) { - return lhs.getSchemaUrl().equals(rhs.getSchemaUrl()); - } - return rhs.getSchemaUrl() == null; - } - - void attachEntityOnEmit(Entity e) { - synchronized (writeLock) { - @Nullable Entity conflict = null; - for (Entity existing : entities) { - if (existing.getType().equals(e.getType())) { - conflict = existing; - } - } - - if (conflict != null) { - if (hasSameSchemaUrl(conflict, e) && conflict.getId().equals(e.getId())) { - // We can merge descriptive attributes. - entities.remove(conflict); - io.opentelemetry.sdk.resources.internal.EntityBuilder newEntity = - Entity.builder(conflict.getType()) - .withId(conflict.getId()) - .withDescription( - conflict.getDescription().toBuilder().putAll(e.getDescription()).build()); - if (conflict.getSchemaUrl() != null) { - newEntity.setSchemaUrl(conflict.getSchemaUrl()); - } - entities.add(newEntity.build()); - } else { - // TODO - use ThrottlingLogger? - logger.log(Level.INFO, "Ignoring new entity, conflicts with existing: ", e); - } - } else { - entities.add(e); - } - - resource.lazySet(EntityUtil.createResource(entities)); - } - } -} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java index a53581bc834..2523cefd31b 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java @@ -7,10 +7,15 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.internal.EntityUtil; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; class TestEntityProvider { @Test @@ -115,4 +120,58 @@ void resource_addsNewEntity() { .containsKey("one.id") .containsKey("two.id"); } + + @Test + void resource_removesEntity() { + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + + provider + .attachOrUpdateEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + + assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); + + assertThat(provider.removeEntity("one")).isTrue(); + assertThat(provider.getResource().getAttributes()).isEmpty(); + } + + @Test + void entityListener_notifiesOnAdd() { + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + + EntityListener listener = mock(EntityListener.class); + provider.onChange(listener); + + provider + .attachOrUpdateEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + ArgumentCaptor entityCapture = ArgumentCaptor.forClass(EntityState.class); + ArgumentCaptor resourceCapture = ArgumentCaptor.forClass(Resource.class); + verify(listener, times(1)).onEntityState(entityCapture.capture(), resourceCapture.capture()); + assertThat(entityCapture.getValue().getType()).isEqualTo("one"); + assertThat(resourceCapture.getValue().getAttributes()).hasSize(1).containsKey("one.id"); + } + + @Test + void entityListener_notifiesOnRemove() { + SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + provider + .attachOrUpdateEntity("one") + .setSchemaUrl("one") + .withId(Attributes.builder().put("one.id", 1).build()) + .emit(); + EntityListener listener = mock(EntityListener.class); + provider.onChange(listener); + + provider.removeEntity("one"); + ArgumentCaptor entityCapture = ArgumentCaptor.forClass(EntityState.class); + ArgumentCaptor resourceCapture = ArgumentCaptor.forClass(Resource.class); + verify(listener, times(1)).onEntityDelete(entityCapture.capture(), resourceCapture.capture()); + assertThat(entityCapture.getValue().getType()).isEqualTo("one"); + assertThat(resourceCapture.getValue().getAttributes()).isEmpty(); + } } From c5af0efdc0b6677c788f6c323c0be4375f03ed8f Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 13:46:32 -0400 Subject: [PATCH 32/42] Remove assertjs helpers, update javadoc. --- .../incubator/entities/EntityProvider.java | 8 ++-- .../sdk/testing/assertj/EntityAssert.java | 45 ------------------- .../assertj/OpenTelemetryAssertions.java | 6 --- 3 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java index b538d39ed42..f9854724c4b 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java @@ -6,15 +6,13 @@ package io.opentelemetry.api.incubator.entities; /** - * A registry for interacting with {@link Resource}s. The name Provider is for consistency + * A registry for interacting with {@code Resource}s. The name Provider is for consistency * with other languages and it is NOT loaded using reflection. - * - * @see Resource */ public interface EntityProvider { /** - * Returns a no-op {@link EntityProvider} which only creates no-op {@link Resource}s which do not - * record nor are emitted. + * Returns a no-op {@link EntityProvider} which only creates no-op {@link EntityBuilder}s which do + * not record nor are emitted. */ static EntityProvider noop() { return NoopEntityProvider.INSTANCE; diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java deleted file mode 100644 index 149f8736d9f..00000000000 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/EntityAssert.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.testing.assertj; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.resources.internal.Entity; -import javax.annotation.Nullable; -import org.assertj.core.api.AbstractAssert; -import org.assertj.core.api.ThrowingConsumer; - -/** Assertions for {@link Entity}. */ -public class EntityAssert extends AbstractAssert { - EntityAssert(@Nullable Entity actual) { - super(actual, EntityAssert.class); - } - - /** Asserts that the entity type is equal to a given string. */ - public EntityAssert hasType(String entityType) { - assertThat(actual.getType()).isEqualTo(entityType); - return this; - } - - /** Asserts that the entity id satisfies the given asserts. */ - public EntityAssert hasIdSatisfying(ThrowingConsumer asserts) { - asserts.accept(actual.getId()); - return this; - } - - /** Asserts that the entity description satisfies the given asserts. */ - public EntityAssert hasDescriptionSatisfying(ThrowingConsumer asserts) { - asserts.accept(actual.getDescription()); - return this; - } - - /** Asserts that the entity schemaUrl is equal to a given string. */ - public EntityAssert hasSchemaUrl(String schemaUrl) { - assertThat(actual.getSchemaUrl()).isEqualTo(schemaUrl); - return this; - } -} diff --git a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java index ed2dda36414..6d2e99735c2 100644 --- a/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java +++ b/sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/OpenTelemetryAssertions.java @@ -9,7 +9,6 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.resources.internal.Entity; import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.SpanData; import java.util.AbstractMap; @@ -52,11 +51,6 @@ public static MetricAssert assertThat(@Nullable MetricData metricData) { return new MetricAssert(metricData); } - /** Returns an assertion for {@link Entity}. */ - public static EntityAssert assertThat(@Nullable Entity entity) { - return new EntityAssert(entity); - } - /** Returns an assertion for {@link EventDataAssert}. */ public static EventDataAssert assertThat(@Nullable EventData eventData) { return new EventDataAssert(eventData); From c70b63d39557e069a029bde28c2cd0bb1fb693a0 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 13:58:25 -0400 Subject: [PATCH 33/42] Fix entity javadoc. --- .../java/io/opentelemetry/sdk/resources/internal/Entity.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java index e8c14cfe835..40cf63e126b 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java @@ -37,14 +37,14 @@ public interface Entity { /** * Returns a map of attributes that identify the entity. * - * @return a map of attributes. + * @return the entity identity. */ Attributes getId(); /** * Returns a map of attributes that describe the entity. * - * @return a map of attributes. + * @return the entity description. */ Attributes getDescription(); @@ -53,7 +53,6 @@ public interface Entity { * does not abide by schema conventions (i.e. is custom). * * @return An OpenTelemetry schema URL. - * @since 1.4.0 */ @Nullable String getSchemaUrl(); From 53ec454e8a4430866e5e9da892c594c0209b935c Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 14:16:58 -0400 Subject: [PATCH 34/42] Fix tests. --- .../entities/TestEntityProvider.java | 3 +- .../sdk/resources/internal/EntityUtil.java | 40 +++-------- .../resources/internal/EntityUtilTest.java | 66 +++++++++---------- 3 files changed, 44 insertions(+), 65 deletions(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java index 2523cefd31b..9bf74c12bff 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java @@ -33,7 +33,8 @@ void defaults_includeServiceAndSdk() { assertThat(EntityUtil.getEntities(provider.getResource())) .satisfiesExactlyInAnyOrder( - e -> assertThat(e).hasType("service"), e -> assertThat(e).hasType("telemetry.sdk")); + e -> assertThat(e.getType()).isEqualTo("service"), + e -> assertThat(e.getType()).isEqualTo("telemetry.sdk")); } @Test diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java index fb147f25319..d2379f1a4e0 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java @@ -66,12 +66,8 @@ static Resource createResourceRaw( return (Resource) result; } } - } catch (NoSuchMethodException nme) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); - } catch (IllegalAccessException iae) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); - } catch (InvocationTargetException ite) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", e); } // Fall back to non-entity behavior? logger.log(Level.WARNING, "Attempting to use entities with unsupported resource"); @@ -86,12 +82,8 @@ public static ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { method.setAccessible(true); method.invoke(rb, e); } - } catch (NoSuchMethodException nme) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); - } catch (IllegalAccessException iae) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); - } catch (InvocationTargetException ite) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ex); } return rb; } @@ -104,12 +96,8 @@ public static ResourceBuilder addAllEntity(ResourceBuilder rb, Collection getEntities(Resource r) { method.setAccessible(true); return (Collection) method.invoke(r); } - } catch (NoSuchMethodException nme) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); - } catch (IllegalAccessException iae) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); - } catch (InvocationTargetException ite) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", e); } return Collections.emptyList(); } @@ -149,12 +133,8 @@ public static Attributes getRawAttributes(Resource r) { method.setAccessible(true); return (Attributes) method.invoke(r); } - } catch (NoSuchMethodException nme) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", nme); - } catch (IllegalAccessException iae) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", iae); - } catch (InvocationTargetException ite) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ite); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", e); } return Attributes.empty(); } diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java index 2732bbf35fd..23aece73575 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java @@ -37,16 +37,14 @@ void testMerge_entities_same_types_and_id() { assertThat(merged).hasSize(1); assertThat(merged) .anySatisfy( - entity -> - assertThat(entity) - .hasType("a") - .hasSchemaUrl("one") - .hasIdSatisfying(id -> assertThat(id).containsEntry("a.id", "a")) - .hasDescriptionSatisfying( - desc -> - assertThat(desc) - .containsEntry("a.desc1", "a") - .containsEntry("a.desc2", "b"))); + entity -> { + assertThat(entity.getType()).isEqualTo("a"); + assertThat(entity.getSchemaUrl()).isEqualTo("one"); + assertThat(entity.getId()).containsEntry("a.id", "a"); + assertThat(entity.getDescription()) + .containsEntry("a.desc1", "a") + .containsEntry("a.desc2", "b"); + }); } @Test @@ -69,17 +67,15 @@ void testMerge_entities_same_types_and_id_different_schema() { assertThat(merged).hasSize(1); assertThat(merged) .anySatisfy( - entity -> - assertThat(entity) - .hasType("a") - .hasSchemaUrl("one") - .hasIdSatisfying(id -> assertThat(id).containsEntry("a.id", "a")) - .hasDescriptionSatisfying( - desc -> - assertThat(desc) - .containsEntry("a.desc1", "a") - // Don't merge between versions. - .doesNotContainKey("a.desc2"))); + entity -> { + assertThat(entity.getType()).isEqualTo("a"); + assertThat(entity.getSchemaUrl()).isEqualTo("one"); + assertThat(entity.getId()).containsEntry("a.id", "a"); + assertThat(entity.getDescription()) + .containsEntry("a.desc1", "a") + // Don't merge between versions. + .doesNotContainKey("a.desc2"); + }); } @Test @@ -102,15 +98,15 @@ void testMerge_entities_same_types_different_id() { assertThat(merged).hasSize(1); assertThat(merged) .satisfiesExactly( - e -> - assertThat(e) - .hasSchemaUrl("one") - .hasIdSatisfying(id -> assertThat(id).containsEntry("a.id", "a")) - .hasDescriptionSatisfying( - desc -> - assertThat(desc) - .containsEntry("a.desc1", "a") - .doesNotContainKey("a.desc2"))); + entity -> { + assertThat(entity.getType()).isEqualTo("a"); + assertThat(entity.getSchemaUrl()).isEqualTo("one"); + assertThat(entity.getId()).containsEntry("a.id", "a"); + assertThat(entity.getDescription()) + .containsEntry("a.desc1", "a") + // Don't merge between different ids. + .doesNotContainKey("a.desc2"); + }); } @Test @@ -131,7 +127,8 @@ void testMerge_entities_separate_types_and_schema() { // Make sure we keep both entities when no conflict. assertThat(merged) .satisfiesExactlyInAnyOrder( - a -> assertThat(a).hasType("a"), b -> assertThat(b).hasType("b")); + a -> assertThat(a.getType()).isEqualTo("a"), + b -> assertThat(b.getType()).isEqualTo("b")); } @Test @@ -220,7 +217,7 @@ void testRawAttributeMerge_entity_with_conflict() { Attributes.builder().put("b", 2).put("c", 2).build(), Arrays.asList( Entity.builder("c").withId(Attributes.builder().put("c", 1).build()).build())); - assertThat(result.getConflicts()).satisfiesExactly(e -> assertThat(e).hasType("c")); + assertThat(result.getConflicts()).satisfiesExactly(e -> assertThat(e.getType()).isEqualTo("c")); assertThat(result.getAttributes()) .hasSize(3) .containsEntry("a", 1) @@ -236,7 +233,7 @@ void testAddEntity_reflection() { Entity.builder("a").withId(Attributes.builder().put("a", 1).build()).build()) .build(); assertThat(EntityUtil.getEntities(result)) - .satisfiesExactlyInAnyOrder(e -> assertThat(e).hasType("a")); + .satisfiesExactlyInAnyOrder(e -> assertThat(e.getType()).isEqualTo("a")); } @Test @@ -250,6 +247,7 @@ void testAddAllEntity_reflection() { .build(); assertThat(EntityUtil.getEntities(result)) .satisfiesExactlyInAnyOrder( - e -> assertThat(e).hasType("a"), e -> assertThat(e).hasType("b")); + e -> assertThat(e.getType()).isEqualTo("a"), + e -> assertThat(e.getType()).isEqualTo("b")); } } From f66057c1196db7cc8c8fbebe70b85bd417f351e0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 09:30:02 +0200 Subject: [PATCH 35/42] extract extended opentelemetry only --- .../api/incubator/ExtendedOpenTelemetry.java | 10 +- .../api/incubator/entities/EntityBuilder.java | 53 --- .../incubator/entities/EntityProvider.java | 38 --- .../incubator/entities/NoopEntityBuilder.java | 31 -- .../entities/NoopEntityProvider.java | 21 -- .../internal/otlp/EntityRefMarshaler.java | 83 ----- .../internal/otlp/ResourceMarshaler.java | 21 +- .../internal/otlp/EntityRefMarshalerTest.java | 87 ----- .../incubator/ExtendedOpenTelemetrySdk.java | 4 +- .../ObfuscatedExtendedOpenTelemerySdk.java | 19 +- .../CurrentThreadExecutorService.java | 137 -------- .../incubator/entities/EntityListener.java | 27 -- .../incubator/entities/EntityState.java | 25 -- .../incubator/entities/ResourceDetector.java | 22 -- .../incubator/entities/SdkEntityBuilder.java | 44 --- .../incubator/entities/SdkEntityProvider.java | 69 ---- .../entities/SdkEntityProviderBuilder.java | 51 --- .../incubator/entities/SdkEntityState.java | 39 --- .../entities/SdkResourceSharedState.java | 161 --------- .../entities/detectors/ServiceDetector.java | 52 --- .../detectors/TelemetrySdkDetector.java | 45 --- .../entities/TestEntityProvider.java | 178 ---------- .../DeclarativeConfigurationCreateTest.java | 2 +- .../opentelemetry/sdk/resources/Resource.java | 127 ++++--- .../sdk/resources/ResourceBuilder.java | 36 +- .../internal/AttributeCheckUtil.java | 63 ---- .../sdk/resources/internal/Entity.java | 73 ---- .../sdk/resources/internal/EntityBuilder.java | 44 --- .../sdk/resources/internal/EntityUtil.java | 311 ------------------ .../internal/RawAttributeMergeResult.java | 32 -- .../sdk/resources/internal/SdkEntity.java | 48 --- .../resources/internal/SdkEntityBuilder.java | 62 ---- .../sdk/resources/ResourceTest.java | 27 -- .../resources/internal/EntityUtilTest.java | 253 -------------- .../sdk/logs/LoggerSharedState.java | 8 +- .../sdk/logs/SdkLoggerProvider.java | 2 +- .../sdk/logs/SdkLoggerProviderBuilder.java | 24 +- .../logs/internal/SdkLoggerProviderUtil.java | 22 -- .../sdk/logs/LoggerSharedStateTest.java | 2 +- .../logs/SdkLoggerProviderBuilderTest.java | 24 +- .../sdk/logs/SdkLoggerProviderTest.java | 16 +- .../sdk/metrics/SdkMeterProvider.java | 3 +- .../sdk/metrics/SdkMeterProviderBuilder.java | 26 +- .../internal/SdkMeterProviderUtil.java | 22 -- .../state/MeterProviderSharedState.java | 15 +- .../sdk/metrics/InstrumentBuilderTest.java | 2 +- .../sdk/trace/SdkTracerProvider.java | 4 +- .../sdk/trace/SdkTracerProviderBuilder.java | 25 +- .../sdk/trace/TracerSharedState.java | 11 +- .../trace/internal/SdkTracerProviderUtil.java | 20 -- .../sdk/trace/SdkSpanBuilderTest.java | 11 +- 51 files changed, 134 insertions(+), 2398 deletions(-) delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java delete mode 100644 api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java delete mode 100644 exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java delete mode 100644 exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java delete mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java delete mode 100644 sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java delete mode 100644 sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java index 3021b24a2c4..73514616cb3 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/ExtendedOpenTelemetry.java @@ -6,12 +6,12 @@ package io.opentelemetry.api.incubator; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.api.incubator.config.ConfigProvider; -/** Extension to {@link OpenTelemetry} that adds {@link EntityProvider}. */ +/** Extension to {@link OpenTelemetry} that supports getting {@link ConfigProvider}. */ public interface ExtendedOpenTelemetry extends OpenTelemetry { - /** Returns the {@link EntityProvider} for this {@link OpenTelemetry}. */ - default EntityProvider getEntityProvider() { - return EntityProvider.noop(); + /** Returns the {@link ConfigProvider} for this {@link OpenTelemetry}. */ + default ConfigProvider getConfigProvider() { + return ConfigProvider.noop(); } } diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java deleted file mode 100644 index 9795ba2f724..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityBuilder.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -import io.opentelemetry.api.common.Attributes; - -/** - * A builder of an Entity that allows to add identifying or descriptive {@link Attributes}, as well - * as type and schema_url. - * - *

Entity represents an object of interest associated with produced telemetry: traces, metrics or - * logs. - * - *

For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service - * entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we - * want to associate metrics with in this case. - * - *

Entities may be also associated with produced telemetry indirectly. For example a service that - * produces telemetry is also related with a process in which the service runs, so we say that the - * Service entity is related to the Process entity. The process normally also runs on a host, so we - * say that the Process entity is related to the Host entity. - */ -public interface EntityBuilder { - /** - * Assign an OpenTelemetry schema URL to the resulting Entity. - * - * @param schemaUrl The URL of the OpenTelemetry schema being used to create this Entity. - * @return this - */ - EntityBuilder setSchemaUrl(String schemaUrl); - - /** - * Modify the descriptive attributes of this Entity. - * - * @param description The {@link Attributes} which describe this Entity. - * @return this - */ - EntityBuilder withDescription(Attributes description); - - /** - * Modify the identifying attributes of this Entity. - * - * @param id The {@link Attributes} which identify this Entity. - * @return this - */ - EntityBuilder withId(Attributes id); - - /** Emits the current entity. */ - void emit(); -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java deleted file mode 100644 index f9854724c4b..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/EntityProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -/** - * A registry for interacting with {@code Resource}s. The name Provider is for consistency - * with other languages and it is NOT loaded using reflection. - */ -public interface EntityProvider { - /** - * Returns a no-op {@link EntityProvider} which only creates no-op {@link EntityBuilder}s which do - * not record nor are emitted. - */ - static EntityProvider noop() { - return NoopEntityProvider.INSTANCE; - } - - /** - * Removes an entity from this resource. - * - * @param entityType the type of entity to remove. - * @return true if entity was found and removed. - */ - boolean removeEntity(String entityType); - - /** - * Attaches an entity to the current {@code Resource}. - * - *

This will only add new entities or update description of existing entities. - * - * @param entityType The type of the entity. - * @return A builder that can construct an entity. - */ - EntityBuilder attachOrUpdateEntity(String entityType); -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java deleted file mode 100644 index 59474533a98..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -import io.opentelemetry.api.common.Attributes; - -final class NoopEntityBuilder implements EntityBuilder { - - static final EntityBuilder INSTANCE = new NoopEntityBuilder(); - - @Override - public EntityBuilder setSchemaUrl(String schemaUrl) { - return this; - } - - @Override - public EntityBuilder withDescription(Attributes description) { - return this; - } - - @Override - public EntityBuilder withId(Attributes id) { - return this; - } - - @Override - public void emit() {} -} diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java deleted file mode 100644 index 08a60093531..00000000000 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/entities/NoopEntityProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.incubator.entities; - -final class NoopEntityProvider implements EntityProvider { - - static final EntityProvider INSTANCE = new NoopEntityProvider(); - - @Override - public boolean removeEntity(String entityType) { - return false; - } - - @Override - public EntityBuilder attachOrUpdateEntity(String entityType) { - return NoopEntityBuilder.INSTANCE; - } -} diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java deleted file mode 100644 index e77ab7f8308..00000000000 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshaler.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.internal.otlp; - -import io.opentelemetry.api.internal.StringUtils; -import io.opentelemetry.exporter.internal.marshal.MarshalerUtil; -import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize; -import io.opentelemetry.exporter.internal.marshal.Serializer; -import io.opentelemetry.proto.common.v1.internal.EntityRef; -import io.opentelemetry.sdk.resources.internal.Entity; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import javax.annotation.Nullable; - -/** - * A Marshaler of {@link io.opentelemetry.sdk.resources.internal.Entity}. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -final class EntityRefMarshaler extends MarshalerWithSize { - @Nullable private final byte[] schemaUrlUtf8; - private final byte[] typeUtf8; - private final byte[][] idKeysUtf8; - private final byte[][] descriptionKeysUtf8; - - @Override - protected void writeTo(Serializer output) throws IOException { - if (schemaUrlUtf8 != null) { - output.writeString(EntityRef.SCHEMA_URL, schemaUrlUtf8); - } - output.writeString(EntityRef.TYPE, typeUtf8); - output.writeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); - output.writeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8); - } - - /** Consttructs an entity reference marshaler from a full entity. */ - static EntityRefMarshaler createForEntity(Entity e) { - byte[] schemaUrlUtf8 = null; - if (!StringUtils.isNullOrEmpty(e.getSchemaUrl())) { - schemaUrlUtf8 = e.getSchemaUrl().getBytes(StandardCharsets.UTF_8); - } - return new EntityRefMarshaler( - schemaUrlUtf8, - e.getType().getBytes(StandardCharsets.UTF_8), - e.getId().asMap().keySet().stream() - .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) - .toArray(byte[][]::new), - e.getDescription().asMap().keySet().stream() - .map(key -> key.getKey().getBytes(StandardCharsets.UTF_8)) - .toArray(byte[][]::new)); - } - - private EntityRefMarshaler( - @Nullable byte[] schemaUrlUtf8, - byte[] typeUtf8, - byte[][] idKeysUtf8, - byte[][] descriptionKeysUtf8) { - super(calculateSize(schemaUrlUtf8, typeUtf8, idKeysUtf8, descriptionKeysUtf8)); - this.schemaUrlUtf8 = schemaUrlUtf8; - this.typeUtf8 = typeUtf8; - this.idKeysUtf8 = idKeysUtf8; - this.descriptionKeysUtf8 = descriptionKeysUtf8; - } - - private static int calculateSize( - @Nullable byte[] schemaUrlUtf8, - byte[] typeUtf8, - byte[][] idKeysUtf8, - byte[][] descriptionKeysUtf8) { - int size = 0; - if (schemaUrlUtf8 != null) { - size += MarshalerUtil.sizeBytes(EntityRef.SCHEMA_URL, schemaUrlUtf8); - } - size += MarshalerUtil.sizeBytes(EntityRef.TYPE, typeUtf8); - MarshalerUtil.sizeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8); - MarshalerUtil.sizeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8); - return size; - } -} diff --git a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java index 8be7df10099..b3395448a79 100644 --- a/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java +++ b/exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java @@ -10,7 +10,6 @@ import io.opentelemetry.exporter.internal.marshal.MarshalerWithSize; import io.opentelemetry.exporter.internal.marshal.Serializer; import io.opentelemetry.proto.resource.v1.internal.Resource; -import io.opentelemetry.sdk.resources.internal.EntityUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -38,10 +37,7 @@ public static ResourceMarshaler create(io.opentelemetry.sdk.resources.Resource r RealResourceMarshaler realMarshaler = new RealResourceMarshaler( - KeyValueMarshaler.createForAttributes(resource.getAttributes()), - EntityUtil.getEntities(resource).stream() - .map(EntityRefMarshaler::createForEntity) - .toArray(MarshalerWithSize[]::new)); + KeyValueMarshaler.createForAttributes(resource.getAttributes())); ByteArrayOutputStream binaryBos = new ByteArrayOutputStream(realMarshaler.getBinarySerializedSize()); @@ -74,26 +70,19 @@ public void writeTo(Serializer output) throws IOException { private static final class RealResourceMarshaler extends MarshalerWithSize { private final KeyValueMarshaler[] attributes; - private final MarshalerWithSize[] entityRefs; - private RealResourceMarshaler(KeyValueMarshaler[] attributes, MarshalerWithSize[] entityRefs) { - super(calculateSize(attributes, entityRefs)); + private RealResourceMarshaler(KeyValueMarshaler[] attributes) { + super(calculateSize(attributes)); this.attributes = attributes; - this.entityRefs = entityRefs; } @Override protected void writeTo(Serializer output) throws IOException { output.serializeRepeatedMessage(Resource.ATTRIBUTES, attributes); - output.serializeRepeatedMessage(Resource.ENTITY_REFS, entityRefs); } - private static int calculateSize( - KeyValueMarshaler[] attributeMarshalers, MarshalerWithSize[] entityRefs) { - int size = 0; - size += MarshalerUtil.sizeRepeatedMessage(Resource.ATTRIBUTES, attributeMarshalers); - size += size += MarshalerUtil.sizeRepeatedMessage(Resource.ENTITY_REFS, entityRefs); - return size; + private static int calculateSize(KeyValueMarshaler[] attributeMarshalers) { + return MarshalerUtil.sizeRepeatedMessage(Resource.ATTRIBUTES, attributeMarshalers); } } } diff --git a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java b/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java deleted file mode 100644 index b5e8cad39f5..00000000000 --- a/exporters/otlp/common/src/test/java/io/opentelemetry/exporter/internal/otlp/EntityRefMarshalerTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.exporter.internal.otlp; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.protobuf.util.JsonFormat; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.exporter.internal.marshal.Marshaler; -import io.opentelemetry.proto.common.v1.EntityRef; -import io.opentelemetry.sdk.resources.internal.Entity; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; - -class EntityRefMarshalerTest { - @Test - void toEntityRefs() { - Entity e = - Entity.builder("test") - .setSchemaUrl("test-url") - .withDescription(Attributes.builder().put("desc.key", "desc.value").build()) - .withId(Attributes.builder().put("id.key", "id.value").build()) - .build(); - EntityRef proto = parse(EntityRef.getDefaultInstance(), EntityRefMarshaler.createForEntity(e)); - assertThat(proto.getType()).isEqualTo("test"); - assertThat(proto.getSchemaUrl()).isEqualTo("test-url"); - assertThat(proto.getIdKeysList()).containsExactly("id.key"); - assertThat(proto.getDescriptionKeysList()).containsExactly("desc.key"); - } - - @SuppressWarnings("unchecked") - private static T parse(T prototype, Marshaler marshaler) { - byte[] serialized = toByteArray(marshaler); - T result; - try { - result = (T) prototype.newBuilderForType().mergeFrom(serialized).build(); - } catch (InvalidProtocolBufferException e) { - throw new UncheckedIOException(e); - } - // Our marshaler should produce the exact same length of serialized output (for example, field - // default values are not outputted), so we check that here. The output itself may have slightly - // different ordering, mostly due to the way we don't output oneof values in field order all the - // tieme. If the lengths are equal and the resulting protos are equal, the marshaling is - // guaranteed to be valid. - assertThat(result.getSerializedSize()).isEqualTo(serialized.length); - - // Compare JSON - String json = toJson(marshaler); - Message.Builder builder = prototype.newBuilderForType(); - try { - JsonFormat.parser().merge(json, builder); - } catch (InvalidProtocolBufferException e) { - throw new UncheckedIOException(e); - } - assertThat(builder.build()).isEqualTo(result); - - return result; - } - - private static byte[] toByteArray(Marshaler marshaler) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - marshaler.writeBinaryTo(bos); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return bos.toByteArray(); - } - - private static String toJson(Marshaler marshaler) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - marshaler.writeJsonTo(bos); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return new String(bos.toByteArray(), StandardCharsets.UTF_8); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java index d4fdb19647b..ffb5511521b 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java @@ -6,14 +6,14 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.incubator.ExtendedOpenTelemetry; -import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.io.Closeable; -/** A new interface for creating OpenTelemetrySdk that supports {@link EntityProvider}. */ +/** A new interface for creating OpenTelemetrySdk that supports getting {@link ConfigProvider}. */ public interface ExtendedOpenTelemetrySdk extends ExtendedOpenTelemetry, Closeable { /** * Shutdown the SDK. Calls {@link SdkTracerProvider#shutdown()}, {@link diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java index d868e0b26a5..17ee5cbe016 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.extension.incubator; +import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.api.incubator.entities.EntityBuilder; import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.api.logs.LoggerBuilder; @@ -36,16 +37,16 @@ final class ObfuscatedExtendedOpenTelemerySdk implements ExtendedOpenTelemetrySd private final ObfuscatedTracerProvider tracerProvider; private final ObfuscatedMeterProvider meterProvider; private final ObfuscatedLoggerProvider loggerProvider; - private final ObfuscatedEntityProvider entityProvider; + private final ObfuscatedEntityProvider configProvider; private final ContextPropagators propagators; ObfuscatedExtendedOpenTelemerySdk( - SdkEntityProvider entityProvider, + ConfigProvider configProvider, SdkTracerProvider tracerProvider, SdkMeterProvider meterProvider, SdkLoggerProvider loggerProvider, ContextPropagators propagators) { - this.entityProvider = new ObfuscatedEntityProvider(entityProvider); + this.configProvider = new ObfuscatedEntityProvider(configProvider); this.tracerProvider = new ObfuscatedTracerProvider(tracerProvider); this.meterProvider = new ObfuscatedMeterProvider(meterProvider); this.loggerProvider = new ObfuscatedLoggerProvider(loggerProvider); @@ -70,6 +71,11 @@ public void close() { shutdown().join(10, TimeUnit.SECONDS); } + @Override + public ConfigProvider getConfigProvider() { + return configProvider; + } + @Override public TracerProvider getTracerProvider() { return tracerProvider; @@ -85,11 +91,6 @@ public LoggerProvider getLogsBridge() { return loggerProvider; } - @Override - public EntityProvider getEntityProvider() { - return entityProvider; - } - @Override public ContextPropagators getPropagators() { return propagators; @@ -99,7 +100,7 @@ public ContextPropagators getPropagators() { public String toString() { return "ExtendedOpenTelemetrySdk{" + "entityProvider=" - + entityProvider.unobfuscate() + + configProvider.unobfuscate() + ", tracerProvider=" + tracerProvider.unobfuscate() + ", meterProvider=" diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java deleted file mode 100644 index 8718ecf1804..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/CurrentThreadExecutorService.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * An executor service that runs all jobs immediately on the current thread. - * - *

We use this so SDK users can determine how to isolate {@link EntityListener}s with the default - * being no isolation of events. - */ -final class CurrentThreadExecutorService implements ExecutorService { - private volatile boolean shutdown = false; - - @Override - public void execute(Runnable command) { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - command.run(); - } - - @Override - public List> invokeAll(Collection> tasks) { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - // Execute all tasks synchronously and collect their Futures - return tasks.stream().map(task -> submit(task)).collect(Collectors.toList()); - } - - @Override - public List> invokeAll( - Collection> tasks, long timeout, TimeUnit unit) { - return invokeAll(tasks); - } - - @Override - public T invokeAny(Collection> tasks) - throws InterruptedException, ExecutionException { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - // Execute all tasks synchronously and return first success. - for (Callable task : tasks) { - try { - // We wrap the task in a `submit` call to get ExecutionExceptions. - return submit(task).get(); - } catch (ExecutionException e) { - // Ignore this error, and try the next one. - } - } - throw new ExecutionException("No tasks completed successfully", null); - } - - @Override - public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - return invokeAny(tasks); - } - - @Override - public Future submit(Callable task) { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - FutureTask future = new FutureTask<>(task); - // Run in this thread. - future.run(); - return future; - } - - @Override - public Future submit(Runnable task) { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - FutureTask future = new FutureTask<>(task, null); - // Run in this thread. - future.run(); - return future; - } - - @Override - public Future submit(Runnable task, T result) { - if (shutdown) { - throw new RejectedExecutionException("ExecutorService is shut down"); - } - FutureTask future = new FutureTask<>(task, result); - // Run in this thread. - future.run(); - return future; - } - - @Override - public boolean isShutdown() { - return shutdown; - } - - @Override - public boolean isTerminated() { - return shutdown; - } - - @Override - public void shutdown() { - shutdown = true; - } - - @Override - public List shutdownNow() { - shutdown = true; - return Collections.emptyList(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) { - return isTerminated(); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java deleted file mode 100644 index 51fc35fd906..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityListener.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.sdk.resources.Resource; - -/** A listener for changes in the EntityState of this SDK. */ -public interface EntityListener { - /** - * Called when an entity has been added or its state has changed. - * - * @param state The current state of the entity. - * @param resource The current state of the Resource. - */ - public void onEntityState(EntityState state, Resource resource); - - /** - * Called when an entity has been removed. - * - * @param state The current state of the removed entity. - * @param resource The current state of the Resource. - */ - public void onEntityDelete(EntityState state, Resource resource); -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java deleted file mode 100644 index 07ef93bf359..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/EntityState.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; - -/** The current state of an Entity. */ -public interface EntityState { - /** Returns the type of the Entity. */ - String getType(); - - /** Returns the schema_url of the Entity, or null. */ - @Nullable - String getSchemaUrl(); - - /** Returns the identity of the Entity. */ - Attributes getId(); - - /** Returns the description of the Entity. */ - Attributes getDescription(); -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java deleted file mode 100644 index a7163908655..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/ResourceDetector.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.incubator.entities.EntityProvider; - -/** - * The Resource detector in the SDK is responsible for detecting possible entities that could - * identify the SDK (called "associated entities"). For Example, if the SDK is running in a - * kubernetes pod, it may provide an Entity for that pod. - */ -public interface ResourceDetector { - /** - * Reports detected entities. - * - * @param provider The provider where entities are reported. - */ - public void report(EntityProvider provider); -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java deleted file mode 100644 index 3cf68e2f56c..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.entities.EntityBuilder; -import io.opentelemetry.sdk.resources.internal.Entity; -import java.util.function.Consumer; - -final class SdkEntityBuilder implements EntityBuilder { - private final io.opentelemetry.sdk.resources.internal.EntityBuilder builder; - private final Consumer emitter; - - SdkEntityBuilder(String entityType, Consumer emitter) { - this.builder = Entity.builder(entityType); - this.emitter = emitter; - } - - @Override - public EntityBuilder setSchemaUrl(String schemaUrl) { - builder.setSchemaUrl(schemaUrl); - return this; - } - - @Override - public EntityBuilder withDescription(Attributes description) { - builder.withDescription(description); - return this; - } - - @Override - public EntityBuilder withId(Attributes id) { - builder.withId(id); - return this; - } - - @Override - public void emit() { - emitter.accept(builder.build()); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java deleted file mode 100644 index aebd4782176..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProvider.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.incubator.entities.EntityBuilder; -import io.opentelemetry.api.incubator.entities.EntityProvider; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.resources.Resource; -import java.util.concurrent.TimeUnit; - -/** The SDK implementation of {@link EntityProvider}. */ -public final class SdkEntityProvider implements EntityProvider { - // TODO - Give control over listener execution model. - // For now, just run everything on the same thread as the entity-attach call. - private final SdkResourceSharedState state = - new SdkResourceSharedState(new CurrentThreadExecutorService()); - - /** - * Obtains the current {@link Resource} for the SDK. - * - * @return the active {@link Resource} for this SDK. - */ - public Resource getResource() { - return state.getResource(); - } - - /** - * Creates a builder for SdkEntityProvider. - * - * @return The new builder. - */ - public static SdkEntityProviderBuilder builder() { - return new SdkEntityProviderBuilder(); - } - - @Override - public String toString() { - return "SdkResourceProvider{}"; - } - - @Override - public boolean removeEntity(String entityType) { - return state.removeEntity(entityType); - } - - @Override - public EntityBuilder attachOrUpdateEntity(String entityType) { - return new SdkEntityBuilder(entityType, state::addOrUpdateEntity); - } - - public void onChange(EntityListener listener) { - state.addListener(listener); - } - - /** - * Shutdown the provider. The resulting {@link CompletableResultCode} completes when all complete. - */ - public CompletableResultCode shutdown() { - return state.shutdown(); - } - - /** Close the provider. Calls {@link #shutdown()} and blocks waiting for it to complete. */ - public void close() { - shutdown().join(10, TimeUnit.SECONDS); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java deleted file mode 100644 index 4a94185b3c4..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityProviderBuilder.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.sdk.extension.incubator.entities.detectors.ServiceDetector; -import io.opentelemetry.sdk.extension.incubator.entities.detectors.TelemetrySdkDetector; -import java.util.ArrayList; -import java.util.List; - -/** A builder for {@link SdkEntityProvider}. */ -public final class SdkEntityProviderBuilder { - private final List detectors = new ArrayList<>(); - private boolean includeDefaults = true; - - /** - * Adds a {@link ResourceDetector} that will be run when constructing this provider. - * - * @param detector The resource detector. - * @return this - */ - public SdkEntityProviderBuilder addDetector(ResourceDetector detector) { - this.detectors.add(detector); - return this; - } - - /** - * Configure whether to include SDK default resoruce detection. - * - * @param include true if defaults should be used. - * @return this - */ - public SdkEntityProviderBuilder includeDefaults(boolean include) { - this.includeDefaults = include; - return this; - } - - public SdkEntityProvider build() { - // TODO - have defaults in the front? - if (includeDefaults) { - detectors.add(new ServiceDetector()); - detectors.add(new TelemetrySdkDetector()); - } - SdkEntityProvider result = new SdkEntityProvider(); - // TODO - Should we move these onto the provider? - detectors.forEach(d -> d.report(result)); - return result; - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java deleted file mode 100644 index de049edd46e..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkEntityState.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.resources.internal.Entity; -import javax.annotation.Nullable; - -final class SdkEntityState implements EntityState { - private final Entity delegate; - - SdkEntityState(Entity delegate) { - this.delegate = delegate; - } - - @Override - public String getType() { - return delegate.getType(); - } - - @Override - @Nullable - public String getSchemaUrl() { - return delegate.getSchemaUrl(); - } - - @Override - public Attributes getId() { - return delegate.getId(); - } - - @Override - public Attributes getDescription() { - return delegate.getDescription(); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java deleted file mode 100644 index afb28929fb1..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/SdkResourceSharedState.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import io.opentelemetry.api.internal.GuardedBy; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.internal.ThrottlingLogger; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.internal.Entity; -import io.opentelemetry.sdk.resources.internal.EntityUtil; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.Nullable; - -/** - * This class does all state and listener management for a {@link Resource} constructed of {@link - * Entity}s. - */ -final class SdkResourceSharedState { - - // The currently advertised Resource to other SDK providers. - private final AtomicReference resource = new AtomicReference<>(Resource.empty()); - private final Object writeLock = new Object(); - private final List listeners = new CopyOnWriteArrayList<>(); - private final ExecutorService listenerExecutor; - - // Our internal storage of registered entities. - @GuardedBy("writeLock") - private final ArrayList entities = new ArrayList<>(); - - private static final ThrottlingLogger logger = - new ThrottlingLogger(Logger.getLogger(SdkResourceSharedState.class.getName())); - - SdkResourceSharedState(ExecutorService listenerExecutor) { - this.listenerExecutor = listenerExecutor; - } - - /** - * Shutdown the provider. The resulting {@link CompletableResultCode} completes when all complete. - */ - CompletableResultCode shutdown() { - // TODO - Actually figure out how to wait for shutdown and deal with pending tasks. - listenerExecutor.shutdown(); - return CompletableResultCode.ofSuccess(); - } - - /** Returns the currently active resource. */ - public Resource getResource() { - Resource result = resource.get(); - // We do this to make NullAway happy. - if (result == null) { - throw new IllegalStateException("SdkResource should never have null resource"); - } - return result; - } - - private static boolean hasSameSchemaUrl(Entity lhs, Entity rhs) { - if (lhs.getSchemaUrl() != null) { - return lhs.getSchemaUrl().equals(rhs.getSchemaUrl()); - } - return rhs.getSchemaUrl() == null; - } - - /** - * Removes an entity by type and notifies listeners. - * - * @param entityType The entity type to remove. - */ - boolean removeEntity(String entityType) { - synchronized (writeLock) { - @Nullable Entity removed = null; - for (Entity existing : entities) { - if (existing.getType().equals(entityType)) { - removed = existing; - } - } - if (removed == null) { - return false; - } - entities.remove(removed); - Resource result = EntityUtil.createResource(entities); - resource.lazySet(result); - publishEntityDelete(new SdkEntityState(removed), result); - return true; - } - } - - /** - * Adds an entity and notifies listeners. - * - *

Note: This will not add an entity on conflict. This will update the description if the - * entity already exists. - * - * @param e The entity type to add. - */ - void addOrUpdateEntity(Entity e) { - synchronized (writeLock) { - @Nullable Entity conflict = null; - for (Entity existing : entities) { - if (existing.getType().equals(e.getType())) { - conflict = existing; - } - } - Entity newState = e; - if (conflict != null) { - if (hasSameSchemaUrl(conflict, e) && conflict.getId().equals(e.getId())) { - // We can merge descriptive attributes. - entities.remove(conflict); - io.opentelemetry.sdk.resources.internal.EntityBuilder newEntity = - Entity.builder(conflict.getType()) - .withId(conflict.getId()) - .withDescription( - conflict.getDescription().toBuilder().putAll(e.getDescription()).build()); - if (conflict.getSchemaUrl() != null) { - newEntity.setSchemaUrl(conflict.getSchemaUrl()); - } - newState = newEntity.build(); - entities.add(newState); - } else { - logger.log(Level.INFO, "Ignoring new entity, conflicts with existing: " + e); - return; - } - } else { - entities.add(e); - } - Resource result = EntityUtil.createResource(entities); - resource.lazySet(result); - publishEntityStateChange(new SdkEntityState(newState), result); - } - } - - @SuppressWarnings("FutureReturnValueIgnored") - private void publishEntityStateChange(EntityState state, Resource resource) { - for (EntityListener listener : listeners) { - // We isolate listener execution via executor, if configured. - // We ignore failures on futures to avoid having one listener block others. - listenerExecutor.submit(() -> listener.onEntityState(state, resource)); - } - } - - @SuppressWarnings("FutureReturnValueIgnored") - private void publishEntityDelete(EntityState deleted, Resource resource) { - for (EntityListener listener : listeners) { - // We isolate listener execution via executor, if configured. - // We ignore failures on futures to avoid having one listener block others. - listenerExecutor.submit(() -> listener.onEntityDelete(deleted, resource)); - } - } - - public void addListener(EntityListener listener) { - listeners.add(listener); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java deleted file mode 100644 index a0acc9b5c38..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/ServiceDetector.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities.detectors; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.entities.EntityProvider; -import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; -import java.util.UUID; - -/** - * Detection for {@code service} entity. - * - *

See: service - * entity - */ -public final class ServiceDetector implements ResourceDetector { - private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.34.0"; - private static final String ENTITY_TYPE = "service"; - private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); - private static final AttributeKey SERVICE_INSTANCE_ID = - AttributeKey.stringKey("service.instance.id"); - private static final UUID FALLBACK_INSTANCE_ID = UUID.randomUUID(); - - private static String getServiceName() { - return System.getenv().getOrDefault("OTEL_SERVICE_NAME", "unknown_service:java"); - } - - private static String getServiceInstanceId() { - // TODO - no way for users to specify a non-default. - return FALLBACK_INSTANCE_ID.toString(); - } - - @Override - public void report(EntityProvider provider) { - // We only run on startup. - provider - .attachOrUpdateEntity(ENTITY_TYPE) - .setSchemaUrl(SCHEMA_URL) - .withId( - // Note: Identifying attributes MUST be provided together. - Attributes.builder() - .put(SERVICE_NAME, getServiceName()) - .put(SERVICE_INSTANCE_ID, getServiceInstanceId()) - .build()) - // TODO - Need to figure out version - .emit(); - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java deleted file mode 100644 index 6c68198f49e..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/detectors/TelemetrySdkDetector.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities.detectors; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.incubator.entities.EntityProvider; -import io.opentelemetry.sdk.common.internal.OtelVersion; -import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; - -/** - * Detection for {@code telemetry.sdk} entity. - * - *

See: teleemtry.sdk - * entity - */ -public final class TelemetrySdkDetector implements ResourceDetector { - private static final String SCHEMA_URL = "https://opentelemetry.io/schemas/1.34.0"; - private static final String ENTITY_TYPE = "telemetry.sdk"; - private static final AttributeKey TELEMETRY_SDK_LANGUAGE = - AttributeKey.stringKey("telemetry.sdk.language"); - private static final AttributeKey TELEMETRY_SDK_NAME = - AttributeKey.stringKey("telemetry.sdk.name"); - private static final AttributeKey TELEMETRY_SDK_VERSION = - AttributeKey.stringKey("telemetry.sdk.version"); - - @Override - public void report(EntityProvider provider) { - provider - .attachOrUpdateEntity(ENTITY_TYPE) - .setSchemaUrl(SCHEMA_URL) - .withId( - Attributes.builder() - .put(TELEMETRY_SDK_NAME, "opentelemetry") - .put(TELEMETRY_SDK_LANGUAGE, "java") - .build()) - .withDescription( - Attributes.builder().put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION).build()) - .emit(); - } -} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java deleted file mode 100644 index 9bf74c12bff..00000000000 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestEntityProvider.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator.entities; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.internal.EntityUtil; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -class TestEntityProvider { - @Test - void defaults_includeServiceAndSdk() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(true).build(); - - assertThat(provider.getResource().getAttributes()) - .containsKey("service.name") - .containsKey("service.instance.id") - .containsKey("telemetry.sdk.language") - .containsKey("telemetry.sdk.name") - .containsKey("telemetry.sdk.version"); - assertThat(provider.getResource().getSchemaUrl()) - .isEqualTo("https://opentelemetry.io/schemas/1.34.0"); - - assertThat(EntityUtil.getEntities(provider.getResource())) - .satisfiesExactlyInAnyOrder( - e -> assertThat(e.getType()).isEqualTo("service"), - e -> assertThat(e.getType()).isEqualTo("telemetry.sdk")); - } - - @Test - void resource_updatesDescription() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .withDescription(Attributes.builder().put("one.desc", "desc").build()) - .emit(); - - assertThat(provider.getResource().getAttributes()) - .hasSize(2) - .containsKey("one.id") - .containsKey("one.desc"); - } - - @Test - void resource_ignoresNewIds() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 2).build()) - .withDescription(Attributes.builder().put("one.desc", "desc").build()) - .emit(); - - assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); - } - - @Test - void resource_ignoresNewSchemaUrl() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("two") - .withId(Attributes.builder().put("one.id", 1).build()) - .withDescription(Attributes.builder().put("one.desc", "desc").build()) - .emit(); - - assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); - } - - @Test - void resource_addsNewEntity() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - - provider - .attachOrUpdateEntity("two") - .setSchemaUrl("two") - .withId(Attributes.builder().put("two.id", 2).build()) - .emit(); - - assertThat(provider.getResource().getAttributes()) - .hasSize(2) - .containsKey("one.id") - .containsKey("two.id"); - } - - @Test - void resource_removesEntity() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - - assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); - - assertThat(provider.removeEntity("one")).isTrue(); - assertThat(provider.getResource().getAttributes()).isEmpty(); - } - - @Test - void entityListener_notifiesOnAdd() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - - EntityListener listener = mock(EntityListener.class); - provider.onChange(listener); - - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - ArgumentCaptor entityCapture = ArgumentCaptor.forClass(EntityState.class); - ArgumentCaptor resourceCapture = ArgumentCaptor.forClass(Resource.class); - verify(listener, times(1)).onEntityState(entityCapture.capture(), resourceCapture.capture()); - assertThat(entityCapture.getValue().getType()).isEqualTo("one"); - assertThat(resourceCapture.getValue().getAttributes()).hasSize(1).containsKey("one.id"); - } - - @Test - void entityListener_notifiesOnRemove() { - SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); - provider - .attachOrUpdateEntity("one") - .setSchemaUrl("one") - .withId(Attributes.builder().put("one.id", 1).build()) - .emit(); - EntityListener listener = mock(EntityListener.class); - provider.onChange(listener); - - provider.removeEntity("one"); - ArgumentCaptor entityCapture = ArgumentCaptor.forClass(EntityState.class); - ArgumentCaptor resourceCapture = ArgumentCaptor.forClass(Resource.class); - verify(listener, times(1)).onEntityDelete(entityCapture.capture(), resourceCapture.capture()); - assertThat(entityCapture.getValue().getType()).isEqualTo("one"); - assertThat(resourceCapture.getValue().getAttributes()).isEmpty(); - } -} diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java index b71a6e7fcc5..b72c0bc8ebe 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java @@ -165,7 +165,7 @@ void create_ModelCustomizer() { DeclarativeConfigurationCreateTest.class.getClassLoader())); assertThat(sdk.toString()) .contains( - "resource=Resource{schemaUrl=null, rawAttributes={" + "resource=Resource{schemaUrl=null, attributes={" + "color=\"blue\", " + "foo=\"bar\", " + "service.name=\"unknown_service:java\", " diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java index e72e5db90a4..c406ef87a5e 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java @@ -9,13 +9,11 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.api.internal.Utils; import io.opentelemetry.sdk.common.internal.OtelVersion; -import io.opentelemetry.sdk.resources.internal.AttributeCheckUtil; -import io.opentelemetry.sdk.resources.internal.Entity; -import io.opentelemetry.sdk.resources.internal.EntityUtil; -import java.util.Collection; -import java.util.Collections; import java.util.Objects; +import java.util.logging.Logger; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; @@ -26,6 +24,8 @@ @Immutable @AutoValue public abstract class Resource { + private static final Logger logger = Logger.getLogger(Resource.class.getName()); + private static final AttributeKey SERVICE_NAME = AttributeKey.stringKey("service.name"); private static final AttributeKey TELEMETRY_SDK_LANGUAGE = AttributeKey.stringKey("telemetry.sdk.language"); @@ -34,6 +34,13 @@ public abstract class Resource { private static final AttributeKey TELEMETRY_SDK_VERSION = AttributeKey.stringKey("telemetry.sdk.version"); + private static final int MAX_LENGTH = 255; + private static final String ERROR_MESSAGE_INVALID_CHARS = + " should be a ASCII string with a length greater than 0 and not exceed " + + MAX_LENGTH + + " characters."; + private static final String ERROR_MESSAGE_INVALID_VALUE = + " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters."; private static final Resource EMPTY = create(Attributes.empty()); private static final Resource TELEMETRY_SDK; @@ -84,7 +91,7 @@ public static Resource empty() { * @return a {@code Resource}. * @throws NullPointerException if {@code attributes} is null. * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable - * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. + * ASCII string or exceed {@link #MAX_LENGTH} characters. */ public static Resource create(Attributes attributes) { return create(attributes, null); @@ -98,27 +105,11 @@ public static Resource create(Attributes attributes) { * @return a {@code Resource}. * @throws NullPointerException if {@code attributes} is null. * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable - * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. + * ASCII string or exceed {@link #MAX_LENGTH} characters. */ public static Resource create(Attributes attributes, @Nullable String schemaUrl) { - return create(attributes, schemaUrl, Collections.emptyList()); - } - - /** - * Returns a {@link Resource}. - * - * @param attributes a map of {@link Attributes} that describe the resource. - * @param schemaUrl The URL of the OpenTelemetry schema used to create this Resource. - * @param entities The set of detected {@link Entity}s that participate in this resource. - * @return a {@code Resource}. - * @throws NullPointerException if {@code attributes} is null. - * @throws IllegalArgumentException if attribute key or attribute value is not a valid printable - * ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters. - */ - static Resource create( - Attributes attributes, @Nullable String schemaUrl, Collection entities) { - AttributeCheckUtil.checkAttributes(Objects.requireNonNull(attributes, "attributes")); - return new AutoValue_Resource(schemaUrl, attributes, entities); + checkAttributes(Objects.requireNonNull(attributes, "attributes")); + return new AutoValue_Resource(schemaUrl, attributes); } /** @@ -130,38 +121,12 @@ static Resource create( @Nullable public abstract String getSchemaUrl(); - /** - * Returns a map of attributes that describe the resource, not associated with entites. - * - * @return a map of attributes. - */ - abstract Attributes getRawAttributes(); - - /** - * Returns a collectoion of associated entities. - * - * @return a collection of entities. - */ - abstract Collection getEntities(); - /** * Returns a map of attributes that describe the resource. * * @return a map of attributes. */ - // @Memoized - This breaks nullaway. - public Attributes getAttributes() { - AttributesBuilder result = Attributes.builder(); - getEntities() - .forEach( - e -> { - result.putAll(e.getId()); - result.putAll(e.getDescription()); - }); - // In merge rules, raw comes last, so we return these last. - result.putAll(getRawAttributes()); - return result.build(); - } + public abstract Attributes getAttributes(); /** * Returns the value for a given resource attribute key. @@ -181,7 +146,63 @@ public T getAttribute(AttributeKey key) { * @return the newly merged {@code Resource}. */ public Resource merge(@Nullable Resource other) { - return EntityUtil.merge(this, other); + if (other == null || other == EMPTY) { + return this; + } + + AttributesBuilder attrBuilder = Attributes.builder(); + attrBuilder.putAll(this.getAttributes()); + attrBuilder.putAll(other.getAttributes()); + + if (other.getSchemaUrl() == null) { + return create(attrBuilder.build(), getSchemaUrl()); + } + if (getSchemaUrl() == null) { + return create(attrBuilder.build(), other.getSchemaUrl()); + } + if (!other.getSchemaUrl().equals(getSchemaUrl())) { + logger.info( + "Attempting to merge Resources with different schemaUrls. " + + "The resulting Resource will have no schemaUrl assigned. Schema 1: " + + getSchemaUrl() + + " Schema 2: " + + other.getSchemaUrl()); + // currently, behavior is undefined if schema URLs don't match. In the future, we may + // apply schema transformations if possible. + return create(attrBuilder.build(), null); + } + return create(attrBuilder.build(), getSchemaUrl()); + } + + private static void checkAttributes(Attributes attributes) { + attributes.forEach( + (key, value) -> { + Utils.checkArgument( + isValidAndNotEmpty(key), "Attribute key" + ERROR_MESSAGE_INVALID_CHARS); + Objects.requireNonNull(value, "Attribute value" + ERROR_MESSAGE_INVALID_VALUE); + }); + } + + /** + * Determines whether the given {@code String} is a valid printable ASCII string with a length not + * exceed {@link #MAX_LENGTH} characters. + * + * @param name the name to be validated. + * @return whether the name is valid. + */ + private static boolean isValid(String name) { + return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); + } + + /** + * Determines whether the given {@code String} is a valid printable ASCII string with a length + * greater than 0 and not exceed {@link #MAX_LENGTH} characters. + * + * @param name the name to be validated. + * @return whether the name is valid. + */ + private static boolean isValidAndNotEmpty(AttributeKey name) { + return !name.getKey().isEmpty() && isValid(name.getKey()); } /** diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java index f2abb8d8b20..9963eeaf541 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java @@ -8,14 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.resources.internal.Entity; -import io.opentelemetry.sdk.resources.internal.EntityUtil; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; import java.util.function.Predicate; -import java.util.stream.Collectors; import javax.annotation.Nullable; /** @@ -27,7 +20,6 @@ public class ResourceBuilder { private final AttributesBuilder attributesBuilder = Attributes.builder(); - private final List entities = new ArrayList<>(); @Nullable private String schemaUrl; /** @@ -202,32 +194,6 @@ public ResourceBuilder setSchemaUrl(String schemaUrl) { /** Create the {@link Resource} from this. */ public Resource build() { - // What checks should we do on "real" resource here? - // Derive schemaUrl from entitiy, if able. - if (schemaUrl == null) { - Set entitySchemas = - entities.stream().map(Entity::getSchemaUrl).collect(Collectors.toSet()); - if (entitySchemas.size() == 1) { - // Updated Entities use same schema, we can preserve it. - schemaUrl = entitySchemas.iterator().next(); - } - } - - // TODO - here we deal with conflicts between entities and raw attributes. - // When adding an entity, we remove any raw attributes it may conflict with. - this.attributesBuilder.removeIf(key -> EntityUtil.hasAttributeKey(this.entities, key)); - return Resource.create(attributesBuilder.build(), schemaUrl, entities); - } - - /** Appends a new entity on to the end of the list of entities. */ - ResourceBuilder add(Entity e) { - this.entities.add(e); - return this; - } - - /** Appends a new collection of entities on to the end of the list of entities. */ - ResourceBuilder addAll(Collection entities) { - this.entities.addAll(entities); - return this; + return Resource.create(attributesBuilder.build(), schemaUrl); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java deleted file mode 100644 index 966c5b58277..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/AttributeCheckUtil.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.internal.StringUtils; -import io.opentelemetry.api.internal.Utils; -import java.util.Objects; - -/** - * Helpers to check resource attributes. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public final class AttributeCheckUtil { - private AttributeCheckUtil() {} - - // Note: Max length is actually configurable by specification. - private static final int MAX_LENGTH = 255; - private static final String ERROR_MESSAGE_INVALID_CHARS = - " should be a ASCII string with a length greater than 0 and not exceed " - + MAX_LENGTH - + " characters."; - private static final String ERROR_MESSAGE_INVALID_VALUE = - " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters."; - - /** Determine if the set of attributes if valid for Resource / Entity. */ - public static void checkAttributes(Attributes attributes) { - attributes.forEach( - (key, value) -> { - Utils.checkArgument( - isValidAndNotEmpty(key), "Attribute key" + ERROR_MESSAGE_INVALID_CHARS); - Objects.requireNonNull(value, "Attribute value" + ERROR_MESSAGE_INVALID_VALUE); - }); - } - - /** - * Determines whether the given {@code String} is a valid printable ASCII string with a length - * greater than 0 and not exceed {@link #MAX_LENGTH} characters. - * - * @param name the name to be validated. - * @return whether the name is valid. - */ - public static boolean isValidAndNotEmpty(AttributeKey name) { - return !name.getKey().isEmpty() && isValid(name.getKey()); - } - - /** - * Determines whether the given {@code String} is a valid printable ASCII string with a length not - * exceed {@link #MAX_LENGTH} characters. - * - * @param name the name to be validated. - * @return whether the name is valid. - */ - public static boolean isValid(String name) { - return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); - } -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java deleted file mode 100644 index 40cf63e126b..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -/** - * Entity represents an object of interest associated with produced telemetry: traces, metrics or - * logs. - * - *

For example, telemetry produced using OpenTelemetry SDK is normally associated with a Service - * entity. Similarly, OpenTelemetry defines system metrics for a host. The Host is the entity we - * want to associate metrics with in this case. - * - *

Entities may be also associated with produced telemetry indirectly. For example a service that - * produces telemetry is also related with a process in which the service runs, so we say that the - * Service entity is related to the Process entity. The process normally also runs on a host, so we - * say that the Process entity is related to the Host entity. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -@Immutable -public interface Entity { - /** - * Returns the entity type string of this entity. Must not be null. - * - * @return the entity type. - */ - String getType(); - - /** - * Returns a map of attributes that identify the entity. - * - * @return the entity identity. - */ - Attributes getId(); - - /** - * Returns a map of attributes that describe the entity. - * - * @return the entity description. - */ - Attributes getDescription(); - - /** - * Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity - * does not abide by schema conventions (i.e. is custom). - * - * @return An OpenTelemetry schema URL. - */ - @Nullable - String getSchemaUrl(); - - /** - * Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}. - */ - EntityBuilder toBuilder(); - - /** - * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. - * - * @param entityType the entity type string of this entity. - */ - public static EntityBuilder builder(String entityType) { - return SdkEntity.builder(entityType); - } -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java deleted file mode 100644 index bb2b21c998b..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.Attributes; - -/** - * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as - * well as type and schema_url. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public interface EntityBuilder { - /** - * Assign an OpenTelemetry schema URL to the resulting Entity. - * - * @param schemaUrl The URL of the OpenTelemetry schema being used to create this Entity. - * @return this - */ - EntityBuilder setSchemaUrl(String schemaUrl); - - /** - * Modify the descriptive attributes of this Entity. - * - * @param description The attributes that describe the Entity. - * @return this - */ - EntityBuilder withDescription(Attributes description); - - /** - * Modify the identifying attributes of this Entity. - * - * @param id The identifying attributes. - * @return this - */ - EntityBuilder withId(Attributes id); - - /** Create the {@link Entity} from this. */ - Entity build(); -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java deleted file mode 100644 index d2379f1a4e0..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.ResourceBuilder; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; -import javax.annotation.Nullable; - -/** - * Helper class for dealing with Entities. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public final class EntityUtil { - private static final Logger logger = Logger.getLogger(EntityUtil.class.getName()); - - private EntityUtil() {} - - /** - * Constructs a new {@link Resource} with Entity support. - * - * @param entities The set of entities the resource needs. - * @return A constructed resource. - */ - public static Resource createResource(Collection entities) { - return createResourceRaw( - Attributes.empty(), EntityUtil.mergeResourceSchemaUrl(entities, null, null), entities); - } - - /** - * Constructs a new {@link Resource} with Entity support. - * - * @param attributes The raw attributes for the resource. - * @param schemaUrl The schema url for the resource. - * @param entities The set of entities the resource needs. - * @return A constructed resource. - */ - static Resource createResourceRaw( - Attributes attributes, @Nullable String schemaUrl, Collection entities) { - try { - Method method = - Resource.class.getDeclaredMethod( - "create", Attributes.class, String.class, Collection.class); - if (method != null) { - method.setAccessible(true); - Object result = method.invoke(null, attributes, schemaUrl, entities); - if (result instanceof Resource) { - return (Resource) result; - } - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", e); - } - // Fall back to non-entity behavior? - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource"); - return Resource.empty(); - } - - /** Appends a new entity on to the end of the list of entities. */ - public static ResourceBuilder addEntity(ResourceBuilder rb, Entity e) { - try { - Method method = ResourceBuilder.class.getDeclaredMethod("add", Entity.class); - if (method != null) { - method.setAccessible(true); - method.invoke(rb, e); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ex); - } - return rb; - } - - /** Appends a new collection of entities on to the end of the list of entities. */ - public static ResourceBuilder addAllEntity(ResourceBuilder rb, Collection e) { - try { - Method method = ResourceBuilder.class.getDeclaredMethod("addAll", Collection.class); - if (method != null) { - method.setAccessible(true); - method.invoke(rb, e); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", ex); - } - return rb; - } - - /** - * Returns a collectoion of associated entities. - * - * @return a collection of entities. - */ - @SuppressWarnings("unchecked") - public static Collection getEntities(Resource r) { - try { - Method method = Resource.class.getDeclaredMethod("getEntities"); - if (method != null) { - method.setAccessible(true); - return (Collection) method.invoke(r); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", e); - } - return Collections.emptyList(); - } - - /** - * Returns a map of attributes that describe the resource, not associated with entites. - * - * @return a map of attributes. - */ - public static Attributes getRawAttributes(Resource r) { - try { - Method method = Resource.class.getDeclaredMethod("getRawAttributes"); - if (method != null) { - method.setAccessible(true); - return (Attributes) method.invoke(r); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - logger.log(Level.WARNING, "Attempting to use entities with unsupported resource", e); - } - return Attributes.empty(); - } - - /** Returns true if any entity in the collection has the attribute key, in id or description. */ - public static boolean hasAttributeKey(Collection entities, AttributeKey key) { - return entities.stream() - .anyMatch( - e -> e.getId().asMap().containsKey(key) || e.getDescription().asMap().containsKey(key)); - } - - /** Decides on a final SchemaURL for OTLP Resource based on entities chosen. */ - @Nullable - static String mergeResourceSchemaUrl( - Collection entities, @Nullable String baseUrl, @Nullable String nextUrl) { - // Check if entities all share the same URL. - Set entitySchemas = - entities.stream().map(Entity::getSchemaUrl).collect(Collectors.toSet()); - // If we have no entities, we preserve previous schema url behavior. - String result = baseUrl; - if (entitySchemas.size() == 1) { - // Updated Entities use same schema, we can preserve it. - result = entitySchemas.iterator().next(); - } else if (entitySchemas.size() > 1) { - // Entities use different schemas, resource must treat this as no schema_url. - result = null; - } - - // If schema url of merging resource is null, we use our current result. - if (nextUrl == null) { - return result; - } - // When there are no entities, we use old schema url merge behavior - if (result == null && entities.isEmpty()) { - return nextUrl; - } - if (!nextUrl.equals(result)) { - logger.info( - "Attempting to merge Resources with different schemaUrls. " - + "The resulting Resource will have no schemaUrl assigned. Schema 1: " - + baseUrl - + " Schema 2: " - + nextUrl); - return null; - } - return result; - } - - /** - * Merges "loose" attributes on resource, removing those which conflict with the set of entities. - * - * @param base loose attributes from base resource - * @param additional additional attributes to add to the resource. - * @param entities the set of entites on the resource. - * @return the new set of raw attributes for Resource and the set of conflicting entities that - * MUST NOT be reported on OTLP resource. - */ - @SuppressWarnings("unchecked") - static final RawAttributeMergeResult mergeRawAttributes( - Attributes base, Attributes additional, Collection entities) { - AttributesBuilder result = base.toBuilder(); - // We know attribute conflicts were handled perviously on the resource, so - // This needs to account for entity merge of new entities, and remove raw - // attributes that would have been removed with new entities. - result.removeIf(key -> hasAttributeKey(entities, key)); - // For every "raw" attribute on the other resource, we merge into the - // resource, but check for entity conflicts from previous entities. - ArrayList conflicts = new ArrayList<>(); - if (!additional.isEmpty()) { - additional.forEach( - (key, value) -> { - for (Entity e : entities) { - if (e.getId().get(key) != null || e.getDescription().get(key) != null) { - // Remove the entity and push all attributes as raw, - // we have an override. - conflicts.add(e); - result.putAll(e.getId()).putAll(e.getDescription()); - } - } - result.put((AttributeKey) key, value); - }); - } - return RawAttributeMergeResult.create(result.build(), conflicts); - } - - /** - * Merges entities according to specification rules. - * - * @param base the initial set of entities. - * @param additional Additional entities to merge with base set. - * @return A new set of entities with no duplicate types. - */ - static Collection mergeEntities(Collection base, Collection additional) { - if (base.isEmpty()) { - return additional; - } - if (additional.isEmpty()) { - return base; - } - Map entities = new HashMap<>(); - base.forEach(e -> entities.put(e.getType(), e)); - for (Entity e : additional) { - if (!entities.containsKey(e.getType())) { - entities.put(e.getType(), e); - } else { - Entity old = entities.get(e.getType()); - // If the entity identity is the same, but schema_url is different: drop the new entity d' - // Note: We could offer configuration in this case - if (old.getSchemaUrl() == null || !old.getSchemaUrl().equals(e.getSchemaUrl())) { - logger.info( - "Discovered conflicting entities. Entity [" - + old.getType() - + "] has different schema url [" - + old.getSchemaUrl() - + "], new entity with schema url[" - + e.getSchemaUrl() - + "] is dropped."); - } else if (!old.getId().equals(e.getId())) { - // If the entity identity is different: drop the new entity d'. - logger.info( - "Discovered conflicting entities. Entity [" - + old.getType() - + "] has identity [" - + old.getId() - + "], new entity [" - + e.getId() - + "] is dropped."); - } else { - // If the entity identity and schema_url are the same, merge the descriptive attributes - // of d' into e': - // For each descriptive attribute da' in d' - // If da'.key does not exist in e', then add da' to ei - // otherwise, ignore. - Entity next = - old.toBuilder() - .withDescription( - Attributes.builder() - .putAll(e.getDescription()) - .putAll(old.getDescription()) - .build()) - .build(); - entities.put(next.getType(), next); - } - } - } - return entities.values(); - } - - /** - * Returns a new, merged {@link Resource} by merging the {@code base} {@code Resource} with the - * {@code next} {@code Resource}. In case of a collision, the "next" {@code Resource} takes - * precedence. - * - * @param base the {@code Resource} into which we merge new values. - * @param next the {@code Resource} that will be merged with {@code base}. - * @return the newly merged {@code Resource}. - */ - public static Resource merge(Resource base, @Nullable Resource next) { - if (next == null || next == Resource.empty()) { - return base; - } - // Merge Algorithm from - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/entities/0264-resource-and-entities.md#entity-merging-and-resource - Collection entities = EntityUtil.mergeEntities(getEntities(base), getEntities(next)); - RawAttributeMergeResult attributeResult = - EntityUtil.mergeRawAttributes(getRawAttributes(base), getRawAttributes(next), entities); - // Remove entities that are conflicting with raw attributes, and therefore in an unknown state. - entities.removeAll(attributeResult.getConflicts()); - // Now figure out schema url for overall resource. - String schemaUrl = - EntityUtil.mergeResourceSchemaUrl(entities, base.getSchemaUrl(), next.getSchemaUrl()); - return createResourceRaw(attributeResult.getAttributes(), schemaUrl, entities); - } -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java deleted file mode 100644 index f1cc081b035..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/RawAttributeMergeResult.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import com.google.auto.value.AutoValue; -import io.opentelemetry.api.common.Attributes; -import java.util.Collection; -import javax.annotation.concurrent.Immutable; - -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ -@Immutable -@AutoValue -abstract class RawAttributeMergeResult { - /** Merged raw attributes. */ - abstract Attributes getAttributes(); - - /** - * Entities in conflict that should be removed from resource to avoid reporting invalid attribute - * sets in OTLP resource. - */ - abstract Collection getConflicts(); - - static final RawAttributeMergeResult create(Attributes attributes, Collection conflicts) { - return new AutoValue_RawAttributeMergeResult(attributes, conflicts); - } -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java deleted file mode 100644 index 2ddf17dde33..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import com.google.auto.value.AutoValue; -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -/** - * SDK implementation of Entity. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -@Immutable -@AutoValue -abstract class SdkEntity implements Entity { - /** - * Returns a {@link Entity}. - * - * @param entityType the entity type string of this entity. - * @param id a map of attributes that identify the entity. - * @param description a map of attributes that describe the entity. - * @return a {@code Entity}. - */ - static final Entity create( - String entityType, Attributes id, Attributes description, @Nullable String schemaUrl) { - return new AutoValue_SdkEntity(entityType, id, description, schemaUrl); - } - - @Override - public final EntityBuilder toBuilder() { - return new SdkEntityBuilder(this); - } - - /** - * Returns a new {@link EntityBuilder} instance for creating arbitrary {@link Entity}. - * - * @param entityType the entity type string of this entity. - */ - public static final EntityBuilder builder(String entityType) { - return new SdkEntityBuilder(entityType); - } -} diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java b/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java deleted file mode 100644 index 2a425f5a0d3..00000000000 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntityBuilder.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import io.opentelemetry.api.common.Attributes; -import javax.annotation.Nullable; - -/** - * A builder of {@link Entity} that allows to add identifying or descriptive {@link Attributes}, as - * well as type and schema_url. - * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -final class SdkEntityBuilder implements EntityBuilder { - private final String entityType; - private Attributes description; - private Attributes id; - @Nullable private String schemaUrl; - - SdkEntityBuilder(String entityType) { - AttributeCheckUtil.isValid(entityType); - this.entityType = entityType; - this.description = Attributes.empty(); - this.id = Attributes.empty(); - } - - SdkEntityBuilder(Entity seed) { - this.entityType = seed.getType(); - this.schemaUrl = seed.getSchemaUrl(); - this.id = seed.getId(); - this.description = seed.getDescription(); - } - - @Override - public EntityBuilder setSchemaUrl(String schemaUrl) { - this.schemaUrl = schemaUrl; - return this; - } - - @Override - public EntityBuilder withDescription(Attributes description) { - AttributeCheckUtil.checkAttributes(description); - this.description = description; - return this; - } - - @Override - public EntityBuilder withId(Attributes id) { - AttributeCheckUtil.checkAttributes(id); - this.id = id; - return this; - } - - @Override - public Entity build() { - return SdkEntity.create(entityType, id, description, schemaUrl); - } -} diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java index 6e170226ed5..1654a001a96 100644 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java +++ b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/ResourceTest.java @@ -13,7 +13,6 @@ import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.stringArrayKey; import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @@ -22,7 +21,6 @@ import io.opentelemetry.api.common.AttributeType; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.resources.internal.Entity; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -236,31 +234,6 @@ void testMergeResources_Resource2_Null() { assertThat(resource.getAttributes()).isEqualTo(expectedAttributes); } - @Test - void testMergeResources_entities_separate_types_and_schema() { - Resource resource1 = - Resource.builder() - .add( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "a").build()) - .build()) - .build(); - Resource resource2 = - Resource.builder() - .add( - Entity.builder("b") - .setSchemaUrl("two") - .withId(Attributes.builder().put("b.id", "b").build()) - .build()) - .build(); - Resource merged = resource1.merge(resource2); - assertThat(merged.getSchemaUrl()).isNull(); - assertThat(merged.getEntities()).hasSize(2); - assertThat(merged.getAttributes()).containsEntry("a.id", "a"); - assertThat(merged.getAttributes()).containsEntry("b.id", "b"); - } - @Test void testDefaultResources() { Resource resource = Resource.getDefault(); diff --git a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java b/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java deleted file mode 100644 index 23aece73575..00000000000 --- a/sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.resources.internal; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.sdk.resources.Resource; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import org.junit.jupiter.api.Test; - -/** Unit tests for {@link EntityUtil}. */ -class EntityUtilTest { - @Test - void testMerge_entities_same_types_and_id() { - Collection base = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "a").build()) - .withDescription(Attributes.builder().put("a.desc1", "a").build()) - .build()); - Collection added = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "a").build()) - .withDescription(Attributes.builder().put("a.desc2", "b").build()) - .build()); - Collection merged = EntityUtil.mergeEntities(base, added); - assertThat(merged).hasSize(1); - assertThat(merged) - .anySatisfy( - entity -> { - assertThat(entity.getType()).isEqualTo("a"); - assertThat(entity.getSchemaUrl()).isEqualTo("one"); - assertThat(entity.getId()).containsEntry("a.id", "a"); - assertThat(entity.getDescription()) - .containsEntry("a.desc1", "a") - .containsEntry("a.desc2", "b"); - }); - } - - @Test - void testMerge_entities_same_types_and_id_different_schema() { - Collection base = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "a").build()) - .withDescription(Attributes.builder().put("a.desc1", "a").build()) - .build()); - Collection added = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("two") - .withId(Attributes.builder().put("a.id", "a").build()) - .withDescription(Attributes.builder().put("a.desc2", "b").build()) - .build()); - Collection merged = EntityUtil.mergeEntities(base, added); - assertThat(merged).hasSize(1); - assertThat(merged) - .anySatisfy( - entity -> { - assertThat(entity.getType()).isEqualTo("a"); - assertThat(entity.getSchemaUrl()).isEqualTo("one"); - assertThat(entity.getId()).containsEntry("a.id", "a"); - assertThat(entity.getDescription()) - .containsEntry("a.desc1", "a") - // Don't merge between versions. - .doesNotContainKey("a.desc2"); - }); - } - - @Test - void testMerge_entities_same_types_different_id() { - Collection base = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "a").build()) - .withDescription(Attributes.builder().put("a.desc1", "a").build()) - .build()); - Collection added = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "b").build()) - .withDescription(Attributes.builder().put("a.desc2", "b").build()) - .build()); - Collection merged = EntityUtil.mergeEntities(base, added); - assertThat(merged).hasSize(1); - assertThat(merged) - .satisfiesExactly( - entity -> { - assertThat(entity.getType()).isEqualTo("a"); - assertThat(entity.getSchemaUrl()).isEqualTo("one"); - assertThat(entity.getId()).containsEntry("a.id", "a"); - assertThat(entity.getDescription()) - .containsEntry("a.desc1", "a") - // Don't merge between different ids. - .doesNotContainKey("a.desc2"); - }); - } - - @Test - void testMerge_entities_separate_types_and_schema() { - Collection base = - Arrays.asList( - Entity.builder("a") - .setSchemaUrl("one") - .withId(Attributes.builder().put("a.id", "a").build()) - .build()); - Collection added = - Arrays.asList( - Entity.builder("b") - .setSchemaUrl("two") - .withId(Attributes.builder().put("b.id", "b").build()) - .build()); - Collection merged = EntityUtil.mergeEntities(base, added); - // Make sure we keep both entities when no conflict. - assertThat(merged) - .satisfiesExactlyInAnyOrder( - a -> assertThat(a.getType()).isEqualTo("a"), - b -> assertThat(b.getType()).isEqualTo("b")); - } - - @Test - void testSchemaUrlMerge_no_entities_differentUrls() { - // If the we find conflicting schema URLs in resource we must drop schema url (set to null). - String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), "one", "two"); - assertThat(result).isNull(); - } - - @Test - void testSchemaUrlMerge_no_entities_base_null() { - // If the our resource had no schema url it abides by, we use the incoming schema url. - String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), null, "two"); - assertThat(result).isEqualTo("two"); - } - - @Test - void testSchemaUrlMerge_no_entities_next_null() { - // If the new resource had no schema url it abides by, we preserve ours. - // NOTE: this is by specification, but seems problematic if conflicts in merge - // cause violation of SchemaURL. - String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), "one", null); - assertThat(result).isEqualTo("one"); - } - - @Test - void testSchemaUrlMerge_entities_same_url() { - // If the new resource had no schema url it abides by, we preserve ours. - // NOTE: this is by specification, but seems problematic if conflicts in merge - // cause violation of SchemaURL. - String result = - EntityUtil.mergeResourceSchemaUrl( - Arrays.asList( - Entity.builder("t") - .setSchemaUrl("one") - .withId(Attributes.builder().put("id", 1).build()) - .build()), - "one", - null); - assertThat(result).isEqualTo("one"); - } - - @Test - void testSchemaUrlMerge_entities_different_url() { - // When entities have conflicting schema urls, we cannot fill out resource schema url, - // no matter what. - String result = - EntityUtil.mergeResourceSchemaUrl( - Arrays.asList( - Entity.builder("t") - .setSchemaUrl("one") - .withId(Attributes.builder().put("id", 1).build()) - .build(), - Entity.builder("t2") - .setSchemaUrl("two") - .withId(Attributes.builder().put("id2", 1).build()) - .build()), - "one", - "one"); - assertThat(result).isEqualTo(null); - } - - @Test - void testRawAttributeMerge_no_entities() { - // When no entities are present all attributes are merged. - RawAttributeMergeResult result = - EntityUtil.mergeRawAttributes( - Attributes.builder().put("a", 1).put("b", 1).build(), - Attributes.builder().put("b", 2).put("c", 2).build(), - Collections.emptyList()); - assertThat(result.getConflicts()).isEmpty(); - assertThat(result.getAttributes()) - .hasSize(3) - .containsEntry("a", 1) - .containsEntry("b", 2) - .containsEntry("c", 2); - } - - @Test - void testRawAttributeMerge_entity_with_conflict() { - // When an entity conflicts with incoming raw attributes, we need to call out that conflict - // so resource merge logic can remove the entity from resource. - RawAttributeMergeResult result = - EntityUtil.mergeRawAttributes( - Attributes.builder().put("a", 1).put("b", 1).build(), - Attributes.builder().put("b", 2).put("c", 2).build(), - Arrays.asList( - Entity.builder("c").withId(Attributes.builder().put("c", 1).build()).build())); - assertThat(result.getConflicts()).satisfiesExactly(e -> assertThat(e.getType()).isEqualTo("c")); - assertThat(result.getAttributes()) - .hasSize(3) - .containsEntry("a", 1) - .containsEntry("b", 2) - .containsEntry("c", 2); - } - - @Test - void testAddEntity_reflection() { - Resource result = - EntityUtil.addEntity( - Resource.builder(), - Entity.builder("a").withId(Attributes.builder().put("a", 1).build()).build()) - .build(); - assertThat(EntityUtil.getEntities(result)) - .satisfiesExactlyInAnyOrder(e -> assertThat(e.getType()).isEqualTo("a")); - } - - @Test - void testAddAllEntity_reflection() { - Resource result = - EntityUtil.addAllEntity( - Resource.builder(), - Arrays.asList( - Entity.builder("a").withId(Attributes.builder().put("a", 1).build()).build(), - Entity.builder("b").withId(Attributes.builder().put("b", 1).build()).build())) - .build(); - assertThat(EntityUtil.getEntities(result)) - .satisfiesExactlyInAnyOrder( - e -> assertThat(e.getType()).isEqualTo("a"), - e -> assertThat(e.getType()).isEqualTo("b")); - } -} diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java index 232822a5f20..5b40f897a32 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LoggerSharedState.java @@ -18,7 +18,7 @@ */ final class LoggerSharedState { private final Object lock = new Object(); - private final Supplier resourceSupplier; + private final Resource resource; private final Supplier logLimitsSupplier; private final LogRecordProcessor logRecordProcessor; private final Clock clock; @@ -26,12 +26,12 @@ final class LoggerSharedState { @Nullable private volatile CompletableResultCode shutdownResult = null; LoggerSharedState( - Supplier resourceSupplier, + Resource resource, Supplier logLimitsSupplier, LogRecordProcessor logRecordProcessor, Clock clock, ExceptionAttributeResolver exceptionAttributeResolver) { - this.resourceSupplier = resourceSupplier; + this.resource = resource; this.logLimitsSupplier = logLimitsSupplier; this.logRecordProcessor = logRecordProcessor; this.clock = clock; @@ -39,7 +39,7 @@ final class LoggerSharedState { } Resource getResource() { - return resourceSupplier.get(); + return resource; } LogLimits getLogLimits() { diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java index aa4f4d63b79..54825caf21e 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProvider.java @@ -53,7 +53,7 @@ public static SdkLoggerProviderBuilder builder() { } SdkLoggerProvider( - Supplier resource, + Resource resource, Supplier logLimitsSupplier, List processors, Clock clock, diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java index 9f8a6f9f682..41ce7d44980 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilder.java @@ -24,7 +24,6 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.function.Supplier; -import javax.annotation.Nullable; /** * Builder class for {@link SdkLoggerProvider} instances. @@ -34,7 +33,6 @@ public final class SdkLoggerProviderBuilder { private final List logRecordProcessors = new ArrayList<>(); - @Nullable private Supplier resourceSupplier = null; private Resource resource = Resource.getDefault(); private Supplier logLimitsSupplier = LogLimits::getDefault; private Clock clock = Clock.getDefault(); @@ -70,22 +68,6 @@ public SdkLoggerProviderBuilder addResource(Resource resource) { return this; } - /** - * Registers a supplier of {@link Resource}. - * - *

This will override any {@link #addResource(Resource)} or {@link #setResource(Resource)} - * calls with the current supplier. - * - *

This method is experimental so not public. You may reflectively call it using {@link - * SdkLoggerProviderUtil#setResourceSupplier(SdkLoggerProviderBuilder, Supplier)}. - * - * @param supplier The supplier of {@link Resource}. - */ - SdkLoggerProviderBuilder setResourceSupplier(Supplier supplier) { - this.resourceSupplier = supplier; - return this; - } - /** * Assign a {@link Supplier} of {@link LogLimits}. {@link LogLimits} will be retrieved each time a * {@link Logger#logRecordBuilder()} is called. @@ -210,12 +192,8 @@ SdkLoggerProviderBuilder setExceptionAttributeResolver( * @return an instance configured with the provided options */ public SdkLoggerProvider build() { - Supplier resolvedSupplier = () -> this.resource; - if (resourceSupplier != null) { - resolvedSupplier = this.resourceSupplier; - } return new SdkLoggerProvider( - resolvedSupplier, + resource, logLimitsSupplier, logRecordProcessors, clock, diff --git a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java index 1b1848ffa55..417601bbad4 100644 --- a/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java +++ b/sdk/logs/src/main/java/io/opentelemetry/sdk/logs/internal/SdkLoggerProviderUtil.java @@ -10,11 +10,9 @@ import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; -import io.opentelemetry.sdk.resources.Resource; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; -import java.util.function.Supplier; /** * A collection of methods that allow use of experimental features prior to availability in public @@ -92,24 +90,4 @@ public static void setExceptionAttributeResolver( "Error calling setExceptionAttributeResolver on SdkLoggerProviderBuilder", e); } } - - /** - * Reflectively assign the {@link Supplier} of {@link Resource} to the {@link - * SdkLoggerProviderBuilder}. - * - * @param sdkLoggerProvider the builder - */ - public static SdkLoggerProviderBuilder setResourceSupplier( - SdkLoggerProviderBuilder sdkLoggerProvider, Supplier resourceSupplier) { - try { - Method method = - SdkLoggerProviderBuilder.class.getDeclaredMethod("setResourceSupplier", Supplier.class); - method.setAccessible(true); - method.invoke(sdkLoggerProvider, resourceSupplier); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - "Error calling setLoggerConfigurator on SdkLoggerProvider", e); - } - return sdkLoggerProvider; - } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java index df059ffb306..7aa4f02aa0a 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/LoggerSharedStateTest.java @@ -25,7 +25,7 @@ void shutdown() { when(logRecordProcessor.shutdown()).thenReturn(code); LoggerSharedState state = new LoggerSharedState( - () -> Resource.empty(), + Resource.empty(), LogLimits::getDefault, logRecordProcessor, Clock.getDefault(), diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java index 4a4a4c17525..5c49d35e5ca 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderBuilderTest.java @@ -5,13 +5,11 @@ package io.opentelemetry.sdk.logs; -import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.resources.Resource; -import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; public class SdkLoggerProviderBuilderTest { @@ -27,25 +25,7 @@ void addResource() { SdkLoggerProvider.builder().addResource(customResource).build(); assertThat(sdkLoggerProvider) - .extracting("sharedState", as(InstanceOfAssertFactories.type(LoggerSharedState.class))) - .extracting(LoggerSharedState::getResource) - .isEqualTo(Resource.getDefault().merge(customResource)); - } - - @Test - void setResourceSupplier() { - Resource customResource = - Resource.create( - Attributes.of( - AttributeKey.stringKey("custom_attribute_key"), "custom_attribute_value")); - - SdkLoggerProvider sdkLoggerProvider = - SdkLoggerProvider.builder().setResourceSupplier(() -> customResource).build(); - - assertThat(sdkLoggerProvider) - .extracting("sharedState", as(InstanceOfAssertFactories.type(LoggerSharedState.class))) - .extracting(LoggerSharedState::getResource) - // Validate the default resource values are NO Longer here when a supplier takes over. - .isEqualTo(customResource); + .extracting("sharedState") + .hasFieldOrPropertyWithValue("resource", Resource.getDefault().merge(customResource)); } } diff --git a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java index 2f580d83e07..c6f0b2885ec 100644 --- a/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java +++ b/sdk/logs/src/test/java/io/opentelemetry/sdk/logs/SdkLoggerProviderTest.java @@ -343,15 +343,15 @@ void close() { @Test void toString_Valid() { when(logRecordProcessor.toString()).thenReturn("MockLogRecordProcessor"); - // Check that components of Logger are to-stringed. assertThat(sdkLoggerProvider.toString()) - .matches("SdkLoggerProvider\\{.*}") - .matches(".*clock=SystemClock\\{}.*") - .matches(".*resource=Resource\\{.*}.*") - .matches( - ".*logLimits=LogLimits\\{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647},.*") - .matches(".*logRecordProcessor=MockLogRecordProcessor, .*") - .matches(".*loggerConfigurator=ScopeConfiguratorImpl\\{conditions=\\[]}.*"); + .isEqualTo( + "SdkLoggerProvider{" + + "clock=SystemClock{}, " + + "resource=Resource{schemaUrl=null, attributes={key=\"value\"}}, " + + "logLimits=LogLimits{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647}, " + + "logRecordProcessor=MockLogRecordProcessor, " + + "loggerConfigurator=ScopeConfiguratorImpl{conditions=[]}" + + "}"); } private static ScopeConfigurator flipConfigurator(boolean enabled) { diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java index 15199027b3b..30ae0b1da5a 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProvider.java @@ -35,7 +35,6 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; import java.util.logging.Logger; /** @@ -66,7 +65,7 @@ public static SdkMeterProviderBuilder builder() { IdentityHashMap metricReaders, List metricProducers, Clock clock, - Supplier resource, + Resource resource, ExemplarFilter exemplarFilter, ScopeConfigurator meterConfigurator) { long startEpochNanos = clock.now(); diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java index 77a4bce64a7..90d0c7a8fef 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/SdkMeterProviderBuilder.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.Objects; import java.util.function.Predicate; -import java.util.function.Supplier; -import javax.annotation.Nullable; /** * Builder class for the {@link SdkMeterProvider}. @@ -41,7 +39,6 @@ public final class SdkMeterProviderBuilder { private static final ExemplarFilter DEFAULT_EXEMPLAR_FILTER = ExemplarFilter.traceBased(); private Clock clock = Clock.getDefault(); - @Nullable private Supplier resourceSupplier = null; private Resource resource = Resource.getDefault(); private final IdentityHashMap metricReaders = new IdentityHashMap<>(); @@ -83,23 +80,6 @@ public SdkMeterProviderBuilder addResource(Resource resource) { return this; } - /** - * Registers a supplier of {@link Resource}. - * - *

This will override any {@link #addResource(Resource)} or {@link #setResource(Resource)} - * calls with the current supplier. - * - *

This method is experimental so not public. You may reflectively call it using {@link - * SdkMeterProviderUtil#setResourceSupplier(SdkMeterProviderBuilder, Supplier)}. - * - * @param supplier The supplier of {@link Resource}. - * @since 1.X.0 - */ - SdkMeterProviderBuilder setResourceSupplier(Supplier supplier) { - this.resourceSupplier = supplier; - return this; - } - /** * Assign an {@link ExemplarFilter} for all metrics created by Meters. * @@ -220,16 +200,12 @@ SdkMeterProviderBuilder addMeterConfiguratorCondition( /** Returns an {@link SdkMeterProvider} built with the configuration of this builder. */ public SdkMeterProvider build() { - Supplier resolvedSupplier = () -> this.resource; - if (resourceSupplier != null) { - resolvedSupplier = this.resourceSupplier; - } return new SdkMeterProvider( registeredViews, metricReaders, metricProducers, clock, - resolvedSupplier, + resource, exemplarFilter, meterConfiguratorBuilder.build()); } diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java index 0af2c8f63b5..9fc690366ed 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/SdkMeterProviderUtil.java @@ -13,11 +13,9 @@ import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; import io.opentelemetry.sdk.metrics.internal.view.AttributesProcessor; import io.opentelemetry.sdk.metrics.internal.view.StringPredicates; -import io.opentelemetry.sdk.resources.Resource; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; -import java.util.function.Supplier; /** * A collection of methods that allow use of experimental features prior to availability in public @@ -51,26 +49,6 @@ public static SdkMeterProviderBuilder setExemplarFilter( return sdkMeterProviderBuilder; } - /** - * Reflectively assign the {@link Supplier} of {@link Resource} to the {@link - * SdkMeterProviderBuilder}. - * - * @param sdkMeterProviderBuilder the builder - */ - public static SdkMeterProviderBuilder setResourceSupplier( - SdkMeterProviderBuilder sdkMeterProviderBuilder, Supplier supplier) { - try { - Method method = - SdkMeterProviderBuilder.class.getDeclaredMethod("setResourceSupplier", Supplier.class); - method.setAccessible(true); - method.invoke(sdkMeterProviderBuilder, supplier); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - "Error calling setResourceSupplier on SdkMeterProviderBuilder", e); - } - return sdkMeterProviderBuilder; - } - /** Reflectively set the {@link ScopeConfigurator} to the {@link SdkMeterProviderBuilder}. */ public static SdkMeterProviderBuilder setMeterConfigurator( SdkMeterProviderBuilder sdkMeterProviderBuilder, diff --git a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java index 8265bf415bd..aaf932d9202 100644 --- a/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java +++ b/sdk/metrics/src/main/java/io/opentelemetry/sdk/metrics/internal/state/MeterProviderSharedState.java @@ -10,7 +10,6 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.internal.exemplar.ExemplarFilter; import io.opentelemetry.sdk.resources.Resource; -import java.util.function.Supplier; import javax.annotation.concurrent.Immutable; /** @@ -24,13 +23,9 @@ public abstract class MeterProviderSharedState { public static MeterProviderSharedState create( - Clock clock, - Supplier resourceSupplier, - ExemplarFilter exemplarFilter, - long startEpochNanos) { + Clock clock, Resource resource, ExemplarFilter exemplarFilter, long startEpochNanos) { MeterProviderSharedState sharedState = - new AutoValue_MeterProviderSharedState( - clock, resourceSupplier, startEpochNanos, exemplarFilter); + new AutoValue_MeterProviderSharedState(clock, resource, startEpochNanos, exemplarFilter); return sharedState; } @@ -40,11 +35,7 @@ public static MeterProviderSharedState create( public abstract Clock getClock(); /** Returns the {@link Resource} to attach telemetry to. */ - public Resource getResource() { - return getResourceSupplier().get(); - } - - abstract Supplier getResourceSupplier(); + public abstract Resource getResource(); /** Returns the timestamp when the {@link SdkMeterProvider} was started, in epoch nanos. */ public abstract long getStartEpochNanos(); diff --git a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java index 3f4c8cdf048..de1a351e619 100644 --- a/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java +++ b/sdk/metrics/src/test/java/io/opentelemetry/sdk/metrics/InstrumentBuilderTest.java @@ -21,7 +21,7 @@ class InstrumentBuilderTest { public static final MeterProviderSharedState PROVIDER_SHARED_STATE = MeterProviderSharedState.create( - TestClock.create(), () -> Resource.getDefault(), ExemplarFilter.alwaysOff(), 0); + TestClock.create(), Resource.getDefault(), ExemplarFilter.alwaysOff(), 0); static final InstrumentationScopeInfo SCOPE = InstrumentationScopeInfo.create("scope-name"); public static final SdkMeter SDK_METER = new SdkMeter( diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java index 9b74070c191..f39ce565731 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProvider.java @@ -49,7 +49,7 @@ public static SdkTracerProviderBuilder builder() { SdkTracerProvider( Clock clock, IdGenerator idsGenerator, - Supplier resourceSupplier, + Resource resource, Supplier spanLimitsSupplier, Sampler sampler, List spanProcessors, @@ -59,7 +59,7 @@ public static SdkTracerProviderBuilder builder() { new TracerSharedState( clock, idsGenerator, - resourceSupplier, + resource, spanLimitsSupplier, sampler, spanProcessors, diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java index ad15e196ce5..71f589d15b6 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/SdkTracerProviderBuilder.java @@ -22,7 +22,6 @@ import java.util.Objects; import java.util.function.Predicate; import java.util.function.Supplier; -import javax.annotation.Nullable; /** Builder of {@link SdkTracerProvider}. */ public final class SdkTracerProviderBuilder { @@ -33,7 +32,6 @@ public final class SdkTracerProviderBuilder { private Clock clock = Clock.getDefault(); private IdGenerator idsGenerator = IdGenerator.random(); private Resource resource = Resource.getDefault(); - @Nullable private Supplier resourceSupplier = null; private Supplier spanLimitsSupplier = SpanLimits::getDefault; private Sampler sampler = DEFAULT_SAMPLER; private ScopeConfiguratorBuilder tracerConfiguratorBuilder = @@ -97,23 +95,6 @@ public SdkTracerProviderBuilder addResource(Resource resource) { return this; } - /** - * Registers a supplier of {@link Resource}. - * - *

This will override any {@link #addResource(Resource)} or {@link #setResource(Resource)} - * calls with the current supplier. - * - *

This method is experimental so not public. You may reflectively call it using {@link - * SdkTracerProviderUtil#setResourceSupplier(SdkTracerProviderBuilder, Supplier)}. - * - * @param supplier The supplier of {@link Resource}. - * @since 1.X.0 - */ - SdkTracerProviderBuilder setResourceSupplier(Supplier supplier) { - this.resourceSupplier = supplier; - return this; - } - /** * Assign an initial {@link SpanLimits} that should be used with this SDK. * @@ -257,14 +238,10 @@ SdkTracerProviderBuilder setExceptionAttributeResolver( * @return The instance. */ public SdkTracerProvider build() { - Supplier resolvedSupplier = () -> resource; - if (resourceSupplier != null) { - resolvedSupplier = resourceSupplier; - } return new SdkTracerProvider( clock, idsGenerator, - resolvedSupplier, + resource, spanLimitsSupplier, sampler, spanProcessors, diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java index f2bd64b946c..74d43076b97 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/TracerSharedState.java @@ -22,7 +22,7 @@ final class TracerSharedState { private final IdGenerator idGenerator; // tracks whether it is safe to skip id validation on ids from the above generator private final boolean idGeneratorSafeToSkipIdValidation; - private final Supplier resourceSupplier; + private final Resource resource; private final Supplier spanLimitsSupplier; private final Sampler sampler; @@ -34,7 +34,7 @@ final class TracerSharedState { TracerSharedState( Clock clock, IdGenerator idGenerator, - Supplier resourceSupplier, + Resource resource, Supplier spanLimitsSupplier, Sampler sampler, List spanProcessors, @@ -42,7 +42,7 @@ final class TracerSharedState { this.clock = clock; this.idGenerator = idGenerator; this.idGeneratorSafeToSkipIdValidation = idGenerator instanceof RandomIdGenerator; - this.resourceSupplier = resourceSupplier; + this.resource = resource; this.spanLimitsSupplier = spanLimitsSupplier; this.sampler = sampler; this.activeSpanProcessor = SpanProcessor.composite(spanProcessors); @@ -61,9 +61,8 @@ boolean isIdGeneratorSafeToSkipIdValidation() { return idGeneratorSafeToSkipIdValidation; } - // Needed for tests. - public Resource getResource() { - return resourceSupplier.get(); + Resource getResource() { + return resource; } /** Returns the current {@link SpanLimits}. */ diff --git a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java index 1fc4ccb2eb1..f9ca8fafc56 100644 --- a/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java +++ b/sdk/trace/src/main/java/io/opentelemetry/sdk/trace/internal/SdkTracerProviderUtil.java @@ -8,13 +8,11 @@ import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.internal.ExceptionAttributeResolver; import io.opentelemetry.sdk.internal.ScopeConfigurator; -import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.function.Predicate; -import java.util.function.Supplier; /** * A collection of methods that allow use of experimental features prior to availability in public @@ -91,22 +89,4 @@ public static void setExceptionAttributeResolver( "Error calling setExceptionAttributeResolver on SdkTracerProviderBuilder", e); } } - - /** - * Reflectively set the {@link Supplier} of {@link Resource} to the {@link - * SdkTracerProviderBuilder}. - */ - public static SdkTracerProviderBuilder setResourceSupplier( - SdkTracerProviderBuilder sdkTracerProviderBuilder, Supplier supplier) { - try { - Method method = - SdkTracerProviderBuilder.class.getDeclaredMethod("setResourceSupplier", Supplier.class); - method.setAccessible(true); - method.invoke(sdkTracerProviderBuilder, supplier); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException( - "Error calling setTracerConfigurator on SdkTracerProvider", e); - } - return sdkTracerProviderBuilder; - } } diff --git a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java index aa81ce1e426..9c9ecdc7e9c 100644 --- a/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java +++ b/sdk/trace/src/test/java/io/opentelemetry/sdk/trace/SdkSpanBuilderTest.java @@ -977,12 +977,8 @@ void spanDataToString() { span.setAttribute("http.url", "https://opentelemetry.io"); span.setStatus(StatusCode.ERROR, "error"); span.end(); + assertThat(span.toSpanData().toString()) - .matches("SpanData\\{.*}") - .matches(".*traceId=[0-9a-f]{32}, .*") - .matches(".*spanId=[0-9a-f]{16}, .*") - .matches(".*traceFlags=01, .*") - .matches(".*traceFlags=01, .*") .matches( "SpanData\\{spanContext=ImmutableSpanContext\\{" + "traceId=[0-9a-f]{32}, " @@ -994,7 +990,10 @@ void spanDataToString() { + "spanId=0000000000000000, " + "traceFlags=00, " + "traceState=ArrayBasedTraceState\\{entries=\\[]}, remote=false, valid=false}, " - + "resource=Resource\\{.*}, " + + "resource=Resource\\{schemaUrl=null, " + + "attributes=\\{service.name=\"unknown_service:java\", " + + "telemetry.sdk.language=\"java\", telemetry.sdk.name=\"opentelemetry\", " + + "telemetry.sdk.version=\"\\d+.\\d+.\\d+(-rc.\\d+)?(-SNAPSHOT)?\"}}, " + "instrumentationScopeInfo=InstrumentationScopeInfo\\{" + "name=SpanBuilderSdkTest, version=null, schemaUrl=null, attributes=\\{}}, " + "name=span_name, " From 06590dc51b4249ab8935048915727610e78723bb Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 09:54:47 +0200 Subject: [PATCH 36/42] extract extended opentelemetry only --- .../ExtendedOpenTelemetrySdkBuilder.java | 42 +++++------- ...> ObfuscatedExtendedOpenTelemetrySdk.java} | 49 ++------------ .../OpenTelemetryConfigurationFactory.java | 2 +- .../TestExtendedOpenTelemetrySdk.java | 66 ------------------- 4 files changed, 25 insertions(+), 134 deletions(-) rename sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/{ObfuscatedExtendedOpenTelemerySdk.java => ObfuscatedExtendedOpenTelemetrySdk.java} (79%) delete mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java index 4fd77ce12e1..cb74295c69b 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -6,34 +6,36 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.api.incubator.config.ConfigProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; -import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProvider; -import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProviderBuilder; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; -import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; -import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; -import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; +import java.util.Objects; import java.util.function.Consumer; -/** A new interface for creating OpenTelemetrySdk that supports {@link EntityProvider}. */ +/** A new interface for creating OpenTelemetrySdk that supports {@link ConfigProvider}. */ public final class ExtendedOpenTelemetrySdkBuilder { - private ContextPropagators propagators = ContextPropagators.noop(); private final SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); private final SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); private final SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); - private final SdkEntityProviderBuilder resourceProviderBuilder = SdkEntityProvider.builder(); + private ContextPropagators propagators = ContextPropagators.noop(); + private ConfigProvider configProvider = ConfigProvider.noop(); /** Sets the {@link ContextPropagators} to use. */ public ExtendedOpenTelemetrySdkBuilder setPropagators(ContextPropagators propagators) { - this.propagators = propagators; + this.propagators = Objects.requireNonNull(propagators, "propagators must not be null"); + return this; + } + + /** Sets the {@link ConfigProvider} to use. */ + public ExtendedOpenTelemetrySdkBuilder setConfigProvider(ConfigProvider configProvider) { + this.configProvider = Objects.requireNonNull(configProvider, "configProvider must not be null"); return this; } @@ -83,21 +85,11 @@ public ExtendedOpenTelemetrySdkBuilder withLoggerProvider( * @see GlobalOpenTelemetry */ public ExtendedOpenTelemetrySdk build() { - SdkEntityProvider resourceProvider = resourceProviderBuilder.build(); - SdkTracerProvider tracerProvider = - SdkTracerProviderUtil.setResourceSupplier( - tracerProviderBuilder, resourceProvider::getResource) - .build(); - SdkMeterProvider meterProvider = - SdkMeterProviderUtil.setResourceSupplier( - meterProviderBuilder, resourceProvider::getResource) - .build(); - SdkLoggerProvider loggerProvider = - SdkLoggerProviderUtil.setResourceSupplier( - loggerProviderBuilder, resourceProvider::getResource) - .build(); - return new ObfuscatedExtendedOpenTelemerySdk( - resourceProvider, tracerProvider, meterProvider, loggerProvider, propagators); + SdkTracerProvider tracerProvider = tracerProviderBuilder.build(); + SdkMeterProvider meterProvider = meterProviderBuilder.build(); + SdkLoggerProvider loggerProvider = loggerProviderBuilder.build(); + return new ObfuscatedExtendedOpenTelemetrySdk( + configProvider, tracerProvider, meterProvider, loggerProvider, propagators); } /** diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java similarity index 79% rename from sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java rename to sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java index 17ee5cbe016..69bdc876dba 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemerySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java @@ -6,8 +6,6 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.incubator.config.ConfigProvider; -import io.opentelemetry.api.incubator.entities.EntityBuilder; -import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.api.logs.LoggerBuilder; import io.opentelemetry.api.logs.LoggerProvider; import io.opentelemetry.api.metrics.MeterBuilder; @@ -17,7 +15,6 @@ import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProvider; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -29,24 +26,24 @@ import javax.annotation.concurrent.ThreadSafe; /** The SDK implementation of {@link ExtendedOpenTelemetrySdk}. */ -final class ObfuscatedExtendedOpenTelemerySdk implements ExtendedOpenTelemetrySdk { +final class ObfuscatedExtendedOpenTelemetrySdk implements ExtendedOpenTelemetrySdk { private static final Logger LOGGER = - Logger.getLogger(ObfuscatedExtendedOpenTelemerySdk.class.getName()); + Logger.getLogger(ObfuscatedExtendedOpenTelemetrySdk.class.getName()); private final AtomicBoolean isShutdown = new AtomicBoolean(false); private final ObfuscatedTracerProvider tracerProvider; private final ObfuscatedMeterProvider meterProvider; private final ObfuscatedLoggerProvider loggerProvider; - private final ObfuscatedEntityProvider configProvider; + private final ConfigProvider configProvider; private final ContextPropagators propagators; - ObfuscatedExtendedOpenTelemerySdk( + ObfuscatedExtendedOpenTelemetrySdk( ConfigProvider configProvider, SdkTracerProvider tracerProvider, SdkMeterProvider meterProvider, SdkLoggerProvider loggerProvider, ContextPropagators propagators) { - this.configProvider = new ObfuscatedEntityProvider(configProvider); + this.configProvider = configProvider; this.tracerProvider = new ObfuscatedTracerProvider(tracerProvider); this.meterProvider = new ObfuscatedMeterProvider(meterProvider); this.loggerProvider = new ObfuscatedLoggerProvider(loggerProvider); @@ -99,8 +96,8 @@ public ContextPropagators getPropagators() { @Override public String toString() { return "ExtendedOpenTelemetrySdk{" - + "entityProvider=" - + configProvider.unobfuscate() + + "configProvider=" + + configProvider + ", tracerProvider=" + tracerProvider.unobfuscate() + ", meterProvider=" @@ -202,36 +199,4 @@ public SdkLoggerProvider unobfuscate() { return delegate; } } - - /** - * This class allows the SDK to unobfuscate an obfuscated static global provider. - * - *

Static global providers are obfuscated when they are returned from the API to prevent users - * from casting them to their SDK specific implementation. For example, we do not want users to - * use patterns like {@code (SdkResourceProvider) openTelemetry.getResourceProvider()}. - */ - @ThreadSafe - // Visible for testing - static class ObfuscatedEntityProvider implements EntityProvider { - - private final SdkEntityProvider delegate; - - ObfuscatedEntityProvider(SdkEntityProvider delegate) { - this.delegate = delegate; - } - - public SdkEntityProvider unobfuscate() { - return delegate; - } - - @Override - public boolean removeEntity(String entityType) { - return delegate.removeEntity(entityType); - } - - @Override - public EntityBuilder attachOrUpdateEntity(String entityType) { - return delegate.attachOrUpdateEntity(entityType); - } - } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index c61ecd432b8..979ff6b9b85 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -34,7 +34,7 @@ public OpenTelemetrySdk create( String fileFormat = model.getFileFormat(); if (fileFormat == null || !SUPPORTED_FILE_FORMATS.matcher(fileFormat).matches()) { throw new DeclarativeConfigException( - "Unsupported file format. Supported formats include 0.4, 1.0*"); + "Unsupported file format '" + fileFormat + "'. Supported formats include 0.4, 1.0*"); } // TODO(jack-berg): log warning if version is not exact match, which may result in unexpected // behavior for experimental properties. diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java deleted file mode 100644 index 80c8321ab2a..00000000000 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/TestExtendedOpenTelemetrySdk.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.metrics.LongCounter; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; -import org.junit.jupiter.api.Test; - -class TestExtendedOpenTelemetrySdk { - private final InMemoryMetricReader sdkMeterReader = InMemoryMetricReader.create(); - - @Test - void endToEnd() { - ExtendedOpenTelemetrySdk otel = - ExtendedOpenTelemetrySdk.builder() - .withMeterProvider(builder -> builder.registerMetricReader(sdkMeterReader)) - .build(); - // Generate our first entity. - otel.getEntityProvider() - .attachOrUpdateEntity("test") - .withId(Attributes.builder().put("test.id", 1).build()) - .emit(); - // Write a metric. - Meter meter = otel.getMeterProvider().get("test.scope"); - LongCounter counter = meter.counterBuilder("testCounter").build(); - counter.add(1, Attributes.empty()); - - // Verify we see the entity and the metric. - assertThat(sdkMeterReader.collectAllMetrics()) - .anySatisfy( - metric -> - assertThat(metric) - .hasName("testCounter") - .hasResourceSatisfying( - resource -> - resource.hasAttributesSatisfying( - attributes -> assertThat(attributes).containsEntry("test.id", 1)))); - - // Now update the resource and check the point. - otel.getEntityProvider() - .attachOrUpdateEntity("test2") - .withId(Attributes.builder().put("test2.id", 1).build()) - .emit(); - // Verify we see the new entity and the metric. - assertThat(sdkMeterReader.collectAllMetrics()) - .anySatisfy( - metric -> - assertThat(metric) - .hasName("testCounter") - .hasResourceSatisfying( - resource -> - resource.hasAttributesSatisfying( - attributes -> - assertThat(attributes) - .containsEntry("test.id", 1) - .containsEntry("test2.id", 1)))); - } -} From d5f2ce5b353a0f803d2aa05d2badc71e022e3458 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 10:18:12 +0200 Subject: [PATCH 37/42] extract extended opentelemetry only --- .../incubator/fileconfig/OpenTelemetryConfigurationFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index 979ff6b9b85..c61ecd432b8 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -34,7 +34,7 @@ public OpenTelemetrySdk create( String fileFormat = model.getFileFormat(); if (fileFormat == null || !SUPPORTED_FILE_FORMATS.matcher(fileFormat).matches()) { throw new DeclarativeConfigException( - "Unsupported file format '" + fileFormat + "'. Supported formats include 0.4, 1.0*"); + "Unsupported file format. Supported formats include 0.4, 1.0*"); } // TODO(jack-berg): log warning if version is not exact match, which may result in unexpected // behavior for experimental properties. From 2ff74e2cd2ac67815fcd03d87c3bb99ff954db2e Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 11:02:45 +0200 Subject: [PATCH 38/42] return extended otel sdk --- .../ExtendedOpenTelemetrySdkBuilder.java | 20 ++++++- .../fileconfig/DeclarativeConfigContext.java | 5 ++ .../fileconfig/DeclarativeConfiguration.java | 8 +-- .../fileconfig/LoggerProviderFactory.java | 17 +++--- .../fileconfig/MeterProviderFactory.java | 13 ++--- .../OpenTelemetryConfigurationFactory.java | 52 ++++++++++--------- .../fileconfig/TracerProviderFactory.java | 16 +++--- 7 files changed, 71 insertions(+), 60 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java index cb74295c69b..d673c0dc503 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -16,6 +16,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import java.io.Closeable; import java.util.Objects; import java.util.function.Consumer; @@ -26,6 +27,10 @@ public final class ExtendedOpenTelemetrySdkBuilder { private final SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); private ContextPropagators propagators = ContextPropagators.noop(); private ConfigProvider configProvider = ConfigProvider.noop(); + private Consumer closeableConsumer = + closeable -> { + // Default no-op closeable consumer + }; /** Sets the {@link ContextPropagators} to use. */ public ExtendedOpenTelemetrySdkBuilder setPropagators(ContextPropagators propagators) { @@ -39,6 +44,11 @@ public ExtendedOpenTelemetrySdkBuilder setConfigProvider(ConfigProvider configPr return this; } + public ExtendedOpenTelemetrySdkBuilder setCloseableConsumer(Consumer configurator) { + this.closeableConsumer = Objects.requireNonNull(configurator, "configurator must not be null"); + return this; + } + /** * Applies a consumer callback to configure the TracerProvider being built for this OpenTelemetry. * @@ -88,8 +98,14 @@ public ExtendedOpenTelemetrySdk build() { SdkTracerProvider tracerProvider = tracerProviderBuilder.build(); SdkMeterProvider meterProvider = meterProviderBuilder.build(); SdkLoggerProvider loggerProvider = loggerProviderBuilder.build(); - return new ObfuscatedExtendedOpenTelemetrySdk( - configProvider, tracerProvider, meterProvider, loggerProvider, propagators); + closeableConsumer.accept(tracerProvider); + closeableConsumer.accept(meterProvider); + closeableConsumer.accept(loggerProvider); + ObfuscatedExtendedOpenTelemetrySdk sdk = + new ObfuscatedExtendedOpenTelemetrySdk( + configProvider, tracerProvider, meterProvider, loggerProvider, propagators); + closeableConsumer.accept(sdk); + return sdk; } /** diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java index 9cc697ecaea..8f69f77a205 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java @@ -7,6 +7,7 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; import java.io.Closeable; @@ -39,6 +40,10 @@ List getCloseables() { return Collections.unmodifiableList(closeables); } + ComponentLoader getComponentLoader() { + return spiHelper.getComponentLoader(); + } + /** * Find a registered {@link ComponentProvider} with {@link ComponentProvider#getType()} matching * {@code type}, {@link ComponentProvider#getName()} matching {@code name}, and call {@link diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfiguration.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfiguration.java index 494138bdaa4..ba15984357d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfiguration.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfiguration.java @@ -15,6 +15,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SamplerModel; import io.opentelemetry.sdk.trace.samplers.Sampler; @@ -82,7 +83,7 @@ private DeclarativeConfiguration() {} * * @throws DeclarativeConfigException if unable to parse or interpret */ - public static OpenTelemetrySdk parseAndCreate(InputStream inputStream) { + public static ExtendedOpenTelemetrySdk parseAndCreate(InputStream inputStream) { OpenTelemetryConfigurationModel configurationModel = parse(inputStream); return create(configurationModel); } @@ -95,7 +96,8 @@ public static OpenTelemetrySdk parseAndCreate(InputStream inputStream) { * @return the {@link OpenTelemetrySdk} * @throws DeclarativeConfigException if unable to interpret */ - public static OpenTelemetrySdk create(OpenTelemetryConfigurationModel configurationModel) { + public static ExtendedOpenTelemetrySdk create( + OpenTelemetryConfigurationModel configurationModel) { return create(configurationModel, DEFAULT_COMPONENT_LOADER); } @@ -109,7 +111,7 @@ public static OpenTelemetrySdk create(OpenTelemetryConfigurationModel configurat * @return the {@link OpenTelemetrySdk} * @throws DeclarativeConfigException if unable to interpret */ - public static OpenTelemetrySdk create( + public static ExtendedOpenTelemetrySdk create( OpenTelemetryConfigurationModel configurationModel, ComponentLoader componentLoader) { SpiHelper spiHelper = SpiHelper.create(componentLoader); diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java index 98b06f4700e..638170498bb 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactory.java @@ -15,14 +15,12 @@ import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; import io.opentelemetry.sdk.logs.LogLimits; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.logs.internal.LoggerConfig; import io.opentelemetry.sdk.logs.internal.SdkLoggerProviderUtil; import java.util.List; -final class LoggerProviderFactory - implements Factory { +final class LoggerProviderFactory { private static final LoggerProviderFactory INSTANCE = new LoggerProviderFactory(); @@ -32,14 +30,13 @@ static LoggerProviderFactory getInstance() { return INSTANCE; } - @Override - public SdkLoggerProviderBuilder create( - LoggerProviderAndAttributeLimits model, DeclarativeConfigContext context) { - SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder(); - + public void configure( + SdkLoggerProviderBuilder builder, + LoggerProviderAndAttributeLimits model, + DeclarativeConfigContext context) { LoggerProviderModel loggerProviderModel = model.getLoggerProvider(); if (loggerProviderModel == null) { - return builder; + return; } LogLimits logLimits = @@ -84,8 +81,6 @@ public SdkLoggerProviderBuilder create( } SdkLoggerProviderUtil.setLoggerConfigurator(builder, configuratorBuilder.build()); } - - return builder; } private static class LoggerConfigFactory diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java index a19ab7e5c99..6accc4544bf 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactory.java @@ -17,15 +17,13 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ViewStreamModel; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.export.CardinalityLimitSelector; import io.opentelemetry.sdk.metrics.internal.MeterConfig; import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil; import java.util.List; -final class MeterProviderFactory implements Factory { - +final class MeterProviderFactory { private static final MeterProviderFactory INSTANCE = new MeterProviderFactory(); private MeterProviderFactory() {} @@ -34,11 +32,8 @@ static MeterProviderFactory getInstance() { return INSTANCE; } - @Override - public SdkMeterProviderBuilder create( - MeterProviderModel model, DeclarativeConfigContext context) { - SdkMeterProviderBuilder builder = SdkMeterProvider.builder(); - + public void configure( + SdkMeterProviderBuilder builder, MeterProviderModel model, DeclarativeConfigContext context) { List readerModels = model.getReaders(); if (readerModels != null) { readerModels.forEach( @@ -94,8 +89,6 @@ public SdkMeterProviderBuilder create( } SdkMeterProviderUtil.setMeterConfigurator(builder, configuratorBuilder.build()); } - - return builder; } private static class MeterConfigFactory diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index c61ecd432b8..3b9578b7f32 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -6,15 +6,15 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; import io.opentelemetry.api.incubator.config.DeclarativeConfigException; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdkBuilder; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.resources.Resource; import java.util.Objects; import java.util.regex.Pattern; final class OpenTelemetryConfigurationFactory - implements Factory { + implements Factory { private static final Pattern SUPPORTED_FILE_FORMATS = Pattern.compile("^(0.4)|(1.0(-rc.\\d*)?)$"); @@ -28,9 +28,9 @@ static OpenTelemetryConfigurationFactory getInstance() { } @Override - public OpenTelemetrySdk create( + public ExtendedOpenTelemetrySdk create( OpenTelemetryConfigurationModel model, DeclarativeConfigContext context) { - OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); + ExtendedOpenTelemetrySdkBuilder builder = ExtendedOpenTelemetrySdk.builder(); String fileFormat = model.getFileFormat(); if (fileFormat == null || !SUPPORTED_FILE_FORMATS.matcher(fileFormat).matches()) { throw new DeclarativeConfigException( @@ -43,49 +43,53 @@ public OpenTelemetrySdk create( return builder.build(); } + builder.setCloseableConsumer(context::addCloseable); + builder.setConfigProvider(SdkConfigProvider.create(model, context.getComponentLoader())); + if (model.getPropagator() != null) { builder.setPropagators( PropagatorFactory.getInstance().create(model.getPropagator(), context)); } - Resource resource = Resource.getDefault(); + Resource resource; if (model.getResource() != null) { resource = ResourceFactory.getInstance().create(model.getResource(), context); + } else { + resource = Resource.getDefault(); } if (model.getLoggerProvider() != null) { - builder.setLoggerProvider( - context.addCloseable( + builder.withLoggerProvider( + sdkLoggerProviderBuilder -> LoggerProviderFactory.getInstance() - .create( + .configure( + sdkLoggerProviderBuilder.setResource(resource), LoggerProviderAndAttributeLimits.create( model.getAttributeLimits(), model.getLoggerProvider()), - context) - .setResource(resource) - .build())); + context)); } if (model.getTracerProvider() != null) { - builder.setTracerProvider( - context.addCloseable( + builder.withTracerProvider( + sdkTracerProviderBuilder -> TracerProviderFactory.getInstance() - .create( + .configure( + sdkTracerProviderBuilder.setResource(resource), TracerProviderAndAttributeLimits.create( model.getAttributeLimits(), model.getTracerProvider()), - context) - .setResource(resource) - .build())); + context)); } if (model.getMeterProvider() != null) { - builder.setMeterProvider( - context.addCloseable( + builder.withMeterProvider( + sdkMeterProviderBuilder -> MeterProviderFactory.getInstance() - .create(model.getMeterProvider(), context) - .setResource(resource) - .build())); + .configure( + sdkMeterProviderBuilder.setResource(resource), + model.getMeterProvider(), + context)); } - return context.addCloseable(builder.build()); + return builder.build(); } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java index 68210f92b07..d25d602461d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactory.java @@ -14,7 +14,6 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProviderModel; import io.opentelemetry.sdk.internal.ScopeConfigurator; import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; -import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.SpanLimits; import io.opentelemetry.sdk.trace.internal.SdkTracerProviderUtil; @@ -22,8 +21,7 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import java.util.List; -final class TracerProviderFactory - implements Factory { +final class TracerProviderFactory { private static final TracerProviderFactory INSTANCE = new TracerProviderFactory(); @@ -33,13 +31,13 @@ static TracerProviderFactory getInstance() { return INSTANCE; } - @Override - public SdkTracerProviderBuilder create( - TracerProviderAndAttributeLimits model, DeclarativeConfigContext context) { - SdkTracerProviderBuilder builder = SdkTracerProvider.builder(); + public void configure( + SdkTracerProviderBuilder builder, + TracerProviderAndAttributeLimits model, + DeclarativeConfigContext context) { TracerProviderModel tracerProviderModel = model.getTracerProvider(); if (tracerProviderModel == null) { - return builder; + return; } SpanLimits spanLimits = @@ -90,8 +88,6 @@ public SdkTracerProviderBuilder create( } SdkTracerProviderUtil.setTracerConfigurator(builder, configuratorBuilder.build()); } - - return builder; } private static class TracerConfigFactory From 10d7c476da0e98c78a0f35c974c760bd4a887a89 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 11:25:04 +0200 Subject: [PATCH 39/42] return extended otel sdk --- .../OpenTelemetryConfigurationFactory.java | 20 +- .../fileconfig/SdkConfigProvider.java | 5 + .../DeclarativeConfigurationCreateTest.java | 4 +- .../fileconfig/LoggerProviderFactoryTest.java | 7 +- .../fileconfig/MeterProviderFactoryTest.java | 7 +- ...OpenTelemetryConfigurationFactoryTest.java | 267 +++++++++--------- .../fileconfig/TracerProviderFactoryTest.java | 6 +- 7 files changed, 163 insertions(+), 153 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index 3b9578b7f32..6fc8312bd2d 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -8,7 +8,10 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProviderModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProviderModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProviderModel; import io.opentelemetry.sdk.resources.Resource; import java.util.Objects; import java.util.regex.Pattern; @@ -58,36 +61,37 @@ public ExtendedOpenTelemetrySdk create( resource = Resource.getDefault(); } - if (model.getLoggerProvider() != null) { + LoggerProviderModel loggerProvider = model.getLoggerProvider(); + if (loggerProvider != null) { builder.withLoggerProvider( sdkLoggerProviderBuilder -> LoggerProviderFactory.getInstance() .configure( sdkLoggerProviderBuilder.setResource(resource), LoggerProviderAndAttributeLimits.create( - model.getAttributeLimits(), model.getLoggerProvider()), + model.getAttributeLimits(), loggerProvider), context)); } - if (model.getTracerProvider() != null) { + TracerProviderModel tracerProvider = model.getTracerProvider(); + if (tracerProvider != null) { builder.withTracerProvider( sdkTracerProviderBuilder -> TracerProviderFactory.getInstance() .configure( sdkTracerProviderBuilder.setResource(resource), TracerProviderAndAttributeLimits.create( - model.getAttributeLimits(), model.getTracerProvider()), + model.getAttributeLimits(), tracerProvider), context)); } - if (model.getMeterProvider() != null) { + MeterProviderModel meterProvider = model.getMeterProvider(); + if (meterProvider != null) { builder.withMeterProvider( sdkMeterProviderBuilder -> MeterProviderFactory.getInstance() .configure( - sdkMeterProviderBuilder.setResource(resource), - model.getMeterProvider(), - context)); + sdkMeterProviderBuilder.setResource(resource), meterProvider, context)); } return builder.build(); diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SdkConfigProvider.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SdkConfigProvider.java index 9bc5192a787..561800d9949 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SdkConfigProvider.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SdkConfigProvider.java @@ -50,4 +50,9 @@ public static SdkConfigProvider create( public DeclarativeConfigProperties getInstrumentationConfig() { return instrumentationConfig; } + + @Override + public String toString() { + return "SdkConfigProvider{" + "instrumentationConfig=" + instrumentationConfig + '}'; + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java index b72c0bc8ebe..6c7a01022ba 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java @@ -15,7 +15,7 @@ import io.opentelemetry.api.incubator.config.DeclarativeConfigException; import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.internal.testing.CleanupExtension; -import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProviderModel; @@ -157,7 +157,7 @@ void create_ModelCustomizer() { .withProcessors( Collections.singletonList( new SpanProcessorModel().withAdditionalProperty("test", null)))); - OpenTelemetrySdk sdk = + ExtendedOpenTelemetrySdk sdk = DeclarativeConfiguration.create( model, // customizer is TestDeclarativeConfigurationCustomizerProvider diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java index 65761fd5c9c..5a67b746eee 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LoggerProviderFactoryTest.java @@ -25,6 +25,7 @@ import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; import io.opentelemetry.sdk.logs.LogLimits; import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.logs.internal.LoggerConfig; import java.io.Closeable; import java.util.ArrayList; @@ -50,11 +51,13 @@ void create(LoggerProviderAndAttributeLimits model, SdkLoggerProvider expectedPr List closeables = new ArrayList<>(); cleanup.addCloseable(expectedProvider); - SdkLoggerProvider provider = LoggerProviderFactory.getInstance().create(model, context).build(); + SdkLoggerProviderBuilder builder = SdkLoggerProvider.builder(); + LoggerProviderFactory.getInstance().configure(builder, model, context); + SdkLoggerProvider provider = builder.build(); cleanup.addCloseable(provider); cleanup.addCloseables(closeables); - assertThat(provider.toString()).isEqualTo(expectedProvider.toString()); + assertThat(provider).hasToString(expectedProvider.toString()); } private static Stream createArguments() { diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java index 93abfd7615b..f06a4f755da 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MeterProviderFactoryTest.java @@ -26,6 +26,7 @@ import io.opentelemetry.sdk.internal.ScopeConfiguratorBuilder; import io.opentelemetry.sdk.metrics.InstrumentSelector; import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder; import io.opentelemetry.sdk.metrics.View; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import io.opentelemetry.sdk.metrics.internal.MeterConfig; @@ -53,11 +54,13 @@ void create(MeterProviderModel model, SdkMeterProvider expectedProvider) { List closeables = new ArrayList<>(); cleanup.addCloseable(expectedProvider); - SdkMeterProvider provider = MeterProviderFactory.getInstance().create(model, context).build(); + SdkMeterProviderBuilder builder = SdkMeterProvider.builder(); + MeterProviderFactory.getInstance().configure(builder, model, context); + SdkMeterProvider provider = builder.build(); cleanup.addCloseable(provider); cleanup.addCloseables(closeables); - assertThat(provider.toString()).isEqualTo(expectedProvider.toString()); + assertThat(provider).hasToString(expectedProvider.toString()); } private static Stream createArguments() { diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index ba535627c0a..d499ca22c65 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -24,6 +24,7 @@ import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; +import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOnSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel; @@ -53,11 +54,8 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ViewSelectorModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ViewStreamModel; import io.opentelemetry.sdk.logs.LogLimits; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.InstrumentSelector; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.View; -import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SpanLimits; import java.io.Closeable; import java.util.ArrayList; @@ -120,7 +118,7 @@ void create_Defaults() { OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder().build(); cleanup.addCloseable(expectedSdk); - OpenTelemetrySdk sdk = + ExtendedOpenTelemetrySdk sdk = OpenTelemetryConfigurationFactory.getInstance() .create(new OpenTelemetryConfigurationModel().withFileFormat("1.0-rc.1"), context); cleanup.addCloseable(sdk); @@ -135,7 +133,7 @@ void create_Disabled() { OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder().build(); cleanup.addCloseable(expectedSdk); - OpenTelemetrySdk sdk = + ExtendedOpenTelemetrySdk sdk = OpenTelemetryConfigurationFactory.getInstance() .create( new OpenTelemetryConfigurationModel() @@ -157,7 +155,7 @@ void create_Disabled() { cleanup.addCloseable(sdk); cleanup.addCloseables(closeables); - assertThat(sdk.toString()).isEqualTo(expectedSdk.toString()); + assertThat(sdk).hasToString(expectedSdk.toString()); } @Test @@ -172,8 +170,87 @@ void create_Configured() { .put("shape", "square") .put("order", "second") .build(); - OpenTelemetrySdk expectedSdk = - OpenTelemetrySdk.builder() + + OpenTelemetryConfigurationModel model = + new OpenTelemetryConfigurationModel() + .withFileFormat("1.0-rc.1") + .withPropagator( + new PropagatorModel() + .withCompositeList("tracecontext,baggage,ottrace,b3multi,b3,jaeger")) + .withResource( + new ResourceModel() + .withDetectionDevelopment( + new ExperimentalResourceDetectionModel() + .withDetectors( + Arrays.asList( + new ExperimentalResourceDetectorModel() + .withAdditionalProperty("order_first", null), + new ExperimentalResourceDetectorModel() + .withAdditionalProperty("order_second", null), + new ExperimentalResourceDetectorModel() + .withAdditionalProperty("shape_color", null)))) + .withAttributes( + Arrays.asList( + new AttributeNameValueModel() + .withName("service.name") + .withValue("my-service"), + new AttributeNameValueModel().withName("key").withValue("val")))) + .withLoggerProvider( + new LoggerProviderModel() + .withLimits( + new LogRecordLimitsModel() + .withAttributeValueLengthLimit(1) + .withAttributeCountLimit(2)) + .withProcessors( + Collections.singletonList( + new LogRecordProcessorModel() + .withBatch( + new BatchLogRecordProcessorModel() + .withExporter( + new LogRecordExporterModel() + .withOtlpHttp(new OtlpHttpExporterModel())))))) + .withTracerProvider( + new TracerProviderModel() + .withLimits( + new SpanLimitsModel() + .withAttributeCountLimit(1) + .withAttributeValueLengthLimit(2) + .withEventCountLimit(3) + .withLinkCountLimit(4) + .withEventAttributeCountLimit(5) + .withLinkAttributeCountLimit(6)) + .withSampler(new SamplerModel().withAlwaysOn(new AlwaysOnSamplerModel())) + .withProcessors( + Collections.singletonList( + new SpanProcessorModel() + .withBatch( + new BatchSpanProcessorModel() + .withExporter( + new SpanExporterModel() + .withOtlpHttp(new OtlpHttpExporterModel())))))) + .withMeterProvider( + new MeterProviderModel() + .withReaders( + Collections.singletonList( + new MetricReaderModel() + .withPeriodic( + new PeriodicMetricReaderModel() + .withExporter( + new PushMetricExporterModel() + .withOtlpHttp(new OtlpHttpMetricExporterModel()))))) + .withViews( + Collections.singletonList( + new ViewModel() + .withSelector( + new ViewSelectorModel().withInstrumentName("instrument-name")) + .withStream( + new ViewStreamModel() + .withName("stream-name") + .withAttributeKeys(null))))); + + ExtendedOpenTelemetrySdk expectedSdk = + ExtendedOpenTelemetrySdk.builder() + .setConfigProvider(SdkConfigProvider.create(model)) .setPropagators( ContextPropagators.create( TextMapPropagator.composite( @@ -183,141 +260,57 @@ void create_Configured() { B3Propagator.injectingMultiHeaders(), B3Propagator.injectingSingleHeader(), JaegerPropagator.getInstance()))) - .setLoggerProvider( - SdkLoggerProvider.builder() - .setResource(expectedResource) - .setLogLimits( - () -> - LogLimits.builder() - .setMaxAttributeValueLength(1) - .setMaxNumberOfAttributes(2) + .withLoggerProvider( + sdkLoggerProviderBuilder -> + sdkLoggerProviderBuilder + .setResource(expectedResource) + .setLogLimits( + () -> + LogLimits.builder() + .setMaxAttributeValueLength(1) + .setMaxNumberOfAttributes(2) + .build()) + .addLogRecordProcessor( + io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor.builder( + OtlpHttpLogRecordExporter.getDefault()) + .build())) + .withTracerProvider( + sdkTracerProviderBuilder -> + sdkTracerProviderBuilder + .setResource(expectedResource) + .setSpanLimits( + SpanLimits.builder() + .setMaxNumberOfAttributes(1) + .setMaxAttributeValueLength(2) + .setMaxNumberOfEvents(3) + .setMaxNumberOfLinks(4) + .setMaxNumberOfAttributesPerEvent(5) + .setMaxNumberOfAttributesPerLink(6) + .build()) + .setSampler(alwaysOn()) + .addSpanProcessor( + io.opentelemetry.sdk.trace.export.BatchSpanProcessor.builder( + OtlpHttpSpanExporter.getDefault()) + .build())) + .withMeterProvider( + sdkMeterProviderBuilder -> + sdkMeterProviderBuilder + .setResource(expectedResource) + .registerMetricReader( + io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( + OtlpHttpMetricExporter.getDefault()) .build()) - .addLogRecordProcessor( - io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor.builder( - OtlpHttpLogRecordExporter.getDefault()) - .build()) - .build()) - .setTracerProvider( - SdkTracerProvider.builder() - .setResource(expectedResource) - .setSpanLimits( - SpanLimits.builder() - .setMaxNumberOfAttributes(1) - .setMaxAttributeValueLength(2) - .setMaxNumberOfEvents(3) - .setMaxNumberOfLinks(4) - .setMaxNumberOfAttributesPerEvent(5) - .setMaxNumberOfAttributesPerLink(6) - .build()) - .setSampler(alwaysOn()) - .addSpanProcessor( - io.opentelemetry.sdk.trace.export.BatchSpanProcessor.builder( - OtlpHttpSpanExporter.getDefault()) - .build()) - .build()) - .setMeterProvider( - SdkMeterProvider.builder() - .setResource(expectedResource) - .registerMetricReader( - io.opentelemetry.sdk.metrics.export.PeriodicMetricReader.builder( - OtlpHttpMetricExporter.getDefault()) - .build()) - .registerView( - InstrumentSelector.builder().setName("instrument-name").build(), - View.builder().setName("stream-name").build()) - .build()) + .registerView( + InstrumentSelector.builder().setName("instrument-name").build(), + View.builder().setName("stream-name").build())) .build(); cleanup.addCloseable(expectedSdk); - OpenTelemetrySdk sdk = - OpenTelemetryConfigurationFactory.getInstance() - .create( - new OpenTelemetryConfigurationModel() - .withFileFormat("1.0-rc.1") - .withPropagator( - new PropagatorModel() - .withCompositeList("tracecontext,baggage,ottrace,b3multi,b3,jaeger")) - .withResource( - new ResourceModel() - .withDetectionDevelopment( - new ExperimentalResourceDetectionModel() - .withDetectors( - Arrays.asList( - new ExperimentalResourceDetectorModel() - .withAdditionalProperty("order_first", null), - new ExperimentalResourceDetectorModel() - .withAdditionalProperty("order_second", null), - new ExperimentalResourceDetectorModel() - .withAdditionalProperty("shape_color", null)))) - .withAttributes( - Arrays.asList( - new AttributeNameValueModel() - .withName("service.name") - .withValue("my-service"), - new AttributeNameValueModel() - .withName("key") - .withValue("val")))) - .withLoggerProvider( - new LoggerProviderModel() - .withLimits( - new LogRecordLimitsModel() - .withAttributeValueLengthLimit(1) - .withAttributeCountLimit(2)) - .withProcessors( - Collections.singletonList( - new LogRecordProcessorModel() - .withBatch( - new BatchLogRecordProcessorModel() - .withExporter( - new LogRecordExporterModel() - .withOtlpHttp( - new OtlpHttpExporterModel())))))) - .withTracerProvider( - new TracerProviderModel() - .withLimits( - new SpanLimitsModel() - .withAttributeCountLimit(1) - .withAttributeValueLengthLimit(2) - .withEventCountLimit(3) - .withLinkCountLimit(4) - .withEventAttributeCountLimit(5) - .withLinkAttributeCountLimit(6)) - .withSampler( - new SamplerModel().withAlwaysOn(new AlwaysOnSamplerModel())) - .withProcessors( - Collections.singletonList( - new SpanProcessorModel() - .withBatch( - new BatchSpanProcessorModel() - .withExporter( - new SpanExporterModel() - .withOtlpHttp( - new OtlpHttpExporterModel())))))) - .withMeterProvider( - new MeterProviderModel() - .withReaders( - Collections.singletonList( - new MetricReaderModel() - .withPeriodic( - new PeriodicMetricReaderModel() - .withExporter( - new PushMetricExporterModel() - .withOtlpHttp( - new OtlpHttpMetricExporterModel()))))) - .withViews( - Collections.singletonList( - new ViewModel() - .withSelector( - new ViewSelectorModel() - .withInstrumentName("instrument-name")) - .withStream( - new ViewStreamModel() - .withName("stream-name") - .withAttributeKeys(null))))), - context); + ExtendedOpenTelemetrySdk sdk = + OpenTelemetryConfigurationFactory.getInstance().create(model, context); cleanup.addCloseable(sdk); cleanup.addCloseables(closeables); - assertThat(sdk.toString()).isEqualTo(expectedSdk.toString()); + assertThat(sdk).hasToString(expectedSdk.toString()); } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java index d565b408baa..19e59443c53 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/TracerProviderFactoryTest.java @@ -54,11 +54,13 @@ void create(TracerProviderAndAttributeLimits model, SdkTracerProvider expectedPr List closeables = new ArrayList<>(); cleanup.addCloseable(expectedProvider); - SdkTracerProvider provider = TracerProviderFactory.getInstance().create(model, context).build(); + SdkTracerProviderBuilder builder = SdkTracerProvider.builder(); + TracerProviderFactory.getInstance().configure(builder, model, context); + SdkTracerProvider provider = builder.build(); cleanup.addCloseable(provider); cleanup.addCloseables(closeables); - assertThat(provider.toString()).isEqualTo(expectedProvider.toString()); + assertThat(provider).hasToString(expectedProvider.toString()); } private static Stream createArguments() { From 215f83252428257766a3bd0fdc725cbeb4aee0c4 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 11:29:00 +0200 Subject: [PATCH 40/42] return extended otel sdk --- .../OpenTelemetryConfigurationFactoryTest.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index d499ca22c65..1711c049bcf 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -22,7 +22,6 @@ import io.opentelemetry.extension.trace.propagation.JaegerPropagator; import io.opentelemetry.extension.trace.propagation.OtTracePropagator; import io.opentelemetry.internal.testing.CleanupExtension; -import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOnSamplerModel; @@ -115,22 +114,26 @@ private static Stream fileFormatArgs() { @Test void create_Defaults() { List closeables = new ArrayList<>(); - OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder().build(); + OpenTelemetryConfigurationModel model = + new OpenTelemetryConfigurationModel().withFileFormat("1.0-rc.1"); + ExtendedOpenTelemetrySdk expectedSdk = + ExtendedOpenTelemetrySdk.builder() + .setConfigProvider(SdkConfigProvider.create(model)) + .build(); cleanup.addCloseable(expectedSdk); ExtendedOpenTelemetrySdk sdk = - OpenTelemetryConfigurationFactory.getInstance() - .create(new OpenTelemetryConfigurationModel().withFileFormat("1.0-rc.1"), context); + OpenTelemetryConfigurationFactory.getInstance().create(model, context); cleanup.addCloseable(sdk); cleanup.addCloseables(closeables); - assertThat(sdk.toString()).isEqualTo(expectedSdk.toString()); + assertThat(sdk).hasToString(expectedSdk.toString()); } @Test void create_Disabled() { List closeables = new ArrayList<>(); - OpenTelemetrySdk expectedSdk = OpenTelemetrySdk.builder().build(); + ExtendedOpenTelemetrySdk expectedSdk = ExtendedOpenTelemetrySdk.builder().build(); cleanup.addCloseable(expectedSdk); ExtendedOpenTelemetrySdk sdk = From c3e86daa414cf3609583fcb76919e454efc14e46 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 12:43:53 +0200 Subject: [PATCH 41/42] obfuscate sdk config provider --- .../ExtendedOpenTelemetrySdkBuilder.java | 6 ++- .../ObfuscatedExtendedOpenTelemetrySdk.java | 43 +++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java index d673c0dc503..f16f5fee852 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -10,6 +10,7 @@ import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.metrics.SdkMeterProvider; @@ -19,6 +20,7 @@ import java.io.Closeable; import java.util.Objects; import java.util.function.Consumer; +import javax.annotation.Nullable; /** A new interface for creating OpenTelemetrySdk that supports {@link ConfigProvider}. */ public final class ExtendedOpenTelemetrySdkBuilder { @@ -26,7 +28,7 @@ public final class ExtendedOpenTelemetrySdkBuilder { private final SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); private final SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); private ContextPropagators propagators = ContextPropagators.noop(); - private ConfigProvider configProvider = ConfigProvider.noop(); + @Nullable private SdkConfigProvider configProvider; private Consumer closeableConsumer = closeable -> { // Default no-op closeable consumer @@ -39,7 +41,7 @@ public ExtendedOpenTelemetrySdkBuilder setPropagators(ContextPropagators propaga } /** Sets the {@link ConfigProvider} to use. */ - public ExtendedOpenTelemetrySdkBuilder setConfigProvider(ConfigProvider configProvider) { + public ExtendedOpenTelemetrySdkBuilder setConfigProvider(SdkConfigProvider configProvider) { this.configProvider = Objects.requireNonNull(configProvider, "configProvider must not be null"); return this; } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java index 69bdc876dba..20a276f361a 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator; import io.opentelemetry.api.incubator.config.ConfigProvider; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.api.logs.LoggerBuilder; import io.opentelemetry.api.logs.LoggerProvider; import io.opentelemetry.api.metrics.MeterBuilder; @@ -15,6 +16,7 @@ import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; @@ -23,6 +25,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; +import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; /** The SDK implementation of {@link ExtendedOpenTelemetrySdk}. */ @@ -34,16 +37,16 @@ final class ObfuscatedExtendedOpenTelemetrySdk implements ExtendedOpenTelemetryS private final ObfuscatedTracerProvider tracerProvider; private final ObfuscatedMeterProvider meterProvider; private final ObfuscatedLoggerProvider loggerProvider; - private final ConfigProvider configProvider; + private final ObfuscatedConfigProvider configProvider; private final ContextPropagators propagators; ObfuscatedExtendedOpenTelemetrySdk( - ConfigProvider configProvider, + @Nullable SdkConfigProvider configProvider, SdkTracerProvider tracerProvider, SdkMeterProvider meterProvider, SdkLoggerProvider loggerProvider, ContextPropagators propagators) { - this.configProvider = configProvider; + this.configProvider = new ObfuscatedConfigProvider(configProvider); this.tracerProvider = new ObfuscatedTracerProvider(tracerProvider); this.meterProvider = new ObfuscatedMeterProvider(meterProvider); this.loggerProvider = new ObfuscatedLoggerProvider(loggerProvider); @@ -97,7 +100,7 @@ public ContextPropagators getPropagators() { public String toString() { return "ExtendedOpenTelemetrySdk{" + "configProvider=" - + configProvider + + configProvider.unobfuscate() + ", tracerProvider=" + tracerProvider.unobfuscate() + ", meterProvider=" @@ -199,4 +202,36 @@ public SdkLoggerProvider unobfuscate() { return delegate; } } + + /** + * This class allows the SDK to unobfuscate an obfuscated static global provider. + * + *

Static global providers are obfuscated when they are returned from the API to prevent users + * from casting them to their SDK specific implementation. For example, we do not want users to + * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. + */ + @ThreadSafe + // Visible for testing + static class ObfuscatedConfigProvider implements ConfigProvider { + + @Nullable private final SdkConfigProvider delegate; + + ObfuscatedConfigProvider(@Nullable SdkConfigProvider delegate) { + this.delegate = delegate; + } + + @Override + @Nullable + public DeclarativeConfigProperties getInstrumentationConfig() { + if (delegate == null) { + return null; + } + return delegate.getInstrumentationConfig(); + } + + @Nullable + public SdkConfigProvider unobfuscate() { + return delegate; + } + } } From 4b7c986ebba965afb9bfe0492841831c77fd088c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 16 Jul 2025 16:05:52 +0200 Subject: [PATCH 42/42] extended otel must extend otel - because we need AutoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk() to return the extended instance --- .../current_vs_latest/opentelemetry-sdk.txt | 3 +- .../incubator/ExtendedOpenTelemetrySdk.java | 60 ++++- .../ExtendedOpenTelemetrySdkBuilder.java | 13 +- .../ObfuscatedExtendedOpenTelemetrySdk.java | 237 ------------------ .../OpenTelemetryConfigurationFactory.java | 2 +- ...OpenTelemetryConfigurationFactoryTest.java | 7 +- .../opentelemetry/sdk/OpenTelemetrySdk.java | 4 +- 7 files changed, 67 insertions(+), 259 deletions(-) delete mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt index 407cc500462..0126773a2c9 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk.txt @@ -1,2 +1,3 @@ Comparing source compatibility of opentelemetry-sdk-1.53.0-SNAPSHOT.jar against opentelemetry-sdk-1.52.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC NON_FINAL (<- FINAL) io.opentelemetry.sdk.OpenTelemetrySdk (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java index ffb5511521b..4a3307cd901 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdk.java @@ -7,24 +7,66 @@ import io.opentelemetry.api.incubator.ExtendedOpenTelemetry; import io.opentelemetry.api.incubator.config.ConfigProvider; -import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.trace.SdkTracerProvider; import java.io.Closeable; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; /** A new interface for creating OpenTelemetrySdk that supports getting {@link ConfigProvider}. */ -public interface ExtendedOpenTelemetrySdk extends ExtendedOpenTelemetry, Closeable { +public class ExtendedOpenTelemetrySdk extends OpenTelemetrySdk + implements ExtendedOpenTelemetry, Closeable { + + private final ObfuscatedConfigProvider configProvider; + + public ExtendedOpenTelemetrySdk( + SdkTracerProvider tracerProvider, + SdkMeterProvider meterProvider, + SdkLoggerProvider loggerProvider, + ContextPropagators propagators, + SdkConfigProvider configProvider) { + super(tracerProvider, meterProvider, loggerProvider, propagators); + this.configProvider = new ObfuscatedConfigProvider(configProvider); + } + + @Override + public ConfigProvider getConfigProvider() { + return configProvider.unobfuscate(); + } + /** - * Shutdown the SDK. Calls {@link SdkTracerProvider#shutdown()}, {@link - * SdkMeterProvider#shutdown()}, and {@link SdkLoggerProvider#shutdown()}. + * This class allows the SDK to unobfuscate an obfuscated static global provider. * - * @return a {@link CompletableResultCode} which completes when all providers are shutdown + *

Static global providers are obfuscated when they are returned from the API to prevent users + * from casting them to their SDK specific implementation. For example, we do not want users to + * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. */ - CompletableResultCode shutdown(); + @ThreadSafe + // Visible for testing + static class ObfuscatedConfigProvider implements ConfigProvider { + + private final SdkConfigProvider delegate; + + ObfuscatedConfigProvider(SdkConfigProvider delegate) { + this.delegate = delegate; + } + + @Override + @Nullable + public DeclarativeConfigProperties getInstrumentationConfig() { + if (delegate == null) { + return null; + } + return delegate.getInstrumentationConfig(); + } - /** Returns a builder for {@link ExtendedOpenTelemetrySdk}. */ - static ExtendedOpenTelemetrySdkBuilder builder() { - return new ExtendedOpenTelemetrySdkBuilder(); + public SdkConfigProvider unobfuscate() { + return delegate; + } } } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java index f16f5fee852..f18ba80e533 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ExtendedOpenTelemetrySdkBuilder.java @@ -11,6 +11,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; import io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; import io.opentelemetry.sdk.metrics.SdkMeterProvider; @@ -20,15 +21,15 @@ import java.io.Closeable; import java.util.Objects; import java.util.function.Consumer; -import javax.annotation.Nullable; -/** A new interface for creating OpenTelemetrySdk that supports {@link ConfigProvider}. */ +/** An builder for creating an {@link ExtendedOpenTelemetrySdk} instance. */ public final class ExtendedOpenTelemetrySdkBuilder { private final SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); private final SdkMeterProviderBuilder meterProviderBuilder = SdkMeterProvider.builder(); private final SdkLoggerProviderBuilder loggerProviderBuilder = SdkLoggerProvider.builder(); private ContextPropagators propagators = ContextPropagators.noop(); - @Nullable private SdkConfigProvider configProvider; + private SdkConfigProvider configProvider = + SdkConfigProvider.create(new OpenTelemetryConfigurationModel()); private Consumer closeableConsumer = closeable -> { // Default no-op closeable consumer @@ -103,9 +104,9 @@ public ExtendedOpenTelemetrySdk build() { closeableConsumer.accept(tracerProvider); closeableConsumer.accept(meterProvider); closeableConsumer.accept(loggerProvider); - ObfuscatedExtendedOpenTelemetrySdk sdk = - new ObfuscatedExtendedOpenTelemetrySdk( - configProvider, tracerProvider, meterProvider, loggerProvider, propagators); + ExtendedOpenTelemetrySdk sdk = + new ExtendedOpenTelemetrySdk( + tracerProvider, meterProvider, loggerProvider, propagators, configProvider); closeableConsumer.accept(sdk); return sdk; } diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java deleted file mode 100644 index 20a276f361a..00000000000 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/ObfuscatedExtendedOpenTelemetrySdk.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.sdk.extension.incubator; - -import io.opentelemetry.api.incubator.config.ConfigProvider; -import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; -import io.opentelemetry.api.logs.LoggerBuilder; -import io.opentelemetry.api.logs.LoggerProvider; -import io.opentelemetry.api.metrics.MeterBuilder; -import io.opentelemetry.api.metrics.MeterProvider; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.api.trace.TracerBuilder; -import io.opentelemetry.api.trace.TracerProvider; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.extension.incubator.fileconfig.SdkConfigProvider; -import io.opentelemetry.sdk.logs.SdkLoggerProvider; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Logger; -import javax.annotation.Nullable; -import javax.annotation.concurrent.ThreadSafe; - -/** The SDK implementation of {@link ExtendedOpenTelemetrySdk}. */ -final class ObfuscatedExtendedOpenTelemetrySdk implements ExtendedOpenTelemetrySdk { - - private static final Logger LOGGER = - Logger.getLogger(ObfuscatedExtendedOpenTelemetrySdk.class.getName()); - private final AtomicBoolean isShutdown = new AtomicBoolean(false); - private final ObfuscatedTracerProvider tracerProvider; - private final ObfuscatedMeterProvider meterProvider; - private final ObfuscatedLoggerProvider loggerProvider; - private final ObfuscatedConfigProvider configProvider; - private final ContextPropagators propagators; - - ObfuscatedExtendedOpenTelemetrySdk( - @Nullable SdkConfigProvider configProvider, - SdkTracerProvider tracerProvider, - SdkMeterProvider meterProvider, - SdkLoggerProvider loggerProvider, - ContextPropagators propagators) { - this.configProvider = new ObfuscatedConfigProvider(configProvider); - this.tracerProvider = new ObfuscatedTracerProvider(tracerProvider); - this.meterProvider = new ObfuscatedMeterProvider(meterProvider); - this.loggerProvider = new ObfuscatedLoggerProvider(loggerProvider); - this.propagators = propagators; - } - - @Override - public CompletableResultCode shutdown() { - if (!isShutdown.compareAndSet(false, true)) { - LOGGER.info("Multiple shutdown calls"); - return CompletableResultCode.ofSuccess(); - } - List results = new ArrayList<>(); - results.add(tracerProvider.unobfuscate().shutdown()); - results.add(meterProvider.unobfuscate().shutdown()); - results.add(loggerProvider.unobfuscate().shutdown()); - return CompletableResultCode.ofAll(results); - } - - @Override - public void close() { - shutdown().join(10, TimeUnit.SECONDS); - } - - @Override - public ConfigProvider getConfigProvider() { - return configProvider; - } - - @Override - public TracerProvider getTracerProvider() { - return tracerProvider; - } - - @Override - public MeterProvider getMeterProvider() { - return meterProvider; - } - - @Override - public LoggerProvider getLogsBridge() { - return loggerProvider; - } - - @Override - public ContextPropagators getPropagators() { - return propagators; - } - - @Override - public String toString() { - return "ExtendedOpenTelemetrySdk{" - + "configProvider=" - + configProvider.unobfuscate() - + ", tracerProvider=" - + tracerProvider.unobfuscate() - + ", meterProvider=" - + meterProvider.unobfuscate() - + ", loggerProvider=" - + loggerProvider.unobfuscate() - + ", propagators=" - + propagators - + "}"; - } - - /** - * This class allows the SDK to unobfuscate an obfuscated static global provider. - * - *

Static global providers are obfuscated when they are returned from the API to prevent users - * from casting them to their SDK specific implementation. For example, we do not want users to - * use patterns like {@code (SdkTracerProvider) openTelemetry.getTracerProvider()}. - */ - @ThreadSafe - // Visible for testing - static class ObfuscatedTracerProvider implements TracerProvider { - - private final SdkTracerProvider delegate; - - ObfuscatedTracerProvider(SdkTracerProvider delegate) { - this.delegate = delegate; - } - - @Override - public Tracer get(String instrumentationScopeName) { - return delegate.get(instrumentationScopeName); - } - - @Override - public Tracer get(String instrumentationScopeName, String instrumentationScopeVersion) { - return delegate.get(instrumentationScopeName, instrumentationScopeVersion); - } - - @Override - public TracerBuilder tracerBuilder(String instrumentationScopeName) { - return delegate.tracerBuilder(instrumentationScopeName); - } - - public SdkTracerProvider unobfuscate() { - return delegate; - } - } - - /** - * This class allows the SDK to unobfuscate an obfuscated static global provider. - * - *

Static global providers are obfuscated when they are returned from the API to prevent users - * from casting them to their SDK specific implementation. For example, we do not want users to - * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. - */ - @ThreadSafe - // Visible for testing - static class ObfuscatedMeterProvider implements MeterProvider { - - private final SdkMeterProvider delegate; - - ObfuscatedMeterProvider(SdkMeterProvider delegate) { - this.delegate = delegate; - } - - @Override - public MeterBuilder meterBuilder(String instrumentationScopeName) { - return delegate.meterBuilder(instrumentationScopeName); - } - - public SdkMeterProvider unobfuscate() { - return delegate; - } - } - - /** - * This class allows the SDK to unobfuscate an obfuscated static global provider. - * - *

Static global providers are obfuscated when they are returned from the API to prevent users - * from casting them to their SDK specific implementation. For example, we do not want users to - * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. - */ - @ThreadSafe - // Visible for testing - static class ObfuscatedLoggerProvider implements LoggerProvider { - - private final SdkLoggerProvider delegate; - - ObfuscatedLoggerProvider(SdkLoggerProvider delegate) { - this.delegate = delegate; - } - - @Override - public LoggerBuilder loggerBuilder(String instrumentationScopeName) { - return delegate.loggerBuilder(instrumentationScopeName); - } - - public SdkLoggerProvider unobfuscate() { - return delegate; - } - } - - /** - * This class allows the SDK to unobfuscate an obfuscated static global provider. - * - *

Static global providers are obfuscated when they are returned from the API to prevent users - * from casting them to their SDK specific implementation. For example, we do not want users to - * use patterns like {@code (SdkMeterProvider) openTelemetry.getMeterProvider()}. - */ - @ThreadSafe - // Visible for testing - static class ObfuscatedConfigProvider implements ConfigProvider { - - @Nullable private final SdkConfigProvider delegate; - - ObfuscatedConfigProvider(@Nullable SdkConfigProvider delegate) { - this.delegate = delegate; - } - - @Override - @Nullable - public DeclarativeConfigProperties getInstrumentationConfig() { - if (delegate == null) { - return null; - } - return delegate.getInstrumentationConfig(); - } - - @Nullable - public SdkConfigProvider unobfuscate() { - return delegate; - } - } -} diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java index 6fc8312bd2d..f07a0d14c99 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactory.java @@ -33,7 +33,7 @@ static OpenTelemetryConfigurationFactory getInstance() { @Override public ExtendedOpenTelemetrySdk create( OpenTelemetryConfigurationModel model, DeclarativeConfigContext context) { - ExtendedOpenTelemetrySdkBuilder builder = ExtendedOpenTelemetrySdk.builder(); + ExtendedOpenTelemetrySdkBuilder builder = new ExtendedOpenTelemetrySdkBuilder(); String fileFormat = model.getFileFormat(); if (fileFormat == null || !SUPPORTED_FILE_FORMATS.matcher(fileFormat).matches()) { throw new DeclarativeConfigException( diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java index 1711c049bcf..1a6d6483812 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/OpenTelemetryConfigurationFactoryTest.java @@ -24,6 +24,7 @@ import io.opentelemetry.internal.testing.CleanupExtension; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdk; +import io.opentelemetry.sdk.extension.incubator.ExtendedOpenTelemetrySdkBuilder; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOnSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AttributeNameValueModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel; @@ -117,7 +118,7 @@ void create_Defaults() { OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel().withFileFormat("1.0-rc.1"); ExtendedOpenTelemetrySdk expectedSdk = - ExtendedOpenTelemetrySdk.builder() + new ExtendedOpenTelemetrySdkBuilder() .setConfigProvider(SdkConfigProvider.create(model)) .build(); cleanup.addCloseable(expectedSdk); @@ -133,7 +134,7 @@ void create_Defaults() { @Test void create_Disabled() { List closeables = new ArrayList<>(); - ExtendedOpenTelemetrySdk expectedSdk = ExtendedOpenTelemetrySdk.builder().build(); + ExtendedOpenTelemetrySdk expectedSdk = new ExtendedOpenTelemetrySdkBuilder().build(); cleanup.addCloseable(expectedSdk); ExtendedOpenTelemetrySdk sdk = @@ -252,7 +253,7 @@ void create_Configured() { .withAttributeKeys(null))))); ExtendedOpenTelemetrySdk expectedSdk = - ExtendedOpenTelemetrySdk.builder() + new ExtendedOpenTelemetrySdkBuilder() .setConfigProvider(SdkConfigProvider.create(model)) .setPropagators( ContextPropagators.create( diff --git a/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java b/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java index 81c1c3dd8c1..225972a09c3 100644 --- a/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java +++ b/sdk/all/src/main/java/io/opentelemetry/sdk/OpenTelemetrySdk.java @@ -28,7 +28,7 @@ /** The SDK implementation of {@link OpenTelemetry}. */ @ThreadSafe -public final class OpenTelemetrySdk implements OpenTelemetry, Closeable { +public class OpenTelemetrySdk implements OpenTelemetry, Closeable { private static final Logger LOGGER = Logger.getLogger(OpenTelemetrySdk.class.getName()); @@ -38,7 +38,7 @@ public final class OpenTelemetrySdk implements OpenTelemetry, Closeable { private final ObfuscatedLoggerProvider loggerProvider; private final ContextPropagators propagators; - OpenTelemetrySdk( + protected OpenTelemetrySdk( SdkTracerProvider tracerProvider, SdkMeterProvider meterProvider, SdkLoggerProvider loggerProvider,