diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..678d4a8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3' + +services: + keycloak: + image: quay.io/keycloak/keycloak:25.0.0 + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_HTTP_RELATIVE_PATH: /auth + KC_HEALTH_ENABLED: 'true' + KC_METRICS_ENABLED: 'true' + KC_COMMUNITY_EVENTS_METRICS_ENABLED: 'true' + JAVA_OPTS: '-agentlib:jdwp=transport=dt_socket,address=*:8787,server=y,suspend=n' + ports: + - 8080:8080 + - 8787:8787 + volumes: + - ./target/keycloak-event-metrics-1.0.1-SNAPSHOT.jar:/opt/keycloak/providers/keycloak-event-metrics.jar + command: [ "start-dev" ] diff --git a/pom.xml b/pom.xml index f9d03e7..2c54613 100644 --- a/pom.xml +++ b/pom.xml @@ -2,13 +2,6 @@ 4.0.0 - - io.kokuwa.maven - maven-parent - 0.6.15 - - - io.kokuwa.keycloak keycloak-event-metrics 1.0.1-SNAPSHOT @@ -105,6 +98,10 @@ keycloak-server-spi-private provided + + org.keycloak + keycloak-model-jpa + org.keycloak keycloak-admin-client diff --git a/src/main/java/io/kokuwa/keycloak/metrics/CommunityProfiles.java b/src/main/java/io/kokuwa/keycloak/metrics/CommunityProfiles.java new file mode 100644 index 0000000..73bef21 --- /dev/null +++ b/src/main/java/io/kokuwa/keycloak/metrics/CommunityProfiles.java @@ -0,0 +1,21 @@ +package io.kokuwa.keycloak.metrics; + +public class CommunityProfiles { + private static final String ENV_EVENTS_METRICS_ENABLED = "KC_COMMUNITY_EVENTS_METRICS_ENABLED"; + private static final String PROP_EVENTS_METRICS_ENABLED = "kc.community.events.metrics.enabled"; + + private static final boolean isEventsMetricsEnabled; + + static { + isEventsMetricsEnabled = + Boolean.parseBoolean(System.getenv(ENV_EVENTS_METRICS_ENABLED)) + || Boolean.parseBoolean(System.getProperty(PROP_EVENTS_METRICS_ENABLED)); + } + + private CommunityProfiles() { + } + + public static boolean isEventsMetricsEnabled() { + return isEventsMetricsEnabled; + } +} diff --git a/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListener.java b/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListener.java index 4ba5144..9c2128d 100644 --- a/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListener.java +++ b/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListener.java @@ -2,16 +2,19 @@ import java.util.Optional; + import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.events.Event; import org.keycloak.events.EventListenerProvider; import org.keycloak.events.admin.AdminEvent; import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.provider.EnvironmentDependentProviderFactory; import io.micrometer.core.instrument.Metrics; - +import io.kokuwa.keycloak.metrics.CommunityProfiles; /** * Listener for {@link Event} and {@link AdminEvent}. * @@ -68,4 +71,5 @@ private String getRealmName(String id) { private String toBlank(Object value) { return value == null ? "" : value.toString(); } + } diff --git a/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListenerFactory.java b/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListenerFactory.java index 1580be7..c7bcbde 100644 --- a/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListenerFactory.java +++ b/src/main/java/io/kokuwa/keycloak/metrics/event/MetricsEventListenerFactory.java @@ -1,41 +1,58 @@ package io.kokuwa.keycloak.metrics.event; +import io.kokuwa.keycloak.metrics.CommunityProfiles; import org.jboss.logging.Logger; +import org.keycloak.Config; import org.keycloak.Config.Scope; import org.keycloak.events.EventListenerProvider; import org.keycloak.events.EventListenerProviderFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.EnvironmentDependentProviderFactory; /** * Factory for {@link MetricsEventListener}. * * @author Stephan Schnabel */ -public class MetricsEventListenerFactory implements EventListenerProviderFactory { - - private static final Logger log = Logger.getLogger(MetricsEventListenerFactory.class); - private boolean replaceIds; - - @Override - public String getId() { - return "metrics-listener"; - } - - @Override - public void init(Scope config) { - replaceIds = "true".equals(System.getenv().getOrDefault("KC_METRICS_EVENT_REPLACE_IDS", "true")); - log.info(replaceIds ? "Configured with model names." : "Configured with model ids."); - } - - @Override - public void postInit(KeycloakSessionFactory factory) {} - - @Override - public EventListenerProvider create(KeycloakSession session) { - return new MetricsEventListener(replaceIds, session); - } - - @Override - public void close() {} +public class MetricsEventListenerFactory implements EventListenerProviderFactory, EnvironmentDependentProviderFactory { + + private static final Logger log = Logger.getLogger(MetricsEventListenerFactory.class); + private boolean replaceIds; + + @Override + public String getId() { + return "metrics-listener"; + } + + @Override + public void init(Scope config) { + replaceIds = "true".equals(System.getenv().getOrDefault("KC_METRICS_EVENT_REPLACE_IDS", "true")); + log.info(replaceIds ? "Configured with model names." : "Configured with model ids."); + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public EventListenerProvider create(KeycloakSession session) { + return new MetricsEventListener(replaceIds, session); + } + + @Override + public void close() { + } + + @Override + public boolean isSupported() { + log.info("MetricsEventListenerFactory is supported:" + !CommunityProfiles.isEventsMetricsEnabled()); + return !CommunityProfiles.isEventsMetricsEnabled(); + } + + @Override + public boolean isSupported(Config.Scope config) { + log.info("MetricsEventListenerFactory is supported:" + !CommunityProfiles.isEventsMetricsEnabled()); + return !CommunityProfiles.isEventsMetricsEnabled(); + } } diff --git a/src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProvider.java b/src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProvider.java new file mode 100644 index 0000000..a4b6689 --- /dev/null +++ b/src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProvider.java @@ -0,0 +1,12 @@ +package io.kokuwa.keycloak.metrics.store; + +import jakarta.persistence.EntityManager; +import org.keycloak.events.jpa.JpaEventStoreProvider; +import org.keycloak.models.KeycloakSession; + +public class MicrometerEventStoreProvider extends JpaEventStoreProvider { + + public MicrometerEventStoreProvider(KeycloakSession session, EntityManager em) { + super(session, em); + } +} \ No newline at end of file diff --git a/src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProviderFactory.java b/src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProviderFactory.java new file mode 100644 index 0000000..806c917 --- /dev/null +++ b/src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProviderFactory.java @@ -0,0 +1,40 @@ +package io.kokuwa.keycloak.metrics.store; + +import io.kokuwa.keycloak.metrics.CommunityProfiles; +import org.jboss.logging.Logger; +import org.keycloak.Config; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.events.EventStoreProvider; +import org.keycloak.events.jpa.JpaEventStoreProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.EnvironmentDependentProviderFactory; + + +public class MicrometerEventStoreProviderFactory extends JpaEventStoreProviderFactory implements EnvironmentDependentProviderFactory { + private static final Logger log = Logger.getLogger(MicrometerEventStoreProviderFactory.class); + + public static final String ID = "jpa"; // Override default event store provider + + @Override + public EventStoreProvider create(KeycloakSession session) { + JpaConnectionProvider connection = session.getProvider(JpaConnectionProvider.class); + return new MicrometerEventStoreProvider(session, connection.getEntityManager()); + } + + @Override + public String getId() { + return ID; + } + + @Override + public boolean isSupported() { + log.info("MicrometerEventStore is supported:"+ CommunityProfiles.isEventsMetricsEnabled()); + return CommunityProfiles.isEventsMetricsEnabled(); + } + + @Override + public boolean isSupported(Config.Scope config) { + log.info("MicrometerEventStore is supported:"+ CommunityProfiles.isEventsMetricsEnabled()); + return CommunityProfiles.isEventsMetricsEnabled(); + } +} diff --git a/src/main/resources/META-INF/services/org.keycloak.events.EventStoreProviderFactory b/src/main/resources/META-INF/services/org.keycloak.events.EventStoreProviderFactory new file mode 100644 index 0000000..db920d9 --- /dev/null +++ b/src/main/resources/META-INF/services/org.keycloak.events.EventStoreProviderFactory @@ -0,0 +1,18 @@ +# +# Copyright 2016 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. +# + +io.kokuwa.keycloak.metrics.store.MicrometerEventStoreProviderFactory \ No newline at end of file