From f0562987633ffbd9595c60d1c4406947ef54b3de Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 18 Jun 2025 10:13:39 -0400 Subject: [PATCH 01/35] 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 5460d5eded6aba4d6126f2b92e9492b638f2bcff Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 12:29:44 -0400 Subject: [PATCH 02/35] Add base entity class, merge logic and test --- .../opentelemetry-sdk-common.txt | 12 +- .../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 + 13 files changed, 444 insertions(+), 86 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/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index 5d251deb80c..c34d159f7a2 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,2 +1,12 @@ Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar -No changes. \ No newline at end of file +***! MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW INTERFACE: io.opentelemetry.sdk.resources.internal.Resource + ---! REMOVED METHOD: PUBLIC(-) java.lang.Object getAttribute(io.opentelemetry.api.common.AttributeKey) + --- REMOVED ANNOTATION: javax.annotation.Nullable + GENERIC TEMPLATES: --- T:java.lang.Object + ---! REMOVED METHOD: PUBLIC(-) io.opentelemetry.sdk.resources.Resource merge(io.opentelemetry.sdk.resources.Resource) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.Resource merge(io.opentelemetry.sdk.resources.internal.Resource) +*** MODIFIED CLASS: PUBLIC io.opentelemetry.sdk.resources.ResourceBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW INTERFACE: io.opentelemetry.sdk.resources.internal.ResourceBuilder 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 664ad1bd4c2..a228cf93f38 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 @@ -123,7 +123,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()); } } @@ -207,7 +208,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); @@ -223,7 +224,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)); } @@ -240,7 +241,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); @@ -256,7 +257,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)); } @@ -277,7 +278,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)); } @@ -299,7 +300,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; } @@ -313,7 +314,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)); } @@ -357,7 +358,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)); } @@ -435,7 +436,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) { @@ -446,7 +447,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); } @@ -491,7 +492,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) { @@ -524,7 +525,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 ce0e66db0bb8d8500bcfc221aacee18a4ab4fd94 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 12:38:30 -0400 Subject: [PATCH 03/35] 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 cb742863e2dc2764d9674ecbad8751c38c6578b6 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 13:47:07 -0400 Subject: [PATCH 04/35] 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 3369be39d9fee373ac321ec5c8dab5a0a45e7ea4 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 13:53:41 -0400 Subject: [PATCH 05/35] 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 2c4aaebfb527f9f0f3b9b65fa1783c659a0fa899 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 14:01:04 -0400 Subject: [PATCH 06/35] Cleanups and reverting silly changes --- .../opentelemetry-sdk-common.txt | 12 +-------- .../opentelemetry-sdk-testing.txt | 10 ++++++- .../prometheus/Otel2PrometheusConverter.java | 27 +++++++++---------- .../prometheus/CollectorIntegrationTest.java | 8 +++--- .../sdk/testing/assertj/EntityAssert.java | 4 +-- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index c34d159f7a2..5d251deb80c 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,12 +1,2 @@ Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar -***! MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW INTERFACE: io.opentelemetry.sdk.resources.internal.Resource - ---! REMOVED METHOD: PUBLIC(-) java.lang.Object getAttribute(io.opentelemetry.api.common.AttributeKey) - --- REMOVED ANNOTATION: javax.annotation.Nullable - GENERIC TEMPLATES: --- T:java.lang.Object - ---! REMOVED METHOD: PUBLIC(-) io.opentelemetry.sdk.resources.Resource merge(io.opentelemetry.sdk.resources.Resource) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.Resource merge(io.opentelemetry.sdk.resources.internal.Resource) -*** MODIFIED CLASS: PUBLIC io.opentelemetry.sdk.resources.ResourceBuilder (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW INTERFACE: io.opentelemetry.sdk.resources.internal.ResourceBuilder +No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt index 2c87bd8600d..32dcc99118b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt @@ -1,2 +1,10 @@ Comparing source compatibility of opentelemetry-sdk-testing-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-testing-1.51.0.jar -No changes. \ No newline at end of file ++++ NEW CLASS: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasDescriptionSatisfying(org.assertj.core.api.ThrowingConsumer) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasIdSatisfying(org.assertj.core.api.ThrowingConsumer) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasSchemaUrl(java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasType(java.lang.String) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert assertThat(io.opentelemetry.sdk.resources.internal.Entity) 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 a228cf93f38..664ad1bd4c2 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 @@ -123,8 +123,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()); } } @@ -208,7 +207,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); @@ -224,7 +223,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)); } @@ -241,7 +240,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); @@ -257,7 +256,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)); } @@ -278,7 +277,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)); } @@ -300,7 +299,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; } @@ -314,7 +313,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)); } @@ -358,7 +357,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)); } @@ -436,7 +435,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) { @@ -447,7 +446,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); } @@ -492,7 +491,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) { @@ -525,7 +524,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 8af2cd1452c4281c724759560a893a742bce3fb5 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 14:25:28 -0400 Subject: [PATCH 07/35] 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 da525bd22e1bbc8b4216f1e39701d77f334180be Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 22:06:39 -0400 Subject: [PATCH 08/35] 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 --- .../opentelemetry-sdk-common.txt | 11 ++- .../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 +-- 14 files changed, 169 insertions(+), 170 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/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index 5d251deb80c..86ea044c753 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,2 +1,11 @@ Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar -No changes. \ No newline at end of file +**** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.resources.Resource create(io.opentelemetry.api.common.Attributes, java.lang.String, java.util.Collection) + *** MODIFIED METHOD: PUBLIC NON_ABSTRACT (<- ABSTRACT) io.opentelemetry.api.common.Attributes getAttributes() + +++* NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Collection getEntities() + +++* NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.Attributes getRawAttributes() +*** MODIFIED CLASS: PUBLIC io.opentelemetry.sdk.resources.ResourceBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.ResourceBuilder add(io.opentelemetry.sdk.resources.internal.Entity) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.ResourceBuilder addAll(java.util.Collection) 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 2429ae150a2..7057d17c099 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 cb0f08a86b5..685c23b9e4f 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 b11956164fd25a1d2cd0795749c607808b4554a9 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 19 Jun 2025 22:17:50 -0400 Subject: [PATCH 09/35] 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 ddf096dd938c22a06a9e5f050e950b3fc138d923 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 20 Jun 2025 18:12:47 -0400 Subject: [PATCH 10/35] Remove all publicly accessible references to Entity from stable packages --- .../opentelemetry-sdk-common.txt | 8 +- .../internal/otlp/ResourceMarshaler.java | 3 +- .../opentelemetry/sdk/resources/Resource.java | 4 +- .../sdk/resources/ResourceBuilder.java | 4 +- .../sdk/resources/internal/EntityUtil.java | 90 ++++++++++++++++++- 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index 86ea044c753..f7d022d0e8b 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,11 +1,5 @@ Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar -**** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) +*** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.resources.Resource create(io.opentelemetry.api.common.Attributes, java.lang.String, java.util.Collection) *** MODIFIED METHOD: PUBLIC NON_ABSTRACT (<- ABSTRACT) io.opentelemetry.api.common.Attributes getAttributes() - +++* NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Collection getEntities() - +++* NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.Attributes getRawAttributes() -*** MODIFIED CLASS: PUBLIC io.opentelemetry.sdk.resources.ResourceBuilder (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.ResourceBuilder add(io.opentelemetry.sdk.resources.internal.Entity) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.ResourceBuilder addAll(java.util.Collection) 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 9c222dd073ab202f7f7297a570c13679eeca11c5 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 20 Jun 2025 19:17:38 -0400 Subject: [PATCH 11/35] 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 2871ff0e9955d7e90433bd381c1db8c877fd9813 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 21 Jun 2025 07:00:23 -0400 Subject: [PATCH 12/35] 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 34b6f601f88bbae5b751e21cff80bafc67d18e53 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 21 Jun 2025 08:45:50 -0400 Subject: [PATCH 13/35] 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 0711350e40a8686442648a41804a7fd170983944 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 5 Jul 2025 15:01:30 -0400 Subject: [PATCH 14/35] 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 ebf95577305d3c9c5e4adec8862b41d63dcb3928 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 5 Jul 2025 15:18:22 -0400 Subject: [PATCH 15/35] 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 73ef00a698c55b6e190119c5fe87e8bea554f81e Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sun, 6 Jul 2025 10:01:29 -0400 Subject: [PATCH 16/35] 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 25fdeaa4454..7f852ef085e 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 @@ -25,6 +25,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. @@ -34,6 +35,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(); @@ -69,6 +71,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. @@ -193,8 +212,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 31b46c65206..d00bb73a50f 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 194aa67bc17..df9a5e21e8f 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 @@ -23,6 +23,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 { @@ -33,6 +34,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 = @@ -96,6 +98,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. * @@ -239,10 +258,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 8d4288e1d05fad8c4361a202e84b36000876e84c Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sun, 6 Jul 2025 10:10:22 -0400 Subject: [PATCH 17/35] 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 f22ec8dc2013d90d02380042b742dd8d2594c7e0 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 09:52:03 -0400 Subject: [PATCH 18/35] 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 d220005d6d72c725c9b5ae293565f3724dcd2803 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 09:52:43 -0400 Subject: [PATCH 19/35] 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 7f852ef085e..d42245386f4 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 @@ -81,7 +81,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 f9dcb5273f283425ab2f3aee570bef206dd56708 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 12:00:05 -0400 Subject: [PATCH 20/35] 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 18afb6015f61811a1e331dc34b2a7ec6b8fba271 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 12:44:57 -0400 Subject: [PATCH 21/35] 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 7ace66507d030038c7ad93f4b193356f2162739b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 12:46:29 -0400 Subject: [PATCH 22/35] 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 d5219d1aed0b40d70858e69eba29112308010b5b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 13:04:00 -0400 Subject: [PATCH 23/35] More cleanups from review. --- docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt | 1 - .../opentelemetry/sdk/resources/internal/SdkEntityBuilder.java | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index f7d022d0e8b..6a88413d8c4 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,5 +1,4 @@ Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar *** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.resources.Resource create(io.opentelemetry.api.common.Attributes, java.lang.String, java.util.Collection) *** MODIFIED METHOD: PUBLIC NON_ABSTRACT (<- ABSTRACT) io.opentelemetry.api.common.Attributes getAttributes() 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 cba06061e6630883990e26474fddd78b55466236 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 16:13:27 -0400 Subject: [PATCH 24/35] 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 a38a544da02e7d6183b297a544d9953f2c1422ee Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 16:17:42 -0400 Subject: [PATCH 25/35] 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 d01f5a3140acca234cd2168d739878c104ec3aaf Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 16:43:19 -0400 Subject: [PATCH 26/35] 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 2560c519254846b48e54a84d337d60ed1ba21450 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Thu, 10 Jul 2025 22:15:01 -0400 Subject: [PATCH 27/35] 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 bf729783fcaae269fe04131a2ddb5e047c93fffa Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 10:01:23 -0400 Subject: [PATCH 28/35] 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 af3bbd92ff87f322474d0ed58aff2f4238ac9057 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 10:17:55 -0400 Subject: [PATCH 29/35] 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 2809916063ea357bbd6977dcf87c11194884202e Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 10:41:55 -0400 Subject: [PATCH 30/35] 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 912ba24f51030b073a7ba5b4010409225013bc04 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 13:31:58 -0400 Subject: [PATCH 31/35] 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 634c4a37e847c52fefaf0a25d21b39d3fdd5c1fb Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 13:46:32 -0400 Subject: [PATCH 32/35] Remove assertjs helpers, update javadoc. --- .../incubator/entities/EntityProvider.java | 8 ++-- .../opentelemetry-sdk-testing.txt | 10 +---- .../sdk/testing/assertj/EntityAssert.java | 45 ------------------- .../assertj/OpenTelemetryAssertions.java | 6 --- 4 files changed, 4 insertions(+), 65 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/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt index 32dcc99118b..2c87bd8600d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-testing.txt @@ -1,10 +1,2 @@ Comparing source compatibility of opentelemetry-sdk-testing-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-testing-1.51.0.jar -+++ NEW CLASS: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasDescriptionSatisfying(org.assertj.core.api.ThrowingConsumer) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasIdSatisfying(org.assertj.core.api.ThrowingConsumer) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasSchemaUrl(java.lang.String) - +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert hasType(java.lang.String) -*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.testing.assertj.EntityAssert assertThat(io.opentelemetry.sdk.resources.internal.Entity) +No changes. \ No newline at end of file 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 5c126c92ae79c6939c48115de4fa6ea4cc8ba18b Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 13:58:25 -0400 Subject: [PATCH 33/35] 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 7a7c426c263f5780a319f1eb3137eb385bcac815 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Fri, 11 Jul 2025 14:16:58 -0400 Subject: [PATCH 34/35] 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 8ad0e5f82c8bef10575d7fcec5d586c83eb52d06 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Sat, 23 Aug 2025 12:16:48 -0400 Subject: [PATCH 35/35] Tweak incubator SDK to support asynchronous configuratio and use EntityListener pattern from OTEP. --- .../ExtendedOpenTelemetrySdkBuilder.java | 14 ++-- .../incubator/entities/EntityListener.java | 8 ++ .../entities/LatestResourceSupplier.java | 75 ++++++++++++++++++ .../incubator/entities/ResourceDetector.java | 3 +- .../incubator/entities/SdkEntityProvider.java | 18 ++--- .../entities/SdkEntityProviderBuilder.java | 20 ++++- .../entities/SdkResourceSharedState.java | 78 +++++++++++++++++-- .../entities/detectors/ServiceDetector.java | 4 +- .../detectors/TelemetrySdkDetector.java | 4 +- .../entities/TestEntityProvider.java | 66 +++++++++++++--- .../entities/TestLatestResourceSupplier.java | 24 ++++++ 11 files changed, 270 insertions(+), 44 deletions(-) create mode 100644 sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/LatestResourceSupplier.java create mode 100644 sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestLatestResourceSupplier.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..c1c0d805264 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.entities.LatestResourceSupplier; import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProvider; import io.opentelemetry.sdk.extension.incubator.entities.SdkEntityProviderBuilder; import io.opentelemetry.sdk.logs.SdkLoggerProvider; @@ -84,17 +85,16 @@ public ExtendedOpenTelemetrySdkBuilder withLoggerProvider( */ public ExtendedOpenTelemetrySdk build() { SdkEntityProvider resourceProvider = resourceProviderBuilder.build(); + // TODO - allow startup delay configuration + LatestResourceSupplier sdkResourceSupplier = new LatestResourceSupplier(200); + resourceProvider.onChange(sdkResourceSupplier); SdkTracerProvider tracerProvider = - SdkTracerProviderUtil.setResourceSupplier( - tracerProviderBuilder, resourceProvider::getResource) + SdkTracerProviderUtil.setResourceSupplier(tracerProviderBuilder, sdkResourceSupplier) .build(); SdkMeterProvider meterProvider = - SdkMeterProviderUtil.setResourceSupplier( - meterProviderBuilder, resourceProvider::getResource) - .build(); + SdkMeterProviderUtil.setResourceSupplier(meterProviderBuilder, sdkResourceSupplier).build(); SdkLoggerProvider loggerProvider = - SdkLoggerProviderUtil.setResourceSupplier( - loggerProviderBuilder, resourceProvider::getResource) + SdkLoggerProviderUtil.setResourceSupplier(loggerProviderBuilder, sdkResourceSupplier) .build(); return new ObfuscatedExtendedOpenTelemerySdk( resourceProvider, tracerProvider, meterProvider, loggerProvider, propagators); 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 index 51fc35fd906..55aece757b3 100644 --- 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 @@ -9,6 +9,14 @@ /** A listener for changes in the EntityState of this SDK. */ public interface EntityListener { + + /** + * Called when the EntityProvider is initialized with full resource state. + * + * @param resource The initialized state of the Resource. + */ + public void onResourceInit(Resource resource); + /** * Called when an entity has been added or its state has changed. * diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/LatestResourceSupplier.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/LatestResourceSupplier.java new file mode 100644 index 00000000000..0458517c7cc --- /dev/null +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/entities/LatestResourceSupplier.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.extension.incubator.entities; + +import io.opentelemetry.sdk.resources.Resource; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +/** + * A supplier of resource which listenes to Entity events. + * + *

This class will wait for availability via ResourceInitialized event before returning from any + * `getResource` call. + */ +public class LatestResourceSupplier implements EntityListener, Supplier { + + private final AtomicReference current = new AtomicReference<>(null); + private final Object initializationLock = new Object(); + private final long maxStartupDelayMs; + + public LatestResourceSupplier(long maxStartupDelayMs) { + this.maxStartupDelayMs = maxStartupDelayMs; + } + + @Override + public void onResourceInit(Resource resource) { + current.lazySet(resource); + // Here we can notify anyone waiting on initialization. + synchronized (initializationLock) { + initializationLock.notifyAll(); + } + } + + @Override + public void onEntityState(EntityState state, Resource resource) { + current.lazySet(resource); + } + + @Override + public void onEntityDelete(EntityState state, Resource resource) { + current.lazySet(resource); + } + + @Override + public Resource get() { + Resource result = this.current.get(); + if (result == null) { + synchronized (initializationLock) { + result = this.current.get(); + long startTime = System.currentTimeMillis(); + boolean stillWaiting = true; + while (result == null || stillWaiting) { + long elapsedTime = System.currentTimeMillis() - startTime; + long remainingTime = maxStartupDelayMs - elapsedTime; + if (remainingTime <= 0) { + stillWaiting = false; + break; + } + try { + initializationLock.wait(remainingTime); + } catch (InterruptedException e) { + break; + } + } + if (result == null) { + result = Resource.getDefault(); + } + } + } + return result; + } +} 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 a7163908655..a80bf0ddbcd 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 @@ -6,6 +6,7 @@ package io.opentelemetry.sdk.extension.incubator.entities; import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; /** * The Resource detector in the SDK is responsible for detecting possible entities that could @@ -18,5 +19,5 @@ public interface ResourceDetector { * * @param provider The provider where entities are reported. */ - public void report(EntityProvider provider); + public CompletableResultCode 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 index aebd4782176..4197c7e12f2 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 @@ -8,23 +8,17 @@ 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.Collection; +import java.util.concurrent.ExecutorService; 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()); + private final SdkResourceSharedState state; - /** - * Obtains the current {@link Resource} for the SDK. - * - * @return the active {@link Resource} for this SDK. - */ - public Resource getResource() { - return state.getResource(); + SdkEntityProvider(ExecutorService executorService, Collection detectors) { + this.state = new SdkResourceSharedState(executorService); + state.beginInitialization(detectors, this); } /** 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 index 4a94185b3c4..48454d2bbe8 100644 --- 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 @@ -8,12 +8,16 @@ 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.Collections; import java.util.List; +import java.util.concurrent.ExecutorService; /** A builder for {@link SdkEntityProvider}. */ public final class SdkEntityProviderBuilder { private final List detectors = new ArrayList<>(); private boolean includeDefaults = true; + // TODO - add configuraiton settings for this. + private ExecutorService executorService = new CurrentThreadExecutorService(); /** * Adds a {@link ResourceDetector} that will be run when constructing this provider. @@ -26,6 +30,17 @@ public SdkEntityProviderBuilder addDetector(ResourceDetector detector) { return this; } + /** + * Sets the excutor service which isolates entity listeners and resource detectors. + * + * @param executorService The executor service to use for async tasks. + * @return this + */ + SdkEntityProviderBuilder setListenerExecutorService(ExecutorService executorService) { + this.executorService = executorService; + return this; + } + /** * Configure whether to include SDK default resoruce detection. * @@ -43,9 +58,6 @@ public SdkEntityProvider build() { 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; + return new SdkEntityProvider(executorService, Collections.unmodifiableCollection(detectors)); } } 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 index afb28929fb1..951bd50f8ef 100644 --- 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 @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.extension.incubator.entities; +import io.opentelemetry.api.incubator.entities.EntityProvider; import io.opentelemetry.api.internal.GuardedBy; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.internal.ThrottlingLogger; @@ -12,9 +13,12 @@ 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.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -28,6 +32,7 @@ final class SdkResourceSharedState { // The currently advertised Resource to other SDK providers. private final AtomicReference resource = new AtomicReference<>(Resource.empty()); + private final AtomicBoolean initialized = new AtomicBoolean(false); private final Object writeLock = new Object(); private final List listeners = new CopyOnWriteArrayList<>(); private final ExecutorService listenerExecutor; @@ -43,6 +48,32 @@ final class SdkResourceSharedState { this.listenerExecutor = listenerExecutor; } + /** + * Begins initializing state with the given resource detectors. + * + *

This will initialize after all resource detectors have completed, or 200 ms. + * + * @param detectors The set of resource detectors that are considered initializing. + * @param provider The entity provider for the resource detectors. + */ + @SuppressWarnings("FutureReturnValueIgnored") + void beginInitialization(Collection detectors, EntityProvider provider) { + // TODO - Should we create a different instance of entity provider for initial resource + // detection? + Collection results = new ArrayList<>(detectors.size()); + for (ResourceDetector d : detectors) { + results.add(d.report(provider)); + } + CompletableResultCode allDone = CompletableResultCode.ofAll(results); + // Ensure our blocking is done using the async model provided to use via + // ExecutorService. + listenerExecutor.submit( + () -> { + allDone.join(200, TimeUnit.MILLISECONDS); + this.initialize(); + }); + } + /** * Shutdown the provider. The resulting {@link CompletableResultCode} completes when all complete. */ @@ -54,6 +85,7 @@ CompletableResultCode shutdown() { /** Returns the currently active resource. */ public Resource getResource() { + Resource result = resource.get(); // We do this to make NullAway happy. if (result == null) { @@ -137,25 +169,55 @@ void addOrUpdateEntity(Entity e) { } } + /** Mark the resource as initialized and notify listeners. */ + @SuppressWarnings("FutureReturnValueIgnored") + private void initialize() { + // Prevent writes so initialize events happen before updates, + // in the event of other issues. + synchronized (writeLock) { + Resource resource = this.resource.get(); + if (resource == null) { + // Catastrophic failure, TODO - some kind of logic error message + return; + } + // We only do this once. + if (initialized.compareAndSet(false, true)) { + for (EntityListener listener : listeners) { + listenerExecutor.submit(() -> listener.onResourceInit(resource)); + } + } + } + } + @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)); + if (initialized.get()) { + 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)); + if (initialized.get()) { + 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)); + } } } + @SuppressWarnings("FutureReturnValueIgnored") public void addListener(EntityListener listener) { listeners.add(listener); + if (initialized.get()) { + // We isolate listener execution via executor, if configured. + // We ignore failures on futures to avoid having one listener block others. + listenerExecutor.submit(() -> listener.onResourceInit(getResource())); + } } } 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 a0acc9b5c38..29e4536bbb4 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 @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; import java.util.UUID; @@ -35,7 +36,7 @@ private static String getServiceInstanceId() { } @Override - public void report(EntityProvider provider) { + public CompletableResultCode report(EntityProvider provider) { // We only run on startup. provider .attachOrUpdateEntity(ENTITY_TYPE) @@ -48,5 +49,6 @@ public void report(EntityProvider provider) { .build()) // TODO - Need to figure out version .emit(); + return CompletableResultCode.ofSuccess(); } } 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 6c68198f49e..458a1afddb9 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 @@ -8,6 +8,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.common.internal.OtelVersion; import io.opentelemetry.sdk.extension.incubator.entities.ResourceDetector; @@ -29,7 +30,7 @@ public final class TelemetrySdkDetector implements ResourceDetector { AttributeKey.stringKey("telemetry.sdk.version"); @Override - public void report(EntityProvider provider) { + public CompletableResultCode report(EntityProvider provider) { provider .attachOrUpdateEntity(ENTITY_TYPE) .setSchemaUrl(SCHEMA_URL) @@ -41,5 +42,6 @@ public void report(EntityProvider provider) { .withDescription( Attributes.builder().put(TELEMETRY_SDK_VERSION, OtelVersion.VERSION).build()) .emit(); + return CompletableResultCode.ofSuccess(); } } 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 9bf74c12bff..6df28a8ff16 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,31 +7,38 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.entities.EntityProvider; +import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.internal.EntityUtil; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; class TestEntityProvider { @Test void defaults_includeServiceAndSdk() { + LatestResourceSupplier resource = new LatestResourceSupplier(200); SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(true).build(); + provider.onChange(resource); - assertThat(provider.getResource().getAttributes()) + assertThat(resource.get().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(resource.get().getSchemaUrl()).isEqualTo("https://opentelemetry.io/schemas/1.34.0"); - assertThat(EntityUtil.getEntities(provider.getResource())) + assertThat(EntityUtil.getEntities(resource.get())) .satisfiesExactlyInAnyOrder( e -> assertThat(e.getType()).isEqualTo("service"), e -> assertThat(e.getType()).isEqualTo("telemetry.sdk")); @@ -39,7 +46,9 @@ void defaults_includeServiceAndSdk() { @Test void resource_updatesDescription() { + LatestResourceSupplier resource = new LatestResourceSupplier(200); SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + provider.onChange(resource); provider .attachOrUpdateEntity("one") @@ -54,7 +63,7 @@ void resource_updatesDescription() { .withDescription(Attributes.builder().put("one.desc", "desc").build()) .emit(); - assertThat(provider.getResource().getAttributes()) + assertThat(resource.get().getAttributes()) .hasSize(2) .containsKey("one.id") .containsKey("one.desc"); @@ -62,7 +71,9 @@ void resource_updatesDescription() { @Test void resource_ignoresNewIds() { + LatestResourceSupplier resource = new LatestResourceSupplier(200); SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + provider.onChange(resource); provider .attachOrUpdateEntity("one") @@ -77,12 +88,14 @@ void resource_ignoresNewIds() { .withDescription(Attributes.builder().put("one.desc", "desc").build()) .emit(); - assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); + assertThat(resource.get().getAttributes()).hasSize(1).containsKey("one.id"); } @Test void resource_ignoresNewSchemaUrl() { SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + LatestResourceSupplier resource = new LatestResourceSupplier(200); + provider.onChange(resource); provider .attachOrUpdateEntity("one") @@ -97,12 +110,14 @@ void resource_ignoresNewSchemaUrl() { .withDescription(Attributes.builder().put("one.desc", "desc").build()) .emit(); - assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); + assertThat(resource.get().getAttributes()).hasSize(1).containsKey("one.id"); } @Test void resource_addsNewEntity() { SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + LatestResourceSupplier resource = new LatestResourceSupplier(200); + provider.onChange(resource); provider .attachOrUpdateEntity("one") @@ -116,7 +131,7 @@ void resource_addsNewEntity() { .withId(Attributes.builder().put("two.id", 2).build()) .emit(); - assertThat(provider.getResource().getAttributes()) + assertThat(resource.get().getAttributes()) .hasSize(2) .containsKey("one.id") .containsKey("two.id"); @@ -125,6 +140,8 @@ void resource_addsNewEntity() { @Test void resource_removesEntity() { SdkEntityProvider provider = SdkEntityProvider.builder().includeDefaults(false).build(); + LatestResourceSupplier resource = new LatestResourceSupplier(200); + provider.onChange(resource); provider .attachOrUpdateEntity("one") @@ -132,10 +149,10 @@ void resource_removesEntity() { .withId(Attributes.builder().put("one.id", 1).build()) .emit(); - assertThat(provider.getResource().getAttributes()).hasSize(1).containsKey("one.id"); + assertThat(resource.get().getAttributes()).hasSize(1).containsKey("one.id"); assertThat(provider.removeEntity("one")).isTrue(); - assertThat(provider.getResource().getAttributes()).isEmpty(); + assertThat(resource.get().getAttributes()).isEmpty(); } @Test @@ -175,4 +192,33 @@ void entityListener_notifiesOnRemove() { assertThat(entityCapture.getValue().getType()).isEqualTo("one"); assertThat(resourceCapture.getValue().getAttributes()).isEmpty(); } + + @Test + void entityListener_initializesAfterTimeout() throws InterruptedException { + // Because we're using same-thread-executor, we know entity provider blocked + // until everything started up. + // Instead we fork the resource detection. + ExecutorService service = Executors.newSingleThreadExecutor(); + ResourceDetector forever = + (EntityProvider provider) -> { + // This will never complete. + return new CompletableResultCode(); + }; + SdkEntityProvider provider = + SdkEntityProvider.builder() + .setListenerExecutorService(service) + .includeDefaults(false) + .addDetector(forever) + .build(); + EntityListener listener = mock(EntityListener.class); + provider.onChange(listener); + // Ensure we haven't seen initialization yet (If this is flaky, remove this) + verify(listener, never()).onResourceInit(any()); + + // Wait long enough that initialization has happened. + Thread.sleep(500); + ArgumentCaptor resourceCapture = ArgumentCaptor.forClass(Resource.class); + verify(listener, times(1)).onResourceInit(resourceCapture.capture()); + assertThat(resourceCapture.getValue().getAttributes()).isEmpty(); + } } diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestLatestResourceSupplier.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestLatestResourceSupplier.java new file mode 100644 index 00000000000..d2d5afee418 --- /dev/null +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/entities/TestLatestResourceSupplier.java @@ -0,0 +1,24 @@ +/* + * 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 io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.Test; + +class TestLatestResourceSupplier { + + @Test + void getResource_defaultsAfterTimeout() { + LatestResourceSupplier supplier = new LatestResourceSupplier(0); + // This will block. We haven't registered our listener, so + // we never get an initialize event. We should still get + // a default resource. + Resource resource = supplier.get(); + assertThat(resource.getAttributes()).containsKey("service.name"); + } +}