Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add counter metrics for client_login, refresh_token and code_to_token #85

Merged
merged 2 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,44 @@ keycloak_failed_login_attempts{realm="test",provider="keycloak",error="invalid_u
keycloak_failed_login_attempts{realm="test",provider="keycloak",error="user_not_found",client_id="application1"} 2.0
```

##### keycloak_client_logins
This counter counts every client login.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly is a client_login and how does it differ from a normal login event (which already has a client label)?


```c
# HELP keycloak_client_logins Total successful client logins
# TYPE keycloak_client_logins gauge
keycloak_client_logins{realm="test",provider="keycloak",client_id="account"} 4.0
keycloak_client_logins{realm="test",provider="github",client_id="application2"} 7.0
```

##### keycloak_failed_client_login_attempts
This counter counts every client login performed that fails, being the error described by the label **error**.
```c
# HELP keycloak_failed_client_login_attempts Total failed client login attempts
# TYPE keycloak_failed_client_login_attempts gauge
keycloak_failed_client_login_attempts{realm="test2",provider="keycloak",error="invalid_client_credentials",client_id="application2"} 5.0
keycloak_failed_client_login_attempts{realm="test2",provider="keycloak",error="client_not_found",client_id="application2"} 3.0
```

##### keycloak_refresh_tokens
This counter counts every refresh token.

```c
# HELP keycloak_refresh_tokens Total number of successful token refreshes
# TYPE keycloak_refresh_tokens gauge
keycloak_refresh_tokens{realm="test3",provider="keycloak",client_id="account"} 1.0
keycloak_refresh_tokens{realm="test3",provider="github",client_id="application3"} 2.0
```

##### keycloak_refresh_tokens_errors
This counter counts every refresh token that fails.

```c
# HELP keycloak_refresh_tokens_errors Total number of failed token refreshes
# TYPE keycloak_refresh_tokens_errors gauge
keycloak_refresh_tokens_errors{realm="test3",provider="keycloak",error="invalid_token",client_id="application3"} 3.0
```

##### keycloak_registrations
This counter counts every new user registration. It also distinguishes registrations by the identity provider used by means of the label **provider** and by client with the label **client_id**..

Expand All @@ -145,6 +183,25 @@ keycloak_registrations_errors{realm="test",provider="keycloak",error="invalid_re
keycloak_registrations_errors{realm="test",provider="keycloak",error="email_in_use",client_id="application1",} 3.0
```

##### keycloak_code_to_tokens
This counter counts every code to token.

```c
# HELP keycloak_code_to_tokens Total number of successful code to token
# TYPE keycloak_code_to_tokens gauge
keycloak_code_to_tokens{realm="test4",provider="keycloak",client_id="account"} 3.0
keycloak_code_to_tokens{realm="test4",provider="github",client_id="application4"} 1.0
```

##### keycloak_code_to_tokens_errors
This counter counts every code to token performed that fails, being the error described by the label **error**.

```c
# HELP keycloak_code_to_tokens_errors Total number of failed code to token
# TYPE keycloak_code_to_tokens_errors gauge
keycloak_code_to_tokens_errors{realm="test4",provider="keycloak",error="invalid_client_credentials",client_id="application4"} 7.0
```

##### keycloak_request_duration
This histogram records the response times per http method and puts them in one of nine buckets:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,33 @@ public void onEvent(Event event) {
case LOGIN:
PrometheusExporter.instance().recordLogin(event);
break;
case CLIENT_LOGIN:
PrometheusExporter.instance().recordClientLogin(event);
break;
case REGISTER:
PrometheusExporter.instance().recordRegistration(event);
break;
case REFRESH_TOKEN:
PrometheusExporter.instance().recordRefreshToken(event);
break;
case CODE_TO_TOKEN:
PrometheusExporter.instance().recordCodeToToken(event);
break;
case REGISTER_ERROR:
PrometheusExporter.instance().recordRegistrationError(event);
break;
case LOGIN_ERROR:
PrometheusExporter.instance().recordLoginError(event);
break;
case CLIENT_LOGIN_ERROR:
PrometheusExporter.instance().recordClientLoginError(event);
break;
case REFRESH_TOKEN_ERROR:
PrometheusExporter.instance().recordRefreshTokenError(event);
break;
case CODE_TO_TOKEN_ERROR:
PrometheusExporter.instance().recordCodeToTokenError(event);
break;
default:
PrometheusExporter.instance().recordGenericEvent(event);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ public final class PrometheusExporter {
final Counter totalFailedLoginAttempts;
final Counter totalRegistrations;
final Counter totalRegistrationsErrors;
final Counter totalRefreshTokens;
final Counter totalRefreshTokensErrors;
final Counter totalClientLogins;
final Counter totalFailedClientLoginAttempts;
final Counter totalCodeToTokens;
final Counter totalCodeToTokensErrors;
final Counter responseErrors;
final Histogram requestDuration;
final PushGateway PUSH_GATEWAY;
Expand Down Expand Up @@ -72,12 +78,55 @@ private PrometheusExporter() {
.labelNames("realm", "provider", "client_id")
.register();

// package private on purpose
totalRegistrationsErrors = Counter.build()
.name("keycloak_registrations_errors")
.help("Total errors on registrations")
.labelNames("realm", "provider", "error", "client_id")
.register();

// package private on purpose
totalRefreshTokens = Counter.build()
.name("keycloak_refresh_tokens")
.help("Total number of successful token refreshes")
.labelNames("realm", "provider", "client_id")
.register();

// package private on purpose
totalRefreshTokensErrors = Counter.build()
.name("keycloak_refresh_tokens_errors")
.help("Total number of failed token refreshes")
.labelNames("realm", "provider", "error", "client_id")
.register();

// package private on purpose
totalClientLogins = Counter.build()
.name("keycloak_client_logins")
.help("Total successful client logins")
.labelNames("realm", "provider", "client_id")
.register();

// package private on purpose
totalFailedClientLoginAttempts = Counter.build()
.name("keycloak_failed_client_login_attempts")
.help("Total failed client login attempts")
.labelNames("realm", "provider", "error", "client_id")
.register();

// package private on purpose
totalCodeToTokens = Counter.build()
.name("keycloak_code_to_tokens")
.help("Total number of successful code to token")
.labelNames("realm", "provider", "client_id")
.register();

// package private on purpose
totalCodeToTokensErrors = Counter.build()
.name("keycloak_code_to_tokens_errors")
.help("Total number of failed code to token")
.labelNames("realm", "provider", "error", "client_id")
.register();

responseErrors = Counter.build()
.name("keycloak_response_errors")
.help("Total number of error responses")
Expand Down Expand Up @@ -210,6 +259,78 @@ public void recordLoginError(final Event event) {
pushAsync();
}

/**
* Increase the number of currently client logged
*
* @param event ClientLogin event
*/
public void recordClientLogin(final Event event) {
final String provider = getIdentityProvider(event);

totalClientLogins.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).inc();
pushAsync();
}

/**
* Increase the number of failed login attempts
*
* @param event ClientLoginError event
*/
public void recordClientLoginError(final Event event) {
final String provider = getIdentityProvider(event);

totalFailedClientLoginAttempts.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getError()), nullToEmpty(event.getClientId())).inc();
pushAsync();
}

/**
* Increase the number of refreshes tokens
*
* @param event RefreshToken event
*/
public void recordRefreshToken(final Event event) {
final String provider = getIdentityProvider(event);

totalRefreshTokens.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).inc();
pushAsync();
}

/**
* Increase the number of failed refreshes tokens attempts
*
* @param event RefreshTokenError event
*/
public void recordRefreshTokenError(final Event event) {
final String provider = getIdentityProvider(event);

totalRefreshTokensErrors.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getError()), nullToEmpty(event.getClientId())).inc();
pushAsync();
}

/**
* Increase the number of code to tokens
*
* @param event CodeToToken event
*/
public void recordCodeToToken(final Event event) {
final String provider = getIdentityProvider(event);

totalCodeToTokens.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getClientId())).inc();
pushAsync();
}

/**
* Increase the number of failed code to tokens attempts
*
* @param event CodeToTokenError event
*/
public void recordCodeToTokenError(final Event event) {
final String provider = getIdentityProvider(event);

totalCodeToTokensErrors.labels(nullToEmpty(event.getRealmId()), provider, nullToEmpty(event.getError()), nullToEmpty(event.getClientId())).inc();
pushAsync();
}

/**
* Record the duration between one request and response
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,90 @@ public void shouldCorrectlyCountRegister() throws IOException {
assertMetric("keycloak_registrations", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_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"));
PrometheusExporter.instance().recordRefreshToken(event1);
assertMetric("keycloak_refresh_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));

// without id provider defined
final Event event2 = createEvent(EventType.REFRESH_TOKEN, DEFAULT_REALM, "THE_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"));
}

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

// without id provider defined
final Event event2 = createEvent(EventType.REFRESH_TOKEN_ERROR, DEFAULT_REALM, "THE_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"));
}

@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"));
PrometheusExporter.instance().recordClientLogin(event1);
assertMetric("keycloak_client_logins", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));

// without id provider defined
final Event event2 = createEvent(EventType.CLIENT_LOGIN, DEFAULT_REALM, "THE_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"));
}

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

// without id provider defined
final Event event2 = createEvent(EventType.CLIENT_LOGIN_ERROR, DEFAULT_REALM, "THE_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"));
}

@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"));
PrometheusExporter.instance().recordCodeToToken(event1);
assertMetric("keycloak_code_to_tokens", 1, tuple("provider", "THE_ID_PROVIDER"), tuple("client_id", "THE_CLIENT_ID"));

// without id provider defined
final Event event2 = createEvent(EventType.CODE_TO_TOKEN, DEFAULT_REALM, "THE_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"));
}

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

// without id provider defined
final Event event2 = createEvent(EventType.CODE_TO_TOKEN_ERROR, DEFAULT_REALM, "THE_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"));
}

@Test
public void shouldCorrectlyRecordGenericEvents() throws IOException {
final Event event1 = createEvent(EventType.UPDATE_EMAIL);
Expand Down