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

4.x: Observability update #7625

Merged
merged 13 commits into from
Sep 22, 2023
Merged
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
22 changes: 10 additions & 12 deletions archetypes/helidon/src/main/archetype/common/observability.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,15 @@ curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
</list>
<list key="Main-helidon-imports" if="${flavor} == 'se'">
<value>io.helidon.metrics.api.MeterRegistry</value>
<value>io.helidon.webserver.observe.metrics.MetricsFeature</value>
<value>io.helidon.webserver.observe.metrics.MetricsObserveProvider</value>
<value>io.helidon.webserver.observe.metrics.MetricsObserver</value>
</list>
<list key="Main-routing" if="${flavor} == 'se'">
<value><![CDATA[
MetricsService metricsService = new MetricsService(config);
]]></value>
</list>
<list key="Main-routing-builder" if="${flavor} == 'se'">
<value><![CDATA[.addFeature(ObserveFeature.create(MetricsObserveProvider.create(MetricsFeature.create())))]]></value>
<value><![CDATA[.addFeature(ObserveFeature.create(MetricsObserver.create()))]]></value>
<value><![CDATA[.register("/metrics-greet", metricsService)]]></value>
</list>
<list key="MetricsService-imports" if="${flavor} == 'se'">
Expand Down Expand Up @@ -463,8 +462,8 @@ curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
</list>
<list key="Main-createRouting" if="${flavor} == 'mp'">
<value template="mustache" order="0"><![CDATA[
HealthSupport health = HealthSupport.builder()
.add(HealthChecks.healthChecks()) // Adds a convenient set of checks
HealthObserver health = HealthObserver.builder()
.addChecks(HealthChecks.healthChecks()) // Adds a convenient set of checks
{{#Main-healthBuilder}}
{{.}}
{{/Main-healthBuilder}}
Expand All @@ -487,7 +486,7 @@ curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
## Try health

```
curl -s -X GET http://localhost:8080/health
curl -s -X GET http://localhost:8080/observe/health
{"outcome":"UP",...

```
Expand All @@ -504,7 +503,7 @@ curl -s -X GET http://localhost:8080/health
</map>
</list>
<list key="Observe-feature-builder" if="${flavor} == 'se'">
<value><![CDATA[.addProvider(HealthObserveProvider.create(HealthFeature.builder()
<value><![CDATA[.addObserver(HealthObserver.builder()
.details(true)
.useSystemServices(false)
.addCheck(() -> HealthCheckResponse.builder()
Expand All @@ -515,13 +514,12 @@ curl -s -X GET http://localhost:8080/health
.status(isStarted())
.detail("time", System.currentTimeMillis())
.build(), HealthCheckType.STARTUP)
.build()))]]></value>
.build())]]></value>
</list>
<list key="Main-helidon-imports" if="${flavor} == 'se'">
<value>io.helidon.health.HealthCheckResponse</value>
<value>io.helidon.health.HealthCheckType</value>
<value>io.helidon.webserver.observe.health.HealthFeature</value>
<value>io.helidon.webserver.observe.health.HealthObserveProvider</value>
<value>io.helidon.webserver.observe.health.HealthObserver</value>
</list>
<list key="MainTest-static-imports" if="${flavor} == 'se'">
<value>org.hamcrest.CoreMatchers.containsString</value>
Expand Down Expand Up @@ -566,8 +564,8 @@ Note the port number reported by the application.
Probe the health endpoints:

```bash
curl -X GET http://localhost:8080/observe/health/
curl -X GET http://localhost:8080/observe/health/ready
curl -X GET http://localhost:8080/observe/observe/health/
curl -X GET http://localhost:8080/observe/observe/health/ready
```

]]></value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<list key="Main-routing">
<value template="mustache"><![CDATA[
ObserveFeature observe = ObserveFeature.builder()
.useSystemServices(false)
.observersDiscoverServices(false)
{{#Observe-feature-builder}}
{{.}}
{{/Observe-feature-builder}}
Expand All @@ -57,12 +57,10 @@
<value>io.helidon.health.checks.DeadlockHealthCheck</value>
<value>io.helidon.health.checks.DiskSpaceHealthCheck</value>
<value>io.helidon.health.checks.HeapMemoryHealthCheck</value>
<value>io.helidon.webserver.observe.health.HealthFeature</value>
<value>io.helidon.webserver.observe.health.HealthObserveProvider</value>
<value>io.helidon.webserver.observe.health.HealthObserver</value>
</list>
<list key="Main-helidon-imports" if="${metrics}">
<value>io.helidon.webserver.observe.metrics.MetricsFeature</value>
<value>io.helidon.webserver.observe.metrics.MetricsObserveProvider</value>
<value>io.helidon.webserver.observe.metrics.MetricsObserver</value>
</list>
<list key="Main-helidon-imports" if="${tracing}">
<value>io.helidon.webserver.http2.Http2Route</value>
Expand Down Expand Up @@ -113,7 +111,7 @@
]]></value>
</list>
<list key="Observe-feature-builder">
<value if="${metrics}"><![CDATA[ .addProvider(MetricsObserveProvider.create())]]></value>
<value if="${metrics}"><![CDATA[ .addObserver(MetricsObserver.create())]]></value>
</list>
<list key="Abstract-tests">
<value if="${metrics}"><![CDATA[
Expand Down
29 changes: 20 additions & 9 deletions builder/api/src/main/java/io/helidon/builder/api/Option.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ private Option() {
* @return custom configuration key
*/
String value() default "";

/**
* If set to {@code true}, the nested configurable object will not have its own config key,
* but will use the config of the current configurable object.
*
* @return whether to merge the nested object into this object
*/
boolean merge() default false;
}

/**
Expand Down Expand Up @@ -90,10 +98,14 @@ private Option() {
* Mark option as sourced from a {@link java.util.ServiceLoader}.
* Use if the configuration may be provided by another module not known to us.
* <p>
* To control whether to discover services or not, you can specify a key {@code config-key-discover-services}
* on the same level as the section for the provider based property. This is aligned with the generated methods on the
* builder, and allows for the shallowest possible configuration tree (this would override {@link #discoverServices()}
* defined on this annotation).
* <p>
* Also there is no difference regardless whether we return a single value, or a list of values.
* If the method returns a list, the provider configuration must be under config key {@code providers} under
* the configured option. On the same level as {@code providers}, there can be {@code discover-services} boolean
* defining whether to look for services from service loader even if not configured in the configuration (this would
* override {@link #discoverServices()} defined on this annotation.
* <p>
* Option called {@code myProvider} that returns a single provider, or an {@link java.util.Optional} provider example
* in configuration:
Expand All @@ -108,14 +120,13 @@ private Option() {
* Option called {@code myProviders} that returns a list of providers in configuration:
* <pre>
* my-type:
* my-providers-discover-services: true # default of this value is controlled by annotation
* my-providers:
* discover-services: true # default of this value is controlled by annotation
* providers:
* provider-id:
* provider-key1: "providerValue"
* provider-key2: "providerValue"
* provider2-id:
* provider2-key1: "provider2Value"
* provider-id:
* provider-key1: "providerValue"
* provider-key2: "providerValue"
* provider2-id:
* provider2-key1: "provider2Value"
* </pre>
*/
@Target(ElementType.METHOD)
Expand Down
31 changes: 20 additions & 11 deletions builder/api/src/main/java/io/helidon/builder/api/Prototype.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER,
/**
* Discover services from configuration.
*
* @param config configuration located at the node of the service providers
* @param config configuration located at the parent node of the service providers
* @param configKey configuration key of the provider list
* (either a list node, or object, where each child is one service)
* @param serviceLoader helidon service loader for the expected type
* @param providerType type of the service provider interface
Expand All @@ -108,19 +109,26 @@ public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER,
*/
default <S extends NamedService, T extends ConfiguredProvider<S>> List<S>
discoverServices(Config config,
String configKey,
HelidonServiceLoader<T> serviceLoader,
Class<T> providerType,
Class<S> configType,
boolean allFromServiceLoader) {
return ProvidedUtil.discoverServices(config, serviceLoader, providerType, configType, allFromServiceLoader);
return ProvidedUtil.discoverServices(config,
configKey,
serviceLoader,
providerType,
configType,
allFromServiceLoader);
}

/**
* Discover service from configuration.
*
* @param config configuration located at the node of the service providers
* @param config configuration located at the parent node of the service providers
* @param configKey configuration key of the provider list
* (either a list node, or object, where each child is one service - this method requires
* zero to one configured services)
* * zero to one configured services)
* @param serviceLoader helidon service loader for the expected type
* @param providerType type of the service provider interface
* @param configType type of the configured service
Expand All @@ -129,15 +137,16 @@ public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER,
* @param <S> type of the expected service
* @param <T> type of the configured service provider that creates instances of S
* @return the first service (ordered by {@link io.helidon.common.Weight} that is discovered, or empty optional if none
* is found
* is found
*/
default <S extends NamedService, T extends ConfiguredProvider<S>> Optional<S>
discoverService(Config config,
HelidonServiceLoader<T> serviceLoader,
Class<T> providerType,
Class<S> configType,
boolean allFromServiceLoader) {
return ProvidedUtil.discoverService(config, serviceLoader, providerType, configType, allFromServiceLoader);
String configKey,
HelidonServiceLoader<T> serviceLoader,
Class<T> providerType,
Class<S> configType,
boolean allFromServiceLoader) {
return ProvidedUtil.discoverService(config, configKey, serviceLoader, providerType, configType, allFromServiceLoader);
}
}

Expand Down Expand Up @@ -185,6 +194,7 @@ public interface Factory<T> {
/**
* The generated interface is public by default. We can switch it to package local
* by setting this property to {@code false}-
*
* @return whether the generated interface should be public
*/
boolean isPublic() default true;
Expand Down Expand Up @@ -235,7 +245,6 @@ public interface Factory<T> {
* A blueprint annotated with this annotation will create a prototype that can be created from a
* {@link io.helidon.common.config.Config} instance. The builder will also have a method {@code config(Config)} that
* reads all options annotated with {@link io.helidon.builder.api.Option.Configured} from the config.
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
Expand Down
44 changes: 15 additions & 29 deletions builder/api/src/main/java/io/helidon/builder/api/ProvidedUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ private ProvidedUtil() {
* Discover service from configuration.
* This method looks for a single provider only.
*
* @param config configuration located at the node of the service providers
* (either a list node, or object, where each child is one service)
* @param config configuration located at the parent node of the service providers
* @param configKey configuration key of the provider list
* (either a list node, or object, where each child is one service)
* @param serviceLoader helidon service loader for the expected type
* @param providerType service provider interface type
* @param configType configured service type
Expand All @@ -75,48 +76,32 @@ private ProvidedUtil() {
*/
static <T extends NamedService> Optional<T>
discoverService(Config config,
String configKey,
HelidonServiceLoader<? extends ConfiguredProvider<T>> serviceLoader,
Class<? extends ConfiguredProvider<T>> providerType,
Class<T> configType,
boolean discoverServices) {
/*
- if we find more than one using service loader, we will use one with higher weight, unless a provider is configured
in config
- if we find more than one in config, it is an error
*/
List<ConfiguredService> configuredServices = new ArrayList<>();

// all child nodes of the current node
List<Config> serviceConfigList = config.asNodeList()
List<Config> serviceConfigList = config.get(configKey).asNodeList()
.orElseGet(List::of);

// if more than one is configured in config, fail
// if more than one exists in service loader, use the first one
if (serviceConfigList.size() > 1) {
throw new ConfigException("There can only be one provider configured for " + config.key());
}

boolean isList = config.isList();

for (Config serviceConfig : serviceConfigList) {
configuredServices.add(configuredService(serviceConfig, isList));
}

List<T> result;
// now we have all service configurations, we can start building up instances
if (config.isList()) {
// driven by order of declaration in config
result = servicesFromList(serviceLoader, providerType, configType, configuredServices, discoverServices);
} else {
// driven by service loader order
result = servicesFromObject(serviceLoader, providerType, configType, configuredServices, discoverServices);
}
List<T> services = discoverServices(config, configKey, serviceLoader, providerType, configType, discoverServices);

return result.isEmpty() ? Optional.empty() : Optional.of(result.get(0));
return services.isEmpty() ? Optional.empty() : Optional.of(services.get(0));
}

/**
* Discover services from configuration.
*
* @param config configuration located at the node of the service providers
* @param config configuration located at the parent node of the service providers
* @param configKey configuration key of the provider list
* (either a list node, or object, where each child is one service)
* @param serviceLoader helidon service loader for the expected type
* @param providerType service provider interface type
Expand All @@ -127,13 +112,14 @@ private ProvidedUtil() {
* @return list of discovered services, ordered by {@link io.helidon.common.Weight} (highest weight first)
*/
static <T extends NamedService> List<T> discoverServices(Config config,
String configKey,
HelidonServiceLoader<? extends ConfiguredProvider<T>> serviceLoader,
Class<? extends ConfiguredProvider<T>> providerType,
Class<T> configType,
boolean allFromServiceLoader) {

boolean discoverServices = config.get("discover-services").asBoolean().orElse(allFromServiceLoader);
Config providersConfig = config.get("providers");
boolean discoverServices = config.get(configKey + "-discover-services").asBoolean().orElse(allFromServiceLoader);
Config providersConfig = config.get(configKey);

List<ConfiguredService> configuredServices = new ArrayList<>();

Expand All @@ -147,7 +133,7 @@ static <T extends NamedService> List<T> discoverServices(Config config,
}

// now we have all service configurations, we can start building up instances
if (config.isList()) {
if (providersConfig.isList()) {
// driven by order of declaration in config
return servicesFromList(serviceLoader, providerType, configType, configuredServices, discoverServices);
} else {
Expand Down
Loading
Loading