-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
103 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 68 additions & 1 deletion
69
src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
37 changes: 24 additions & 13 deletions
37
src/main/java/io/kokuwa/keycloak/metrics/store/MicrometerEventStoreProviderFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |