First draft of implementation (#1)

* First draft of implementation

Readme will follow

* Rename id.

* Exclude `commons-io` to disable build warnings

* Simplify metrics and add documentation
This commit is contained in:
Stephan Schnabel 2023-03-03 12:32:46 +01:00
parent dc38b3715e
commit c8977d7175
Signed by: stephan.schnabel
GPG key ID: E07AF5BA239FE543
23 changed files with 1345 additions and 1 deletions

View file

@ -0,0 +1,32 @@
package io.kokuwa.keycloak.metrics;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
/**
* Listener for {@link Event} and {@link AdminEvent}.
*
* @author Stephan Schnabel
*/
public class MicrometerEventListener implements EventListenerProvider, AutoCloseable {
private final MicrometerEventRecorder recorder;
MicrometerEventListener(MicrometerEventRecorder recorder) {
this.recorder = recorder;
}
@Override
public void onEvent(Event event) {
recorder.userEvent(event);
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
recorder.adminEvent(event);
}
@Override
public void close() {}
}

View file

@ -0,0 +1,42 @@
package io.kokuwa.keycloak.metrics;
import javax.enterprise.inject.spi.CDI;
import org.keycloak.Config.Scope;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import io.micrometer.core.instrument.MeterRegistry;
/**
* Factory for {@link MicrometerEventListener}, uses {@link MeterRegistry} from CDI.
*
* @author Stephan Schnabel
*/
public class MicrometerEventListenerFactory implements EventListenerProviderFactory {
private MicrometerEventRecorder recorder;
@Override
public String getId() {
return "metrics-listener";
}
@Override
public void init(Scope config) {}
@Override
public void postInit(KeycloakSessionFactory factory) {
recorder = new MicrometerEventRecorder(CDI.current().select(MeterRegistry.class).get());
}
@Override
public EventListenerProvider create(KeycloakSession session) {
return new MicrometerEventListener(recorder);
}
@Override
public void close() {}
}

View file

@ -0,0 +1,32 @@
package io.kokuwa.keycloak.metrics;
import org.keycloak.events.EventListenerSpi;
import org.keycloak.provider.Provider;
/**
* Factory for {@link MicrometerEventListener}.
*
* @author Stephan Schnabel
*/
public class MicrometerEventListenerSpi extends EventListenerSpi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "Micrometer Metrics Provider";
}
@Override
public Class<? extends Provider> getProviderClass() {
return MicrometerEventListener.class;
}
@Override
public Class<MicrometerEventListenerFactory> getProviderFactoryClass() {
return MicrometerEventListenerFactory.class;
}
}

View file

@ -0,0 +1,51 @@
package io.kokuwa.keycloak.metrics;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
/**
* Micrometer based recorder for events.
*
* @author Stephan Schnabel
*/
public class MicrometerEventRecorder {
private final Map<String, Counter> counters = new HashMap<>();
private final MeterRegistry registry;
MicrometerEventRecorder(MeterRegistry registry) {
this.registry = registry;
}
void adminEvent(AdminEvent event) {
counter("keycloak_event_admin",
"realm", toBlankIfNull(event.getRealmId()),
"resource", toBlankIfNull(event.getResourceType()),
"operation", toBlankIfNull(event.getOperationType()),
"error", toBlankIfNull(event.getError()));
}
void userEvent(Event event) {
counter("keycloak_event_user",
"realm", toBlankIfNull(event.getRealmId()),
"type", toBlankIfNull(event.getType()),
"client", toBlankIfNull(event.getClientId()),
"error", toBlankIfNull(event.getError()));
}
private void counter(String counter, String... tags) {
counters.computeIfAbsent(counter + Arrays.toString(tags), string -> registry.counter(counter, tags))
.increment();
}
private String toBlankIfNull(Object value) {
return value == null ? "" : value.toString();
}
}