Skip to content

Commit

Permalink
4.x: Observability update (helidon-io#7625)
Browse files Browse the repository at this point in the history
* Design proposal - observability

* Added a health only example.

* Health, metrics, log, info, log updated to new approach.

* Fix usages of observe feature, and fix problems

* Remove the requirement to use `providers` as a sub-key when using providers in builders. This aligns the keys with builder methods, and simplifies configuration.

* Fix SE Quickstart to use config again.
Add OpenApiFeature.create(Config) method

* Reformat OpenApiFeature class, as it had static methods mixed with constants, enums before code etc.

* Mp quickstart test a bit nicer.

* Copyright and checkstyle fixes.

* Small improvement for handling next on not found

* Fix all tests.

* Global config accessible from io.helidon.config.Config class.

* Archetype updates to new observability API
Class model fix to correctly import arrays
  • Loading branch information
tomas-langer authored Sep 22, 2023
1 parent 4b95513 commit 6097186
Show file tree
Hide file tree
Showing 105 changed files with 3,591 additions and 2,058 deletions.
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

0 comments on commit 6097186

Please sign in to comment.