parent
4d44aaf81b
commit
001b2636d6
|
|
@ -25,8 +25,9 @@ import org.springframework.http.client.ClientHttpRequestFactory;
|
|||
|
||||
|
||||
/**
|
||||
* Documented {@link io.micrometer.common.KeyValue KeyValues} for {@link ClientHttpRequestFactory HTTP client observations}.
|
||||
* Documented {@link io.micrometer.common.KeyValue KeyValues} for {@link ClientHttpRequestFactory HTTP client} observations.
|
||||
* <p>This class is used by automated tools to document KeyValues attached to the HTTP client observations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
|
|
@ -38,7 +39,7 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati
|
|||
HTTP_REQUEST {
|
||||
@Override
|
||||
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
|
||||
return DefaultClientHttpObservationConvention.class;
|
||||
return DefaultClientRequestObservationConvention.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,19 +19,18 @@ package org.springframework.http.client.observation;
|
|||
import io.micrometer.observation.transport.RequestReplySenderContext;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Context that holds information for metadata collection
|
||||
* during the {@link ClientHttpRequestFactory client HTTP} observations.
|
||||
* during the {@link ClientHttpObservationDocumentation#HTTP_REQUEST client HTTP exchanges} observations.
|
||||
* <p>This context also extends {@link RequestReplySenderContext} for propagating tracing
|
||||
* information with the HTTP client exchange.
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ClientHttpObservationContext extends RequestReplySenderContext<ClientHttpRequest, ClientHttpResponse> {
|
||||
public class ClientRequestObservationContext extends RequestReplySenderContext<ClientHttpRequest, ClientHttpResponse> {
|
||||
|
||||
@Nullable
|
||||
private String uriTemplate;
|
||||
|
|
@ -40,8 +39,8 @@ public class ClientHttpObservationContext extends RequestReplySenderContext<Clie
|
|||
* Create an observation context for {@link ClientHttpRequest} observations.
|
||||
* @param request the HTTP client request
|
||||
*/
|
||||
public ClientHttpObservationContext(ClientHttpRequest request) {
|
||||
super(ClientHttpObservationContext::setRequestHeader);
|
||||
public ClientRequestObservationContext(ClientHttpRequest request) {
|
||||
super(ClientRequestObservationContext::setRequestHeader);
|
||||
this.setCarrier(request);
|
||||
}
|
||||
|
||||
|
|
@ -20,15 +20,17 @@ import io.micrometer.observation.Observation;
|
|||
import io.micrometer.observation.ObservationConvention;
|
||||
|
||||
/**
|
||||
* Interface for an {@link ObservationConvention} related to RestTemplate HTTP exchanges.
|
||||
* Interface for an {@link ObservationConvention} for
|
||||
* {@link ClientHttpObservationDocumentation#HTTP_REQUEST client HTTP exchanges}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ClientHttpObservationConvention extends ObservationConvention<ClientHttpObservationContext> {
|
||||
public interface ClientRequestObservationConvention extends ObservationConvention<ClientRequestObservationContext> {
|
||||
|
||||
@Override
|
||||
default boolean supportsContext(Observation.Context context) {
|
||||
return context instanceof ClientHttpObservationContext;
|
||||
return context instanceof ClientRequestObservationContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,18 +21,19 @@ import java.io.IOException;
|
|||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.observation.HttpOutcome;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation for a {@link ClientHttpObservationConvention},
|
||||
* extracting information from the {@link ClientHttpObservationContext}.
|
||||
* Default implementation for a {@link ClientRequestObservationConvention},
|
||||
* extracting information from the {@link ClientRequestObservationContext}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultClientHttpObservationConvention implements ClientHttpObservationConvention {
|
||||
public class DefaultClientRequestObservationConvention implements ClientRequestObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.client.requests";
|
||||
|
||||
|
|
@ -44,18 +45,23 @@ public class DefaultClientHttpObservationConvention implements ClientHttpObserva
|
|||
|
||||
private static final KeyValue STATUS_CLIENT_ERROR = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "CLIENT_ERROR");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_SUCCESS = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "SUCCESS");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_UNKNOWN = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "UNKNOWN");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_NONE = KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "none");
|
||||
|
||||
private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, "none");
|
||||
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.client.requests"}.
|
||||
*/
|
||||
public DefaultClientHttpObservationConvention() {
|
||||
public DefaultClientRequestObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +69,7 @@ public class DefaultClientHttpObservationConvention implements ClientHttpObserva
|
|||
* Create a convention with a custom name.
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultClientHttpObservationConvention(String name) {
|
||||
public DefaultClientRequestObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
|
@ -73,23 +79,23 @@ public class DefaultClientHttpObservationConvention implements ClientHttpObserva
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ClientHttpObservationContext context) {
|
||||
public String getContextualName(ClientRequestObservationContext context) {
|
||||
return "http " + context.getCarrier().getMethod().name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ClientHttpObservationContext context) {
|
||||
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
|
||||
return KeyValues.of(uri(context), method(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
protected KeyValue uri(ClientHttpObservationContext context) {
|
||||
protected KeyValue uri(ClientRequestObservationContext context) {
|
||||
if (context.getUriTemplate() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.URI, context.getUriTemplate());
|
||||
}
|
||||
return URI_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue method(ClientHttpObservationContext context) {
|
||||
protected KeyValue method(ClientRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod().name());
|
||||
}
|
||||
|
|
@ -98,7 +104,7 @@ public class DefaultClientHttpObservationConvention implements ClientHttpObserva
|
|||
}
|
||||
}
|
||||
|
||||
protected KeyValue status(ClientHttpObservationContext context) {
|
||||
protected KeyValue status(ClientRequestObservationContext context) {
|
||||
ClientHttpResponse response = context.getResponse();
|
||||
if (response == null) {
|
||||
return STATUS_CLIENT_ERROR;
|
||||
|
|
@ -111,7 +117,7 @@ public class DefaultClientHttpObservationConvention implements ClientHttpObserva
|
|||
}
|
||||
}
|
||||
|
||||
protected KeyValue exception(ClientHttpObservationContext context) {
|
||||
protected KeyValue exception(ClientRequestObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
|
|
@ -121,36 +127,51 @@ public class DefaultClientHttpObservationConvention implements ClientHttpObserva
|
|||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected static KeyValue outcome(ClientHttpObservationContext context) {
|
||||
protected static KeyValue outcome(ClientRequestObservationContext context) {
|
||||
if (context.getResponse() != null) {
|
||||
try {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(context.getResponse().getStatusCode());
|
||||
return httpOutcome.asKeyValue();
|
||||
return HttpOutcome.forStatus(context.getResponse().getStatusCode());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
return HttpOutcome.UNKNOWN.asKeyValue();
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ClientHttpObservationContext context) {
|
||||
public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) {
|
||||
return KeyValues.of(requestUri(context), clientName(context));
|
||||
}
|
||||
|
||||
protected KeyValue requestUri(ClientHttpObservationContext context) {
|
||||
protected KeyValue requestUri(ClientRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getURI().toASCIIString());
|
||||
}
|
||||
return HTTP_URL_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue clientName(ClientHttpObservationContext context) {
|
||||
protected KeyValue clientName(ClientRequestObservationContext context) {
|
||||
if (context.getCarrier() != null && context.getCarrier().getURI().getHost() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, context.getCarrier().getURI().getHost());
|
||||
}
|
||||
return CLIENT_NAME_NONE;
|
||||
}
|
||||
|
||||
static class HttpOutcome {
|
||||
|
||||
static KeyValue forStatus(HttpStatusCode statusCode) {
|
||||
if (statusCode.is2xxSuccessful()) {
|
||||
return HTTP_OUTCOME_SUCCESS;
|
||||
}
|
||||
else if (statusCode instanceof HttpStatus status){
|
||||
return KeyValue.of("outcome", status.series().name());
|
||||
}
|
||||
else {
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.observation;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default {@link ServerRequestObservationConvention}.
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultServerRequestObservationConvention implements ServerRequestObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.server.requests";
|
||||
|
||||
private static final KeyValue METHOD_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, "UNKNOWN");
|
||||
|
||||
private static final KeyValue STATUS_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "UNKNOWN");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_SUCCESS = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "SUCCESS");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_ROOT = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "root");
|
||||
|
||||
private static final KeyValue URI_NOT_FOUND = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "NOT_FOUND");
|
||||
|
||||
private static final KeyValue URI_REDIRECTION = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "REDIRECTION");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "UNKNOWN");
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.server.requests"}.
|
||||
*/
|
||||
public DefaultServerRequestObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a convention with a custom name.
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultServerRequestObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ServerRequestObservationContext context) {
|
||||
return "http " + context.getCarrier().getMethod().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(method(context), uri(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(uriExpanded(context));
|
||||
}
|
||||
|
||||
protected KeyValue method(ServerRequestObservationContext context) {
|
||||
return (context.getCarrier() != null) ? KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod()) : METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue status(ServerRequestObservationContext context) {
|
||||
return (context.getResponse() != null) ? KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, Integer.toString(context.getResponse().getStatus())) : STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue uri(ServerRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
String pattern = context.getPathPattern();
|
||||
if (pattern != null) {
|
||||
if (pattern.isEmpty()) {
|
||||
return URI_ROOT;
|
||||
}
|
||||
return KeyValue.of("uri", pattern);
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
HttpStatus status = HttpStatus.resolve(context.getResponse().getStatus());
|
||||
if (status != null) {
|
||||
if (status.is3xxRedirection()) {
|
||||
return URI_REDIRECTION;
|
||||
}
|
||||
if (status == HttpStatus.NOT_FOUND) {
|
||||
return URI_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return URI_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue exception(ServerRequestObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION,
|
||||
StringUtils.hasText(simpleName) ? simpleName : error.getClass().getName());
|
||||
}
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue outcome(ServerRequestObservationContext context) {
|
||||
if (context.getResponse() != null) {
|
||||
HttpStatusCode statusCode = HttpStatusCode.valueOf(context.getResponse().getStatus());
|
||||
return HttpOutcome.forStatus(statusCode);
|
||||
}
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue uriExpanded(ServerRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
|
||||
}
|
||||
return HTTP_URL_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
static class HttpOutcome {
|
||||
|
||||
static KeyValue forStatus(HttpStatusCode statusCode) {
|
||||
if (statusCode.is2xxSuccessful()) {
|
||||
return HTTP_OUTCOME_SUCCESS;
|
||||
}
|
||||
else if (statusCode instanceof HttpStatus status){
|
||||
return KeyValue.of("outcome", status.series().name());
|
||||
}
|
||||
else {
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.observation;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
|
||||
/**
|
||||
* The outcome of an HTTP request.
|
||||
* <p>Used as the {@code "outcome"} {@link io.micrometer.common.KeyValue}
|
||||
* for HTTP {@link io.micrometer.observation.Observation observations}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Andy Wilkinson
|
||||
* @since 6.0
|
||||
*/
|
||||
public enum HttpOutcome {
|
||||
|
||||
/**
|
||||
* Outcome of the request was informational.
|
||||
*/
|
||||
INFORMATIONAL,
|
||||
|
||||
/**
|
||||
* Outcome of the request was success.
|
||||
*/
|
||||
SUCCESS,
|
||||
|
||||
/**
|
||||
* Outcome of the request was redirection.
|
||||
*/
|
||||
REDIRECTION,
|
||||
|
||||
/**
|
||||
* Outcome of the request was client error.
|
||||
*/
|
||||
CLIENT_ERROR,
|
||||
|
||||
/**
|
||||
* Outcome of the request was server error.
|
||||
*/
|
||||
SERVER_ERROR,
|
||||
|
||||
/**
|
||||
* Outcome of the request was unknown.
|
||||
*/
|
||||
UNKNOWN;
|
||||
|
||||
private final KeyValue keyValue;
|
||||
|
||||
HttpOutcome() {
|
||||
this.keyValue = KeyValue.of("outcome", name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Outcome} as a {@link KeyValue} named {@code outcome}.
|
||||
* @return the {@code outcome} {@code KeyValue}
|
||||
*/
|
||||
public KeyValue asKeyValue() {
|
||||
return this.keyValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code HttpOutcome} for the given HTTP {@code status} code.
|
||||
* @param status the HTTP status code
|
||||
* @return the matching HttpOutcome
|
||||
*/
|
||||
public static HttpOutcome forStatus(HttpStatusCode status) {
|
||||
if (status.is1xxInformational()) {
|
||||
return INFORMATIONAL;
|
||||
}
|
||||
else if (status.is2xxSuccessful()) {
|
||||
return SUCCESS;
|
||||
}
|
||||
else if (status.is3xxRedirection()) {
|
||||
return REDIRECTION;
|
||||
}
|
||||
else if (status.is4xxClientError()) {
|
||||
return CLIENT_ERROR;
|
||||
}
|
||||
else if (status.is5xxServerError()) {
|
||||
return SERVER_ERROR;
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.http.observation;
|
||||
|
||||
import io.micrometer.common.docs.KeyName;
|
||||
import io.micrometer.observation.Observation;
|
||||
|
|
@ -25,10 +25,11 @@ import io.micrometer.observation.docs.ObservationDocumentation;
|
|||
* Documented {@link io.micrometer.common.KeyValue KeyValues} for the HTTP server observations
|
||||
* for Servlet-based web applications.
|
||||
* <p>This class is used by automated tools to document KeyValues attached to the HTTP server observations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public enum HttpRequestsObservationDocumentation implements ObservationDocumentation {
|
||||
public enum ServerHttpObservationDocumentation implements ObservationDocumentation {
|
||||
|
||||
/**
|
||||
* HTTP server request observations.
|
||||
|
|
@ -36,7 +37,7 @@ public enum HttpRequestsObservationDocumentation implements ObservationDocumenta
|
|||
HTTP_REQUESTS {
|
||||
@Override
|
||||
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
|
||||
return DefaultHttpRequestsObservationConvention.class;
|
||||
return DefaultServerRequestObservationConvention.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.http.observation;
|
||||
|
||||
import io.micrometer.observation.transport.RequestReplyReceiverContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
|
@ -23,18 +23,20 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Context that holds information for metadata collection during observations for Servlet web application.
|
||||
* Context that holds information for metadata collection during observations
|
||||
* for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS Servlet HTTP exchanges}.
|
||||
* <p>This context also extends {@link RequestReplyReceiverContext} for propagating
|
||||
* tracing information with the HTTP server exchange.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class HttpRequestsObservationContext extends RequestReplyReceiverContext<HttpServletRequest, HttpServletResponse> {
|
||||
public class ServerRequestObservationContext extends RequestReplyReceiverContext<HttpServletRequest, HttpServletResponse> {
|
||||
|
||||
@Nullable
|
||||
private String pathPattern;
|
||||
|
||||
public HttpRequestsObservationContext(HttpServletRequest request, HttpServletResponse response) {
|
||||
public ServerRequestObservationContext(HttpServletRequest request, HttpServletResponse response) {
|
||||
super(HttpServletRequest::getHeader);
|
||||
setCarrier(request);
|
||||
setResponse(response);
|
||||
|
|
@ -14,21 +14,22 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.http.observation;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationConvention;
|
||||
|
||||
/**
|
||||
* Interface for an {@link ObservationConvention} related to Servlet HTTP exchanges.
|
||||
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS Servlet HTTP exchanges}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface HttpRequestsObservationConvention extends ObservationConvention<HttpRequestsObservationContext> {
|
||||
public interface ServerRequestObservationConvention extends ObservationConvention<ServerRequestObservationContext> {
|
||||
|
||||
@Override
|
||||
default boolean supportsContext(Observation.Context context) {
|
||||
return context instanceof HttpRequestsObservationContext;
|
||||
return context instanceof ServerRequestObservationContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Base support for HTTP {@link io.micrometer.observation.Observation}.
|
||||
* Instrumentation for {@link io.micrometer.observation.Observation observing} HTTP applications.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.observation.reactive;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
/**
|
||||
* Default {@link ServerRequestObservationConvention}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultServerRequestObservationConvention implements ServerRequestObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.server.requests";
|
||||
|
||||
private static final KeyValue METHOD_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, "UNKNOWN");
|
||||
|
||||
private static final KeyValue STATUS_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "UNKNOWN");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_SUCCESS = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "SUCCESS");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_ROOT = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "root");
|
||||
|
||||
private static final KeyValue URI_NOT_FOUND = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "NOT_FOUND");
|
||||
|
||||
private static final KeyValue URI_REDIRECTION = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.URI, "REDIRECTION");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_UNKNOWN = KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "UNKNOWN");
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.server.requests"}.
|
||||
*/
|
||||
public DefaultServerRequestObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a convention with a custom name.
|
||||
*
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultServerRequestObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ServerRequestObservationContext context) {
|
||||
return "http " + context.getCarrier().getMethod().name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(method(context), uri(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context));
|
||||
}
|
||||
|
||||
protected KeyValue method(ServerRequestObservationContext context) {
|
||||
return (context.getCarrier() != null) ? KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod().name()) : METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue status(ServerRequestObservationContext context) {
|
||||
if (context.isConnectionAborted()) {
|
||||
return STATUS_UNKNOWN;
|
||||
}
|
||||
return (context.getResponse() != null) ? KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, Integer.toString(context.getResponse().getStatusCode().value())) : STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue uri(ServerRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
PathPattern pattern = context.getPathPattern();
|
||||
if (pattern != null) {
|
||||
if (pattern.toString().isEmpty()) {
|
||||
return URI_ROOT;
|
||||
}
|
||||
return KeyValue.of("uri", pattern.toString());
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
HttpStatus status = HttpStatus.resolve(context.getResponse().getStatusCode().value());
|
||||
if (status != null) {
|
||||
if (status.is3xxRedirection()) {
|
||||
return URI_REDIRECTION;
|
||||
}
|
||||
if (status == HttpStatus.NOT_FOUND) {
|
||||
return URI_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return URI_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue exception(ServerRequestObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION,
|
||||
StringUtils.hasText(simpleName) ? simpleName : error.getClass().getName());
|
||||
}
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue outcome(ServerRequestObservationContext context) {
|
||||
if (context.isConnectionAborted()) {
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
return HttpOutcome.forStatus(context.getResponse().getStatusCode());
|
||||
}
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue httpUrl(ServerRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
String uriExpanded = context.getCarrier().getPath().toString();
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, uriExpanded);
|
||||
}
|
||||
return HTTP_URL_UNKNOWN;
|
||||
}
|
||||
|
||||
static class HttpOutcome {
|
||||
|
||||
static KeyValue forStatus(HttpStatusCode statusCode) {
|
||||
if (statusCode.is2xxSuccessful()) {
|
||||
return HTTP_OUTCOME_SUCCESS;
|
||||
}
|
||||
else if (statusCode instanceof HttpStatus status){
|
||||
return KeyValue.of("outcome", status.series().name());
|
||||
}
|
||||
else {
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
package org.springframework.http.observation.reactive;
|
||||
|
||||
import io.micrometer.common.docs.KeyName;
|
||||
import io.micrometer.observation.Observation;
|
||||
|
|
@ -25,10 +25,11 @@ import io.micrometer.observation.docs.ObservationDocumentation;
|
|||
* Documented {@link io.micrometer.common.KeyValue KeyValues} for the HTTP server observations
|
||||
* for reactive web applications.
|
||||
* <p>This class is used by automated tools to document KeyValues attached to the HTTP server observations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public enum HttpRequestsObservationDocumentation implements ObservationDocumentation {
|
||||
public enum ServerHttpObservationDocumentation implements ObservationDocumentation {
|
||||
|
||||
/**
|
||||
* HTTP server request observations.
|
||||
|
|
@ -36,7 +37,7 @@ public enum HttpRequestsObservationDocumentation implements ObservationDocumenta
|
|||
HTTP_REQUESTS {
|
||||
@Override
|
||||
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
|
||||
return DefaultHttpRequestsObservationConvention.class;
|
||||
return DefaultServerRequestObservationConvention.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
package org.springframework.http.observation.reactive;
|
||||
|
||||
import io.micrometer.observation.transport.RequestReplyReceiverContext;
|
||||
|
||||
|
|
@ -25,20 +25,22 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
/**
|
||||
* Context that holds information for metadata collection during observations for reactive web applications.
|
||||
* Context that holds information for metadata collection during observations
|
||||
* for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS reactive HTTP exchanges}.
|
||||
* <p>This context also extends {@link RequestReplyReceiverContext} for propagating
|
||||
* tracing information with the HTTP server exchange.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class HttpRequestsObservationContext extends RequestReplyReceiverContext<ServerHttpRequest, ServerHttpResponse> {
|
||||
public class ServerRequestObservationContext extends RequestReplyReceiverContext<ServerHttpRequest, ServerHttpResponse> {
|
||||
|
||||
@Nullable
|
||||
private PathPattern pathPattern;
|
||||
|
||||
private boolean connectionAborted;
|
||||
|
||||
public HttpRequestsObservationContext(ServerWebExchange exchange) {
|
||||
public ServerRequestObservationContext(ServerWebExchange exchange) {
|
||||
super((request, key) -> request.getHeaders().getFirst(key));
|
||||
setCarrier(exchange.getRequest());
|
||||
setResponse(exchange.getResponse());
|
||||
|
|
@ -74,7 +76,13 @@ public class HttpRequestsObservationContext extends RequestReplyReceiverContext<
|
|||
return this.connectionAborted;
|
||||
}
|
||||
|
||||
void setConnectionAborted(boolean connectionAborted) {
|
||||
/**
|
||||
* Set whether the current connection was aborted by the client, resulting
|
||||
* in a {@link reactor.core.publisher.SignalType#CANCEL cancel signal} on the reactive chain,
|
||||
* or an {@code AbortedException} when reading the request.
|
||||
* @param connectionAborted if the connection has been aborted
|
||||
*/
|
||||
public void setConnectionAborted(boolean connectionAborted) {
|
||||
this.connectionAborted = connectionAborted;
|
||||
}
|
||||
|
||||
|
|
@ -14,21 +14,22 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
package org.springframework.http.observation.reactive;
|
||||
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationConvention;
|
||||
|
||||
/**
|
||||
* Interface for an {@link ObservationConvention} related to reactive HTTP exchanges.
|
||||
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS reactive HTTP exchanges}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface HttpRequestsObservationConvention extends ObservationConvention<HttpRequestsObservationContext> {
|
||||
public interface ServerRequestObservationConvention extends ObservationConvention<ServerRequestObservationContext> {
|
||||
|
||||
@Override
|
||||
default boolean supportsContext(Observation.Context context) {
|
||||
return context instanceof HttpRequestsObservationContext;
|
||||
return context instanceof ServerRequestObservationContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
* Instrumentation for {@link io.micrometer.observation.Observation observing} web applications.
|
||||
* Instrumentation for {@link io.micrometer.observation.Observation observing} reactive HTTP applications.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.http.observation.reactive;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
|
|
@ -43,10 +43,10 @@ import org.springframework.http.ResponseEntity;
|
|||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.observation.ClientHttpObservationContext;
|
||||
import org.springframework.http.client.observation.ClientHttpObservationConvention;
|
||||
import org.springframework.http.client.observation.ClientHttpObservationDocumentation;
|
||||
import org.springframework.http.client.observation.DefaultClientHttpObservationConvention;
|
||||
import org.springframework.http.client.observation.ClientRequestObservationContext;
|
||||
import org.springframework.http.client.observation.ClientRequestObservationConvention;
|
||||
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
|
||||
import org.springframework.http.client.support.InterceptingHttpAccessor;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
|
|
@ -125,7 +125,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
|
||||
private static final boolean kotlinSerializationProtobufPresent;
|
||||
|
||||
private static final ClientHttpObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientHttpObservationConvention();
|
||||
private static final ClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention();
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = RestTemplate.class.getClassLoader();
|
||||
|
|
@ -155,7 +155,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
@Nullable
|
||||
private ClientHttpObservationConvention observationConvention;
|
||||
private ClientRequestObservationConvention observationConvention;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -358,13 +358,13 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
/**
|
||||
* Configure an {@link ObservationConvention} that sets the name of the
|
||||
* {@link Observation observation} as well as its {@link io.micrometer.common.KeyValues}
|
||||
* extracted from the {@link ClientHttpObservationContext}.
|
||||
* If none set, the {@link DefaultClientHttpObservationConvention default convention} will be used.
|
||||
* extracted from the {@link ClientRequestObservationContext}.
|
||||
* If none set, the {@link DefaultClientRequestObservationConvention default convention} will be used.
|
||||
* @param observationConvention the observation convention to use
|
||||
* @since 6.0
|
||||
* @see #setObservationRegistry(ObservationRegistry)
|
||||
*/
|
||||
public void setObservationConvention(ClientHttpObservationConvention observationConvention) {
|
||||
public void setObservationConvention(ClientRequestObservationConvention observationConvention) {
|
||||
Assert.notNull(observationConvention, "observationConvention must not be null");
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
|
@ -853,7 +853,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
ResourceAccessException exception = createResourceAccessException(url, method, ex);
|
||||
throw exception;
|
||||
}
|
||||
ClientHttpObservationContext observationContext = new ClientHttpObservationContext(request);
|
||||
ClientRequestObservationContext observationContext = new ClientRequestObservationContext(request);
|
||||
observationContext.setUriTemplate(uriTemplate);
|
||||
Observation observation = ClientHttpObservationDocumentation.HTTP_REQUEST.observation(this.observationConvention,
|
||||
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).start();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.web.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
|
@ -28,45 +28,49 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.observation.DefaultServerRequestObservationConvention;
|
||||
import org.springframework.http.observation.ServerHttpObservationDocumentation;
|
||||
import org.springframework.http.observation.ServerRequestObservationContext;
|
||||
import org.springframework.http.observation.ServerRequestObservationConvention;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
|
||||
/**
|
||||
* {@link jakarta.servlet.Filter} that creates {@link Observation observations}
|
||||
* for HTTP exchanges. This collects information about the execution time and
|
||||
* information gathered from the {@link HttpRequestsObservationContext}.
|
||||
* <p>Web Frameworks can fetch the current {@link HttpRequestsObservationContext context}
|
||||
* information gathered from the {@link ServerRequestObservationContext}.
|
||||
* <p>Web Frameworks can fetch the current {@link ServerRequestObservationContext context}
|
||||
* as a {@link #CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE request attribute} and contribute
|
||||
* additional information to it.
|
||||
* The configured {@link HttpRequestsObservationConvention} will use this context to collect
|
||||
* The configured {@link ServerRequestObservationConvention} will use this context to collect
|
||||
* {@link io.micrometer.common.KeyValue metadata} and attach it to the observation.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class HttpRequestsObservationFilter extends OncePerRequestFilter {
|
||||
public class ServerHttpObservationFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* Name of the request attribute holding the {@link HttpRequestsObservationContext context} for the current observation.
|
||||
* Name of the request attribute holding the {@link ServerRequestObservationContext context} for the current observation.
|
||||
*/
|
||||
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = HttpRequestsObservationFilter.class.getName() + ".context";
|
||||
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".context";
|
||||
|
||||
private static final HttpRequestsObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultHttpRequestsObservationConvention();
|
||||
private static final ServerRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultServerRequestObservationConvention();
|
||||
|
||||
private static final String CURRENT_OBSERVATION_ATTRIBUTE = HttpRequestsObservationFilter.class.getName() + ".observation";
|
||||
private static final String CURRENT_OBSERVATION_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".observation";
|
||||
|
||||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
private final HttpRequestsObservationConvention observationConvention;
|
||||
private final ServerRequestObservationConvention observationConvention;
|
||||
|
||||
/**
|
||||
* Create an {@code HttpRequestsObservationFilter} that records observations
|
||||
* against the given {@link ObservationRegistry}. The default
|
||||
* {@link DefaultHttpRequestsObservationConvention convention} will be used.
|
||||
* {@link DefaultServerRequestObservationConvention convention} will be used.
|
||||
* @param observationRegistry the registry to use for recording observations
|
||||
*/
|
||||
public HttpRequestsObservationFilter(ObservationRegistry observationRegistry) {
|
||||
public ServerHttpObservationFilter(ObservationRegistry observationRegistry) {
|
||||
this(observationRegistry, DEFAULT_OBSERVATION_CONVENTION);
|
||||
}
|
||||
|
||||
|
|
@ -76,18 +80,18 @@ public class HttpRequestsObservationFilter extends OncePerRequestFilter {
|
|||
* @param observationRegistry the registry to use for recording observations
|
||||
* @param observationConvention the convention to use for all recorded observations
|
||||
*/
|
||||
public HttpRequestsObservationFilter(ObservationRegistry observationRegistry, HttpRequestsObservationConvention observationConvention) {
|
||||
public ServerHttpObservationFilter(ObservationRegistry observationRegistry, ServerRequestObservationConvention observationConvention) {
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current {@link HttpRequestsObservationContext observation context} from the given request, if available.
|
||||
* Get the current {@link ServerRequestObservationContext observation context} from the given request, if available.
|
||||
* @param request the current request
|
||||
* @return the current observation context
|
||||
*/
|
||||
public static Optional<HttpRequestsObservationContext> findObservationContext(HttpServletRequest request) {
|
||||
return Optional.ofNullable((HttpRequestsObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
|
||||
public static Optional<ServerRequestObservationContext> findObservationContext(HttpServletRequest request) {
|
||||
return Optional.ofNullable((ServerRequestObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -124,8 +128,8 @@ public class HttpRequestsObservationFilter extends OncePerRequestFilter {
|
|||
private Observation createOrFetchObservation(HttpServletRequest request, HttpServletResponse response) {
|
||||
Observation observation = (Observation) request.getAttribute(CURRENT_OBSERVATION_ATTRIBUTE);
|
||||
if (observation == null) {
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(request, response);
|
||||
observation = HttpRequestsObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(request, response);
|
||||
observation = ServerHttpObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
|
||||
DEFAULT_OBSERVATION_CONVENTION, () -> context, this.observationRegistry).start();
|
||||
request.setAttribute(CURRENT_OBSERVATION_ATTRIBUTE, observation);
|
||||
request.setAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observation.getContext());
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
package org.springframework.web.filter.reactive;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
|
@ -24,6 +24,10 @@ import io.micrometer.observation.ObservationRegistry;
|
|||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.observation.reactive.DefaultServerRequestObservationConvention;
|
||||
import org.springframework.http.observation.reactive.ServerHttpObservationDocumentation;
|
||||
import org.springframework.http.observation.reactive.ServerRequestObservationContext;
|
||||
import org.springframework.http.observation.reactive.ServerRequestObservationConvention;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
|
@ -33,39 +37,39 @@ import org.springframework.web.server.WebFilterChain;
|
|||
/**
|
||||
* {@link org.springframework.web.server.WebFilter} that creates {@link Observation observations}
|
||||
* for HTTP exchanges. This collects information about the execution time and
|
||||
* information gathered from the {@link HttpRequestsObservationContext}.
|
||||
* <p>Web Frameworks can fetch the current {@link HttpRequestsObservationContext context}
|
||||
* information gathered from the {@link ServerRequestObservationContext}.
|
||||
* <p>Web Frameworks can fetch the current {@link ServerRequestObservationContext context}
|
||||
* as a {@link #CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE request attribute} and contribute
|
||||
* additional information to it.
|
||||
* The configured {@link HttpRequestsObservationConvention} will use this context to collect
|
||||
* The configured {@link ServerRequestObservationConvention} will use this context to collect
|
||||
* {@link io.micrometer.common.KeyValue metadata} and attach it to the observation.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class HttpRequestsObservationWebFilter implements WebFilter {
|
||||
public class ServerHttpObservationFilter implements WebFilter {
|
||||
|
||||
/**
|
||||
* Name of the request attribute holding the {@link HttpRequestsObservationContext context} for the current observation.
|
||||
* Name of the request attribute holding the {@link ServerRequestObservationContext context} for the current observation.
|
||||
*/
|
||||
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = HttpRequestsObservationWebFilter.class.getName() + ".context";
|
||||
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".context";
|
||||
|
||||
private static final HttpRequestsObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultHttpRequestsObservationConvention();
|
||||
private static final ServerRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultServerRequestObservationConvention();
|
||||
|
||||
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS = Set.of("AbortedException",
|
||||
"ClientAbortException", "EOFException", "EofException");
|
||||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
private final HttpRequestsObservationConvention observationConvention;
|
||||
private final ServerRequestObservationConvention observationConvention;
|
||||
|
||||
/**
|
||||
* Create an {@code HttpRequestsObservationWebFilter} that records observations
|
||||
* against the given {@link ObservationRegistry}. The default
|
||||
* {@link DefaultHttpRequestsObservationConvention convention} will be used.
|
||||
* {@link DefaultServerRequestObservationConvention convention} will be used.
|
||||
* @param observationRegistry the registry to use for recording observations
|
||||
*/
|
||||
public HttpRequestsObservationWebFilter(ObservationRegistry observationRegistry) {
|
||||
public ServerHttpObservationFilter(ObservationRegistry observationRegistry) {
|
||||
this(observationRegistry, DEFAULT_OBSERVATION_CONVENTION);
|
||||
}
|
||||
|
||||
|
|
@ -75,29 +79,29 @@ public class HttpRequestsObservationWebFilter implements WebFilter {
|
|||
* @param observationRegistry the registry to use for recording observations
|
||||
* @param observationConvention the convention to use for all recorded observations
|
||||
*/
|
||||
public HttpRequestsObservationWebFilter(ObservationRegistry observationRegistry, HttpRequestsObservationConvention observationConvention) {
|
||||
public ServerHttpObservationFilter(ObservationRegistry observationRegistry, ServerRequestObservationConvention observationConvention) {
|
||||
this.observationRegistry = observationRegistry;
|
||||
this.observationConvention = observationConvention;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current {@link HttpRequestsObservationContext observation context} from the given request, if available.
|
||||
* Get the current {@link ServerRequestObservationContext observation context} from the given request, if available.
|
||||
* @param exchange the current exchange
|
||||
* @return the current observation context
|
||||
*/
|
||||
public static Optional<HttpRequestsObservationContext> findObservationContext(ServerWebExchange exchange) {
|
||||
public static Optional<ServerRequestObservationContext> findObservationContext(ServerWebExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
HttpRequestsObservationContext observationContext = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange);
|
||||
exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
|
||||
return chain.filter(exchange).transformDeferred(call -> filter(exchange, observationContext, call));
|
||||
}
|
||||
|
||||
private Publisher<Void> filter(ServerWebExchange exchange, HttpRequestsObservationContext observationContext, Mono<Void> call) {
|
||||
Observation observation = HttpRequestsObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
|
||||
private Publisher<Void> filter(ServerWebExchange exchange, ServerRequestObservationContext observationContext, Mono<Void> call) {
|
||||
Observation observation = ServerHttpObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
|
||||
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
|
||||
observation.start();
|
||||
return call.doOnEach(signal -> {
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.observation.HttpOutcome;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default {@link HttpRequestsObservationConvention}.
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultHttpRequestsObservationConvention implements HttpRequestsObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.server.requests";
|
||||
|
||||
private static final KeyValue METHOD_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.METHOD, "UNKNOWN");
|
||||
|
||||
private static final KeyValue STATUS_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.STATUS, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_ROOT = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "root");
|
||||
|
||||
private static final KeyValue URI_NOT_FOUND = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "NOT_FOUND");
|
||||
|
||||
private static final KeyValue URI_REDIRECTION = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "REDIRECTION");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "UNKNOWN");
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.server.requests"}.
|
||||
*/
|
||||
public DefaultHttpRequestsObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a convention with a custom name.
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultHttpRequestsObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(HttpRequestsObservationContext context) {
|
||||
return "http " + context.getCarrier().getMethod().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(HttpRequestsObservationContext context) {
|
||||
return KeyValues.of(method(context), uri(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(HttpRequestsObservationContext context) {
|
||||
return KeyValues.of(uriExpanded(context));
|
||||
}
|
||||
|
||||
protected KeyValue method(HttpRequestsObservationContext context) {
|
||||
return (context.getCarrier() != null) ? KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod()) : METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue status(HttpRequestsObservationContext context) {
|
||||
return (context.getResponse() != null) ? KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.STATUS, Integer.toString(context.getResponse().getStatus())) : STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue uri(HttpRequestsObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
String pattern = context.getPathPattern();
|
||||
if (pattern != null) {
|
||||
if (pattern.isEmpty()) {
|
||||
return URI_ROOT;
|
||||
}
|
||||
return KeyValue.of("uri", pattern);
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
HttpStatus status = HttpStatus.resolve(context.getResponse().getStatus());
|
||||
if (status != null) {
|
||||
if (status.is3xxRedirection()) {
|
||||
return URI_REDIRECTION;
|
||||
}
|
||||
if (status == HttpStatus.NOT_FOUND) {
|
||||
return URI_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return URI_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue exception(HttpRequestsObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
return KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.EXCEPTION,
|
||||
StringUtils.hasText(simpleName) ? simpleName : error.getClass().getName());
|
||||
}
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue outcome(HttpRequestsObservationContext context) {
|
||||
if (context.getResponse() != null) {
|
||||
HttpStatusCode statusCode = HttpStatusCode.valueOf(context.getResponse().getStatus());
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(statusCode);
|
||||
return httpOutcome.asKeyValue();
|
||||
}
|
||||
return HttpOutcome.UNKNOWN.asKeyValue();
|
||||
}
|
||||
|
||||
protected KeyValue uriExpanded(HttpRequestsObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(HttpRequestsObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
|
||||
}
|
||||
return HTTP_URL_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.observation.HttpOutcome;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
/**
|
||||
* Default {@link HttpRequestsObservationConvention}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultHttpRequestsObservationConvention implements HttpRequestsObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.server.requests";
|
||||
|
||||
private static final KeyValue METHOD_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.METHOD, "UNKNOWN");
|
||||
|
||||
private static final KeyValue STATUS_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.STATUS, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "UNKNOWN");
|
||||
|
||||
private static final KeyValue URI_ROOT = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "root");
|
||||
|
||||
private static final KeyValue URI_NOT_FOUND = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "NOT_FOUND");
|
||||
|
||||
private static final KeyValue URI_REDIRECTION = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.URI, "REDIRECTION");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_UNKNOWN = KeyValue.of(HttpRequestsObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "UNKNOWN");
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.server.requests"}.
|
||||
*/
|
||||
public DefaultHttpRequestsObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a convention with a custom name.
|
||||
*
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultHttpRequestsObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(HttpRequestsObservationContext context) {
|
||||
return "http " + context.getCarrier().getMethod().name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(HttpRequestsObservationContext context) {
|
||||
return KeyValues.of(method(context), uri(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(HttpRequestsObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context));
|
||||
}
|
||||
|
||||
protected KeyValue method(HttpRequestsObservationContext context) {
|
||||
return (context.getCarrier() != null) ? KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod().name()) : METHOD_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue status(HttpRequestsObservationContext context) {
|
||||
if (context.isConnectionAborted()) {
|
||||
return STATUS_UNKNOWN;
|
||||
}
|
||||
return (context.getResponse() != null) ? KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.STATUS, Integer.toString(context.getResponse().getStatusCode().value())) : STATUS_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue uri(HttpRequestsObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
PathPattern pattern = context.getPathPattern();
|
||||
if (pattern != null) {
|
||||
if (pattern.toString().isEmpty()) {
|
||||
return URI_ROOT;
|
||||
}
|
||||
return KeyValue.of("uri", pattern.toString());
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
HttpStatus status = HttpStatus.resolve(context.getResponse().getStatusCode().value());
|
||||
if (status != null) {
|
||||
if (status.is3xxRedirection()) {
|
||||
return URI_REDIRECTION;
|
||||
}
|
||||
if (status == HttpStatus.NOT_FOUND) {
|
||||
return URI_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return URI_UNKNOWN;
|
||||
}
|
||||
|
||||
protected KeyValue exception(HttpRequestsObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
return KeyValue.of(HttpRequestsObservationDocumentation.LowCardinalityKeyNames.EXCEPTION,
|
||||
StringUtils.hasText(simpleName) ? simpleName : error.getClass().getName());
|
||||
}
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue outcome(HttpRequestsObservationContext context) {
|
||||
if (context.isConnectionAborted()) {
|
||||
return HttpOutcome.UNKNOWN.asKeyValue();
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(context.getResponse().getStatusCode());
|
||||
return httpOutcome.asKeyValue();
|
||||
}
|
||||
return HttpOutcome.UNKNOWN.asKeyValue();
|
||||
}
|
||||
|
||||
protected KeyValue httpUrl(HttpRequestsObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
String uriExpanded = context.getCarrier().getPath().toString();
|
||||
return KeyValue.of(HttpRequestsObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, uriExpanded);
|
||||
}
|
||||
return HTTP_URL_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* Instrumentation for {@link io.micrometer.observation.Observation observing} reactive web applications.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.web.observation.reactive;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
|
|
@ -34,15 +34,15 @@ import static org.mockito.BDDMockito.given;
|
|||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultClientHttpObservationConvention}.
|
||||
* Tests for {@link DefaultClientRequestObservationConvention}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class DefaultClientHttpObservationConventionTests {
|
||||
class DefaultClientRequestObservationConventionTests {
|
||||
|
||||
private final MockClientHttpRequest request = new MockClientHttpRequest(HttpMethod.GET, "/test");
|
||||
|
||||
private final DefaultClientHttpObservationConvention observationConvention = new DefaultClientHttpObservationConvention();
|
||||
private final DefaultClientRequestObservationConvention observationConvention = new DefaultClientRequestObservationConvention();
|
||||
|
||||
@Test
|
||||
void shouldHaveName() {
|
||||
|
|
@ -51,20 +51,20 @@ class DefaultClientHttpObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void shouldHaveContextualName() {
|
||||
ClientHttpObservationContext context = new ClientHttpObservationContext(this.request);
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext(this.request);
|
||||
assertThat(this.observationConvention.getContextualName(context)).isEqualTo("http get");
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsOnlyClientHttpObservationContext() {
|
||||
ClientHttpObservationContext context = new ClientHttpObservationContext(this.request);
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext(this.request);
|
||||
assertThat(this.observationConvention.supportsContext(context)).isTrue();
|
||||
assertThat(this.observationConvention.supportsContext(new Observation.Context())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void addsKeyValuesForRequestWithUriTemplate() {
|
||||
ClientHttpObservationContext context = createContext(
|
||||
ClientRequestObservationContext context = createContext(
|
||||
new MockClientHttpRequest(HttpMethod.GET, "/resource/{id}", 42), new MockClientHttpResponse());
|
||||
context.setUriTemplate("/resource/{id}");
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context))
|
||||
|
|
@ -76,7 +76,7 @@ class DefaultClientHttpObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void addsKeyValuesForRequestWithoutUriTemplate() {
|
||||
ClientHttpObservationContext context = createContext(
|
||||
ClientRequestObservationContext context = createContext(
|
||||
new MockClientHttpRequest(HttpMethod.GET, "/resource/42"), new MockClientHttpResponse());
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context))
|
||||
.contains(KeyValue.of("method", "GET"), KeyValue.of("uri", "none"));
|
||||
|
|
@ -86,7 +86,7 @@ class DefaultClientHttpObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void addsClientNameForRequestWithHost() {
|
||||
ClientHttpObservationContext context = createContext(
|
||||
ClientRequestObservationContext context = createContext(
|
||||
new MockClientHttpRequest(HttpMethod.GET, "https://localhost:8080/resource/42"),
|
||||
new MockClientHttpResponse());
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("client.name", "localhost"));
|
||||
|
|
@ -94,15 +94,15 @@ class DefaultClientHttpObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void addsKeyValueForNonResolvableStatus() throws Exception {
|
||||
ClientHttpObservationContext context = new ClientHttpObservationContext(this.request);
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext(this.request);
|
||||
ClientHttpResponse response = mock(ClientHttpResponse.class);
|
||||
context.setResponse(response);
|
||||
given(response.getStatusCode()).willThrow(new IOException("test error"));
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).contains(KeyValue.of("status", "IO_ERROR"));
|
||||
}
|
||||
|
||||
private ClientHttpObservationContext createContext(ClientHttpRequest request, ClientHttpResponse response) {
|
||||
ClientHttpObservationContext context = new ClientHttpObservationContext(request);
|
||||
private ClientRequestObservationContext createContext(ClientHttpRequest request, ClientHttpResponse response) {
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext(request);
|
||||
context.setResponse(response);
|
||||
return context;
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.http.observation;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.Observation;
|
||||
|
|
@ -26,18 +26,18 @@ import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultHttpRequestsObservationConvention}.
|
||||
* Tests for {@link DefaultServerRequestObservationConvention}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class DefaultHttpRequestsObservationConventionTests {
|
||||
class DefaultServerRequestObservationConventionTests {
|
||||
|
||||
private final DefaultHttpRequestsObservationConvention convention = new DefaultHttpRequestsObservationConvention();
|
||||
private final DefaultServerRequestObservationConvention convention = new DefaultServerRequestObservationConvention();
|
||||
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/resource");
|
||||
|
||||
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
private final HttpRequestsObservationContext context = new HttpRequestsObservationContext(this.request, this.response);
|
||||
private final ServerRequestObservationContext context = new ServerRequestObservationContext(this.request, this.response);
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.observation;
|
||||
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpOutcome}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class HttpOutcomeTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {100, 101, 102})
|
||||
void shouldResolveInformational(int code) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(HttpStatusCode.valueOf(code));
|
||||
assertThat(httpOutcome).isEqualTo(HttpOutcome.INFORMATIONAL);
|
||||
assertThat(httpOutcome.asKeyValue()).isEqualTo(KeyValue.of("outcome", "INFORMATIONAL"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {200, 202, 226})
|
||||
void shouldResolveSuccess(int code) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(HttpStatusCode.valueOf(code));
|
||||
assertThat(httpOutcome).isEqualTo(HttpOutcome.SUCCESS);
|
||||
assertThat(httpOutcome.asKeyValue()).isEqualTo(KeyValue.of("outcome", "SUCCESS"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {300, 302, 303})
|
||||
void shouldResolveRedirection(int code) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(HttpStatusCode.valueOf(code));
|
||||
assertThat(httpOutcome).isEqualTo(HttpOutcome.REDIRECTION);
|
||||
assertThat(httpOutcome.asKeyValue()).isEqualTo(KeyValue.of("outcome", "REDIRECTION"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {400, 404, 405})
|
||||
void shouldResolveClientError(int code) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(HttpStatusCode.valueOf(code));
|
||||
assertThat(httpOutcome).isEqualTo(HttpOutcome.CLIENT_ERROR);
|
||||
assertThat(httpOutcome.asKeyValue()).isEqualTo(KeyValue.of("outcome", "CLIENT_ERROR"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {500, 502, 503})
|
||||
void shouldResolveServerError(int code) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(HttpStatusCode.valueOf(code));
|
||||
assertThat(httpOutcome).isEqualTo(HttpOutcome.SERVER_ERROR);
|
||||
assertThat(httpOutcome.asKeyValue()).isEqualTo(KeyValue.of("outcome", "SERVER_ERROR"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {600, 799, 855})
|
||||
void shouldResolveUnknown(int code) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(HttpStatusCode.valueOf(code));
|
||||
assertThat(httpOutcome).isEqualTo(HttpOutcome.UNKNOWN);
|
||||
assertThat(httpOutcome.asKeyValue()).isEqualTo(KeyValue.of("outcome", "UNKNOWN"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
package org.springframework.http.observation.reactive;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.Observation;
|
||||
|
|
@ -29,12 +29,12 @@ import org.springframework.web.util.pattern.PathPatternParser;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultHttpRequestsObservationConvention}.
|
||||
* Tests for {@link DefaultServerRequestObservationConvention}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class DefaultHttpRequestsObservationConventionTests {
|
||||
class DefaultServerRequestObservationConventionTests {
|
||||
|
||||
private final DefaultHttpRequestsObservationConvention convention = new DefaultHttpRequestsObservationConvention();
|
||||
private final DefaultServerRequestObservationConvention convention = new DefaultServerRequestObservationConvention();
|
||||
|
||||
|
||||
@Test
|
||||
|
|
@ -45,14 +45,14 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
@Test
|
||||
void shouldHaveContextualName() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource"));
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
assertThat(convention.getContextualName(context)).isEqualTo("http get");
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsOnlyHttpRequestsObservationContext() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource"));
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
assertThat(this.convention.supportsContext(context)).isTrue();
|
||||
assertThat(this.convention.supportsContext(new Observation.Context())).isFalse();
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
void addsKeyValuesForExchange() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource"));
|
||||
exchange.getResponse().setRawStatusCode(201);
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
|
||||
assertThat(this.convention.getLowCardinalityKeyValues(context)).hasSize(5)
|
||||
.contains(KeyValue.of("method", "POST"), KeyValue.of("uri", "UNKNOWN"), KeyValue.of("status", "201"),
|
||||
|
|
@ -74,7 +74,7 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
void addsKeyValuesForExchangeWithPathPattern() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource"));
|
||||
exchange.getResponse().setRawStatusCode(200);
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
PathPattern pathPattern = getPathPattern("/test/{name}");
|
||||
context.setPathPattern(pathPattern);
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
@Test
|
||||
void addsKeyValuesForErrorExchange() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource"));
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
context.setError(new IllegalArgumentException("custom error"));
|
||||
exchange.getResponse().setRawStatusCode(500);
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
@Test
|
||||
void addsKeyValuesForRedirectExchange() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/redirect"));
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
exchange.getResponse().setRawStatusCode(302);
|
||||
exchange.getResponse().getHeaders().add("Location", "https://example.org/other");
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
@Test
|
||||
void addsKeyValuesForNotFoundExchange() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/notFound"));
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
exchange.getResponse().setRawStatusCode(404);
|
||||
|
||||
assertThat(this.convention.getLowCardinalityKeyValues(context)).hasSize(5)
|
||||
|
|
@ -130,7 +130,7 @@ class DefaultHttpRequestsObservationConventionTests {
|
|||
@Test
|
||||
void addsKeyValuesForCancelledExchange() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/resource"));
|
||||
HttpRequestsObservationContext context = new HttpRequestsObservationContext(exchange);
|
||||
ServerRequestObservationContext context = new ServerRequestObservationContext(exchange);
|
||||
context.setConnectionAborted(true);
|
||||
exchange.getResponse().setRawStatusCode(200);
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.observation.ClientHttpObservationContext;
|
||||
import org.springframework.http.client.observation.ClientRequestObservationContext;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
|
@ -192,15 +192,15 @@ class RestTemplateObservationTests {
|
|||
.hasObservationWithNameEqualTo("http.client.requests").that();
|
||||
}
|
||||
|
||||
static class ContextAssertionObservationHandler implements ObservationHandler<ClientHttpObservationContext> {
|
||||
static class ContextAssertionObservationHandler implements ObservationHandler<ClientRequestObservationContext> {
|
||||
|
||||
@Override
|
||||
public boolean supportsContext(Observation.Context context) {
|
||||
return context instanceof ClientHttpObservationContext;
|
||||
return context instanceof ClientRequestObservationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(ClientHttpObservationContext context) {
|
||||
public void onStart(ClientRequestObservationContext context) {
|
||||
assertThat(context.getCarrier()).isNotNull();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation;
|
||||
package org.springframework.web.filter;
|
||||
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
|
|
@ -23,6 +23,7 @@ import jakarta.servlet.ServletException;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.observation.ServerRequestObservationContext;
|
||||
import org.springframework.web.testfixture.servlet.MockFilterChain;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
||||
|
|
@ -31,14 +32,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpRequestsObservationFilter}.
|
||||
* Tests for {@link ServerHttpObservationFilter}.
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class HttpRequestsObservationFilterTests {
|
||||
class ServerHttpObservationFilterTests {
|
||||
|
||||
private final TestObservationRegistry observationRegistry = TestObservationRegistry.create();
|
||||
|
||||
private final HttpRequestsObservationFilter filter = new HttpRequestsObservationFilter(this.observationRegistry);
|
||||
private final ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry);
|
||||
|
||||
private final MockFilterChain mockFilterChain = new MockFilterChain();
|
||||
|
||||
|
|
@ -51,8 +52,8 @@ class HttpRequestsObservationFilterTests {
|
|||
void filterShouldFillObservationContext() throws Exception {
|
||||
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
|
||||
|
||||
HttpRequestsObservationContext context = (HttpRequestsObservationContext) this.request
|
||||
.getAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE);
|
||||
ServerRequestObservationContext context = (ServerRequestObservationContext) this.request
|
||||
.getAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE);
|
||||
assertThat(context).isNotNull();
|
||||
assertThat(context.getCarrier()).isEqualTo(this.request);
|
||||
assertThat(context.getResponse()).isEqualTo(this.response);
|
||||
|
|
@ -66,8 +67,8 @@ class HttpRequestsObservationFilterTests {
|
|||
this.request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, customError);
|
||||
this.filter.doFilter(this.request, this.response, this.mockFilterChain);
|
||||
|
||||
HttpRequestsObservationContext context = (HttpRequestsObservationContext) this.request
|
||||
.getAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE);
|
||||
ServerRequestObservationContext context = (ServerRequestObservationContext) this.request
|
||||
.getAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE);
|
||||
assertThat(context.getError()).isEqualTo(customError);
|
||||
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR");
|
||||
}
|
||||
|
|
@ -81,8 +82,8 @@ class HttpRequestsObservationFilterTests {
|
|||
throw new ServletException(customError);
|
||||
});
|
||||
}).isInstanceOf(ServletException.class);
|
||||
HttpRequestsObservationContext context = (HttpRequestsObservationContext) this.request
|
||||
.getAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE);
|
||||
ServerRequestObservationContext context = (ServerRequestObservationContext) this.request
|
||||
.getAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE);
|
||||
assertThat(context.getError()).isEqualTo(customError);
|
||||
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS");
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.observation.reactive;
|
||||
package org.springframework.web.filter.reactive;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
|
|
@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.observation.reactive.ServerRequestObservationContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
|
||||
|
|
@ -34,22 +35,22 @@ import org.springframework.web.testfixture.server.MockServerWebExchange;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpRequestsObservationWebFilter}.
|
||||
* Tests for {@link ServerHttpObservationFilter}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class HttpRequestsObservationWebFilterTests {
|
||||
class ServerHttpObservationFilterTests {
|
||||
|
||||
private final TestObservationRegistry observationRegistry = TestObservationRegistry.create();
|
||||
|
||||
private final HttpRequestsObservationWebFilter filter = new HttpRequestsObservationWebFilter(this.observationRegistry);
|
||||
private final ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry);
|
||||
|
||||
@Test
|
||||
void filterShouldFillObservationContext() {
|
||||
ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource"));
|
||||
exchange.getResponse().setRawStatusCode(200);
|
||||
WebFilterChain filterChain = createFilterChain(filterExchange -> {
|
||||
Optional<HttpRequestsObservationContext> observationContext = HttpRequestsObservationWebFilter.findObservationContext(filterExchange);
|
||||
Optional<ServerRequestObservationContext> observationContext = ServerHttpObservationFilter.findObservationContext(filterExchange);
|
||||
assertThat(observationContext).isPresent();
|
||||
assertThat(observationContext.get().getCarrier()).isEqualTo(exchange.getRequest());
|
||||
assertThat(observationContext.get().getResponse()).isEqualTo(exchange.getResponse());
|
||||
|
|
@ -68,7 +69,7 @@ class HttpRequestsObservationWebFilterTests {
|
|||
StepVerifier.create(this.filter.filter(exchange, filterChain))
|
||||
.expectError(IllegalArgumentException.class)
|
||||
.verify();
|
||||
Optional<HttpRequestsObservationContext> observationContext = HttpRequestsObservationWebFilter.findObservationContext(exchange);
|
||||
Optional<ServerRequestObservationContext> observationContext = ServerHttpObservationFilter.findObservationContext(exchange);
|
||||
assertThat(observationContext.get().getError()).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR");
|
||||
}
|
||||
|
|
@ -22,12 +22,13 @@ import io.micrometer.observation.ObservationConvention;
|
|||
import io.micrometer.observation.docs.ObservationDocumentation;
|
||||
|
||||
/**
|
||||
* Documented {@link io.micrometer.common.KeyValue KeyValues} for the {@link WebClient} observations.
|
||||
* Documented {@link io.micrometer.common.KeyValue KeyValues} for the {@link WebClient HTTP client} observations.
|
||||
* <p>This class is used by automated tools to document KeyValues attached to the HTTP client observations.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public enum ClientObservationDocumentation implements ObservationDocumentation {
|
||||
public enum ClientHttpObservationDocumentation implements ObservationDocumentation {
|
||||
|
||||
/**
|
||||
* Observation created for an HTTP client exchange.
|
||||
|
|
@ -35,17 +36,17 @@ public enum ClientObservationDocumentation implements ObservationDocumentation {
|
|||
HTTP_REQUEST {
|
||||
@Override
|
||||
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
|
||||
return DefaultClientObservationConvention.class;
|
||||
return DefaultClientRequestObservationConvention.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyName[] getLowCardinalityKeyNames() {
|
||||
return ClientObservationDocumentation.LowCardinalityKeyNames.values();
|
||||
return ClientHttpObservationDocumentation.LowCardinalityKeyNames.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyName[] getHighCardinalityKeyNames() {
|
||||
return ClientObservationDocumentation.HighCardinalityKeyNames.values();
|
||||
return ClientHttpObservationDocumentation.HighCardinalityKeyNames.values();
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -22,12 +22,12 @@ import org.springframework.lang.Nullable;
|
|||
|
||||
/**
|
||||
* Context that holds information for metadata collection
|
||||
* during the HTTP client observations.
|
||||
* during the {@link ClientHttpObservationDocumentation#HTTP_REQUEST HTTP client exchange observations}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ClientObservationContext extends RequestReplySenderContext<ClientRequest, ClientResponse> {
|
||||
public class ClientRequestObservationContext extends RequestReplySenderContext<ClientRequest, ClientResponse> {
|
||||
|
||||
@Nullable
|
||||
private String uriTemplate;
|
||||
|
|
@ -35,8 +35,8 @@ public class ClientObservationContext extends RequestReplySenderContext<ClientRe
|
|||
private boolean aborted;
|
||||
|
||||
|
||||
public ClientObservationContext() {
|
||||
super(ClientObservationContext::setRequestHeader);
|
||||
public ClientRequestObservationContext() {
|
||||
super(ClientRequestObservationContext::setRequestHeader);
|
||||
}
|
||||
|
||||
private static void setRequestHeader(@Nullable ClientRequest request, String name, String value) {
|
||||
|
|
@ -20,15 +20,17 @@ import io.micrometer.observation.Observation;
|
|||
import io.micrometer.observation.ObservationConvention;
|
||||
|
||||
/**
|
||||
* Interface for an {@link ObservationConvention} related to client HTTP exchanges.
|
||||
* Interface for an {@link ObservationConvention} related to
|
||||
* {@link ClientHttpObservationDocumentation#HTTP_REQUEST HTTP client exchange observations}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface ClientObservationConvention extends ObservationConvention<ClientObservationContext> {
|
||||
public interface ClientRequestObservationConvention extends ObservationConvention<ClientRequestObservationContext> {
|
||||
|
||||
@Override
|
||||
default boolean supportsContext(Observation.Context context) {
|
||||
return context instanceof ClientObservationContext;
|
||||
return context instanceof ClientRequestObservationContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
import io.micrometer.observation.ObservationConvention;
|
||||
|
||||
import org.springframework.http.client.observation.ClientHttpObservationDocumentation;
|
||||
import org.springframework.http.observation.HttpOutcome;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation for a {@code WebClient} {@link ObservationConvention},
|
||||
* extracting information from the {@link ClientObservationContext}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultClientObservationConvention implements ClientObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.client.requests";
|
||||
|
||||
private static final KeyValue URI_NONE = KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.URI, "none");
|
||||
|
||||
private static final KeyValue METHOD_NONE = KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.METHOD, "none");
|
||||
|
||||
private static final KeyValue STATUS_IO_ERROR = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "IO_ERROR");
|
||||
|
||||
private static final KeyValue STATUS_CLIENT_ERROR = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "CLIENT_ERROR");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_NONE = KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "none");
|
||||
|
||||
private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, "none");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.client.requests"}.
|
||||
*/
|
||||
public DefaultClientObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a convention with a custom name.
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultClientObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ClientObservationContext context) {
|
||||
return "http " + context.getCarrier().method().name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ClientObservationContext context) {
|
||||
return KeyValues.of(uri(context), method(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
protected KeyValue uri(ClientObservationContext context) {
|
||||
if (context.getUriTemplate() != null) {
|
||||
return KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.URI, context.getUriTemplate());
|
||||
}
|
||||
return URI_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue method(ClientObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().method().name());
|
||||
}
|
||||
else {
|
||||
return METHOD_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyValue status(ClientObservationContext context) {
|
||||
if (context.isAborted()) {
|
||||
return STATUS_CLIENT_ERROR;
|
||||
}
|
||||
ClientResponse response = context.getResponse();
|
||||
if (response != null) {
|
||||
return KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(response.statusCode().value()));
|
||||
}
|
||||
if (context.getError() != null && context.getError() instanceof IOException) {
|
||||
return STATUS_IO_ERROR;
|
||||
}
|
||||
return STATUS_CLIENT_ERROR;
|
||||
}
|
||||
|
||||
protected KeyValue exception(ClientObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
return KeyValue.of(ClientObservationDocumentation.LowCardinalityKeyNames.EXCEPTION,
|
||||
StringUtils.hasText(simpleName) ? simpleName : error.getClass().getName());
|
||||
}
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue outcome(ClientObservationContext context) {
|
||||
if (context.isAborted()) {
|
||||
return HttpOutcome.UNKNOWN.asKeyValue();
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
HttpOutcome httpOutcome = HttpOutcome.forStatus(context.getResponse().statusCode());
|
||||
return httpOutcome.asKeyValue();
|
||||
}
|
||||
return HttpOutcome.UNKNOWN.asKeyValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ClientObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context), clientName(context));
|
||||
}
|
||||
|
||||
protected KeyValue httpUrl(ClientObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ClientObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().url().toASCIIString());
|
||||
}
|
||||
return HTTP_URL_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue clientName(ClientObservationContext context) {
|
||||
if (context.getCarrier() != null && context.getCarrier().url().getHost() != null) {
|
||||
return KeyValue.of(ClientObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, context.getCarrier().url().getHost());
|
||||
}
|
||||
return CLIENT_NAME_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation for a {@link ClientRequestObservationConvention},
|
||||
* extracting information from the {@link ClientRequestObservationContext}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 6.0
|
||||
*/
|
||||
public class DefaultClientRequestObservationConvention implements ClientRequestObservationConvention {
|
||||
|
||||
private static final String DEFAULT_NAME = "http.client.requests";
|
||||
|
||||
private static final KeyValue URI_NONE = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.URI, "none");
|
||||
|
||||
private static final KeyValue METHOD_NONE = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, "none");
|
||||
|
||||
private static final KeyValue STATUS_IO_ERROR = KeyValue.of(org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "IO_ERROR");
|
||||
|
||||
private static final KeyValue STATUS_CLIENT_ERROR = KeyValue.of(org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, "CLIENT_ERROR");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_SUCCESS = KeyValue.of(org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "SUCCESS");
|
||||
|
||||
private static final KeyValue HTTP_OUTCOME_UNKNOWN = KeyValue.of(org.springframework.http.client.observation.ClientHttpObservationDocumentation.LowCardinalityKeyNames.OUTCOME, "UNKNOWN");
|
||||
|
||||
private static final KeyValue EXCEPTION_NONE = KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, "none");
|
||||
|
||||
private static final KeyValue HTTP_URL_NONE = KeyValue.of(org.springframework.http.client.observation.ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, "none");
|
||||
|
||||
private static final KeyValue CLIENT_NAME_NONE = KeyValue.of(org.springframework.http.client.observation.ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, "none");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
/**
|
||||
* Create a convention with the default name {@code "http.client.requests"}.
|
||||
*/
|
||||
public DefaultClientRequestObservationConvention() {
|
||||
this(DEFAULT_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a convention with a custom name.
|
||||
* @param name the observation name
|
||||
*/
|
||||
public DefaultClientRequestObservationConvention(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ClientRequestObservationContext context) {
|
||||
return "http " + context.getCarrier().method().name().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
|
||||
return KeyValues.of(uri(context), method(context), status(context), exception(context), outcome(context));
|
||||
}
|
||||
|
||||
protected KeyValue uri(ClientRequestObservationContext context) {
|
||||
if (context.getUriTemplate() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.URI, context.getUriTemplate());
|
||||
}
|
||||
return URI_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue method(ClientRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().method().name());
|
||||
}
|
||||
else {
|
||||
return METHOD_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyValue status(ClientRequestObservationContext context) {
|
||||
if (context.isAborted()) {
|
||||
return STATUS_CLIENT_ERROR;
|
||||
}
|
||||
ClientResponse response = context.getResponse();
|
||||
if (response != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(response.statusCode().value()));
|
||||
}
|
||||
if (context.getError() != null && context.getError() instanceof IOException) {
|
||||
return STATUS_IO_ERROR;
|
||||
}
|
||||
return STATUS_CLIENT_ERROR;
|
||||
}
|
||||
|
||||
protected KeyValue exception(ClientRequestObservationContext context) {
|
||||
Throwable error = context.getError();
|
||||
if (error != null) {
|
||||
String simpleName = error.getClass().getSimpleName();
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION,
|
||||
StringUtils.hasText(simpleName) ? simpleName : error.getClass().getName());
|
||||
}
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue outcome(ClientRequestObservationContext context) {
|
||||
if (context.isAborted()) {
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
if (context.getResponse() != null) {
|
||||
return HttpOutcome.forStatus(context.getResponse().statusCode());
|
||||
}
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context), clientName(context));
|
||||
}
|
||||
|
||||
protected KeyValue httpUrl(ClientRequestObservationContext context) {
|
||||
if (context.getCarrier() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().url().toASCIIString());
|
||||
}
|
||||
return HTTP_URL_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue clientName(ClientRequestObservationContext context) {
|
||||
if (context.getCarrier() != null && context.getCarrier().url().getHost() != null) {
|
||||
return KeyValue.of(ClientHttpObservationDocumentation.HighCardinalityKeyNames.CLIENT_NAME, context.getCarrier().url().getHost());
|
||||
}
|
||||
return CLIENT_NAME_NONE;
|
||||
}
|
||||
|
||||
static class HttpOutcome {
|
||||
|
||||
static KeyValue forStatus(HttpStatusCode statusCode) {
|
||||
if (statusCode.is2xxSuccessful()) {
|
||||
return HTTP_OUTCOME_SUCCESS;
|
||||
}
|
||||
else if (statusCode instanceof HttpStatus status){
|
||||
return KeyValue.of("outcome", status.series().name());
|
||||
}
|
||||
else {
|
||||
return HTTP_OUTCOME_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ class DefaultWebClient implements WebClient {
|
|||
private static final Mono<ClientResponse> NO_HTTP_CLIENT_RESPONSE_ERROR = Mono.error(
|
||||
() -> new IllegalStateException("The underlying HTTP client completed without emitting a response."));
|
||||
|
||||
private static final DefaultClientObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientObservationConvention();
|
||||
private static final DefaultClientRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultClientRequestObservationConvention();
|
||||
|
||||
private final ExchangeFunction exchangeFunction;
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ class DefaultWebClient implements WebClient {
|
|||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
private final ClientObservationConvention observationConvention;
|
||||
private final ClientRequestObservationConvention observationConvention;
|
||||
|
||||
private final DefaultWebClientBuilder builder;
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ class DefaultWebClient implements WebClient {
|
|||
@Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap<String, String> defaultCookies,
|
||||
@Nullable Consumer<RequestHeadersSpec<?>> defaultRequest,
|
||||
@Nullable Map<Predicate<HttpStatusCode>, Function<ClientResponse, Mono<? extends Throwable>>> statusHandlerMap,
|
||||
ObservationRegistry observationRegistry, ClientObservationConvention observationConvention,
|
||||
ObservationRegistry observationRegistry, ClientRequestObservationConvention observationConvention,
|
||||
DefaultWebClientBuilder builder) {
|
||||
|
||||
this.exchangeFunction = exchangeFunction;
|
||||
|
|
@ -449,12 +449,12 @@ class DefaultWebClient implements WebClient {
|
|||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public Mono<ClientResponse> exchange() {
|
||||
ClientObservationContext observationContext = new ClientObservationContext();
|
||||
ClientRequestObservationContext observationContext = new ClientRequestObservationContext();
|
||||
ClientRequest request = (this.inserter != null ?
|
||||
initRequestBuilder().body(this.inserter).build() :
|
||||
initRequestBuilder().build());
|
||||
return Mono.defer(() -> {
|
||||
Observation observation = ClientObservationDocumentation.HTTP_REQUEST.observation(observationConvention,
|
||||
Observation observation = ClientHttpObservationDocumentation.HTTP_REQUEST.observation(observationConvention,
|
||||
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry).start();
|
||||
observationContext.setCarrier(request);
|
||||
observationContext.setUriTemplate((String) request.attribute(URI_TEMPLATE_ATTRIBUTE).orElse(null));
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
|
|||
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
@Nullable
|
||||
private ClientObservationConvention observationConvention;
|
||||
private ClientRequestObservationConvention observationConvention;
|
||||
|
||||
|
||||
public DefaultWebClientBuilder() {
|
||||
|
|
@ -288,7 +288,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public WebClient.Builder observationConvention(ClientObservationConvention observationConvention) {
|
||||
public WebClient.Builder observationConvention(ClientRequestObservationConvention observationConvention) {
|
||||
Assert.notNull(observationConvention, "observationConvention must not be null");
|
||||
this.observationConvention = observationConvention;
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -347,12 +347,12 @@ public interface WebClient {
|
|||
|
||||
/**
|
||||
* Provide an {@link ObservationConvention} to use for collecting
|
||||
* metadata for the current observation. Will use {@link DefaultClientObservationConvention}
|
||||
* metadata for the request observation. Will use {@link DefaultClientRequestObservationConvention}
|
||||
* if none provided.
|
||||
* @param observationConvention the observation convention to use
|
||||
* @since 6.0
|
||||
*/
|
||||
Builder observationConvention(ClientObservationConvention observationConvention);
|
||||
Builder observationConvention(ClientRequestObservationConvention observationConvention);
|
||||
|
||||
/**
|
||||
* Apply the given {@code Consumer} to this builder instance.
|
||||
|
|
|
|||
|
|
@ -28,13 +28,13 @@ import org.springframework.http.HttpStatus;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultClientObservationConvention}.
|
||||
* Tests for {@link DefaultClientRequestObservationConvention}.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class DefaultClientObservationConventionTests {
|
||||
class DefaultClientRequestObservationConventionTests {
|
||||
|
||||
private final DefaultClientObservationConvention observationConvention = new DefaultClientObservationConvention();
|
||||
private final DefaultClientRequestObservationConvention observationConvention = new DefaultClientRequestObservationConvention();
|
||||
|
||||
@Test
|
||||
void shouldHaveName() {
|
||||
|
|
@ -43,20 +43,20 @@ class DefaultClientObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void shouldHaveContextualName() {
|
||||
ClientObservationContext context = new ClientObservationContext();
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext();
|
||||
context.setCarrier(ClientRequest.create(HttpMethod.GET, URI.create("/test")).build());
|
||||
assertThat(this.observationConvention.getContextualName(context)).isEqualTo("http get");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOnlySupportWebClientObservationContext() {
|
||||
assertThat(this.observationConvention.supportsContext(new ClientObservationContext())).isTrue();
|
||||
assertThat(this.observationConvention.supportsContext(new ClientRequestObservationContext())).isTrue();
|
||||
assertThat(this.observationConvention.supportsContext(new Observation.Context())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddKeyValuesForNullExchange() {
|
||||
ClientObservationContext context = new ClientObservationContext();
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext();
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).hasSize(5)
|
||||
.contains(KeyValue.of("method", "none"), KeyValue.of("uri", "none"), KeyValue.of("status", "CLIENT_ERROR"),
|
||||
KeyValue.of("exception", "none"), KeyValue.of("outcome", "UNKNOWN"));
|
||||
|
|
@ -66,7 +66,7 @@ class DefaultClientObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void shouldAddKeyValuesForExchangeWithException() {
|
||||
ClientObservationContext context = new ClientObservationContext();
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext();
|
||||
context.setError(new IllegalStateException("Could not create client request"));
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context)).hasSize(5)
|
||||
.contains(KeyValue.of("method", "none"), KeyValue.of("uri", "none"), KeyValue.of("status", "CLIENT_ERROR"),
|
||||
|
|
@ -79,7 +79,7 @@ class DefaultClientObservationConventionTests {
|
|||
void shouldAddKeyValuesForRequestWithUriTemplate() {
|
||||
ClientRequest request = ClientRequest.create(HttpMethod.GET, URI.create("/resource/42"))
|
||||
.attribute(WebClient.class.getName() + ".uriTemplate", "/resource/{id}").build();
|
||||
ClientObservationContext context = createContext(request);
|
||||
ClientRequestObservationContext context = createContext(request);
|
||||
context.setUriTemplate("/resource/{id}");
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context))
|
||||
.contains(KeyValue.of("exception", "none"), KeyValue.of("method", "GET"), KeyValue.of("uri", "/resource/{id}"),
|
||||
|
|
@ -90,7 +90,7 @@ class DefaultClientObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void shouldAddKeyValuesForRequestWithoutUriTemplate() {
|
||||
ClientObservationContext context = createContext(ClientRequest.create(HttpMethod.GET, URI.create("/resource/42")).build());
|
||||
ClientRequestObservationContext context = createContext(ClientRequest.create(HttpMethod.GET, URI.create("/resource/42")).build());
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(context))
|
||||
.contains(KeyValue.of("method", "GET"), KeyValue.of("uri", "none"));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).hasSize(2).contains(KeyValue.of("http.url", "/resource/42"));
|
||||
|
|
@ -98,12 +98,12 @@ class DefaultClientObservationConventionTests {
|
|||
|
||||
@Test
|
||||
void shouldAddClientNameKeyValueForRequestWithHost() {
|
||||
ClientObservationContext context = createContext(ClientRequest.create(HttpMethod.GET, URI.create("https://localhost:8080/resource/42")).build());
|
||||
ClientRequestObservationContext context = createContext(ClientRequest.create(HttpMethod.GET, URI.create("https://localhost:8080/resource/42")).build());
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(context)).contains(KeyValue.of("client.name", "localhost"));
|
||||
}
|
||||
|
||||
private ClientObservationContext createContext(ClientRequest request) {
|
||||
ClientObservationContext context = new ClientObservationContext();
|
||||
private ClientRequestObservationContext createContext(ClientRequest request) {
|
||||
ClientRequestObservationContext context = new ClientRequestObservationContext();
|
||||
context.setCarrier(request);
|
||||
context.setResponse(ClientResponse.create(HttpStatus.OK).build());
|
||||
return context;
|
||||
|
|
@ -32,7 +32,7 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
|
|||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.RouterFunctions;
|
||||
|
|
@ -223,7 +223,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
|
|||
if (matchingPattern != null) {
|
||||
servletRequest.removeAttribute(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE);
|
||||
servletRequest.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, matchingPattern.getPatternString());
|
||||
HttpRequestsObservationFilter.findObservationContext(request.servletRequest())
|
||||
ServerHttpObservationFilter.findObservationContext(request.servletRequest())
|
||||
.ifPresent(context -> context.setPathPattern(matchingPattern.getPatternString()));
|
||||
}
|
||||
servletRequest.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handlerFunction);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import org.springframework.util.AntPathMatcher;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
|
@ -356,7 +356,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping i
|
|||
HttpServletRequest request) {
|
||||
|
||||
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
|
||||
HttpRequestsObservationFilter.findObservationContext(request)
|
||||
ServerHttpObservationFilter.findObservationContext(request)
|
||||
.ifPresent(context -> context.setPathPattern(bestMatchingPattern));
|
||||
request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ import org.springframework.web.HttpMediaTypeNotSupportedException;
|
|||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
|
||||
import org.springframework.web.servlet.mvc.condition.NameValueExpression;
|
||||
|
|
@ -173,7 +173,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
|||
request.setAttribute(MATRIX_VARIABLES_ATTRIBUTE, result.getMatrixVariables());
|
||||
}
|
||||
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern.getPatternString());
|
||||
HttpRequestsObservationFilter.findObservationContext(request)
|
||||
ServerHttpObservationFilter.findObservationContext(request)
|
||||
.ifPresent(context -> context.setPathPattern(bestPattern.getPatternString()));
|
||||
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe
|
|||
uriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
|
||||
}
|
||||
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
|
||||
HttpRequestsObservationFilter.findObservationContext(request)
|
||||
ServerHttpObservationFilter.findObservationContext(request)
|
||||
.ifPresent(context -> context.setPathPattern(bestPattern));
|
||||
request.setAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.observation.HttpRequestsObservationContext;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.http.observation.ServerRequestObservationContext;
|
||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
|
|
@ -174,15 +174,15 @@ class RouterFunctionMappingTests {
|
|||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/match");
|
||||
assertThat(HttpRequestsObservationFilter.findObservationContext(request))
|
||||
assertThat(ServerHttpObservationFilter.findObservationContext(request))
|
||||
.hasValueSatisfying(context -> assertThat(context.getPathPattern()).isEqualTo("/match"));
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE)).isEqualTo(handlerFunction);
|
||||
}
|
||||
|
||||
private MockHttpServletRequest createTestRequest(String path) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", path);
|
||||
request.setAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE,
|
||||
new HttpRequestsObservationContext(request, new MockHttpServletResponse()));
|
||||
request.setAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE,
|
||||
new ServerRequestObservationContext(request, new MockHttpServletResponse()));
|
||||
ServletRequestPathUtils.parseAndCache(request);
|
||||
return request;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.observation.ServerRequestObservationContext;
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
|
@ -46,11 +47,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.filter.ServerHttpObservationFilter;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.observation.HttpRequestsObservationContext;
|
||||
import org.springframework.web.observation.HttpRequestsObservationFilter;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
|
@ -295,8 +295,8 @@ class RequestMappingInfoHandlerMappingTests {
|
|||
void handleMatchBestMatchingPatternAttributeInObservationContext(TestRequestMappingInfoHandlerMapping mapping) {
|
||||
RequestMappingInfo key = RequestMappingInfo.paths("/{path1}/2", "/**").build();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
|
||||
HttpRequestsObservationContext observationContext = new HttpRequestsObservationContext(request, new MockHttpServletResponse());
|
||||
request.setAttribute(HttpRequestsObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
|
||||
ServerRequestObservationContext observationContext = new ServerRequestObservationContext(request, new MockHttpServletResponse());
|
||||
request.setAttribute(ServerHttpObservationFilter.CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
|
||||
mapping.handleMatch(key, "/1/2", request);
|
||||
|
||||
assertThat(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)).isEqualTo("/{path1}/2");
|
||||
|
|
|
|||
Loading…
Reference in New Issue