Skip to content

Commit

Permalink
Change metrics approach
Browse files Browse the repository at this point in the history
  • Loading branch information
rtufisi committed Jun 22, 2024
1 parent 4c17a89 commit 1aec0ad
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 19 deletions.
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ services:
ports:
- 8080:8080
- 8787:8787
- 9000:9000
volumes:
- ./target/keycloak-event-metrics-1.0.1-SNAPSHOT.jar:/opt/keycloak/providers/keycloak-event-metrics.jar
command: [ "start-dev" ]
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ public class MetricsEventListenerFactory implements EventListenerProviderFactory

private static final Logger log = Logger.getLogger(MetricsEventListenerFactory.class);
private boolean replaceIds;
public static final String ID = "metrics-listener";

@Override
public String getId() {
return "metrics-listener";
return ID;
}

@Override
Expand All @@ -44,15 +45,19 @@ public EventListenerProvider create(KeycloakSession session) {
public void close() {
}


//to avoid double metric registration
@Override
public boolean isSupported() {
log.info("MetricsEventListenerFactory is supported:" + !CommunityProfiles.isEventsMetricsEnabled());
return !CommunityProfiles.isEventsMetricsEnabled();
var defaultEventsMetricsDisabled = !CommunityProfiles.isEventsMetricsEnabled();
log.info("MetricsEventListenerFactory is enabled:" + defaultEventsMetricsDisabled);
return defaultEventsMetricsDisabled;
}

@Override
public boolean isSupported(Config.Scope config) {
log.info("MetricsEventListenerFactory is supported:" + !CommunityProfiles.isEventsMetricsEnabled());
return !CommunityProfiles.isEventsMetricsEnabled();
var defaultEventsMetricsDisabled = !CommunityProfiles.isEventsMetricsEnabled();
log.info("MetricsEventListenerFactory is enabled:" + defaultEventsMetricsDisabled);
return defaultEventsMetricsDisabled;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
package io.kokuwa.keycloak.metrics.store;

import io.kokuwa.keycloak.metrics.CommunityProfiles;
import io.micrometer.core.instrument.Metrics;
import jakarta.persistence.EntityManager;
import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.jpa.JpaEventStoreProvider;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;

import java.util.Optional;

public class MicrometerEventStoreProvider extends JpaEventStoreProvider {
private static final Logger log = Logger.getLogger(MicrometerEventStoreProvider.class);

private final boolean replaceIds;
private final KeycloakSession session;

public MicrometerEventStoreProvider(KeycloakSession session, EntityManager em) {
public MicrometerEventStoreProvider(KeycloakSession session,
EntityManager em,
boolean replaceIds) {
super(session, em);
this.replaceIds = replaceIds;
this.session = session;
}

@Override
public void onEvent(Event event) {
var isEventsEnable = CommunityProfiles.isEventsMetricsEnabled();
if (isEventsEnable) {
Metrics.counter("keycloak_event_user",
"realm", toBlank(replaceIds ? getRealmName(event.getRealmId()) : event.getRealmId()),
"type", toBlank(event.getType()),
"client", toBlank(event.getClientId()),
"error", toBlank(event.getError()))
.increment();
}
super.onEvent(event);
}

@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
var isEventsEnable = CommunityProfiles.isEventsMetricsEnabled();
if (isEventsEnable) {
Metrics.counter("keycloak_event_admin",
"realm", toBlank(replaceIds ? getRealmName(event.getRealmId()) : event.getRealmId()),
"resource", toBlank(event.getResourceType()),
"operation", toBlank(event.getOperationType()),
"error", toBlank(event.getError()))
.increment();
}
super.onEvent(event, includeRepresentation);
}

@Override
public void close() {
}

private String getRealmName(String id) {
return Optional.ofNullable(session.getContext()).map(KeycloakContext::getRealm)
.filter(realm -> id == null || id.equals(realm.getId()))
.or(() -> {
log.tracev("Context realm was empty with id {0}", id);
return Optional.ofNullable(id).map(session.realms()::getRealm);
})
.map(RealmModel::getName)
.orElseGet(() -> {
log.warnv("Failed to find realm with id {0}", id);
return id;
});
}

private String toBlank(Object value) {
return value == null ? "" : value.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
package io.kokuwa.keycloak.metrics.store;

import io.kokuwa.keycloak.metrics.CommunityProfiles;
import io.kokuwa.keycloak.metrics.event.MetricsEventListenerFactory;
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;

import java.util.stream.Collectors;

public class MicrometerEventStoreProviderFactory extends JpaEventStoreProviderFactory implements EnvironmentDependentProviderFactory {
public class MicrometerEventStoreProviderFactory extends JpaEventStoreProviderFactory {
private static final Logger log = Logger.getLogger(MicrometerEventStoreProviderFactory.class);

public static final String ID = "jpa"; // Override default event store provider
private boolean replaceIds;

@Override
public EventStoreProvider create(KeycloakSession session) {
//removeMetricsEventsListenerIfExists(session);

JpaConnectionProvider connection = session.getProvider(JpaConnectionProvider.class);
return new MicrometerEventStoreProvider(session, connection.getEntityManager());
return new MicrometerEventStoreProvider(session, connection.getEntityManager(), replaceIds);
}

@Override
public String getId() {
return ID;
public void init(Config.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 boolean isSupported() {
log.info("MicrometerEventStore is supported:"+ CommunityProfiles.isEventsMetricsEnabled());
return CommunityProfiles.isEventsMetricsEnabled();
public String getId() {
return ID;
}

@Override
public boolean isSupported(Config.Scope config) {
log.info("MicrometerEventStore is supported:"+ CommunityProfiles.isEventsMetricsEnabled());
return CommunityProfiles.isEventsMetricsEnabled();
// we need to remove 'metrics-listener' if already exists in realm, and KC_COMMUNITY_EVENTS_METRICS_ENABLED=true otherwise the keycloak_1 | 2024-06-22 06:37:29,059 ERROR [org.keycloak.services] (executor-thread-3) KC-SERVICES0083: Event listener 'metrics-listener' registered, but provider not found
//Todo: This approach gives Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_C38 ON PUBLIC.REALM_EVENTS_LISTENERS(REALM_ID, ""VALUE"") VALUES ( /* key:4 */ '32497f09-6079-42d3-8f56-9e4654b53e5e', 'jboss-logging')"; SQL statement:
//Todo: Approach is same on AdminRealmEvents.updateRealmEventsConfig. xgp any idea why?
private static void removeMetricsEventsListenerIfExists(KeycloakSession session) {
var eventsMetricsEnabled = CommunityProfiles.isEventsMetricsEnabled();
if (eventsMetricsEnabled) {
var events = session.getContext().getRealm().getEventsListenersStream()
.filter(eventListener -> !MetricsEventListenerFactory.ID.equals(eventListener))
.collect(Collectors.toSet());

session.getContext().getRealm().setEventsListeners(events);
}
}
}

0 comments on commit 1aec0ad

Please sign in to comment.