Skip to content

Add ObservationRegistry support to ConfigClientRequestTemplateFactory#3221

Open
LuccasAps wants to merge 6 commits intospring-cloud:mainfrom
LuccasAps:add-observability-config-server-rest-template
Open

Add ObservationRegistry support to ConfigClientRequestTemplateFactory#3221
LuccasAps wants to merge 6 commits intospring-cloud:mainfrom
LuccasAps:add-observability-config-server-rest-template

Conversation

@LuccasAps
Copy link
Copy Markdown

What this PR does

Closes #2972

The RestTemplate used in ConfigServerConfigDataLoader was not instrumented
with Micrometer observations, making it impossible to monitor the behavior of
the Config Server integration through metrics and traces.

Changes

ConfigClientRequestTemplateFactory

  • Added ObservationRegistry as an optional field
  • Added a new constructor that accepts ObservationRegistry
  • The existing constructor defaults to ObservationRegistry.NOOP to maintain backward compatibility
  • When a non-NOOP registry is provided, applies ObservationRestTemplateCustomizer to the created RestTemplate

ConfigServerBootstrapper

  • Added withObservationRegistry(ObservationRegistry) hook so developers can provide an ObservationRegistry during the bootstrap phase

ConfigServerConfigDataLocationResolver

  • Updated ConfigClientRequestTemplateFactory registration to pass the ObservationRegistry from the BootstrapContext when available, falling back to ObservationRegistry.NOOP

Usage

SpringApplication app = new SpringApplication(MyApp.class);
app.addBootstrapRegistryInitializer(
    new ConfigServerBootstrapper()
        .withObservationRegistry(myObservationRegistry)
);
app.run(args);

Backward compatibility

No breaking changes. When no ObservationRegistry is provided,
ObservationRegistry.NOOP is used and behavior is identical to before.

Closes spring-cloud#2972

Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@ryanjbaxter ryanjbaxter left a comment

Choose a reason for hiding this comment

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

ConfigServerBootstrapper and ConfigClientRequestTemplateFactory will result in ClassNotFoundExceptions since micrometer is brought in via optional actuator dependencies. We probably want to subclass both of these classes with the observation changes and only create them when ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null)

Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Copy link
Copy Markdown
Author

@LuccasAps LuccasAps left a comment

Choose a reason for hiding this comment

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

Thanks for the review! I've addressed all the feedback:

  • Removed the null check for observationRegistry
  • Moved ObservationRestTemplateCustomizer instantiation to the constructor
  • Translated test comments to English
  • Used registry variable directly in the constructor instead of ObservationRegistry.NOOP
  • Added integration test for withObservationRegistry

Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
@LuccasAps
Copy link
Copy Markdown
Author

ConfigServerBootstrapper and ConfigClientRequestTemplateFactory will result in ClassNotFoundExceptions since micrometer is brought in via optional actuator dependencies. We probably want to subclass both of these classes with the observation changes and only create them when ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null)

Sorry! I had already applied the individual file changes before seeing this comment.
After reading it, I refactored the implementation to use subclasses as suggested:

  • Created ObservationConfigClientRequestTemplateFactory extending ConfigClientRequestTemplateFactory with all Micrometer-specific imports isolated
  • Created ObservationConfigServerBootstrapper extending ConfigServerBootstrapper with the withObservationRegistry hook
  • Used ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null) in ConfigServerConfigDataLocationResolver to conditionally instantiate the observation subclass
  • Removed all Micrometer imports from the base classes to prevent ClassNotFoundException when Actuator is not on the classpath


private ObservationRegistry observationRegistry;

static ObservationConfigServerBootstrapper create() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason why this is package private but the constructor is public?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Just following the superclass pattern, happy to change if needed!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes makes sense to be consistent with the visibility (in both places)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't se this addressed

Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Copy link
Copy Markdown
Author

@LuccasAps LuccasAps left a comment

Choose a reason for hiding this comment

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

Added a check for ObservationRestTemplateCustomizer as well. Thanks for catching that!

Copy link
Copy Markdown
Contributor

@ryanjbaxter ryanjbaxter 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 it would be worth while adding a test that uses @ClassPathExclusions to test when micrometer and restclient are not on the classpath. You can see an example of such a test here https://github.com/spring-cloud/spring-cloud-commons/blob/3a1c2dfc5cf73fcabcf53dc0ba6c2da215454de6/spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/ManagementServerPortUtilsTests.java#L30


private ObservationRegistry observationRegistry;

static ObservationConfigServerBootstrapper create() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't se this addressed

Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
@LuccasAps
Copy link
Copy Markdown
Author

micrometer

After investigating the dependency tree, I found that micrometer-observation
(which contains ObservationRegistry) is actually a direct compile dependency of
spring-web, not of the Actuator:

spring-web -> micrometer-observation -> ObservationRegistry

This means ObservationRegistry will always be present on the classpath, making the
first ClassUtils.isPresent check redundant. The class that is truly optional is
ObservationRestTemplateCustomizer from spring-boot-restclient.

For now I kept both checks and both exclusions in @ClassPathExclusions. Should we
simplify to only check/exclude spring-boot-restclient? Happy to update if you think
that's the right approach.

public class ConfigServerConfigDataWithoutMicrometerTests {

@Test
void contextStartsWithoutMicrometer() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would be good to verify the correct beans are created

* @author Luccas Asaphe
*
*/
@ClassPathExclusions({ "spring-boot-starter-actuator-*.jar", "spring-boot-restclient-*.jar" })
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Excluding actuator is not needed since it is brought in by web

@ryanjbaxter
Copy link
Copy Markdown
Contributor

For now I kept both checks and both exclusions in @ClassPathExclusions. Should we
simplify to only check/exclude spring-boot-restclient? Happy to update if you think
that's the right approach.

Looks like there is just one check, and that is fine:

https://github.com/spring-cloud/spring-cloud-config/pull/3221/changes#diff-942b499b60cc628a4dfe9bab68d5fa1dcb65016ba3f23042ff697a2bb172a104R245-R246

…lasspath

Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Copy link
Copy Markdown
Author

@LuccasAps LuccasAps left a comment

Choose a reason for hiding this comment

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

Updated the test to verify that ConfigClientRequestTemplateFactory (base class) is created when spring-boot-restclient is not on the classpath, and removed the spring-boot-starter-actuator exclusion since it is not needed — only spring-boot-restclient needs to be excluded as it contains ObservationRestTemplateCustomizer.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configure observability for RestTemplate used in ConfigServerConfigDataLoader

3 participants