Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ComponentLoader to support more auto configuration scenarios #6217

Merged
merged 21 commits into from Mar 6, 2024

Conversation

zeitlinger
Copy link
Member

@zeitlinger zeitlinger commented Feb 9, 2024

Part of open-telemetry/opentelemetry-java-instrumentation#10409

Here's a PoC to show that the functionality actually works for spring boot: open-telemetry/opentelemetry-java-instrumentation#10453

Why do we need the new component loader public interface?

Let's say a spring user wants to create a new span exporter - then they would create the following spring class:

@Configuration
public class OtelConfig {
  @Bean
  ConfigurableSpanExporterProvider mySpanExporter() {
    Return new MySpanExporterProvider()
  }
}

public class MySpanExporterProvider implements ConfigurableSpanExporterProvider {

}

(They would also need to set otel.spans.exporter to use the span exporter.)

Normally this would not have any effect on the SDK auto-config, because Spring beans are different from the SPI way of loading things.

The component loader allows the spring starter to create an adapter, which is shown here

@zeitlinger
Copy link
Member Author

@jack-berg please take a look 😄

Copy link
Member

@jack-berg jack-berg left a comment

Choose a reason for hiding this comment

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

I think I can get behind this but need to think about it a little more. If we go in this direction, I think we'll want to incubate the ComponentLoader class in an internal class for some period of time.

Copy link

codecov bot commented Feb 19, 2024

Codecov Report

Attention: Patch coverage is 88.23529% with 4 lines in your changes are missing coverage. Please review.

Project coverage is 91.04%. Comparing base (1d4a2f4) to head (22996e0).
Report is 2 commits behind head on main.

Files Patch % Lines
.../sdk/autoconfigure/internal/AutoConfigureUtil.java 71.42% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6217      +/-   ##
============================================
- Coverage     91.06%   91.04%   -0.02%     
- Complexity     5695     5705      +10     
============================================
  Files           621      621              
  Lines         16667    16703      +36     
  Branches       1707     1709       +2     
============================================
+ Hits          15177    15208      +31     
- Misses          997     1002       +5     
  Partials        493      493              

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

@zeitlinger
Copy link
Member Author

@jack-berg added all suggested changes - and even figured out how to appease jCmpApi (hopefully correctly).

@zeitlinger
Copy link
Member Author

@zeitlinger
Copy link
Member Author

An additional SPI is needed for ConfigProperties - this is needed to create an adapter for spring properties: https://github.com/zeitlinger/opentelemetry-java/blob/c1534b5513b29c6ce6b8020bbf05a5bead68e5a2/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java#L571-L585

sorry, just saw that there is already setConfig

@@ -368,6 +369,13 @@ public AutoConfiguredOpenTelemetrySdkBuilder setServiceClassLoader(
return this;
}

/** Sets the {@link ComponentLoader} to be used to load SPI implementations. */
public AutoConfiguredOpenTelemetrySdkBuilder setComponentLoader(ComponentLoader componentLoader) {
Copy link
Member

Choose a reason for hiding this comment

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

Actually this is problematic. Can't have a public API reference an internal class, since this would break when we move ComponentLoader to a public package. The way we've implemented this stuff in the past is:

  • ComponentLoader is in an internal package
  • AutoConfiguredOpenTelemetrySdkBuilder#setComponentLoader is package private
  • A public static method is added to a class in an internal package which reflectively calls AutoConfiguredOpenTelemetrySdkBuilder#setComponentLoader. The natural place for this is AutoConfigureUtil. If a user wants to use the experimental functionality, they can call the public static method on the internal AutoConfigureUtil until we promote the functionality to the public API.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah, that's why we have those reflective methods 😄

Copy link
Member

Choose a reason for hiding this comment

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

Its strange but it works 😅

@@ -115,7 +116,7 @@ public final class AutoConfiguredOpenTelemetrySdkBuilder implements AutoConfigur
* {@link #addPropertiesSupplier(Supplier)} and {@link #addPropertiesCustomizer(Function)} will
* have no effect if this method is used.
*/
AutoConfiguredOpenTelemetrySdkBuilder setConfig(ConfigProperties config) {
public AutoConfiguredOpenTelemetrySdkBuilder setConfig(ConfigProperties config) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain the reasoning for this? Its pretty drastic to want to call this and negate calls to addPropertiesSupplier and addPropertiesCustomizer.

Its currently only being used in tests, and even those usages aren't necessary. I honestly think we ought to delete it altogether rather than promote it to the public API.

Copy link
Member Author

Choose a reason for hiding this comment

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

Spring needs a different implementation of ConfigProperties, because of limitations in list and map handling - see https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/d8aa0f5b48e2dee28498b6f95d3d0017eb673cbf/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/resources/SpringConfigProperties.java#L102-L117

The alternative would be to stringify maps and lists - and then let them be parsed using DefaultConfigProperties - that doesn't look right, IMO.

Copy link
Member Author

Choose a reason for hiding this comment

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

My suggestion: Make this method experimental like setComponentLoader

Copy link
Member

Choose a reason for hiding this comment

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

My suggestion: Make this method experimental like setComponentLoader

What would be the path to stabilizing? I think we should stabilize setComponentLoader after we integrate it into spring autoconfigure and confirm there we didn't miss anything. But overriding the config here conflicts with autoconfigure patterns by erasing property suppliers and customizers provided via SPI. Overriding ConfigProperties means that spring autoconfigure would interact with most SPIs normally, except those involving customizing config properties. Would be surprising to users.

Copy link
Member

Choose a reason for hiding this comment

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

What about an experimental method to customize ConfigProperties? Something like:

AutoConfiguredOpenTelemetrySdkBuilder setConfigPropertiesCustomizer(Function<ConfigProperties, ConfigProperties> configPropertiesCustomizer)

The idea is that it follows a similar pattern to the customization methods in AutoConfigurationCustomizer like AutoConfigurationCustomizer#addPropertiesCustomizer(Function<ConfigProperties, Map<String, String>>), but is able to return a custom ConfigProperties instance instead of Map<String, String>. We can limit this method to AutoConfigurationOpenTelemetrySdkBuilder (i.e. do not add it to AutoConfigurationCustomizer). Spring autoconfiguration's implementation of ConfigProperties can fallback to the ConfigProperties instance provided by autoconfigure if a particular property is undefined in spring's configuration system.

Copy link
Member Author

Choose a reason for hiding this comment

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

That idea is quite elegant.

I don't think a spring user would expect that capability, because spring can already do that: https://stackoverflow.com/questions/29072628/how-can-i-override-spring-boot-application-properties-programmatically

Copy link
Member Author

Choose a reason for hiding this comment

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

I think your proposal could be good for distros of the spring starter, so I'll implement it.

@@ -115,6 +115,8 @@ if (!project.hasProperty("otel.release") && !project.name.startsWith("bom")) {
// Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130
// with some changes.
val exclusions = mutableListOf<String>()
// Generics are not detected correctly
exclusions.add("CLASS_GENERIC_TEMPLATE_CHANGED")
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't need this anymore.

Copy link
Member Author

Choose a reason for hiding this comment

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

not for this PR - but it's still true that jApiCmp will trip over generics - I'm fine to take it out, of course.

Copy link
Member

@jack-berg jack-berg left a comment

Choose a reason for hiding this comment

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

Couple more comments but I think we're close.

Comment on lines -186 to +182
{
double ratio =
config.getDouble("otel.traces.sampler.arg", DEFAULT_TRACEIDRATIO_SAMPLE_RATIO);
return Sampler.parentBased(Sampler.traceIdRatioBased(ratio));
}
return Sampler.parentBased(ratioSampler(config));
Copy link
Member

Choose a reason for hiding this comment

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

this is a nice change, but seems unrelated to this PR? in the future splitting out unrelated changes can make life easier for reviewers

Comment on lines 30 to 41
/**
* Load implementations of an ordered SPI (i.e. implements {@link Ordered}).
*
* @param spiClass the SPI class
* @param <T> the SPI type
* @return list of SPI implementations, in order
*/
default <T extends Ordered> List<T> loadOrdered(Class<T> spiClass) {
return StreamSupport.stream(load(spiClass).spliterator(), false)
.sorted(Comparator.comparing(Ordered::order))
.collect(Collectors.toList());
}
Copy link
Member

Choose a reason for hiding this comment

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

do we need this method? (can inline as private method in SpiHelper?)

Copy link
Member Author

Choose a reason for hiding this comment

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

the method is meant for overriding.
In spring, the spring beans are given a higher priority - https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/10453/files#diff-8c1885af89564bfbd461edbbf02fe19e23e40a2e54f6e264d41d786b760e5d26R40-R53.

I'm still unsure if that's what users actually expect though.

It affects

  • AutoConfigurationCustomizerProvider
  • ResourceProvider

Copy link
Member Author

Choose a reason for hiding this comment

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

can't find a really compelling reason - I'll remove it.

@zeitlinger
Copy link
Member Author

@jack-berg can you check again?

@jack-berg jack-berg merged commit 56c11e5 into open-telemetry:main Mar 6, 2024
18 checks passed
@zeitlinger zeitlinger deleted the componet-loader branch March 11, 2024 13:59
@zeitlinger
Copy link
Member Author

Thanks @jack-berg for getting it into this release 🎉

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.

None yet

4 participants