Skip to content

Commit

Permalink
Add option to replace model ids with model names, fix #3
Browse files Browse the repository at this point in the history
  • Loading branch information
sschnabe committed Mar 7, 2023
1 parent 62bdac4 commit c844bb7
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 53 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,23 @@ keycloak_event_admin_total{error="",operation="CREATE",realm="1fdb3465-1675-49e8
keycloak_event_admin_total{error="",operation="CREATE",realm="9039a0b5-e8c9-437a-a02e-9d91b04548a4",resource="USER",} 1.0
```

## Configuration

### `KC_METRICS_EVENT_REPLACE_IDS`

If set to `true` than replace model ids with names:

* [RealmModel#getId()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/RealmModel.java#L82) with [RealmModel#getName()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/RealmModel.java#L84)
* [ClientModel#getId()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/ClientModel.java#L106) with [ClientModel#getClientId()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/ClientModel.java#L112)

Metrics:

```txt
keycloak_event_user_total{client="test-client",error="",realm="test-realm",type="LOGIN",} 2.0
keycloak_event_user_total{client="other-client",error="",realm="other-realm",type="LOGIN",} 1.0
keycloak_event_user_total{client="other-client",error="invalid_user_credentials",realm="other-realm",type="LOGIN_ERROR",} 1.0
``
## Installation
### Testcontainers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.kokuwa.keycloak.metrics;

import java.util.Optional;

import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;

import io.micrometer.core.instrument.MeterRegistry;

Expand All @@ -13,26 +17,31 @@
*/
public class MicrometerEventListener implements EventListenerProvider, AutoCloseable {

private static final Logger log = Logger.getLogger(MicrometerEventListener.class);
private final MeterRegistry registry;
private final KeycloakSession session;
private final boolean replace;

public MicrometerEventListener(MeterRegistry registry) {
public MicrometerEventListener(MeterRegistry registry, KeycloakSession session, boolean replaceId) {
this.registry = registry;
this.session = session;
this.replace = replaceId;
}

@Override
public void onEvent(Event event) {
registry.counter("keycloak_event_user",
"realm", toBlank(event.getRealmId()),
"realm", toBlank(replace ? getRealmName(event.getRealmId()) : event.getRealmId()),
"type", toBlank(event.getType()),
"client", toBlank(event.getClientId()),
"client", toBlank(replace ? getClientId(event.getRealmId(), event.getClientId()) : event.getClientId()),
"error", toBlank(event.getError()))
.increment();
}

@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
registry.counter("keycloak_event_admin",
"realm", toBlank(event.getRealmId()),
"realm", toBlank(replace ? getRealmName(event.getRealmId()) : event.getRealmId()),
"resource", toBlank(event.getResourceType()),
"operation", toBlank(event.getOperationType()),
"error", toBlank(event.getError()))
Expand All @@ -45,4 +54,24 @@ public void close() {}
private String toBlank(Object value) {
return value == null ? "" : value.toString();
}

private String getRealmName(String realmId) {
var model = session.realms().getRealm(realmId);
if (model == null) {
log.warnv("Failed to resolve realm with id", realmId);
return realmId;
}
return model.getName();
}

private String getClientId(String realmId, String clientId) {
var model = Optional.ofNullable(session.realms().getRealm(realmId))
.map(realm -> realm.getClientById(clientId))
.orElse(null);
if (model == null) {
log.warnv("Failed to resolve client with id {} in realm {}", clientId, realmId);
return clientId;
}
return model.getClientId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import javax.enterprise.inject.spi.CDI;

import org.jboss.logging.Logger;
import org.keycloak.Config.Scope;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
Expand All @@ -17,15 +18,20 @@
*/
public class MicrometerEventListenerFactory implements EventListenerProviderFactory {

private static final Logger log = Logger.getLogger(MicrometerEventListener.class);
private MeterRegistry registry;
private boolean replace;

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

@Override
public void init(Scope config) {}
public void init(Scope config) {
replace = "true".equals(System.getenv("KC_METRICS_EVENT_REPLACE_IDS"));
log.info(replace ? "Configured with model names." : "Configured with model ids.");
}

@Override
public void postInit(KeycloakSessionFactory factory) {
Expand All @@ -34,7 +40,7 @@ public void postInit(KeycloakSessionFactory factory) {

@Override
public EventListenerProvider create(KeycloakSession session) {
return new MicrometerEventListener(registry);
return new MicrometerEventListener(registry, session, replace);
}

@Override
Expand Down
35 changes: 20 additions & 15 deletions src/test/java/io/kokuwa/keycloak/metrics/KeycloakIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,42 @@ public class KeycloakIT {
@Test
void loginAndAttempts(KeycloakClient keycloak, Prometheus prometheus) {

var clientId1 = UUID.randomUUID().toString();
var realmName1 = UUID.randomUUID().toString();
var username1 = UUID.randomUUID().toString();
var password1 = UUID.randomUUID().toString();
keycloak.createRealm(realmName1);
keycloak.createClient(realmName1, clientId1);
keycloak.createUser(realmName1, username1, password1);

var clientId2 = UUID.randomUUID().toString();
var realmName2 = UUID.randomUUID().toString();
var username2 = UUID.randomUUID().toString();
var password2 = UUID.randomUUID().toString();
var realmId1 = keycloak.createRealm(realmName1);
var realmId2 = keycloak.createRealm(realmName2);
keycloak.createUser(realmName1, username1, password1);
keycloak.createRealm(realmName2);
keycloak.createClient(realmName2, clientId2);
keycloak.createUser(realmName2, username2, password2);

prometheus.scrap();
var loginBefore = prometheus.userEvent(EventType.LOGIN);
var loginBefore1 = prometheus.userEvent(EventType.LOGIN, realmId1);
var loginBefore2 = prometheus.userEvent(EventType.LOGIN, realmId2);
var loginBefore1 = prometheus.userEvent(EventType.LOGIN, realmName1, clientId1);
var loginBefore2 = prometheus.userEvent(EventType.LOGIN, realmName2, clientId2);
var loginErrorBefore = prometheus.userEvent(EventType.LOGIN_ERROR);
var loginErrorBefore1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId1);
var loginErrorBefore2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId2);
var loginErrorBefore1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName1, clientId1);
var loginErrorBefore2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName2, clientId2);

assertTrue(keycloak.login(realmName1, username1, password1));
assertTrue(keycloak.login(realmName1, username1, password1));
assertTrue(keycloak.login(realmName2, username2, password2));
assertFalse(keycloak.login(realmName2, username2, "nope"));
assertTrue(keycloak.login(clientId1, realmName1, username1, password1));
assertTrue(keycloak.login(clientId1, realmName1, username1, password1));
assertTrue(keycloak.login(clientId2, realmName2, username2, password2));
assertFalse(keycloak.login(clientId2, realmName2, username2, "nope"));

prometheus.scrap();
var loginAfter = prometheus.userEvent(EventType.LOGIN);
var loginAfter1 = prometheus.userEvent(EventType.LOGIN, realmId1);
var loginAfter2 = prometheus.userEvent(EventType.LOGIN, realmId2);
var loginAfter1 = prometheus.userEvent(EventType.LOGIN, realmName1, clientId1);
var loginAfter2 = prometheus.userEvent(EventType.LOGIN, realmName2, clientId2);
var loginErrorAfter = prometheus.userEvent(EventType.LOGIN_ERROR);
var loginErrorAfter1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId1);
var loginErrorAfter2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId2);
var loginErrorAfter1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName1, clientId1);
var loginErrorAfter2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName2, clientId2);

assertAll("prometheus",
() -> assertEquals(loginBefore + 3, loginAfter, "login success total"),
Expand Down
Loading

0 comments on commit c844bb7

Please sign in to comment.