diff --git a/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsFilter.java b/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsFilter.java index 1eef527..c18fa3a 100644 --- a/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsFilter.java +++ b/src/main/java/org/jboss/aerogear/keycloak/metrics/MetricsFilter.java @@ -23,20 +23,25 @@ public final class MetricsFilter implements ContainerRequestFilter, ContainerRes private static final String METRICS_REQUEST_TIMESTAMP = "metrics.requestTimestamp"; private static final MetricsFilter INSTANCE = new MetricsFilter(); + private static final boolean URI_METRICS_ENABLED = Boolean.parseBoolean(System.getenv("URI_METRICS_ENABLED")); + // relevant response content types to be measured private static final Set contentTypes = new HashSet<>(); + static { contentTypes.add(MediaType.APPLICATION_JSON_TYPE); contentTypes.add(MediaType.APPLICATION_XML_TYPE); contentTypes.add(MediaType.TEXT_HTML_TYPE); } + private static final Set CONTENT_TYPES = Collections.unmodifiableSet(contentTypes); public static MetricsFilter instance() { return INSTANCE; } - private MetricsFilter() { } + private MetricsFilter() { + } @Override public void filter(ContainerRequestContext req) { @@ -48,12 +53,19 @@ public void filter(ContainerRequestContext req, ContainerResponseContext res) { int status = res.getStatus(); String resource = ResourceExtractor.getResource(req.getUriInfo()); + String uri = ResourceExtractor.getURI(req.getUriInfo()); - PrometheusExporter.instance().recordResponseTotal(status, req.getMethod(), resource); - if (status >= 400) { - PrometheusExporter.instance().recordResponseError(status, req.getMethod(), resource); + if (URI_METRICS_ENABLED) { + PrometheusExporter.instance().recordResponseTotal(status, req.getMethod(), resource, uri); + if (status >= 400) { + PrometheusExporter.instance().recordResponseError(status, req.getMethod(), resource, uri); + } + } else { + PrometheusExporter.instance().recordResponseTotal(status, req.getMethod(), resource); + if (status >= 400) { + PrometheusExporter.instance().recordResponseError(status, req.getMethod(), resource); + } } - // Record request duration if timestamp property is present // and only if it is relevant (skip pictures) if (req.getProperty(METRICS_REQUEST_TIMESTAMP) != null && @@ -61,7 +73,11 @@ public void filter(ContainerRequestContext req, ContainerResponseContext res) { long time = (long) req.getProperty(METRICS_REQUEST_TIMESTAMP); long dur = System.currentTimeMillis() - time; LOG.trace("Duration is calculated as " + dur + " ms."); - PrometheusExporter.instance().recordRequestDuration(dur, req.getMethod(), resource); + if (URI_METRICS_ENABLED) { + PrometheusExporter.instance().recordRequestDuration(status, dur, req.getMethod(), resource, uri); + } else { + PrometheusExporter.instance().recordRequestDuration(status, dur, req.getMethod(), resource); + } } } 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 2dc89c5..fe7cd00 100644 --- a/src/main/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporter.java +++ b/src/main/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporter.java @@ -37,7 +37,7 @@ public final class PrometheusExporter { private final static String PROMETHEUS_PUSHGATEWAY_GROUPINGKEY_INSTANCE = "PROMETHEUS_GROUPING_KEY_INSTANCE"; private final static Pattern PROMETHEUS_PUSHGATEWAY_GROUPINGKEY_INSTANCE_ENVVALUE_PATTERN = Pattern.compile("ENVVALUE:(.+?)"); - + private final static String PROMETHEUS_PUSHGATEWAY_JOB = "PROMETHEUS_PUSHGATEWAY_JOB"; private static PrometheusExporter INSTANCE; @@ -149,24 +149,46 @@ private PrometheusExporter() { .labelNames("realm", "provider", "error", "client_id") .register(); - responseTotal = Counter.build() + final boolean URI_METRICS_ENABLED = Boolean.parseBoolean(System.getenv("URI_METRICS_ENABLED")); + if (URI_METRICS_ENABLED){ + responseTotal = Counter.build() + .name("keycloak_response_total") + .help("Total number of responses") + .labelNames("code", "method", "resource", "uri") + .register(); + + responseErrors = Counter.build() + .name("keycloak_response_errors") + .help("Total number of error responses") + .labelNames("code", "method", "resource", "uri") + .register(); + + requestDuration = Histogram.build() + .name("keycloak_request_duration") + .help("Request duration") + .buckets(50, 100, 250, 500, 1000, 2000, 10000, 30000) + .labelNames("code", "method", "resource", "uri") + .register(); + } else { + responseTotal = Counter.build() .name("keycloak_response_total") .help("Total number of responses") .labelNames("code", "method", "resource") .register(); - responseErrors = Counter.build() + responseErrors = Counter.build() .name("keycloak_response_errors") .help("Total number of error responses") .labelNames("code", "method", "resource") .register(); - requestDuration = Histogram.build() + requestDuration = Histogram.build() .name("keycloak_request_duration") .help("Request duration") .buckets(50, 100, 250, 500, 1000, 2000, 10000, 30000) - .labelNames("method", "resource") + .labelNames("code", "method", "resource") .register(); + } // Counters for all user events for (EventType type : EventType.values()) { @@ -367,8 +389,30 @@ public void recordCodeToTokenError(final Event event) { * @param amt The duration in milliseconds * @param method HTTP method of the request */ - public void recordRequestDuration(double amt, String method, String resource) { - requestDuration.labels(method, resource).observe(amt); + public void recordRequestDuration(int code, double amt, String method, String resource, String uri) { + requestDuration.labels(Integer.toString(code), method, resource, uri).observe(amt); + pushAsync(); + } + + /** + * Record the duration between one request and response + * + * @param amt The duration in milliseconds + * @param method HTTP method of the request + */ + public void recordRequestDuration(int code, double amt, String method, String resource) { + requestDuration.labels(Integer.toString(code), method, resource).observe(amt); + pushAsync(); + } + + /** + * Increase the response total count by a given method and response code + * + * @param code The returned http status code + * @param method The request method used + */ + public void recordResponseTotal(int code, String method, String resource, String uri) { + responseTotal.labels(Integer.toString(code), method, resource, uri).inc(); pushAsync(); } @@ -383,6 +427,17 @@ public void recordResponseTotal(int code, String method, String resource) { pushAsync(); } + /** + * Increase the response error count by a given method and response code + * + * @param code The returned http status code + * @param method The request method used + */ + public void recordResponseError(int code, String method, String resource, String uri) { + responseErrors.labels(Integer.toString(code), method, resource, uri).inc(); + pushAsync(); + } + /** * Increase the response error count by a given method and response code * diff --git a/src/main/java/org/jboss/aerogear/keycloak/metrics/ResourceExtractor.java b/src/main/java/org/jboss/aerogear/keycloak/metrics/ResourceExtractor.java index 6a2d55e..8069906 100644 --- a/src/main/java/org/jboss/aerogear/keycloak/metrics/ResourceExtractor.java +++ b/src/main/java/org/jboss/aerogear/keycloak/metrics/ResourceExtractor.java @@ -4,13 +4,15 @@ import javax.ws.rs.core.UriInfo; import java.util.List; +import java.util.regex.*; class ResourceExtractor { private final static Logger logger = Logger.getLogger(ResourceExtractor.class); private static final boolean IS_RESOURCE_SCRAPING_DISABLED = Boolean.getBoolean("RESOURCE_SCRAPING_DISABLED"); - + private static final boolean URI_METRICS_ENABLED = Boolean.getBoolean("URI_METRICS_ENABLED"); + private static final boolean URI_METRICS_DETAILED = Boolean.getBoolean("URI_METRICS_DETAILED"); private ResourceExtractor() { } @@ -35,12 +37,12 @@ private ResourceExtractor() { * @param uriInfo {@link UriInfo} object obtained from JAX-RS * @return The resource name. */ - static String getResource(UriInfo uriInfo) { + static String getResource(UriInfo uriInfo) { if (!IS_RESOURCE_SCRAPING_DISABLED) { List matchedURIs = uriInfo.getMatchedURIs(); if (matchedURIs.size() >= 2) { // A special case for all static resources - we're not interested in - // evey particular resource - just an aggregate with all other endpoints. + // every particular resource - just an aggregate with all other endpoints. if ("resources".equals(matchedURIs.get(matchedURIs.size() - 1))) { return ""; } @@ -54,4 +56,29 @@ static String getResource(UriInfo uriInfo) { return ""; } + /** + * This method obtains a list of resource info from the {@link UriInfo} object and returns the resource URI. + * @param uriInfo {@link UriInfo} object obtained from JAX-RS + * @return The resource uri. + */ + static String getURI(UriInfo uriInfo) { + if (URI_METRICS_ENABLED) { + List matchedURIs = uriInfo.getMatchedURIs(); + StringBuilder sb = new StringBuilder(); + if (matchedURIs.get(0).contains("/token")) + { + String uri = matchedURIs.get(0); + + if(URI_METRICS_DETAILED) { + sb.append(uri); + } else { + String[] realm = uri.split("/"); + uri=uri.replace(realm[1], "{realm}"); + sb.append(uri); + } + } + return sb.toString(); + } + return ""; + } } 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 f7dcc06..0f142a0 100644 --- a/src/test/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporterTest.java +++ b/src/test/java/org/jboss/aerogear/keycloak/metrics/PrometheusExporterTest.java @@ -265,28 +265,31 @@ public void shouldCorrectlyRecordGenericAdminEvents() throws IOException { @Test public void shouldCorrectlyRecordResponseDurations() throws IOException { - PrometheusExporter.instance().recordRequestDuration(5, "GET", "admin,admin/serverinfo"); + environmentVariables.set("URI_METRICS_ENABLED", "true"); + PrometheusExporter.instance().recordRequestDuration(200, 5, "GET", "admin,admin/serverinfo", "auth/realm"); assertGenericMetric("keycloak_request_duration_count", 1, - tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo")); + tuple("code","200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm")); assertGenericMetric("keycloak_request_duration_sum", 5, - tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo")); + tuple("code","200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm")); } @Test public void shouldCorrectlyRecordResponseTotal() throws IOException { - PrometheusExporter.instance().recordResponseTotal(200, "GET", "admin,admin/serverinfo"); - PrometheusExporter.instance().recordResponseTotal(500, "POST", "admin,admin/serverinfo"); + environmentVariables.set("URI_METRICS_ENABLED", "true"); + PrometheusExporter.instance().recordResponseTotal(200, "GET", "admin,admin/serverinfo", "auth/realm"); + PrometheusExporter.instance().recordResponseTotal(500, "POST", "admin,admin/serverinfo", "auth/realm"); assertGenericMetric("keycloak_response_total", 1, - tuple("code", "200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo")); + tuple("code", "200"), tuple("method", "GET"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm")); assertGenericMetric("keycloak_response_total", 1, - tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo")); + tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm")); } @Test public void shouldCorrectlyRecordResponseErrors() throws IOException { - PrometheusExporter.instance().recordResponseError(500, "POST", "admin,admin/serverinfo"); + environmentVariables.set("URI_METRICS_ENABLED", "true"); + PrometheusExporter.instance().recordResponseError(500, "POST", "admin,admin/serverinfo", "auth/realm"); assertGenericMetric("keycloak_response_errors", 1, - tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo")); + tuple("code", "500"), tuple("method", "POST"), tuple("resource", "admin,admin/serverinfo"), tuple("uri", "auth/realm")); } @Test