[KEYCLOAK-13639] - Improvements to metrics and health status
This commit is contained in:
parent
21c7af1c53
commit
42b9141326
7 changed files with 108 additions and 46 deletions
|
@ -37,6 +37,10 @@ import io.quarkus.deployment.IsDevelopment;
|
||||||
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
|
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
|
||||||
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
|
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
|
||||||
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
|
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfig;
|
||||||
|
import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler;
|
||||||
|
import io.quarkus.vertx.http.deployment.RouteBuildItem;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
|
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -66,7 +70,9 @@ import io.quarkus.deployment.annotations.Record;
|
||||||
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
||||||
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
|
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
|
||||||
import io.quarkus.vertx.http.deployment.FilterBuildItem;
|
import io.quarkus.vertx.http.deployment.FilterBuildItem;
|
||||||
|
import org.keycloak.services.NotFoundHandler;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
import org.keycloak.services.health.KeycloakMetricsHandler;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
||||||
import org.keycloak.util.Environment;
|
import org.keycloak.util.Environment;
|
||||||
|
@ -75,6 +81,8 @@ class KeycloakProcessor {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(KeycloakProcessor.class);
|
private static final Logger logger = Logger.getLogger(KeycloakProcessor.class);
|
||||||
|
|
||||||
|
private static final String DEFAULT_HEALTH_ENDPOINT = "/health";
|
||||||
|
|
||||||
@BuildStep
|
@BuildStep
|
||||||
FeatureBuildItem getFeature() {
|
FeatureBuildItem getFeature() {
|
||||||
return new FeatureBuildItem("keycloak");
|
return new FeatureBuildItem("keycloak");
|
||||||
|
@ -195,13 +203,41 @@ class KeycloakProcessor {
|
||||||
indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem("org.keycloak", "keycloak-services"));
|
indexDependencyBuildItemBuildProducer.produce(new IndexDependencyBuildItem("org.keycloak", "keycloak-services"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Record(ExecutionTime.RUNTIME_INIT)
|
|
||||||
@BuildStep
|
@BuildStep
|
||||||
void initializeFilter(BuildProducer<FilterBuildItem> routes, KeycloakRecorder recorder) {
|
void initializeFilter(BuildProducer<FilterBuildItem> filters) {
|
||||||
Optional<Boolean> metricsEnabled = Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled"));
|
filters.produce(new FilterBuildItem(new QuarkusRequestFilter(),FilterBuildItem.AUTHORIZATION - 10));
|
||||||
|
}
|
||||||
|
|
||||||
routes.produce(new FilterBuildItem(recorder.createFilter(metricsEnabled.orElse(false)),
|
/**
|
||||||
FilterBuildItem.AUTHORIZATION - 10));
|
* <p>Initialize metrics and health endpoints.
|
||||||
|
*
|
||||||
|
* <p>The only reason for manually registering these endpoints is that by default they run as blocking hence
|
||||||
|
* running in a different thread than the worker thread started by {@link QuarkusRequestFilter}.
|
||||||
|
* See https://github.com/quarkusio/quarkus/issues/12990.
|
||||||
|
*
|
||||||
|
* <p>By doing this, custom health checks such as {@link org.keycloak.services.health.KeycloakReadyHealthCheck} is
|
||||||
|
* executed within an active {@link org.keycloak.models.KeycloakSession}, making possible to use it when calculating the
|
||||||
|
* status.
|
||||||
|
*
|
||||||
|
* @param routes
|
||||||
|
*/
|
||||||
|
@BuildStep
|
||||||
|
void initializeMetrics(BuildProducer<RouteBuildItem> routes) {
|
||||||
|
Handler<RoutingContext> healthHandler;
|
||||||
|
Handler<RoutingContext> metricsHandler;
|
||||||
|
|
||||||
|
if (isMetricsEnabled()) {
|
||||||
|
healthHandler = new SmallRyeHealthHandler();
|
||||||
|
metricsHandler = new KeycloakMetricsHandler();
|
||||||
|
} else {
|
||||||
|
healthHandler = new NotFoundHandler();
|
||||||
|
metricsHandler = new NotFoundHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT, healthHandler));
|
||||||
|
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/live"), healthHandler));
|
||||||
|
routes.produce(new RouteBuildItem(DEFAULT_HEALTH_ENDPOINT.concat("/ready"), healthHandler));
|
||||||
|
routes.produce(new RouteBuildItem(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT, metricsHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
@BuildStep(onlyIf = IsDevelopment.class)
|
@BuildStep(onlyIf = IsDevelopment.class)
|
||||||
|
@ -310,4 +346,8 @@ class KeycloakProcessor {
|
||||||
throw new RuntimeException("No valid ConfigProvider found");
|
throw new RuntimeException("No valid ConfigProvider found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isMetricsEnabled() {
|
||||||
|
return Configuration.getOptionalBooleanValue(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX.concat("metrics.enabled")).orElse(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,8 @@ public class QuarkusRequestFilter extends AbstractRequestFilter implements Handl
|
||||||
// we don't really care about the result because any exception thrown should be handled by the parent class
|
// we don't really care about the result because any exception thrown should be handled by the parent class
|
||||||
};
|
};
|
||||||
|
|
||||||
private Predicate<RoutingContext> enabledEndpoints;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(RoutingContext context) {
|
public void handle(RoutingContext context) {
|
||||||
if (!enabledEndpoints.test(context)) {
|
|
||||||
context.fail(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// our code should always be run as blocking until we don't provide a better support for running non-blocking code
|
// our code should always be run as blocking until we don't provide a better support for running non-blocking code
|
||||||
// in the event loop
|
// in the event loop
|
||||||
context.vertx().executeBlocking(promise -> {
|
context.vertx().executeBlocking(promise -> {
|
||||||
|
@ -102,8 +95,4 @@ public class QuarkusRequestFilter extends AbstractRequestFilter implements Handl
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEnabledEndpoints(Predicate<RoutingContext> disabledEndpoints) {
|
|
||||||
this.enabledEndpoints = disabledEndpoints;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ import java.util.function.Predicate;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import io.smallrye.config.ConfigValue;
|
import io.smallrye.config.ConfigValue;
|
||||||
import io.vertx.core.Handler;
|
|
||||||
import io.vertx.ext.web.RoutingContext;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.QuarkusKeycloakSessionFactory;
|
import org.keycloak.QuarkusKeycloakSessionFactory;
|
||||||
import org.keycloak.cli.ShowConfigCommand;
|
import org.keycloak.cli.ShowConfigCommand;
|
||||||
|
@ -45,7 +43,6 @@ import org.keycloak.provider.Spi;
|
||||||
import io.quarkus.runtime.annotations.Recorder;
|
import io.quarkus.runtime.annotations.Recorder;
|
||||||
import liquibase.logging.LogFactory;
|
import liquibase.logging.LogFactory;
|
||||||
import liquibase.servicelocator.ServiceLocator;
|
import liquibase.servicelocator.ServiceLocator;
|
||||||
import org.keycloak.provider.quarkus.QuarkusRequestFilter;
|
|
||||||
import org.keycloak.util.Environment;
|
import org.keycloak.util.Environment;
|
||||||
|
|
||||||
@Recorder
|
@Recorder
|
||||||
|
@ -213,23 +210,4 @@ public class KeycloakRecorder {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Handler<RoutingContext> createFilter(boolean metricsEnabled) {
|
|
||||||
QuarkusRequestFilter handler = new QuarkusRequestFilter();
|
|
||||||
|
|
||||||
handler.setEnabledEndpoints(new Predicate<RoutingContext>() {
|
|
||||||
@Override
|
|
||||||
public boolean test(RoutingContext context) {
|
|
||||||
|
|
||||||
if (context.request().uri().startsWith("/metrics") ||
|
|
||||||
context.request().uri().startsWith("/health")) {
|
|
||||||
return metricsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.services;
|
||||||
|
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
|
public class NotFoundHandler implements Handler<RoutingContext> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(RoutingContext event) {
|
||||||
|
event.fail(404);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.services.health;
|
||||||
|
|
||||||
|
import io.quarkus.smallrye.metrics.runtime.SmallRyeMetricsHandler;
|
||||||
|
|
||||||
|
public class KeycloakMetricsHandler extends SmallRyeMetricsHandler {
|
||||||
|
|
||||||
|
public static final String DEFAULT_METRICS_ENDPOINT = "/metrics";
|
||||||
|
|
||||||
|
public KeycloakMetricsHandler() {
|
||||||
|
setMetricsPath(DEFAULT_METRICS_ENDPOINT);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ quarkus.http.root-path=/
|
||||||
quarkus.application.name=Keycloak
|
quarkus.application.name=Keycloak
|
||||||
quarkus.banner.enabled=false
|
quarkus.banner.enabled=false
|
||||||
|
|
||||||
# Disable the default data source health check by Agroal extension, since we provide our own (default is true)
|
# Disable health checks from extensions, since we provide our own (default is true)
|
||||||
quarkus.datasource.health.enabled=false
|
quarkus.health.extensions.enabled=false
|
||||||
|
|
||||||
|
# Default transaction timeout
|
||||||
|
quarkus.transaction-manager.default-transaction-timeout=300
|
||||||
|
|
||||||
|
|
|
@ -88,9 +88,6 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public void commit() {
|
public void commit() {
|
||||||
try {
|
try {
|
||||||
if (Status.STATUS_NO_TRANSACTION == tm.getStatus()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug("JtaTransactionWrapper commit");
|
logger.debug("JtaTransactionWrapper commit");
|
||||||
tm.commit();
|
tm.commit();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -103,9 +100,6 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
|
||||||
@Override
|
@Override
|
||||||
public void rollback() {
|
public void rollback() {
|
||||||
try {
|
try {
|
||||||
if (Status.STATUS_NO_TRANSACTION == tm.getStatus()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug("JtaTransactionWrapper rollback");
|
logger.debug("JtaTransactionWrapper rollback");
|
||||||
tm.rollback();
|
tm.rollback();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
Loading…
Reference in a new issue