Skip to content

Commit da525bd

Browse files
committed
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
1 parent 8af2cd1 commit da525bd

14 files changed

Lines changed: 169 additions & 170 deletions

File tree

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
Comparing source compatibility of opentelemetry-sdk-common-1.52.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.51.0.jar
2-
No changes.
2+
**** MODIFIED CLASS: PUBLIC ABSTRACT io.opentelemetry.sdk.resources.Resource (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.sdk.resources.Resource create(io.opentelemetry.api.common.Attributes, java.lang.String, java.util.Collection<io.opentelemetry.sdk.resources.internal.Entity>)
5+
*** MODIFIED METHOD: PUBLIC NON_ABSTRACT (<- ABSTRACT) io.opentelemetry.api.common.Attributes getAttributes()
6+
+++* NEW METHOD: PUBLIC(+) ABSTRACT(+) java.util.Collection<io.opentelemetry.sdk.resources.internal.Entity> getEntities()
7+
+++* NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.common.Attributes getRawAttributes()
8+
*** MODIFIED CLASS: PUBLIC io.opentelemetry.sdk.resources.ResourceBuilder (not serializable)
9+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
10+
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.ResourceBuilder add(io.opentelemetry.sdk.resources.internal.Entity)
11+
+++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.resources.ResourceBuilder addAll(java.util.Collection<io.opentelemetry.sdk.resources.internal.Entity>)

exporters/otlp/common/src/main/java/io/opentelemetry/exporter/internal/otlp/ResourceMarshaler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ public static ResourceMarshaler create(io.opentelemetry.sdk.resources.Resource r
3838
RealResourceMarshaler realMarshaler =
3939
new RealResourceMarshaler(
4040
KeyValueMarshaler.createForAttributes(resource.getAttributes()),
41-
// TODO(jsuereth): This will support EntityRef in the future.
42-
new EntityRefMarshaler[] {});
41+
resource.getEntities().stream()
42+
.map(EntityRefMarshaler::createForEntity)
43+
.toArray(MarshalerWithSize[]::new));
4344

4445
ByteArrayOutputStream binaryBos =
4546
new ByteArrayOutputStream(realMarshaler.getBinarySerializedSize());

sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigurationCreateTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ void create_ModelCustomizer() {
165165
DeclarativeConfigurationCreateTest.class.getClassLoader()));
166166
assertThat(sdk.toString())
167167
.contains(
168-
"resource=Resource{schemaUrl=null, attributes={"
168+
"resource=Resource{schemaUrl=null, rawAttributes={"
169169
+ "color=\"blue\", "
170170
+ "foo=\"bar\", "
171171
+ "service.name=\"unknown_service:java\", "

sdk/all/src/test/java/io/opentelemetry/sdk/OpenTelemetrySdkTest.java

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -406,34 +406,13 @@ void stringRepresentation() {
406406
.setPropagators(ContextPropagators.create(propagator))
407407
.build();
408408

409+
// Test that toString delegates to underlying classes, and make sure their toString is also
410+
// nice.
409411
assertThat(sdk.toString())
410-
.isEqualTo(
411-
"OpenTelemetrySdk{"
412-
+ "tracerProvider=SdkTracerProvider{"
413-
+ "clock=SystemClock{}, "
414-
+ "idGenerator=RandomIdGenerator{}, "
415-
+ "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, "
416-
+ "spanLimitsSupplier=SpanLimitsValue{maxNumberOfAttributes=128, maxNumberOfEvents=128, maxNumberOfLinks=128, maxNumberOfAttributesPerEvent=128, maxNumberOfAttributesPerLink=128, maxAttributeValueLength=2147483647}, "
417-
+ "sampler=ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler,remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler,localParentNotSampled:AlwaysOffSampler}, "
418-
+ "spanProcessor=SimpleSpanProcessor{spanExporter=MultiSpanExporter{spanExporters=[MockSpanExporter{}, MockSpanExporter{}]}, exportUnsampledSpans=false}, "
419-
+ "tracerConfigurator=ScopeConfiguratorImpl{conditions=[]}"
420-
+ "}, "
421-
+ "meterProvider=SdkMeterProvider{"
422-
+ "clock=SystemClock{}, "
423-
+ "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, "
424-
+ "metricReaders=[PeriodicMetricReader{exporter=MockMetricExporter{}, intervalNanos=60000000000}], "
425-
+ "metricProducers=[], "
426-
+ "views=[RegisteredView{instrumentSelector=InstrumentSelector{instrumentName=instrument}, view=View{name=new-instrument, aggregation=DefaultAggregation, attributesProcessor=NoopAttributesProcessor{}, cardinalityLimit=2000}}], "
427-
+ "meterConfigurator=ScopeConfiguratorImpl{conditions=[]}"
428-
+ "}, "
429-
+ "loggerProvider=SdkLoggerProvider{"
430-
+ "clock=SystemClock{}, "
431-
+ "resource=Resource{schemaUrl=null, attributes={service.name=\"otel-test\"}}, "
432-
+ "logLimits=LogLimits{maxNumberOfAttributes=128, maxAttributeValueLength=2147483647}, "
433-
+ "logRecordProcessor=SimpleLogRecordProcessor{logRecordExporter=MultiLogRecordExporter{logRecordExporters=[MockLogRecordExporter{}, MockLogRecordExporter{}]}}, "
434-
+ "loggerConfigurator=ScopeConfiguratorImpl{conditions=[]}"
435-
+ "}, "
436-
+ "propagators=DefaultContextPropagators{textMapPropagator=MockTextMapPropagator{}}"
437-
+ "}");
412+
.matches("OpenTelemetrySdk\\{.*}")
413+
.matches("OpenTelemetrySdk\\{tracerProvider=SdkTracerProvider\\{.*}.*}")
414+
.matches("OpenTelemetrySdk\\{.*, meterProvider=SdkMeterProvider\\{.*}.*}")
415+
.matches("OpenTelemetrySdk\\{.*, loggerProvider=SdkLoggerProvider\\{.*}.*}")
416+
.matches("OpenTelemetrySdk\\{.*, propagators=DefaultContextPropagators\\{.*}}");
438417
}
439418
}

sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
import io.opentelemetry.api.common.AttributesBuilder;
1212
import io.opentelemetry.sdk.common.internal.OtelVersion;
1313
import io.opentelemetry.sdk.resources.internal.AttributeCheckUtil;
14+
import io.opentelemetry.sdk.resources.internal.Entity;
15+
import io.opentelemetry.sdk.resources.internal.EntityUtil;
16+
import java.util.Collection;
17+
import java.util.Collections;
1418
import java.util.Objects;
15-
import java.util.logging.Logger;
1619
import javax.annotation.Nullable;
1720
import javax.annotation.concurrent.Immutable;
1821

@@ -23,8 +26,6 @@
2326
@Immutable
2427
@AutoValue
2528
public abstract class Resource {
26-
private static final Logger logger = Logger.getLogger(Resource.class.getName());
27-
2829
private static final AttributeKey<String> SERVICE_NAME = AttributeKey.stringKey("service.name");
2930
private static final AttributeKey<String> TELEMETRY_SDK_LANGUAGE =
3031
AttributeKey.stringKey("telemetry.sdk.language");
@@ -100,8 +101,24 @@ public static Resource create(Attributes attributes) {
100101
* ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters.
101102
*/
102103
public static Resource create(Attributes attributes, @Nullable String schemaUrl) {
104+
return create(attributes, schemaUrl, Collections.emptyList());
105+
}
106+
107+
/**
108+
* Returns a {@link Resource}.
109+
*
110+
* @param attributes a map of {@link Attributes} that describe the resource.
111+
* @param schemaUrl The URL of the OpenTelemetry schema used to create this Resource.
112+
* @param entities The set of detected {@link Entity}s that participate in this resource.
113+
* @return a {@code Resource}.
114+
* @throws NullPointerException if {@code attributes} is null.
115+
* @throws IllegalArgumentException if attribute key or attribute value is not a valid printable
116+
* ASCII string or exceed {@link AttributeCheckUtil#MAX_LENGTH} characters.
117+
*/
118+
public static Resource create(
119+
Attributes attributes, @Nullable String schemaUrl, Collection<Entity> entities) {
103120
AttributeCheckUtil.checkAttributes(Objects.requireNonNull(attributes, "attributes"));
104-
return new AutoValue_Resource(schemaUrl, attributes);
121+
return new AutoValue_Resource(schemaUrl, attributes, entities);
105122
}
106123

107124
/**
@@ -113,12 +130,38 @@ public static Resource create(Attributes attributes, @Nullable String schemaUrl)
113130
@Nullable
114131
public abstract String getSchemaUrl();
115132

133+
/**
134+
* Returns a map of attributes that describe the resource, not associated with entites.
135+
*
136+
* @return a map of attributes.
137+
*/
138+
public abstract Attributes getRawAttributes();
139+
140+
/**
141+
* Returns a collectoion of associated entities.
142+
*
143+
* @return a collection of entities.
144+
*/
145+
public abstract Collection<Entity> getEntities();
146+
116147
/**
117148
* Returns a map of attributes that describe the resource.
118149
*
119150
* @return a map of attributes.
120151
*/
121-
public abstract Attributes getAttributes();
152+
// @Memoized - This breaks nullaway.
153+
public Attributes getAttributes() {
154+
AttributesBuilder result = Attributes.builder();
155+
getEntities()
156+
.forEach(
157+
e -> {
158+
result.putAll(e.getId());
159+
result.putAll(e.getDescription());
160+
});
161+
// In merge rules, raw comes last, so we return these last.
162+
result.putAll(getRawAttributes());
163+
return result.build();
164+
}
122165

123166
/**
124167
* Returns the value for a given resource attribute key.
@@ -138,32 +181,7 @@ public <T> T getAttribute(AttributeKey<T> key) {
138181
* @return the newly merged {@code Resource}.
139182
*/
140183
public Resource merge(@Nullable Resource other) {
141-
if (other == null || other == EMPTY) {
142-
return this;
143-
}
144-
145-
AttributesBuilder attrBuilder = Attributes.builder();
146-
attrBuilder.putAll(this.getAttributes());
147-
attrBuilder.putAll(other.getAttributes());
148-
149-
if (other.getSchemaUrl() == null) {
150-
return create(attrBuilder.build(), getSchemaUrl());
151-
}
152-
if (getSchemaUrl() == null) {
153-
return create(attrBuilder.build(), other.getSchemaUrl());
154-
}
155-
if (!other.getSchemaUrl().equals(getSchemaUrl())) {
156-
logger.info(
157-
"Attempting to merge Resources with different schemaUrls. "
158-
+ "The resulting Resource will have no schemaUrl assigned. Schema 1: "
159-
+ getSchemaUrl()
160-
+ " Schema 2: "
161-
+ other.getSchemaUrl());
162-
// currently, behavior is undefined if schema URLs don't match. In the future, we may
163-
// apply schema transformations if possible.
164-
return create(attrBuilder.build(), null);
165-
}
166-
return create(attrBuilder.build(), getSchemaUrl());
184+
return EntityUtil.merge(this, other);
167185
}
168186

169187
/**

sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@
88
import io.opentelemetry.api.common.AttributeKey;
99
import io.opentelemetry.api.common.Attributes;
1010
import io.opentelemetry.api.common.AttributesBuilder;
11+
import io.opentelemetry.sdk.resources.internal.Entity;
12+
import io.opentelemetry.sdk.resources.internal.EntityUtil;
13+
import java.util.ArrayList;
14+
import java.util.Collection;
15+
import java.util.List;
16+
import java.util.Set;
1117
import java.util.function.Predicate;
18+
import java.util.stream.Collectors;
1219
import javax.annotation.Nullable;
1320

1421
/**
@@ -20,6 +27,7 @@
2027
public class ResourceBuilder {
2128

2229
private final AttributesBuilder attributesBuilder = Attributes.builder();
30+
private final List<Entity> entities = new ArrayList<>();
2331
@Nullable private String schemaUrl;
2432

2533
/**
@@ -194,6 +202,32 @@ public ResourceBuilder setSchemaUrl(String schemaUrl) {
194202

195203
/** Create the {@link Resource} from this. */
196204
public Resource build() {
197-
return Resource.create(attributesBuilder.build(), schemaUrl);
205+
// What checks should we do on "real" resource here?
206+
// Derive schemaUrl from entitiy, if able.
207+
if (schemaUrl == null) {
208+
Set<String> entitySchemas =
209+
entities.stream().map(Entity::getSchemaUrl).collect(Collectors.toSet());
210+
if (entitySchemas.size() == 1) {
211+
// Updated Entities use same schema, we can preserve it.
212+
schemaUrl = entitySchemas.iterator().next();
213+
}
214+
}
215+
216+
// TODO - here we deal with conflicts between entities and raw attributes.
217+
// When adding an entity, we remove any raw attributes it may conflict with.
218+
this.attributesBuilder.removeIf(key -> EntityUtil.hasAttributeKey(this.entities, key));
219+
return Resource.create(attributesBuilder.build(), schemaUrl, entities);
220+
}
221+
222+
/** Appends a new entity on to the end of the list of entities. */
223+
public ResourceBuilder add(Entity e) {
224+
this.entities.add(e);
225+
return this;
226+
}
227+
228+
/** Appends a new collection of entities on to the end of the list of entities. */
229+
public ResourceBuilder addAll(Collection<Entity> entities) {
230+
this.entities.addAll(entities);
231+
return this;
198232
}
199233
}

sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.opentelemetry.api.common.AttributeKey;
99
import io.opentelemetry.api.common.Attributes;
1010
import io.opentelemetry.api.common.AttributesBuilder;
11+
import io.opentelemetry.sdk.resources.Resource;
1112
import java.util.ArrayList;
1213
import java.util.Collection;
1314
import java.util.HashMap;
@@ -29,7 +30,8 @@ public final class EntityUtil {
2930
private EntityUtil() {}
3031

3132
/** Returns true if any entity in the collection has the attribute key, in id or description. */
32-
static final <T> boolean hasAttributeKey(Collection<Entity> entities, AttributeKey<T> key) {
33+
public static final <T> boolean hasAttributeKey(
34+
Collection<Entity> entities, AttributeKey<T> key) {
3335
return entities.stream()
3436
.anyMatch(
3537
e -> e.getId().asMap().containsKey(key) || e.getDescription().asMap().containsKey(key));
@@ -178,4 +180,30 @@ public static final Collection<Entity> mergeEntities(
178180
}
179181
return entities.values();
180182
}
183+
184+
/**
185+
* Returns a new, merged {@link Resource} by merging the {@code base} {@code Resource} with the
186+
* {@code next} {@code Resource}. In case of a collision, the "next" {@code Resource} takes
187+
* precedence.
188+
*
189+
* @param base the {@code Resource} into which we merge new values.
190+
* @param next the {@code Resource} that will be merged with {@code base}.
191+
* @return the newly merged {@code Resource}.
192+
*/
193+
public static Resource merge(Resource base, @Nullable Resource next) {
194+
if (next == null || next == Resource.empty()) {
195+
return base;
196+
}
197+
// Merge Algorithm from
198+
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/entities/0264-resource-and-entities.md#entity-merging-and-resource
199+
Collection<Entity> entities = EntityUtil.mergeEntities(base.getEntities(), next.getEntities());
200+
RawAttributeMergeResult attributeResult =
201+
EntityUtil.mergeRawAttributes(base.getRawAttributes(), next.getRawAttributes(), entities);
202+
// Remove entiites that are conflicting with raw attributes, and therefore in an unknown state.
203+
entities.removeAll(attributeResult.getConflicts());
204+
// Now figure out schema url for overall resource.
205+
String schemaUrl =
206+
EntityUtil.mergeResourceSchemaUrl(entities, base.getSchemaUrl(), next.getSchemaUrl());
207+
return Resource.create(attributeResult.getAttributes(), schemaUrl, entities);
208+
}
181209
}

sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntity.java

Lines changed: 0 additions & 62 deletions
This file was deleted.

sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/ResourceWithEntityBuilder.java

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)