Skip to content

Commit

Permalink
Merge pull request #1 from rtufisi/test-default-events-store
Browse files Browse the repository at this point in the history
Test default events metrics handling
  • Loading branch information
xgp authored Jun 24, 2024
2 parents bdf9159 + 6165134 commit 66fb9a0
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 492 deletions.
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Provides metrics for Keycloak user/admin events and user/client/session count. T

[aerogear/keycloak-metrics-spi](https://github.com/aerogear/keycloak-metrics-spi) is an alternative to this plugin but is not well maintained. This implementation is different:

* no Prometheus push (event listener only adds counter to Micrometer)
* no Prometheus push (event store custom implementation only adds counter to Micrometer)
* no realm specific Prometheus endpoint, only `/metrics` (from Quarkus)
* no jvm/http metrics, this is [already](https://www.keycloak.org/server/configuration-metrics#_available_metrics) included in Keycloak
* different metric names, can relace model ids with name (see [configuration](#kc_metrics_event_replace_ids))
Expand Down Expand Up @@ -56,6 +56,10 @@ keycloak_event_admin_total{error="",operation="CREATE",realm="9039a0b5-e8c9-437a

## Configuration

### `KC_COMMUNITY_EVENTS_METRICS_ENABLED`

Set to `true` (the default false) than the events metrics gets counted using micrometer

### `KC_METRICS_EVENT_REPLACE_IDS`

Set to `true` (the default value) than replace model ids from events with names:
Expand Down
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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
- 9000:9000
volumes:
- ./target/keycloak-event-metrics-1.0.1-SNAPSHOT.jar:/opt/keycloak/providers/keycloak-event-metrics.jar
command: [ "start-dev" ]
13 changes: 5 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.kokuwa.maven</groupId>
<artifactId>maven-parent</artifactId>
<version>0.6.15</version>
<relativePath/>
</parent>

<groupId>io.kokuwa.keycloak</groupId>
<artifactId>keycloak-event-metrics</artifactId>
<version>1.0.1-SNAPSHOT</version>
Expand Down Expand Up @@ -68,7 +61,7 @@
<!-- ===================================================================== -->
<!-- ============================== Libaries ============================= -->
<!-- ===================================================================== -->

<java.version>17</java.version>
<version.org.keycloak>24.0.5</version.org.keycloak>
<version.org.keycloak.test>${version.org.keycloak}</version.org.keycloak.test>
<version.org.testcontainers>1.18.3</version.org.testcontainers>
Expand Down Expand Up @@ -105,6 +98,10 @@
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/io/kokuwa/keycloak/metrics/CommunityProfiles.java
Original file line number Diff line number Diff line change
@@ -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;
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.kokuwa.keycloak.metrics.store;

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.AbstractKeycloakTransaction;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;

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;
private final boolean isEventsMetricsEnabled;

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

@Override
public void onEvent(Event event) {
super.onEvent(event);
if (isEventsMetricsEnabled) {
log.info("User Event registered ");
countUserEvent(event);
}
}

@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
super.onEvent(event, includeRepresentation);
if (isEventsMetricsEnabled) {
countAdminEvent(event);
}
}

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

void countUserEvent(Event event) {
session
.getTransactionManager()
.enlistAfterCompletion(
new AbstractKeycloakTransaction() {
@Override
protected void commitImpl() {
KeycloakModelUtils.runJobInTransaction(
session.getKeycloakSessionFactory(),
(s) -> {
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();
});
}

@Override
protected void rollbackImpl() {
}
});
}

void countAdminEvent(AdminEvent event) {
session
.getTransactionManager()
.enlistAfterCompletion(
new AbstractKeycloakTransaction() {
@Override
protected void commitImpl() {
KeycloakModelUtils.runJobInTransaction(
session.getKeycloakSessionFactory(),
(s) -> {
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();
});
}

@Override
protected void rollbackImpl() {
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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;

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;
private boolean isEventsMetricsEnabled;

@Override
public EventStoreProvider create(KeycloakSession session) {
JpaConnectionProvider connection = session.getProvider(JpaConnectionProvider.class);
return new MicrometerEventStoreProvider(session, connection.getEntityManager(), replaceIds, isEventsMetricsEnabled);
}

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.");

isEventsMetricsEnabled = CommunityProfiles.isEventsMetricsEnabled();
log.info("Admin event metrics enabled: " + isEventsMetricsEnabled);
}

@Override
public String getId() {
return ID;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 66fb9a0

Please sign in to comment.