Skip to content

Commit

Permalink
json event logger
Browse files Browse the repository at this point in the history
  • Loading branch information
dasniko committed Jan 24, 2024
1 parent 43cc40b commit 535633b
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 0 deletions.
4 changes: 4 additions & 0 deletions event-listener/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package dasniko.keycloak.events;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.UriInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerTransaction;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.slf4j.event.Level;

import java.util.Map;

@Slf4j(topic = "org.keycloak.events")
public class JsonEventListenerProvider implements EventListenerProvider {

private final KeycloakSession session;
private final ObjectMapper mapper;
private final Level successLevel;
private final Level errorLevel;
private final EventListenerTransaction tx = new EventListenerTransaction(this::sendAdminEvent, this::logEvent);

public JsonEventListenerProvider(KeycloakSession session, ObjectMapper mapper, Level successLevel, Level errorLevel) {
this.session = session;
this.mapper = mapper;
this.successLevel = successLevel;
this.errorLevel = errorLevel;

session.getTransactionManager().enlistAfterCompletion(tx);
}

@Override
public void onEvent(Event event) {
tx.addEvent(event);
}

@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
tx.addAdminEvent(event, includeRepresentation);
}

@Override
public void close() {
}

private void logEvent(Event event) {
Level level = event.getError() != null ? errorLevel : successLevel;

if (log.isEnabledForLevel(level)) {
String s = null;
try {
Map<String, Object> map = mapper.convertValue(event, new TypeReference<>() {});

AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
if(authSession!=null) {
map.put("authSessionParentId", authSession.getParentSession().getId());
map.put("authSessionTabId", authSession.getTabId());
}

if (log.isTraceEnabled()) {
setKeycloakContext(map);
}

s = mapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
log.error("Error while trying to JSONify event %s".formatted(ToStringBuilder.reflectionToString(event)), e);
}

log.atLevel(log.isTraceEnabled() ? Level.TRACE : level).log(s);
}
}

private void sendAdminEvent(AdminEvent event, boolean includeRepresentation) {
Level level = event.getError() != null ? errorLevel : successLevel;

if (log.isEnabledForLevel(level)) {
String s = null;
try {
Map<String, Object> map = mapper.convertValue(event, new TypeReference<>() {
});

if (log.isTraceEnabled()) {
setKeycloakContext(map);
}

s = mapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
log.error("Error while trying to JSONify admin event %s".formatted(ToStringBuilder.reflectionToString(event)), e);
}

log.atLevel(log.isTraceEnabled() ? Level.TRACE : level).log(s);
}
}

private void setKeycloakContext(Map<String, Object> map) {
KeycloakContext context = session.getContext();
UriInfo uriInfo = context.getUri();
if (uriInfo != null) {
map.put("requestUri", uriInfo.getRequestUri().toString());
}
HttpHeaders headers = context.getRequestHeaders();
if (headers != null) {
map.put("cookies", headers.getCookies());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package dasniko.keycloak.events;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auto.service.AutoService;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.slf4j.event.Level;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

@AutoService(EventListenerProviderFactory.class)
public class JsonEventListenerProviderFactory implements EventListenerProviderFactory {

public static final String PROVIDER_ID = "json-logging";

private static final ObjectMapper mapper = new ObjectMapper();

private Level successLevel;
private Level errorLevel;

@Override
public EventListenerProvider create(KeycloakSession keycloakSession) {
return new JsonEventListenerProvider(keycloakSession, mapper, successLevel, errorLevel);
}

@Override
public void init(Config.Scope config) {
successLevel = Level.valueOf(config.get("success-level", "debug").toUpperCase());
errorLevel = Level.valueOf(config.get("error-level", "warn").toUpperCase());
}

@Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
}

@Override
public void close() {
}

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

@Override
public List<ProviderConfigProperty> getConfigMetadata() {
String[] logLevels = Arrays.stream(Level.values())
.map(Level::name)
.map(String::toLowerCase)
.sorted(Comparator.naturalOrder())
.toArray(String[]::new);
return ProviderConfigurationBuilder.create()
.property()
.name("success-level")
.type("string")
.helpText("The log level for success messages.")
.options(logLevels)
.defaultValue("debug")
.add()
.property()
.name("error-level")
.type("string")
.helpText("The log level for error messages.")
.options(logLevels)
.defaultValue("warn")
.add()
.build();
}
}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
Expand Down

0 comments on commit 535633b

Please sign in to comment.