Skip to content

Commit 6097186

Browse files
authored
4.x: Observability update (helidon-io#7625)
* 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
1 parent 4b95513 commit 6097186

105 files changed

Lines changed: 3591 additions & 2058 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

archetypes/helidon/src/main/archetype/common/observability.xml

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,15 @@ curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
152152
</list>
153153
<list key="Main-helidon-imports" if="${flavor} == 'se'">
154154
<value>io.helidon.metrics.api.MeterRegistry</value>
155-
<value>io.helidon.webserver.observe.metrics.MetricsFeature</value>
156-
<value>io.helidon.webserver.observe.metrics.MetricsObserveProvider</value>
155+
<value>io.helidon.webserver.observe.metrics.MetricsObserver</value>
157156
</list>
158157
<list key="Main-routing" if="${flavor} == 'se'">
159158
<value><![CDATA[
160159
MetricsService metricsService = new MetricsService(config);
161160
]]></value>
162161
</list>
163162
<list key="Main-routing-builder" if="${flavor} == 'se'">
164-
<value><![CDATA[.addFeature(ObserveFeature.create(MetricsObserveProvider.create(MetricsFeature.create())))]]></value>
163+
<value><![CDATA[.addFeature(ObserveFeature.create(MetricsObserver.create()))]]></value>
165164
<value><![CDATA[.register("/metrics-greet", metricsService)]]></value>
166165
</list>
167166
<list key="MetricsService-imports" if="${flavor} == 'se'">
@@ -463,8 +462,8 @@ curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
463462
</list>
464463
<list key="Main-createRouting" if="${flavor} == 'mp'">
465464
<value template="mustache" order="0"><![CDATA[
466-
HealthSupport health = HealthSupport.builder()
467-
.add(HealthChecks.healthChecks()) // Adds a convenient set of checks
465+
HealthObserver health = HealthObserver.builder()
466+
.addChecks(HealthChecks.healthChecks()) // Adds a convenient set of checks
468467
{{#Main-healthBuilder}}
469468
{{.}}
470469
{{/Main-healthBuilder}}
@@ -487,7 +486,7 @@ curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics
487486
## Try health
488487
489488
```
490-
curl -s -X GET http://localhost:8080/health
489+
curl -s -X GET http://localhost:8080/observe/health
491490
{"outcome":"UP",...
492491
493492
```
@@ -504,7 +503,7 @@ curl -s -X GET http://localhost:8080/health
504503
</map>
505504
</list>
506505
<list key="Observe-feature-builder" if="${flavor} == 'se'">
507-
<value><![CDATA[.addProvider(HealthObserveProvider.create(HealthFeature.builder()
506+
<value><![CDATA[.addObserver(HealthObserver.builder()
508507
.details(true)
509508
.useSystemServices(false)
510509
.addCheck(() -> HealthCheckResponse.builder()
@@ -515,13 +514,12 @@ curl -s -X GET http://localhost:8080/health
515514
.status(isStarted())
516515
.detail("time", System.currentTimeMillis())
517516
.build(), HealthCheckType.STARTUP)
518-
.build()))]]></value>
517+
.build())]]></value>
519518
</list>
520519
<list key="Main-helidon-imports" if="${flavor} == 'se'">
521520
<value>io.helidon.health.HealthCheckResponse</value>
522521
<value>io.helidon.health.HealthCheckType</value>
523-
<value>io.helidon.webserver.observe.health.HealthFeature</value>
524-
<value>io.helidon.webserver.observe.health.HealthObserveProvider</value>
522+
<value>io.helidon.webserver.observe.health.HealthObserver</value>
525523
</list>
526524
<list key="MainTest-static-imports" if="${flavor} == 'se'">
527525
<value>org.hamcrest.CoreMatchers.containsString</value>
@@ -566,8 +564,8 @@ Note the port number reported by the application.
566564
Probe the health endpoints:
567565
568566
```bash
569-
curl -X GET http://localhost:8080/observe/health/
570-
curl -X GET http://localhost:8080/observe/health/ready
567+
curl -X GET http://localhost:8080/observe/observe/health/
568+
curl -X GET http://localhost:8080/observe/observe/health/ready
571569
```
572570
573571
]]></value>

archetypes/helidon/src/main/archetype/se/custom/observability.xml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<list key="Main-routing">
3434
<value template="mustache"><![CDATA[
3535
ObserveFeature observe = ObserveFeature.builder()
36-
.useSystemServices(false)
36+
.observersDiscoverServices(false)
3737
{{#Observe-feature-builder}}
3838
{{.}}
3939
{{/Observe-feature-builder}}
@@ -57,12 +57,10 @@
5757
<value>io.helidon.health.checks.DeadlockHealthCheck</value>
5858
<value>io.helidon.health.checks.DiskSpaceHealthCheck</value>
5959
<value>io.helidon.health.checks.HeapMemoryHealthCheck</value>
60-
<value>io.helidon.webserver.observe.health.HealthFeature</value>
61-
<value>io.helidon.webserver.observe.health.HealthObserveProvider</value>
60+
<value>io.helidon.webserver.observe.health.HealthObserver</value>
6261
</list>
6362
<list key="Main-helidon-imports" if="${metrics}">
64-
<value>io.helidon.webserver.observe.metrics.MetricsFeature</value>
65-
<value>io.helidon.webserver.observe.metrics.MetricsObserveProvider</value>
63+
<value>io.helidon.webserver.observe.metrics.MetricsObserver</value>
6664
</list>
6765
<list key="Main-helidon-imports" if="${tracing}">
6866
<value>io.helidon.webserver.http2.Http2Route</value>
@@ -113,7 +111,7 @@
113111
]]></value>
114112
</list>
115113
<list key="Observe-feature-builder">
116-
<value if="${metrics}"><![CDATA[ .addProvider(MetricsObserveProvider.create())]]></value>
114+
<value if="${metrics}"><![CDATA[ .addObserver(MetricsObserver.create())]]></value>
117115
</list>
118116
<list key="Abstract-tests">
119117
<value if="${metrics}"><![CDATA[

builder/api/src/main/java/io/helidon/builder/api/Option.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ private Option() {
4444
* @return custom configuration key
4545
*/
4646
String value() default "";
47+
48+
/**
49+
* If set to {@code true}, the nested configurable object will not have its own config key,
50+
* but will use the config of the current configurable object.
51+
*
52+
* @return whether to merge the nested object into this object
53+
*/
54+
boolean merge() default false;
4755
}
4856

4957
/**
@@ -90,10 +98,14 @@ private Option() {
9098
* Mark option as sourced from a {@link java.util.ServiceLoader}.
9199
* Use if the configuration may be provided by another module not known to us.
92100
* <p>
101+
* To control whether to discover services or not, you can specify a key {@code config-key-discover-services}
102+
* on the same level as the section for the provider based property. This is aligned with the generated methods on the
103+
* builder, and allows for the shallowest possible configuration tree (this would override {@link #discoverServices()}
104+
* defined on this annotation).
105+
* <p>
106+
* Also there is no difference regardless whether we return a single value, or a list of values.
93107
* If the method returns a list, the provider configuration must be under config key {@code providers} under
94108
* the configured option. On the same level as {@code providers}, there can be {@code discover-services} boolean
95-
* defining whether to look for services from service loader even if not configured in the configuration (this would
96-
* override {@link #discoverServices()} defined on this annotation.
97109
* <p>
98110
* Option called {@code myProvider} that returns a single provider, or an {@link java.util.Optional} provider example
99111
* in configuration:
@@ -108,14 +120,13 @@ private Option() {
108120
* Option called {@code myProviders} that returns a list of providers in configuration:
109121
* <pre>
110122
* my-type:
123+
* my-providers-discover-services: true # default of this value is controlled by annotation
111124
* my-providers:
112-
* discover-services: true # default of this value is controlled by annotation
113-
* providers:
114-
* provider-id:
115-
* provider-key1: "providerValue"
116-
* provider-key2: "providerValue"
117-
* provider2-id:
118-
* provider2-key1: "provider2Value"
125+
* provider-id:
126+
* provider-key1: "providerValue"
127+
* provider-key2: "providerValue"
128+
* provider2-id:
129+
* provider2-key1: "provider2Value"
119130
* </pre>
120131
*/
121132
@Target(ElementType.METHOD)

builder/api/src/main/java/io/helidon/builder/api/Prototype.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER,
9595
/**
9696
* Discover services from configuration.
9797
*
98-
* @param config configuration located at the node of the service providers
98+
* @param config configuration located at the parent node of the service providers
99+
* @param configKey configuration key of the provider list
99100
* (either a list node, or object, where each child is one service)
100101
* @param serviceLoader helidon service loader for the expected type
101102
* @param providerType type of the service provider interface
@@ -108,19 +109,26 @@ public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER,
108109
*/
109110
default <S extends NamedService, T extends ConfiguredProvider<S>> List<S>
110111
discoverServices(Config config,
112+
String configKey,
111113
HelidonServiceLoader<T> serviceLoader,
112114
Class<T> providerType,
113115
Class<S> configType,
114116
boolean allFromServiceLoader) {
115-
return ProvidedUtil.discoverServices(config, serviceLoader, providerType, configType, allFromServiceLoader);
117+
return ProvidedUtil.discoverServices(config,
118+
configKey,
119+
serviceLoader,
120+
providerType,
121+
configType,
122+
allFromServiceLoader);
116123
}
117124

118125
/**
119126
* Discover service from configuration.
120127
*
121-
* @param config configuration located at the node of the service providers
128+
* @param config configuration located at the parent node of the service providers
129+
* @param configKey configuration key of the provider list
122130
* (either a list node, or object, where each child is one service - this method requires
123-
* zero to one configured services)
131+
* * zero to one configured services)
124132
* @param serviceLoader helidon service loader for the expected type
125133
* @param providerType type of the service provider interface
126134
* @param configType type of the configured service
@@ -129,15 +137,16 @@ public interface ConfiguredBuilder<BUILDER, PROTOTYPE> extends Builder<BUILDER,
129137
* @param <S> type of the expected service
130138
* @param <T> type of the configured service provider that creates instances of S
131139
* @return the first service (ordered by {@link io.helidon.common.Weight} that is discovered, or empty optional if none
132-
* is found
140+
* is found
133141
*/
134142
default <S extends NamedService, T extends ConfiguredProvider<S>> Optional<S>
135143
discoverService(Config config,
136-
HelidonServiceLoader<T> serviceLoader,
137-
Class<T> providerType,
138-
Class<S> configType,
139-
boolean allFromServiceLoader) {
140-
return ProvidedUtil.discoverService(config, serviceLoader, providerType, configType, allFromServiceLoader);
144+
String configKey,
145+
HelidonServiceLoader<T> serviceLoader,
146+
Class<T> providerType,
147+
Class<S> configType,
148+
boolean allFromServiceLoader) {
149+
return ProvidedUtil.discoverService(config, configKey, serviceLoader, providerType, configType, allFromServiceLoader);
141150
}
142151
}
143152

@@ -185,6 +194,7 @@ public interface Factory<T> {
185194
/**
186195
* The generated interface is public by default. We can switch it to package local
187196
* by setting this property to {@code false}-
197+
*
188198
* @return whether the generated interface should be public
189199
*/
190200
boolean isPublic() default true;
@@ -235,7 +245,6 @@ public interface Factory<T> {
235245
* A blueprint annotated with this annotation will create a prototype that can be created from a
236246
* {@link io.helidon.common.config.Config} instance. The builder will also have a method {@code config(Config)} that
237247
* reads all options annotated with {@link io.helidon.builder.api.Option.Configured} from the config.
238-
*
239248
*/
240249
@Target(ElementType.TYPE)
241250
@Retention(RetentionPolicy.CLASS)

builder/api/src/main/java/io/helidon/builder/api/ProvidedUtil.java

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ private ProvidedUtil() {
6363
* Discover service from configuration.
6464
* This method looks for a single provider only.
6565
*
66-
* @param config configuration located at the node of the service providers
67-
* (either a list node, or object, where each child is one service)
66+
* @param config configuration located at the parent node of the service providers
67+
* @param configKey configuration key of the provider list
68+
* (either a list node, or object, where each child is one service)
6869
* @param serviceLoader helidon service loader for the expected type
6970
* @param providerType service provider interface type
7071
* @param configType configured service type
@@ -75,48 +76,32 @@ private ProvidedUtil() {
7576
*/
7677
static <T extends NamedService> Optional<T>
7778
discoverService(Config config,
79+
String configKey,
7880
HelidonServiceLoader<? extends ConfiguredProvider<T>> serviceLoader,
7981
Class<? extends ConfiguredProvider<T>> providerType,
8082
Class<T> configType,
8183
boolean discoverServices) {
82-
/*
83-
- if we find more than one using service loader, we will use one with higher weight, unless a provider is configured
84-
in config
85-
- if we find more than one in config, it is an error
86-
*/
87-
List<ConfiguredService> configuredServices = new ArrayList<>();
8884

8985
// all child nodes of the current node
90-
List<Config> serviceConfigList = config.asNodeList()
86+
List<Config> serviceConfigList = config.get(configKey).asNodeList()
9187
.orElseGet(List::of);
9288

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

97-
boolean isList = config.isList();
98-
99-
for (Config serviceConfig : serviceConfigList) {
100-
configuredServices.add(configuredService(serviceConfig, isList));
101-
}
102-
103-
List<T> result;
104-
// now we have all service configurations, we can start building up instances
105-
if (config.isList()) {
106-
// driven by order of declaration in config
107-
result = servicesFromList(serviceLoader, providerType, configType, configuredServices, discoverServices);
108-
} else {
109-
// driven by service loader order
110-
result = servicesFromObject(serviceLoader, providerType, configType, configuredServices, discoverServices);
111-
}
95+
List<T> services = discoverServices(config, configKey, serviceLoader, providerType, configType, discoverServices);
11296

113-
return result.isEmpty() ? Optional.empty() : Optional.of(result.get(0));
97+
return services.isEmpty() ? Optional.empty() : Optional.of(services.get(0));
11498
}
11599

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

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

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

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

149135
// now we have all service configurations, we can start building up instances
150-
if (config.isList()) {
136+
if (providersConfig.isList()) {
151137
// driven by order of declaration in config
152138
return servicesFromList(serviceLoader, providerType, configType, configuredServices, discoverServices);
153139
} else {

0 commit comments

Comments
 (0)