Skip to content

Entity SDK - Initial opt-in SDK features#8464

Open
jsuereth wants to merge 10 commits into
open-telemetry:mainfrom
jsuereth:wip-entity-sdk-for-reals
Open

Entity SDK - Initial opt-in SDK features#8464
jsuereth wants to merge 10 commits into
open-telemetry:mainfrom
jsuereth:wip-entity-sdk-for-reals

Conversation

@jsuereth

@jsuereth jsuereth commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

This is a more up-to-date PR accounting for changes from OpenTelemetry Configuraiton work for Entities.

What this does:

  • Implements Entity necessary components in core SDK, hidden in internal as much as feasible (this needs @open-telemetry/java-maintainers feedback on some of the challenges here)
  • Creates "public facing" SDK interface in sdk-extensions/incubating
  • Attempts to write in feature flag to turn on Entity capabilities.

A few issues though:

  • We want ResourceDetection to upgrade to use entities going forward. I have lots of questions for configuration I'll need to discuss with those folks and @jack-berg w/ Entities SIG to see a path forward here
  • Resource is exposed in the SDK as an autovalue - which has compatibility limitations. I"m not sure how to keep the auto API compat bot happy here as effectively you can't subclass this without breaking Java's visibiltiy rules, but also it appears like it can be subclassed if you do bytecode rewritting so it technically is a bincompat change.
  • The way I'm supporting Config is prretty bogus. I tried to keep it isolated but I'm not happy with it. Additionally the ResourceFactory code at some point is only keeping attributes for Resource - which is hugely problematic for Entity. I'd like to refactor the whole kit and kaboodle but I know stabilizaiton is coming

- Create new Entity Detectors that are completely separate from existing
  reosurce detectors so entity must be FULL opt-in before behavior
  changes, even if OTLP is non-breaking.
- Wire "either/or" scenarios into resource factory and declarative
  config.

TODOs we need to sort out:

- Features of ResourceConfiguration spec that interact poorly with
  entities, e.g. attribute-specific filters that aren't entity aware,
  raw attribute definitions.
- Adding `env` as a supported detector for reading in Entity env
  variable propagation.
- Dealing with oddities of ResourceBuilder/Factory wanting to only use
  attributes in Java - we worked around it for now.

@jack-berg jack-berg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to handle some merge conflicts since declarative config has been undergoing construction:

  • It moved out of the incubator into a dedicated artifact in #8265
  • We started commiting generated POJOs to VCS in #8408

As mentioned in the SIG meeting, I don't think you need the incubator copies of Entity interfaces. I pushed a commit here to delete them: jack-berg@3ea17f4

Per the discussion, resource detectors will need to updated to optionally produce entity-aware resources. If the user opts in, then those resource detectors can call the internal APIs from opentelemetry-sdk-common to produce those.

Comment thread sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java Outdated
Comment thread sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java Outdated
Comment thread sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java Outdated
Comment thread sdk/common/src/main/java/io/opentelemetry/sdk/resources/Resource.java Outdated
*
* @return a map of attributes.
*/
abstract Attributes getRawAttributes();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are loose attributes, i.e. those that weren't attached to an Enttiy. We need this for backwards compatibility

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you're memoizing during initialization, is this still needed? I see exactly one usage in ResourceBuilder.putAll(Resource). I suppose keeping this preserves the rawness of the attributes (as opposed to being associated with an entity) during merging, but is there any functional difference from just changing:

https://github.com/jsuereth/opentelemetry-java/blob/6d786022f4bf23d1a0abe4059db8dab7572c4f5c/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java#L176-L186

To:

  /** Puts all attributes from {@link Resource} into this. */
  public ResourceBuilder putAll(Resource resource) {
    if (resource != null) {
      // Preserve entities when merging resources.
      entities.addAll(resource.getEntities());
      attributesBuilder.putAll(resource.getAttributes());
    }
    return this;
  }

Or better yet, compute the raw attributes during putAll:

  /** Puts all attributes from {@link Resource} into this. */
  public ResourceBuilder putAll(Resource resource) {
    if (resource != null) {
      // Preserve entities when merging resources.
      entities.addAll(resource.getEntities());
      AttributesBuilder rawAttributes = resource.getAttributes().toBuilder();
      rawAttributes.removeIf(
          key ->
              resource.getEntities().stream()
                  .anyMatch(
                      entity ->
                          entity.getId().get(key) != null
                              || entity.getDescription().get(key) != null));
      attributesBuilder.putAll(rawAttributes.build());
    }
    return this;
  }

Or better better yet (😛), extract a dedicated static function for computing the raw attributes for a resource (since I did end up finding one more use of raw attributes in EntityUtil):

  public static Attributes getRawAttributes(Resource resource) {
    AttributesBuilder rawAttributes = resource.getAttributes().toBuilder();
    rawAttributes.removeIf(
        key ->
            resource.getEntities().stream()
                .anyMatch(
                    entity ->
                        entity.getId().get(key) != null
                            || entity.getDescription().get(key) != null));
    return rawAttributes.build();
  }

Comment thread sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/SdkEntity.java Outdated
if (context.isEntitiesEnabled()) {
List<ExperimentalResourceDetectorModel> detectorModels = detectionModel.getDetectors();
if (detectorModels != null) {
List<EntityDetector> detectors = new ArrayList<>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPIs work differently in declarative config than in autoconfigure. Rather than having a dedicated SPI per extension plugin interface (i.e. ResourceProvider, ConfigurableSpanExporterProvider, etc), there's a single ComponentProvider SPI. Implementations specify the type of extension plugin interface they provide via ComponentProvider.getType().

Also, we we're discussing in DMs about adjusting the semantics of existing declarative configuration resource provider types to make them entity aware, such that YAML like:

resource:
  attributes_list: ${OTEL_RESOURCE_ATTRIBUTES}
  detection/development: # /development properties may not be supported in all SDKs
    detectors:
      - env: # We add this - it allows runtimes, like Cloud Run or AWS lambda to pass resource identity via ENV variable without blowing away existing working setups.
      - service:
      - host:
      - process:
      - container:

Which is valid today. The env, service, host, process, container detectors would be updated to produce entity-aware Resources.

Going this route, I think there's basically nothing that needs to be done in ResourceFactory. It will start configuring entity-aware resources when the detectors are updated to produce them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - I think this is a LOT cleaner and goes towards the no-breaking-change / easy-to-adopt goals we had for entities.

I'll cut all this out and come back to you with a renewed PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok - first cut is done, doing some more cleanup - but probably worth a review already

@otelbot otelbot Bot added the api-change Changes to public API surface area label Jun 25, 2026
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.77596% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.78%. Comparing base (77eb68d) to head (6d78602).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...lemetry/sdk/autoconfigure/EnvironmentResource.java 64.86% 25 Missing and 27 partials ⚠️
...entelemetry/sdk/resources/internal/EntityUtil.java 75.45% 18 Missing and 9 partials ⚠️
...try/exporter/internal/otlp/EntityRefMarshaler.java 90.00% 0 Missing and 3 partials ⚠️
...try/sdk/resources/internal/AttributeCheckUtil.java 75.00% 0 Missing and 2 partials ⚠️
...ure/declarativeconfig/ServiceResourceDetector.java 95.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8464      +/-   ##
============================================
- Coverage     90.94%   90.78%   -0.16%     
- Complexity    10210    10270      +60     
============================================
  Files          1013     1020       +7     
  Lines         27175    27510     +335     
  Branches       3184     3240      +56     
============================================
+ Hits          24714    24975     +261     
- Misses         1734     1768      +34     
- Partials        727      767      +40     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Simplify API for using entities.
- Update  so we can use current resource detection unchanged with entities.
- Memoize full attributes manually when creating a resource.
@otelbot otelbot Bot removed the api-change Changes to public API surface area label Jun 25, 2026
@jsuereth jsuereth changed the title [DRAFT] Entity SDK prototype Entity SDK - Initial opt-in SDK features Jun 25, 2026

@jack-berg jack-berg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good

* <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 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this is wired into the ResourceMarshaler so it doesn't actually do anything. Let's either wire in, or pull until a future PR when we're wiring in. No use having unused code published.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad I think I accidentally reverted that - will fix

/** Constants for experimental entity SDK features. */
final class EntityExperimentConstants {

static final String EXPERIMENTAL_ENTITIES_ENABLED = "otel.experimental.entities.enabled";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add this as a constant in EnvironmentResource - no need for a separate class.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used by all the resource detectors - I can put it in each individually or do you think they should use different values?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean it will be used by all the resource detectors? Because right now I only see usage in one place - EnvironmentResrouce.

If its going to be used by resource detectors, should go in opentelemetry-sdk-extension-autoconfigure-spi because that's the dependency detectors take when implementing ResourceProvider


private EnvironmentResource() {}

private static final class Segment {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you see the comment at the top of this class about us having build tooling to make a copy of this for declarative config?

 * <p>This class is intentionally self-contained (no dependencies on other autoconfigure-internal
 * classes) so that it can be copied wholesale into declarative configuration without pulling in
 * additional dependencies. Do not add dependencies on non-API, non-SPI classes.

The idea is we want to reuse this logic for parsing .resource.attributes_list:

file_format: "1.1"
resource:
  attributes_list: ${OTEL_RESOURCE_ATTRIBUTES}

Maybe you have the same thing in mind and need the entity parsing to come over to declarative config too? Actually, looking at the code, there is no opportunity for the declarative config code path to reach this entities stuff since it only spoofs otel.resource.attributes. Should break apart and wire in the entities bit in ResourceConfiguration.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - I think likely we need to split some of this apart.

I think the way declarative config and entities interact with env is actually at odds - we've had this discussion in the past.

I'll detangle this and show you what I mean

props.put(EntityExperimentConstants.EXPERIMENTAL_ENTITIES_ENABLED, "true");
props.put(
EnvironmentResource.ENTITIES_PROPERTY,
"process{process.pid=1234}[process.executable.name=java]@http://schema;host{host.id=myhost}");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably add a parameterized test to hammer this entities parsing logic with a bunch of different success and failure cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was working on that :)

otelJava.osgiServiceLoaderProvides.set(listOf(
"io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider",
"io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider",
"io.opentelemetry.sdk.extension.incubator.resources.EntityDetector",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remnant

api(project(":sdk-extensions:autoconfigure-spi"))

compileOnly(project(":api:incubator"))
compileOnly(project(":sdk-extensions:incubator"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both these changes in this file are remnant from when you still had work in the sdk-extensions:incubator.

*
* @return a map of attributes.
*/
abstract Attributes getRawAttributes();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you're memoizing during initialization, is this still needed? I see exactly one usage in ResourceBuilder.putAll(Resource). I suppose keeping this preserves the rawness of the attributes (as opposed to being associated with an entity) during merging, but is there any functional difference from just changing:

https://github.com/jsuereth/opentelemetry-java/blob/6d786022f4bf23d1a0abe4059db8dab7572c4f5c/sdk/common/src/main/java/io/opentelemetry/sdk/resources/ResourceBuilder.java#L176-L186

To:

  /** Puts all attributes from {@link Resource} into this. */
  public ResourceBuilder putAll(Resource resource) {
    if (resource != null) {
      // Preserve entities when merging resources.
      entities.addAll(resource.getEntities());
      attributesBuilder.putAll(resource.getAttributes());
    }
    return this;
  }

Or better yet, compute the raw attributes during putAll:

  /** Puts all attributes from {@link Resource} into this. */
  public ResourceBuilder putAll(Resource resource) {
    if (resource != null) {
      // Preserve entities when merging resources.
      entities.addAll(resource.getEntities());
      AttributesBuilder rawAttributes = resource.getAttributes().toBuilder();
      rawAttributes.removeIf(
          key ->
              resource.getEntities().stream()
                  .anyMatch(
                      entity ->
                          entity.getId().get(key) != null
                              || entity.getDescription().get(key) != null));
      attributesBuilder.putAll(rawAttributes.build());
    }
    return this;
  }

Or better better yet (😛), extract a dedicated static function for computing the raw attributes for a resource (since I did end up finding one more use of raw attributes in EntityUtil):

  public static Attributes getRawAttributes(Resource resource) {
    AttributesBuilder rawAttributes = resource.getAttributes().toBuilder();
    rawAttributes.removeIf(
        key ->
            resource.getEntities().stream()
                .anyMatch(
                    entity ->
                        entity.getId().get(key) != null
                            || entity.getDescription().get(key) != null));
    return rawAttributes.build();
  }

* @param entityType the entity type string of this entity.
*/
static EntityBuilder builder(String entityType) {
return SdkEntity.builder(entityType);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#nit:

Suggested change
return SdkEntity.builder(entityType);
return new SdkEntityBuilder(entityType)

Can get rid of EntityBuilder builder(String) method from SdkEntity.

*
* @return the entity identity.
*/
Attributes getId();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can entities have empty IDs? Curious if ID should be a required, non-empty constructor parameter.

@SuppressWarnings("JdkObsolete") // Recommended alternative was introduced in java 10
static Resource createEnvironmentResource(ConfigProperties config) {
boolean entitiesEnabled =
config.getBoolean(EntityExperimentConstants.EXPERIMENTAL_ENTITIES_ENABLED, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting way to get around having OTEL_ENTITIES and not OTEL_EXPERIMENTAL_ENTITIES or something similarly qualified as experimental. Allows us to use the ultimate target env var OTEL_ENTITIES while retaining the ability to change the semantics since users have to opt in.

No precedent for this type of thing but I think its fine. WDYT @open-telemetry/java-approvers ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants