diff --git a/README.md b/README.md
index 0d06359..3ccd505 100644
--- a/README.md
+++ b/README.md
@@ -333,6 +333,25 @@ This counter counts the number of response errors (responses where the http stat
keycloak_response_errors{code="500",method="GET",} 1
```
+##### keycloak_online_sessions
+This gauge indicates the number of online sessions.
+
+```c
+# HELP keycloak_online_sessions Total online sessions
+# TYPE keycloak_online_sessions gauge
+keycloak_online_sessions{realm="test",client_id="application1",} 1.0
+```
+
+##### keycloak_offline_sessions
+This gauge indicates the number of offline sessions.
+
+```c
+# HELP keycloak_offline_sessions Total offline sessions
+# TYPE keycloak_offline_sessions gauge
+keycloak_offline_sessions{realm="test",client_id="application1",} 1.0
+```
+
+
#### Metrics URI
The URI can be added to the metrics by setting the environment variable ```URI_METRICS_ENABLED``` to `true`.
This will output a consolidated realm URI value to the metrics. The realm value is replaced with a generic `{realm}` value
diff --git a/pom.xml b/pom.xml
index c57dfe6..91abf63 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,6 +64,17 @@
1.19.0
test
+
+ org.apache.commons
+ commons-collections4
+ 4.4
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+
diff --git a/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListener.java b/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListener.java
index d9be8b8..21fd8e3 100644
--- a/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListener.java
+++ b/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListener.java
@@ -4,6 +4,7 @@
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.models.KeycloakSession;
public class MetricsEventListener implements EventListenerProvider {
@@ -11,6 +12,12 @@ public class MetricsEventListener implements EventListenerProvider {
private final static Logger logger = Logger.getLogger(MetricsEventListener.class);
+ private final KeycloakSession keycloakSession;
+
+ public MetricsEventListener(KeycloakSession keycloakSession) {
+ this.keycloakSession = keycloakSession;
+ }
+
@Override
public void onEvent(Event event) {
logEventDetails(event);
@@ -18,9 +25,14 @@ public void onEvent(Event event) {
switch (event.getType()) {
case LOGIN:
PrometheusExporter.instance().recordLogin(event);
+ PrometheusExporter.instance().recordSessions(event, keycloakSession);
break;
case CLIENT_LOGIN:
PrometheusExporter.instance().recordClientLogin(event);
+ PrometheusExporter.instance().recordSessions(event, keycloakSession);
+ break;
+ case LOGOUT:
+ PrometheusExporter.instance().recordSessions(event, keycloakSession);
break;
case REGISTER:
PrometheusExporter.instance().recordRegistration(event);
diff --git a/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListenerFactory.java b/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListenerFactory.java
index 9da066b..ed8f56f 100644
--- a/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListenerFactory.java
+++ b/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsEventListenerFactory.java
@@ -10,7 +10,7 @@ public class MetricsEventListenerFactory implements EventListenerProviderFactory
@Override
public EventListenerProvider create(KeycloakSession session) {
- return new MetricsEventListener();
+ return new MetricsEventListener(session);
}
@Override
diff --git a/src/main/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporter.java b/src/main/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporter.java
index ed05a29..4a6dd88 100644
--- a/src/main/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporter.java
+++ b/src/main/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporter.java
@@ -2,15 +2,20 @@
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
+import io.prometheus.client.Gauge;
import io.prometheus.client.Histogram;
import io.prometheus.client.exporter.PushGateway;
import io.prometheus.client.exporter.common.TextFormat;
import io.prometheus.client.hotspot.DefaultExports;
+import org.apache.commons.collections4.MapUtils;
import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -60,6 +65,8 @@ public final class PrometheusExporter {
final Counter responseTotal;
final Counter responseErrors;
final Histogram requestDuration;
+ final Gauge totalOnlineSessions;
+ final Gauge totalOfflineSessions;
final PushGateway PUSH_GATEWAY;
private PrometheusExporter() {
@@ -149,6 +156,18 @@ private PrometheusExporter() {
.labelNames("realm", "provider", "error", "client_id")
.register();
+ totalOnlineSessions = Gauge.build()
+ .name("keycloak_online_sessions")
+ .help("Total online sessions")
+ .labelNames("realm", "provider", "client_id")
+ .register();
+
+ totalOfflineSessions = Gauge.build()
+ .name("keycloak_offline_sessions")
+ .help("Total offline sessions")
+ .labelNames("realm", "provider", "client_id")
+ .register();
+
final boolean URI_METRICS_ENABLED = Boolean.parseBoolean(System.getenv("URI_METRICS_ENABLED"));
if (URI_METRICS_ENABLED){
responseTotal = Counter.build()
@@ -274,6 +293,25 @@ public void recordLogin(final Event event) {
pushAsync();
}
+ public void recordSessions(final Event event, final KeycloakSession keycloakSession) {
+ final String provider = getIdentityProvider(event);
+ final RealmModel realmModel = keycloakSession.realms().getRealm(event.getRealmId());
+ final ClientModel clientModel = keycloakSession.clients().getClientByClientId(realmModel, event.getClientId());
+
+ if(clientModel != null) {
+ final Map onlineClientSessionStats = keycloakSession.sessions().getActiveClientSessionStats(realmModel, false);
+ final Optional onlineSessionCount = Optional.ofNullable(MapUtils.emptyIfNull(onlineClientSessionStats).get(clientModel.getId()));
+
+ final Map offlineClientSessionStats = keycloakSession.sessions().getActiveClientSessionStats(realmModel, true);
+ final Optional offlineSessionCount = Optional.ofNullable(MapUtils.emptyIfNull(offlineClientSessionStats).get(clientModel.getId()));
+
+ totalOnlineSessions.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).set(onlineSessionCount.orElse(0L));
+ totalOfflineSessions.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).set(offlineSessionCount.orElse(0L));
+
+ pushAsync();
+ }
+ }
+
/**
* Increase the number registered users
*
diff --git a/src/test/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporterTest.java b/src/test/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporterTest.java
index 0f142a0..fee1d92 100644
--- a/src/test/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporterTest.java
+++ b/src/test/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporterTest.java
@@ -7,25 +7,57 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;
+import org.junit.runner.RunWith;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.UserSessionProvider;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Map;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
@SuppressWarnings("unchecked")
+@RunWith(MockitoJUnitRunner.class)
public class PrometheusExporterTest {
private static final String DEFAULT_REALM = "myrealm";
+ private static final String DEFAULT_CLIENT_CLIENT_ID = "THE_CLIENT_CLIENT_ID";
+ private static final String DEFAULT_CLIENT_ID = "THE_CLIENT_ID";
+ private static final String OTHER_CLIENT_CLIENT_ID = "OTHER_CLIENT_CLIENT_ID";
+ private static final String OTHER_CLIENT_ID = "OTHER_CLIENT_ID";
+
+ @Mock
+ private KeycloakSession keycloakSession;
+ @Mock
+ private UserSessionProvider userSessionProvider;
+ @Mock
+ private RealmProvider realmProvider;
+ @Mock
+ private ClientProvider clientProvider;
+ @Mock
+ private RealmModel realm;
+ @Mock
+ private ClientModel client1;
+ @Mock
+ private ClientModel client2;
@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
@@ -36,6 +68,15 @@ public void resetSingleton() throws SecurityException, NoSuchFieldException, Ill
instance.setAccessible(true);
instance.set(null, null);
CollectorRegistry.defaultRegistry.clear();
+
+ Mockito.when(keycloakSession.sessions()).thenReturn(userSessionProvider);
+ Mockito.when(keycloakSession.realms()).thenReturn(realmProvider);
+ Mockito.when(realmProvider.getRealm(DEFAULT_REALM)).thenReturn(realm);
+ Mockito.when(keycloakSession.clients()).thenReturn(clientProvider);
+ Mockito.when(clientProvider.getClientByClientId(realm, DEFAULT_CLIENT_CLIENT_ID)).thenReturn(client1);
+ Mockito.when(clientProvider.getClientByClientId(realm, OTHER_CLIENT_CLIENT_ID)).thenReturn(client2);
+ Mockito.when(client1.getId()).thenReturn(DEFAULT_CLIENT_ID);
+ Mockito.when(client2.getId()).thenReturn(OTHER_CLIENT_ID);
}
@Test
@@ -53,178 +94,178 @@ public void shouldRegisterCountersForAllKeycloakEvents() {
@Test
public void shouldCorrectlyCountLoginAttemptsForSuccessfulAndFailedAttempts() throws IOException {
// with LOGIN event
- final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, "THE_CLIENT_ID");
+ final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
PrometheusExporter.instance().recordLogin(login1);
- assertMetric("keycloak_login_attempts", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_logins", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_login_attempts", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_logins", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// with LOGIN_ERROR event
- final Event event2 = createEvent(EventType.LOGIN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found");
+ final Event event2 = createEvent(EventType.LOGIN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found");
PrometheusExporter.instance().recordLoginError(event2);
- assertMetric("keycloak_login_attempts", 2, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_login_attempts", 2, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountLoginWhenIdentityProviderIsDefined() throws IOException {
- final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordLogin(login1);
- assertMetric("keycloak_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
- final Event login2 = createEvent(EventType.LOGIN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event login2 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordLogin(login2);
- assertMetric("keycloak_logins", 2, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 2, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountLoginWhenIdentityProviderIsNotDefined() throws IOException {
final Event login1 = createEvent(EventType.LOGIN);
PrometheusExporter.instance().recordLogin(login1);
- assertMetric("keycloak_logins", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
final Event login2 = createEvent(EventType.LOGIN);
PrometheusExporter.instance().recordLogin(login2);
- assertMetric("keycloak_logins", 2, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 2, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountLoginsFromDifferentProviders() throws IOException {
// with id provider defined
- final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordLogin(login1);
- assertMetric("keycloak_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event login2 = createEvent(EventType.LOGIN, DEFAULT_REALM, "THE_CLIENT_ID");
+ final Event login2 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
PrometheusExporter.instance().recordLogin(login2);
- assertMetric("keycloak_logins", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldRecordLoginsPerRealm() throws IOException {
// realm 1
- final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event login1 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordLogin(login1);
// realm 2
- final Event login2 = createEvent(EventType.LOGIN, "OTHER_REALM", "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event login2 = createEvent(EventType.LOGIN, "OTHER_REALM", DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordLogin(login2);
- assertMetric("keycloak_logins", 1, DEFAULT_REALM, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_logins", 1, "OTHER_REALM", tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_logins", 1, DEFAULT_REALM, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_logins", 1, "OTHER_REALM", tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountLoginError() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.LOGIN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.LOGIN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordLoginError(event1);
- assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.LOGIN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found");
+ final Event event2 = createEvent(EventType.LOGIN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found");
PrometheusExporter.instance().recordLoginError(event2);
- assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_failed_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountRegister() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.REGISTER, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.REGISTER, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordRegistration(event1);
- assertMetric("keycloak_registrations", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_registrations", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.REGISTER, DEFAULT_REALM, "THE_CLIENT_ID");
+ final Event event2 = createEvent(EventType.REGISTER, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
PrometheusExporter.instance().recordRegistration(event2);
- assertMetric("keycloak_registrations", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_registrations", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_registrations", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_registrations", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountRefreshTokens() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.REFRESH_TOKEN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.REFRESH_TOKEN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordRefreshToken(event1);
- assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.REFRESH_TOKEN, DEFAULT_REALM, "THE_CLIENT_ID");
+ final Event event2 = createEvent(EventType.REFRESH_TOKEN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
PrometheusExporter.instance().recordRefreshToken(event2);
- assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountRefreshTokensErrors() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.REFRESH_TOKEN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.REFRESH_TOKEN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordRefreshTokenError(event1);
- assertMetric("keycloak_refresh_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_refresh_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.REFRESH_TOKEN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found");
+ final Event event2 = createEvent(EventType.REFRESH_TOKEN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found");
PrometheusExporter.instance().recordRefreshTokenError(event2);
- assertMetric("keycloak_refresh_tokens_errors", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_refresh_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_refresh_tokens_errors", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_refresh_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountClientLogins() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.CLIENT_LOGIN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.CLIENT_LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordClientLogin(event1);
- assertMetric("keycloak_client_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_client_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.CLIENT_LOGIN, DEFAULT_REALM, "THE_CLIENT_ID");
+ final Event event2 = createEvent(EventType.CLIENT_LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
PrometheusExporter.instance().recordClientLogin(event2);
- assertMetric("keycloak_client_logins", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_client_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_client_logins", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_client_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountClientLoginAttempts() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.CLIENT_LOGIN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.CLIENT_LOGIN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordClientLoginError(event1);
- assertMetric("keycloak_failed_client_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_failed_client_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.CLIENT_LOGIN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found");
+ final Event event2 = createEvent(EventType.CLIENT_LOGIN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found");
PrometheusExporter.instance().recordClientLoginError(event2);
- assertMetric("keycloak_failed_client_login_attempts", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_failed_client_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_failed_client_login_attempts", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_failed_client_login_attempts", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountCodeToTokens() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.CODE_TO_TOKEN, DEFAULT_REALM, "THE_CLIENT_ID", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.CODE_TO_TOKEN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordCodeToToken(event1);
- assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.CODE_TO_TOKEN, DEFAULT_REALM, "THE_CLIENT_ID");
+ final Event event2 = createEvent(EventType.CODE_TO_TOKEN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
PrometheusExporter.instance().recordCodeToToken(event2);
- assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "keycloak"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
public void shouldCorrectlyCountCodeToTokensErrors() throws IOException {
// with id provider defined
- final Event event1 = createEvent(EventType.CODE_TO_TOKEN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
+ final Event event1 = createEvent(EventType.CODE_TO_TOKEN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found", tuple("identity_provider", "THE_ID_PROVIDER"));
PrometheusExporter.instance().recordCodeToTokenError(event1);
- assertMetric("keycloak_code_to_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_code_to_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
// without id provider defined
- final Event event2 = createEvent(EventType.CODE_TO_TOKEN_ERROR, DEFAULT_REALM, "THE_CLIENT_ID", "user_not_found");
+ final Event event2 = createEvent(EventType.CODE_TO_TOKEN_ERROR, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, "user_not_found");
PrometheusExporter.instance().recordCodeToTokenError(event2);
- assertMetric("keycloak_code_to_tokens_errors", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
- assertMetric("keycloak_code_to_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", "THE_CLIENT_ID"));
+ assertMetric("keycloak_code_to_tokens_errors", 1, tuple("provider", "keycloak"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_code_to_tokens_errors", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("error", "user_not_found"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
}
@Test
@@ -292,6 +333,45 @@ public void shouldCorrectlyRecordResponseErrors() throws IOException {
tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm"));
}
+ @Test
+ public void shouldUpdateSessionsOnlyForEventClient() throws IOException {
+ final Event event = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
+
+ final Map onlineSessionsMap = new HashMap<>();
+ final Map offlineSessionsMap = new HashMap<>();
+ Mockito.when(userSessionProvider.getActiveClientSessionStats(realm, false)).thenReturn(onlineSessionsMap);
+ Mockito.when(userSessionProvider.getActiveClientSessionStats(realm, true)).thenReturn(offlineSessionsMap);
+
+ onlineSessionsMap.put(DEFAULT_CLIENT_ID, 3L);
+ onlineSessionsMap.put(OTHER_CLIENT_ID, 10L);
+
+ PrometheusExporter.instance().recordSessions(event, keycloakSession);
+ assertMetric("keycloak_online_sessions", 3.0, DEFAULT_REALM, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertNoMetric("keycloak_online_sessions", 10.0, DEFAULT_REALM, tuple("provider", "keycloak"), tuple("client_id", OTHER_CLIENT_CLIENT_ID));
+ }
+
+ @Test
+ public void shouldCorrectlyRecordSessions() throws IOException {
+ final Event event1 = createEvent(EventType.LOGIN, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID);
+ final Event event2 = createEvent(EventType.LOGIN, DEFAULT_REALM, OTHER_CLIENT_CLIENT_ID);
+
+ final Map onlineSessionsMap = new HashMap<>();
+ final Map offlineSessionsMap = new HashMap<>();
+ Mockito.when(userSessionProvider.getActiveClientSessionStats(realm, false)).thenReturn(onlineSessionsMap);
+ Mockito.when(userSessionProvider.getActiveClientSessionStats(realm, true)).thenReturn(offlineSessionsMap);
+
+ onlineSessionsMap.put(DEFAULT_CLIENT_ID, 3L);
+ onlineSessionsMap.put(OTHER_CLIENT_ID, 0L);
+ offlineSessionsMap.put(OTHER_CLIENT_ID, 5L);
+
+ PrometheusExporter.instance().recordSessions(event1, keycloakSession);
+ PrometheusExporter.instance().recordSessions(event2, keycloakSession);
+ assertMetric("keycloak_online_sessions", 3.0, DEFAULT_REALM, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_offline_sessions", 0.0, DEFAULT_REALM, tuple("provider", "keycloak"), tuple("client_id", DEFAULT_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_online_sessions", 0.0, DEFAULT_REALM, tuple("provider", "keycloak"), tuple("client_id", OTHER_CLIENT_CLIENT_ID));
+ assertMetric("keycloak_offline_sessions", 5.0, DEFAULT_REALM, tuple("provider", "keycloak"), tuple("client_id", OTHER_CLIENT_CLIENT_ID));
+ }
+
@Test
public void shouldTolerateNullLabels() throws IOException {
final Event nullEvent = new Event();
@@ -362,6 +442,26 @@ private void assertMetric(String metricName, double metricValue, String realm, T
}
}
+ private void assertNoMetric(String metricName, double metricValue, String realm, Tuple... labels) throws IOException {
+ try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
+ PrometheusExporter.instance().export(stream);
+ String result = new String(stream.toByteArray());
+
+ final StringBuilder builder = new StringBuilder();
+
+ builder.append(metricName).append("{");
+ builder.append("realm").append("=\"").append(realm).append("\",");
+
+ for (Tuple label : labels) {
+ builder.append(label.left).append("=\"").append(label.right).append("\",");
+ }
+
+ builder.append("} ").append(metricValue);
+
+ MatcherAssert.assertThat(result, not(containsString(builder.toString())));
+ }
+ }
+
private void assertMetric(String metricName, double metricValue, Tuple... labels) throws IOException {
this.assertMetric(metricName, metricValue, DEFAULT_REALM, labels);
}
@@ -387,7 +487,7 @@ private Event createEvent(EventType type, String realm, String clientId, String
}
private Event createEvent(EventType type, Tuple... tuples) {
- return this.createEvent(type, DEFAULT_REALM, "THE_CLIENT_ID", (String) null, tuples);
+ return this.createEvent(type, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID, (String) null, tuples);
}
private Event createEvent(EventType type, String realm, String clientId, Tuple... tuples) {
@@ -395,11 +495,11 @@ private Event createEvent(EventType type, String realm, String clientId, Tuple... tuples) {
- return this.createEvent(type, realm, "THE_CLIENT_ID", (String) null, tuples);
+ return this.createEvent(type, realm, DEFAULT_CLIENT_CLIENT_ID, (String) null, tuples);
}
private Event createEvent(EventType type) {
- return createEvent(type, DEFAULT_REALM, "THE_CLIENT_ID",(String) null);
+ return createEvent(type, DEFAULT_REALM, DEFAULT_CLIENT_CLIENT_ID,(String) null);
}
private static Tuple tuple(L left, R right) {