Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.internal.Entity}.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
final class EntityRefMarshaler extends MarshalerWithSize {
@Nullable private final byte[] schemaUrlUtf8;
private final byte[] typeUtf8;
private final byte[][] idKeysUtf8;
private final byte[][] descriptionKeysUtf8;

@Override
protected void writeTo(Serializer output) throws IOException {
if (schemaUrlUtf8 != null) {
output.writeString(EntityRef.SCHEMA_URL, schemaUrlUtf8);
}
output.writeString(EntityRef.TYPE, typeUtf8);
output.writeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8);
output.writeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8);
}

/** Consttructs an entity reference marshaler from a full entity. */
static EntityRefMarshaler createForEntity(Entity e) {
byte[] schemaUrlUtf8 = null;
if (!StringUtils.isNullOrEmpty(e.getSchemaUrl())) {
schemaUrlUtf8 = e.getSchemaUrl().getBytes(StandardCharsets.UTF_8);
}
return new EntityRefMarshaler(
schemaUrlUtf8,
e.getType().getBytes(StandardCharsets.UTF_8),
e.getId().asMap().keySet().stream()
.map(key -> key.getKey().getBytes(StandardCharsets.UTF_8))
.toArray(byte[][]::new),
e.getDescription().asMap().keySet().stream()
.map(key -> key.getKey().getBytes(StandardCharsets.UTF_8))
.toArray(byte[][]::new));
}

private EntityRefMarshaler(
@Nullable byte[] schemaUrlUtf8,
byte[] typeUtf8,
byte[][] idKeysUtf8,
byte[][] descriptionKeysUtf8) {
super(calculateSize(schemaUrlUtf8, typeUtf8, idKeysUtf8, descriptionKeysUtf8));
this.schemaUrlUtf8 = schemaUrlUtf8;
this.typeUtf8 = typeUtf8;
this.idKeysUtf8 = idKeysUtf8;
this.descriptionKeysUtf8 = descriptionKeysUtf8;
}

private static int calculateSize(
@Nullable byte[] schemaUrlUtf8,
byte[] typeUtf8,
byte[][] idKeysUtf8,
byte[][] descriptionKeysUtf8) {
int size = 0;
if (schemaUrlUtf8 != null) {
size += MarshalerUtil.sizeBytes(EntityRef.SCHEMA_URL, schemaUrlUtf8);
}
size += MarshalerUtil.sizeBytes(EntityRef.TYPE, typeUtf8);
MarshalerUtil.sizeRepeatedString(EntityRef.ID_KEYS, idKeysUtf8);
MarshalerUtil.sizeRepeatedString(EntityRef.DESCRIPTION_KEYS, descriptionKeysUtf8);
return size;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.internal.otlp;

import static org.assertj.core.api.Assertions.assertThat;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.proto.common.v1.EntityRef;
import io.opentelemetry.sdk.resources.internal.Entity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;

class EntityRefMarshalerTest {
@Test
void toEntityRefs() {
Entity e =
Entity.builder("test")
.setSchemaUrl("test-url")
.withDescription(Attributes.builder().put("desc.key", "desc.value").build())
.withId(Attributes.builder().put("id.key", "id.value").build())
.build();
EntityRef proto = parse(EntityRef.getDefaultInstance(), EntityRefMarshaler.createForEntity(e));
assertThat(proto.getType()).isEqualTo("test");
assertThat(proto.getSchemaUrl()).isEqualTo("test-url");
assertThat(proto.getIdKeysList()).containsExactly("id.key");
assertThat(proto.getDescriptionKeysList()).containsExactly("desc.key");
}

@SuppressWarnings("unchecked")
private static <T extends Message> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.autoconfigure;

import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.extension.incubator.resources.EntityDetector;
import io.opentelemetry.sdk.extension.incubator.resources.internal.ExtendedEntityUtil;
import io.opentelemetry.sdk.resources.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;

final class IncubatingEntityUtil {

private IncubatingEntityUtil() {}

@Nullable
static Resource configureEntityResource(
ConfigProperties config,
SpiHelper spiHelper,
Set<String> enabledProviders,
Set<String> disabledProviders) {

List<EntityDetector> detectors = new ArrayList<>();
for (EntityDetector detector : spiHelper.loadOrdered(EntityDetector.class)) {
String fqcn = detector.getClass().getName();
String shortName = detector.getName();
if (!enabledProviders.isEmpty()
&& !enabledProviders.contains(fqcn)
&& !enabledProviders.contains(shortName)) {
continue;
}
if (disabledProviders.contains(fqcn) || disabledProviders.contains(shortName)) {
continue;
}
detectors.add(detector);
}

if (detectors.isEmpty()) {
return null;
}

return ExtendedEntityUtil.runDetection(detectors, config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@
*/
public final class ResourceConfiguration {

private static final boolean ENTITY_INCUBATOR_AVAILABLE;

static {
boolean incubatorAvailable = false;
try {
Class.forName(
"io.opentelemetry.sdk.extension.incubator.resources.EntityDetector",
false,
ResourceConfiguration.class.getClassLoader());
incubatorAvailable = true;
} catch (ClassNotFoundException e) {
// Not available
}
ENTITY_INCUBATOR_AVAILABLE = incubatorAvailable;
}

// Visible for testing
static final String DISABLED_ATTRIBUTE_KEYS = "otel.resource.disabled.keys";
static final String ENABLED_RESOURCE_PROVIDERS = "otel.java.enabled.resource.providers";
Expand Down Expand Up @@ -66,23 +82,35 @@ static Resource configureResource(
Set<String> enabledProviders = new HashSet<>(config.getList(ENABLED_RESOURCE_PROVIDERS));
Set<String> disabledProviders = new HashSet<>(config.getList(DISABLED_RESOURCE_PROVIDERS));

for (ResourceProvider resourceProvider : spiHelper.loadOrdered(ResourceProvider.class)) {
if (!enabledProviders.isEmpty()
&& !enabledProviders.contains(resourceProvider.getClass().getName())) {
continue;
}
if (disabledProviders.contains(resourceProvider.getClass().getName())) {
continue;
// If Entity experiment is enabled, we use a new flow to instantiate resources.
boolean entitiesEnabled = config.getBoolean("otel.experimental.entities.enabled", false);
if (entitiesEnabled && ENTITY_INCUBATOR_AVAILABLE) {
Resource entityResource =
IncubatingEntityUtil.configureEntityResource(
config, spiHelper, enabledProviders, disabledProviders);
if (entityResource != null) {
result = entityResource;
}
if (resourceProvider instanceof ConditionalResourceProvider
&& !((ConditionalResourceProvider) resourceProvider).shouldApply(config, result)) {
continue;
} else {

for (ResourceProvider resourceProvider : spiHelper.loadOrdered(ResourceProvider.class)) {
if (!enabledProviders.isEmpty()
&& !enabledProviders.contains(resourceProvider.getClass().getName())) {
continue;
}
if (disabledProviders.contains(resourceProvider.getClass().getName())) {
continue;
}
if (resourceProvider instanceof ConditionalResourceProvider
&& !((ConditionalResourceProvider) resourceProvider).shouldApply(config, result)) {
continue;
}
result = result.merge(resourceProvider.createResource(config));
}
result = result.merge(resourceProvider.createResource(config));
}

result = filterAttributes(result, config);

// TODO(jsuereth): Should filter attributes be used with entities?
result = filterAttributes(result, config);
}
return resourceCustomizer.apply(result, config);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
import io.opentelemetry.api.incubator.config.ConfigProvider;
import io.opentelemetry.api.incubator.config.InstrumentationConfigUtil;
Expand All @@ -34,14 +35,19 @@
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.internal.Entity;
import io.opentelemetry.sdk.resources.internal.EntityUtil;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
Expand Down Expand Up @@ -243,4 +249,53 @@ void configFile_ConfigProvider() {
.getScalarList("request_captured_headers", String.class))
.isEqualTo(Arrays.asList("Content-Type", "Accept"));
}

@Test
void entitiesEnabled() {
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("otel.experimental.entities.enabled", "true"));

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).build();
OpenTelemetrySdk openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
cleanup.addCloseable(openTelemetrySdk);

Resource resource = autoConfiguredOpenTelemetrySdk.getResource();

Collection<Entity> entities = EntityUtil.getEntities(resource);
assertThat(entities)
.anyMatch(
e ->
e.getType().equals("telemetry.sdk")
&& "opentelemetry"
.equals(e.getId().get(AttributeKey.stringKey("telemetry.sdk.name")))
&& "java"
.equals(e.getId().get(AttributeKey.stringKey("telemetry.sdk.language"))));
}

@Test
void entitiesEnabled_WithEnabledProviders() {
Map<String, String> props = new HashMap<>();
props.put("otel.experimental.entities.enabled", "true");
props.put("otel.service.name", "my-filtered-service");
props.put("otel.java.enabled.resource.providers", "service");
ConfigProperties config = DefaultConfigProperties.createFromMap(props);

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).build();
OpenTelemetrySdk openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
cleanup.addCloseable(openTelemetrySdk);

Resource resource = autoConfiguredOpenTelemetrySdk.getResource();

Collection<Entity> entities = EntityUtil.getEntities(resource);
assertThat(entities)
.anyMatch(
e ->
e.getType().equals("service")
&& "my-filtered-service"
.equals(e.getId().get(AttributeKey.stringKey("service.name"))));
assertThat(entities).noneMatch(e -> e.getType().equals("telemetry.sdk"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ private InternalTelemetryVersion getInternalTelemetryVersion() {
}
}

boolean isEntitiesEnabled() {
if (configProvider == null) {
return false;
}
// TODO - Check with Jack if this is the right thing to do for config flags,
// of if we want to put this in the experimental resource config space.
return Boolean.TRUE.equals(
configProvider
.getInstrumentationConfig("otel_sdk")
.getBoolean("experimental_entities_enabled"));
}

/**
* Find a registered {@link ComponentProvider} with {@link ComponentProvider#getType()} matching
* {@code type}, {@link ComponentProvider#getName()} matching {@code name}, and call {@link
Expand Down
Loading
Loading