8000 Show OpenTelemetry Metrics feature in community build, but allow downstream projects to hide it by michalvavrik · Pull Request #49002 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
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
34 changes: 31 additions & 3 deletions common/src/main/java/org/keycloak/common/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ public enum Feature {

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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Out of scope - These enum constructors are getting to be really fun to manage 😁 We really need some better approach, like the Features SPI.

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.

Yep, there should at least be a state builder or something.

() -> isClassAvailable("io.quarkus.micrometer.opentelemetry.runtime.MicrometerOtelBridgeRecorder"),
null, OPENTELEMETRY),

DECLARATIVE_UI("declarative ui spi", Type.EXPERIMENTAL),

Expand Down Expand Up @@ -187,6 +189,7 @@ public enum Feature {
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;
Expand All @@ -213,11 +216,16 @@ public enum Feature {
}

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)) {
Expand Down Expand Up @@ -283,6 +291,10 @@ public FeatureUpdatePolicy getUpdatePolicy() {
return updatePolicy;
}

private boolean isVisible() {
return isAvailable() || !hideWhenUnavailable;
}

public enum Type {
// in priority order

Expand Down Expand Up @@ -459,11 +471,18 @@ private static Map<String, TreeSet<Feature>> getOrderedFeatures() {
}

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());
}

/**
Expand Down Expand Up @@ -582,6 +601,15 @@ private void logUnsupportedFeatures(Feature.Type type, Set<Feature> checkedFeatu
}
}

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,
Expand Down
6 changes: 4 additions & 2 deletions docs/guides/observability/telemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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).
Expand All @@ -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>

Expand Down
22 changes: 17 additions & 5 deletions quarkus/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
19 changes: 14 additions & 5 deletions quarkus/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;

Expand Down Expand Up @@ -187,6 +188,9 @@ private static Set<String> metrics() {

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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -127,43 +128,47 @@ public List<? extends PropertyMapper<?>> getPropertyMappers() {
.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
.build()
);
}

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -1064,6 +1065,7 @@ public void httpOptimizedSerializers() {

@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);
Expand Down Expand Up @@ -1152,6 +1154,7 @@ public void otelLogsHeaders() {

@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
Expand Down Expand Up @@ -1630,6 +1633,7 @@ public void otelLogs() {

@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);
Expand Down Expand Up @@ -1783,6 +1787,7 @@ public void otelMetrics() {

@Test
public void otelAll() {
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
// tracing
pseudoLaunch("start-dev", "--tracing-enabled=true");
assertConfig("tracing-enabled", "true");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
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 {

Expand Down Expand Up @@ -159,6 +160,7 @@ public void metrics() {

@Test
public void otelMetrics(){
assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
assertIgnoredArtifacts(IgnoredArtifacts.OTEL_METRICS, TelemetryOptions.TELEMETRY_METRICS_ENABLED);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.Map;

import org.keycloak.common.Profile;

import org.junit.Assume;
import org.junit.Test;

public class TelemetryConfigurationTest extends AbstractConfigurationTest {
Expand Down Expand Up @@ -126,6 +129,7 @@ public void logsPriority() {

@Test
public void metricsDefaults() {
Assume.assumeTrue(Profile.Feature.OPENTELEMETRY_METRICS.isAvailable());
initConfig();

assertConfig(Map.of(
Expand All @@ -146,6 +150,7 @@ public void metricsDefaults() {

@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(
Expand Down
6 changes: 6 additions & 0 deletions services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading
0