-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13884 from oscarfh/add-metrics-to-rest-clients
Add client metrics to rest client extension
- Loading branch information
Showing
13 changed files
with
332 additions
and
76 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
...deployment/src/main/java/io/quarkus/micrometer/deployment/binder/HttpClientProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package io.quarkus.micrometer.deployment.binder; | ||
|
||
import java.util.function.BooleanSupplier; | ||
|
||
import io.quarkus.deployment.annotations.BuildProducer; | ||
import io.quarkus.deployment.annotations.BuildStep; | ||
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; | ||
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; | ||
import io.quarkus.micrometer.runtime.MicrometerRecorder; | ||
import io.quarkus.micrometer.runtime.config.MicrometerConfig; | ||
|
||
public class HttpClientProcessor { | ||
// Avoid referencing optional dependencies | ||
|
||
// Rest client listener SPI | ||
private static final String REST_CLIENT_LISTENER_CLASS_NAME = "org.eclipse.microprofile.rest.client.spi.RestClientListener"; | ||
private static final Class<?> REST_CLIENT_LISTENER_CLASS = MicrometerRecorder | ||
.getClassForName(REST_CLIENT_LISTENER_CLASS_NAME); | ||
|
||
// Rest Client listener | ||
private static final String REST_CLIENT_METRICS_LISTENER = "io.quarkus.micrometer.runtime.binder.RestClientMetrics"; | ||
|
||
static class HttpClientEnabled implements BooleanSupplier { | ||
MicrometerConfig mConfig; | ||
|
||
public boolean getAsBoolean() { | ||
return REST_CLIENT_LISTENER_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.httpClient); | ||
} | ||
} | ||
|
||
@BuildStep(onlyIf = HttpClientEnabled.class) | ||
void registerRestClientListener(BuildProducer<NativeImageResourceBuildItem> resource, | ||
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) { | ||
resource.produce(new NativeImageResourceBuildItem( | ||
"META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener")); | ||
reflectiveClass | ||
.produce(new ReflectiveClassBuildItem(true, true, REST_CLIENT_METRICS_LISTENER)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...sions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/HttpTags.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package io.quarkus.micrometer.runtime.binder; | ||
|
||
import java.util.regex.Pattern; | ||
|
||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.binder.http.Outcome; | ||
|
||
public class HttpTags { | ||
public static final Tag URI_NOT_FOUND = Tag.of("uri", "NOT_FOUND"); | ||
public static final Tag URI_REDIRECTION = Tag.of("uri", "REDIRECTION"); | ||
public static final Tag URI_ROOT = Tag.of("uri", "root"); | ||
static final Tag URI_UNKNOWN = Tag.of("uri", "UNKNOWN"); | ||
|
||
static final Tag STATUS_UNKNOWN = Tag.of("status", "UNKNOWN"); | ||
public static final Tag STATUS_RESET = Tag.of("status", "RESET"); | ||
|
||
public static final Tag METHOD_UNKNOWN = Tag.of("method", "UNKNOWN"); | ||
|
||
public static final Pattern TRAILING_SLASH_PATTERN = Pattern.compile("/$"); | ||
public static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("//+"); | ||
|
||
/** | ||
* Creates an {@code method} {@code Tag} derived from the given {@code HTTP method}. | ||
* | ||
* @param method the HTTP method | ||
* @return the outcome tag | ||
*/ | ||
public static Tag method(String method) { | ||
return method == null ? METHOD_UNKNOWN : Tag.of("method", method); | ||
} | ||
|
||
/** | ||
* Creates a {@code status} tag based on the status of the given {@code response code}. | ||
* | ||
* @param statusCode the HTTP response code | ||
* @return the status tag derived from the status of the response | ||
*/ | ||
public static Tag status(int statusCode) { | ||
return (statusCode > 0) ? Tag.of("status", Integer.toString(statusCode)) : STATUS_UNKNOWN; | ||
} | ||
|
||
/** | ||
* Creates an {@code outcome} {@code Tag} derived from the given {@code response code}. | ||
* | ||
* @param statusCode the HTTP response code | ||
* @return the outcome tag | ||
*/ | ||
public static Tag outcome(int statusCode) { | ||
return Outcome.forStatus(statusCode).asTag(); | ||
} | ||
|
||
/** | ||
* Creates a {@code uri} tag based on the URI of the given {@code request}. | ||
* Falling back to {@code REDIRECTION} for 3xx responses, {@code NOT_FOUND} | ||
* for 404 responses, {@code root} for requests with no path info, and {@code UNKNOWN} | ||
* for all other requests. | ||
* | ||
* | ||
* @param pathInfo | ||
* @param code status code of the response | ||
* @return the uri tag derived from the request | ||
*/ | ||
public static Tag uri(String pathInfo, int code) { | ||
if (code > 0) { | ||
if (code / 100 == 3) { | ||
return URI_REDIRECTION; | ||
} else if (code == 404) { | ||
return URI_NOT_FOUND; | ||
} | ||
} | ||
if (pathInfo == null) { | ||
return URI_UNKNOWN; | ||
} | ||
if (pathInfo.isEmpty() || "/".equals(pathInfo)) { | ||
return URI_ROOT; | ||
} | ||
|
||
// Use first segment of request path | ||
return Tag.of("uri", pathInfo); | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
...rometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/RestClientMetrics.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package io.quarkus.micrometer.runtime.binder; | ||
|
||
import java.io.IOException; | ||
|
||
import javax.ws.rs.client.ClientRequestContext; | ||
import javax.ws.rs.client.ClientRequestFilter; | ||
import javax.ws.rs.client.ClientResponseContext; | ||
import javax.ws.rs.client.ClientResponseFilter; | ||
|
||
import org.eclipse.microprofile.rest.client.RestClientBuilder; | ||
import org.eclipse.microprofile.rest.client.spi.RestClientListener; | ||
import org.jboss.logging.Logger; | ||
|
||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.Metrics; | ||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.Tags; | ||
import io.micrometer.core.instrument.Timer; | ||
|
||
public class RestClientMetrics implements RestClientListener { | ||
private static final Logger log = Logger.getLogger(RestClientMetrics.class); | ||
private static final String HTTP_CLIENT_METRIC_NAME = "http.client.requests"; | ||
private final static String REQUEST_TIMER_SAMPLE_PROPERTY = "requestStartTime"; | ||
|
||
final MeterRegistry registry = Metrics.globalRegistry; | ||
final MetricsClientRequestFilter clientRequestFilter = new MetricsClientRequestFilter(); | ||
final MetricsClientResponseFilter clientResponseFilter = new MetricsClientResponseFilter(); | ||
|
||
@Override | ||
public void onNewClient(Class<?> serviceInterface, RestClientBuilder builder) { | ||
builder.register(clientRequestFilter); | ||
builder.register(clientResponseFilter); | ||
} | ||
|
||
class MetricsClientRequestFilter implements ClientRequestFilter { | ||
@Override | ||
public void filter(ClientRequestContext requestContext) throws IOException { | ||
requestContext.setProperty(REQUEST_TIMER_SAMPLE_PROPERTY, Timer.start(registry)); | ||
} | ||
} | ||
|
||
class MetricsClientResponseFilter implements ClientResponseFilter { | ||
@Override | ||
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { | ||
Timer.Sample sample = getRequestSample(requestContext); | ||
if (sample != null) { | ||
String requestPath = getRequestPath(requestContext); | ||
int statusCode = responseContext.getStatus(); | ||
Timer.Builder builder = Timer.builder(HTTP_CLIENT_METRIC_NAME) | ||
.tags(Tags.of( | ||
HttpTags.method(requestContext.getMethod()), | ||
HttpTags.uri(requestPath, statusCode), | ||
HttpTags.outcome(statusCode), | ||
HttpTags.status(statusCode), | ||
clientName(requestContext))); | ||
|
||
sample.stop(builder.register(registry)); | ||
} | ||
} | ||
|
||
private String getRequestPath(ClientRequestContext requestContext) { | ||
return requestContext.getUri().getPath(); | ||
} | ||
|
||
private Timer.Sample getRequestSample(ClientRequestContext requestContext) { | ||
return (Timer.Sample) requestContext.getProperty(REQUEST_TIMER_SAMPLE_PROPERTY); | ||
} | ||
|
||
private Tag clientName(ClientRequestContext requestContext) { | ||
String host = requestContext.getUri().getHost(); | ||
if (host == null) { | ||
host = "none"; | ||
} | ||
return Tag.of("clientName", host); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
...crometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/HttpClientConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package io.quarkus.micrometer.runtime.config; | ||
|
||
import java.util.Optional; | ||
|
||
import io.quarkus.runtime.annotations.ConfigGroup; | ||
import io.quarkus.runtime.annotations.ConfigItem; | ||
|
||
/** | ||
* Build / static runtime config for outbound HTTP requests | ||
*/ | ||
@ConfigGroup | ||
public class HttpClientConfig implements MicrometerConfig.CapabilityEnabled { | ||
/** | ||
* Outbound HTTP request metrics support. | ||
* <p> | ||
* Support for HTTP client metrics will be enabled if Micrometer | ||
* support is enabled, the REST client feature is enabled, | ||
* and either this value is true, or this value is unset and | ||
* {@code quarkus.micrometer.binder-enabled-default} is true. | ||
*/ | ||
@ConfigItem | ||
public Optional<Boolean> enabled; | ||
|
||
@Override | ||
public Optional<Boolean> getEnabled() { | ||
return enabled; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return this.getClass().getSimpleName() | ||
+ "{enabled=" + enabled | ||
+ '}'; | ||
} | ||
} |
Oops, something went wrong.