mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
chore(quarkus): only show OTel Metrics in community build (#49002)
* Closes: https://github.com/keycloak/keycloak/issues/48997 Signed-off-by: Michal Vavřík <michal.vavrik@aol.com>
This commit is contained in:
parent
ce12c7184c
commit
fc667a827a
10 changed files with 99 additions and 21 deletions
|
|
@ -137,7 +137,9 @@ public class Profile {
|
|||
|
||||
OPENTELEMETRY("OpenTelemetry support", Type.DEFAULT),
|
||||
OPENTELEMETRY_LOGS("OpenTelemetry Logs support", Type.PREVIEW, OPENTELEMETRY),
|
||||
OPENTELEMETRY_METRICS("Micrometer to OpenTelemetry bridge support for metrics", Type.EXPERIMENTAL, OPENTELEMETRY),
|
||||
OPENTELEMETRY_METRICS("Micrometer to OpenTelemetry bridge support for metrics", Type.EXPERIMENTAL, 1, false, true,
|
||||
() -> isClassAvailable("io.quarkus.micrometer.opentelemetry.runtime.MicrometerOtelBridgeRecorder"),
|
||||
null, OPENTELEMETRY),
|
||||
|
||||
DECLARATIVE_UI("declarative ui spi", Type.EXPERIMENTAL),
|
||||
|
||||
|
|
@ -187,6 +189,7 @@ public class Profile {
|
|||
private final String unversionedKey;
|
||||
private final String key;
|
||||
private final BooleanSupplier isAvailable;
|
||||
private final boolean hideWhenUnavailable;
|
||||
private final FeatureUpdatePolicy updatePolicy;
|
||||
private final Set<Feature> dependencies;
|
||||
private final boolean deprecated;
|
||||
|
|
@ -213,11 +216,16 @@ public class Profile {
|
|||
}
|
||||
|
||||
Feature(String label, Type type, int version, boolean deprecated, BooleanSupplier isAvailable, FeatureUpdatePolicy updatePolicy, Feature... dependencies) {
|
||||
this(label, type, version, deprecated, false, isAvailable, updatePolicy, dependencies);
|
||||
}
|
||||
|
||||
Feature(String label, Type type, int version, boolean deprecated, boolean hideWhenUnavailable, BooleanSupplier isAvailable, FeatureUpdatePolicy updatePolicy, Feature... dependencies) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.deprecated = type == Type.DEPRECATED || deprecated;
|
||||
this.isAvailable = isAvailable;
|
||||
this.hideWhenUnavailable = hideWhenUnavailable;
|
||||
this.updatePolicy = updatePolicy == null ? FeatureUpdatePolicy.ROLLING : updatePolicy;
|
||||
this.key = name().toLowerCase().replaceAll("_", "-");
|
||||
if (this.name().endsWith("_V" + version)) {
|
||||
|
|
@ -283,6 +291,10 @@ public class Profile {
|
|||
return updatePolicy;
|
||||
}
|
||||
|
||||
private boolean isVisible() {
|
||||
return isAvailable() || !hideWhenUnavailable;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
// in priority order
|
||||
|
||||
|
|
@ -459,11 +471,18 @@ public class Profile {
|
|||
}
|
||||
|
||||
public static Set<String> getAllUnversionedFeatureNames() {
|
||||
return Collections.unmodifiableSet(getOrderedFeatures().keySet());
|
||||
return Collections.unmodifiableSet(getOrderedFeatures().entrySet().stream()
|
||||
.filter(e -> e.getValue().stream().anyMatch(Feature::isVisible))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
public static Set<String> getDisableableUnversionedFeatureNames() {
|
||||
return getOrderedFeatures().keySet().stream().filter(f -> !ESSENTIAL_FEATURES.contains(f)).collect(Collectors.toSet());
|
||||
return getOrderedFeatures().entrySet().stream()
|
||||
.filter(e -> !ESSENTIAL_FEATURES.contains(e.getKey()))
|
||||
.filter(e -> e.getValue().stream().anyMatch(Feature::isVisible))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -582,6 +601,15 @@ public class Profile {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean isClassAvailable(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public enum FeatureUpdatePolicy {
|
||||
// Always allow a rolling update when the Feature is enabled/disabled
|
||||
ROLLING,
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ As stated for the general configuration of headers, you can configure custom req
|
|||
|
||||
<@kc.start parameters="--telemetry-logs-header-Authorization='Bearer logs-token'"/>
|
||||
|
||||
<@profile.ifCommunity>
|
||||
|
||||
== Metrics
|
||||
|
||||
WARNING: The OpenTelemetry Metrics feature is currently experimental, and it is not recommended for use in production.
|
||||
|
|
@ -178,8 +180,6 @@ As stated for the general configuration of headers, you can configure custom req
|
|||
|
||||
<@kc.start parameters="--telemetry-metrics-header-Authorization='Bearer metrics-token'"/>
|
||||
|
||||
<@profile.ifCommunity>
|
||||
|
||||
== Development setup
|
||||
|
||||
For development purposes, you can use the https://github.com/grafana/docker-otel-lgtm[Grafana OTel-LGTM service], containing OpenTelemetry Collector and backends for logs (Loki), metrics (Prometheus), and traces (Tempo).
|
||||
|
|
@ -206,8 +206,10 @@ Then, you can navigate to Grafana UI by accessing `+localhost:3000+` and then yo
|
|||
=== Logs
|
||||
<@opts.includeOptions includedOptions="telemetry-logs-*"/>
|
||||
|
||||
<@profile.ifCommunity>
|
||||
=== Metrics
|
||||
<@opts.includeOptions includedOptions="metrics-enabled telemetry-metrics-*"/>
|
||||
</@profile.ifCommunity>
|
||||
|
||||
</@opts.printRelevantOptions>
|
||||
|
||||
|
|
|
|||
|
|
@ -276,11 +276,6 @@
|
|||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-opentelemetry-deployment</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-micrometer-opentelemetry-deployment</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-internal</artifactId>
|
||||
|
|
@ -338,4 +333,21 @@
|
|||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-micrometer-opentelemetry-deployment</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -125,11 +125,6 @@
|
|||
<groupId>io.opentelemetry.instrumentation</groupId>
|
||||
<artifactId>opentelemetry-apache-httpclient-4.3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-micrometer-opentelemetry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.apicatalog</groupId>
|
||||
<artifactId>titanium-json-ld</artifactId>
|
||||
|
|
@ -860,5 +855,19 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>community</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>!product</name>
|
||||
</property>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-micrometer-opentelemetry</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import org.keycloak.config.MetricsOptions;
|
|||
import org.keycloak.config.OpenApiOptions;
|
||||
import org.keycloak.config.TelemetryOptions;
|
||||
import org.keycloak.config.database.Database;
|
||||
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
|
|
@ -187,6 +188,9 @@ public class IgnoredArtifacts {
|
|||
|
||||
private static Set<String> otelMetrics() {
|
||||
boolean isOtelMetricsEnabled = Configuration.isTrue(TelemetryOptions.TELEMETRY_METRICS_ENABLED);
|
||||
if (isOtelMetricsEnabled && !Profile.Feature.OPENTELEMETRY_METRICS.isAvailable()) {
|
||||
throw new PropertyException("The OpenTelemetry Metrics feature is not available in this distribution.");
|
||||
}
|
||||
return !isOtelMetricsEnabled ? OTEL_METRICS : emptySet();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public class TelemetryPropertyMappers implements PropertyMapperGrouping{
|
|||
@Override
|
||||
public List<? extends PropertyMapper<?>> getPropertyMappers() {
|
||||
TELEMETRY_HEADERS_CACHE = null;
|
||||
boolean metricsAvailable = Profile.Feature.OPENTELEMETRY_METRICS.isAvailable();
|
||||
return List.of(
|
||||
fromFeature(Profile.Feature.OPENTELEMETRY)
|
||||
.transformer(TelemetryPropertyMappers::checkIfDependantsAreEnabled)
|
||||
|
|
@ -127,36 +128,36 @@ public class TelemetryPropertyMappers implements PropertyMapperGrouping{
|
|||
.isMasked(true) // it may contain sensitive information
|
||||
.build(),
|
||||
// Telemetry Metrics
|
||||
fromOption(TELEMETRY_METRICS_ENABLED)
|
||||
fromOption(metricsAvailable ? TELEMETRY_METRICS_ENABLED : toHidden(TELEMETRY_METRICS_ENABLED))
|
||||
.isEnabled(TelemetryPropertyMappers::isOtelMetricsFeatureEnabled, OTEL_METRICS_FEATURE_ENABLED_MSG)
|
||||
.to("quarkus.otel.metrics.enabled")
|
||||
.build(),
|
||||
fromOption(TELEMETRY_METRICS_ENDPOINT)
|
||||
fromOption(metricsAvailable ? TELEMETRY_METRICS_ENDPOINT : toHidden(TELEMETRY_METRICS_ENDPOINT))
|
||||
.isEnabled(TelemetryPropertyMappers::isTelemetryMetricsEnabled, OTEL_METRICS_ENABLED_MSG)
|
||||
.mapFrom(TelemetryOptions.TELEMETRY_ENDPOINT)
|
||||
.to("quarkus.otel.exporter.otlp.metrics.endpoint")
|
||||
.paramLabel("url")
|
||||
.validator(TelemetryPropertyMappers::validateEndpoint)
|
||||
.build(),
|
||||
fromOption(TELEMETRY_METRICS_PROTOCOL)
|
||||
fromOption(metricsAvailable ? TELEMETRY_METRICS_PROTOCOL : toHidden(TELEMETRY_METRICS_PROTOCOL))
|
||||
.isEnabled(TelemetryPropertyMappers::isTelemetryMetricsEnabled, OTEL_METRICS_ENABLED_MSG)
|
||||
.mapFrom(TelemetryOptions.TELEMETRY_PROTOCOL)
|
||||
.to("quarkus.otel.exporter.otlp.metrics.protocol")
|
||||
.paramLabel("protocol")
|
||||
.build(),
|
||||
fromOption(TELEMETRY_METRICS_INTERVAL)
|
||||
fromOption(metricsAvailable ? TELEMETRY_METRICS_INTERVAL : toHidden(TELEMETRY_METRICS_INTERVAL))
|
||||
.isEnabled(TelemetryPropertyMappers::isTelemetryMetricsEnabled, OTEL_METRICS_ENABLED_MSG)
|
||||
.to("quarkus.otel.metric.export.interval")
|
||||
.paramLabel("duration")
|
||||
.validator(TelemetryPropertyMappers::validateDuration)
|
||||
.build(),
|
||||
fromOption(TELEMETRY_METRICS_HEADERS)
|
||||
fromOption(metricsAvailable ? TELEMETRY_METRICS_HEADERS : toHidden(TELEMETRY_METRICS_HEADERS))
|
||||
.isEnabled(TelemetryPropertyMappers::isTelemetryMetricsEnabled, OTEL_METRICS_ENABLED_MSG)
|
||||
.to("quarkus.otel.exporter.otlp.metrics.headers")
|
||||
.transformer((value, context) -> transformTelemetryHeaders(TELEMETRY_METRICS_HEADER, value))
|
||||
.isMasked(true)
|
||||
.build(),
|
||||
fromOption(TELEMETRY_METRICS_HEADER)
|
||||
fromOption(metricsAvailable ? TELEMETRY_METRICS_HEADER : toHidden(TELEMETRY_METRICS_HEADER))
|
||||
.isEnabled(TelemetryPropertyMappers::isTelemetryMetricsEnabled, OTEL_METRICS_ENABLED_MSG)
|
||||
.paramLabel("<value>")
|
||||
.isMasked(true) // it may contain sensitive information
|
||||
|
|
@ -164,6 +165,10 @@ public class TelemetryPropertyMappers implements PropertyMapperGrouping{
|
|||
);
|
||||
}
|
||||
|
||||
private static <T> Option<T> toHidden(Option<T> option) {
|
||||
return option.toBuilder().hidden().build();
|
||||
}
|
||||
|
||||
private static String checkIfDependantsAreEnabled(String value, ConfigSourceInterceptorContext context) {
|
||||
if (TelemetryPropertyMappers.isTelemetryLogsEnabled() || TelemetryPropertyMappers.isTelemetryMetricsEnabled() || TracingPropertyMappers.isTracingEnabled()) {
|
||||
return Boolean.TRUE.toString();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.keycloak.quarkus.runtime.configuration.AbstractConfigurationTest;
|
|||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import picocli.CommandLine;
|
||||
|
|
@ -1064,6 +1065,7 @@ public class PicocliTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void telemetryParentHeaders() {
|
||||
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
// tracing enabled
|
||||
var nonRunningPicocli = pseudoLaunch("start-dev", "--tracing-enabled=true", "--telemetry-header-Authorization=Bearer asdlkfjadsflkj");
|
||||
assertNoError(nonRunningPicocli);
|
||||
|
|
@ -1152,6 +1154,7 @@ public class PicocliTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void otelMetricsHeaders() {
|
||||
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
// Otel Metrics is disabled
|
||||
var nonRunningPicocli = pseudoLaunch("start-dev", "--features=opentelemetry-metrics", "--metrics-enabled=true", "--telemetry-metrics-enabled=false", "--telemetry-metrics-header-Authorization=Bearer");
|
||||
assertError(nonRunningPicocli, "Unknown option:"); //for some reason, the wildcard options does not respect the isEnabled() when disabled
|
||||
|
|
@ -1630,6 +1633,7 @@ public class PicocliTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void otelMetrics() {
|
||||
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
// parent feature disabled
|
||||
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev", "--feature-opentelemetry=disabled", "--feature-opentelemetry-metrics=enabled");
|
||||
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
|
||||
|
|
@ -1783,6 +1787,7 @@ public class PicocliTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void otelAll() {
|
||||
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
// tracing
|
||||
pseudoLaunch("start-dev", "--tracing-enabled=true");
|
||||
assertConfig("tracing-enabled", "true");
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import static org.hamcrest.CoreMatchers.not;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class IgnoredArtifactsTest extends AbstractConfigurationTest {
|
||||
|
||||
|
|
@ -159,6 +160,7 @@ public class IgnoredArtifactsTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void otelMetrics(){
|
||||
assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
assertIgnoredArtifacts(IgnoredArtifacts.OTEL_METRICS, TelemetryOptions.TELEMETRY_METRICS_ENABLED);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package org.keycloak.quarkus.runtime.configuration;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.common.Profile;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TelemetryConfigurationTest extends AbstractConfigurationTest {
|
||||
|
|
@ -126,6 +129,7 @@ public class TelemetryConfigurationTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void metricsDefaults() {
|
||||
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
|
|
@ -146,6 +150,7 @@ public class TelemetryConfigurationTest extends AbstractConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void metricsPriorities() {
|
||||
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
|
||||
ConfigArgsConfigSource.setCliArgs("--features=opentelemetry-metrics", "--metrics-enabled=true", "--telemetry-metrics-enabled=true", "--telemetry-metrics-endpoint=localhost:2000", "--telemetry-metrics-protocol=http/protobuf");
|
||||
initConfig();
|
||||
assertConfig(Map.of(
|
||||
|
|
|
|||
|
|
@ -128,6 +128,12 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- needed for FeatureCompatibilityMetadataProviderTest to detect OPENTELEMETRY_METRICS as available -->
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-micrometer-opentelemetry</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
|
|
|
|||
Loading…
Reference in a new issue