diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java b/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java index 44f06737c0c..b70826c8782 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationDocumentation.java @@ -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. *

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> getDefaultConvention() { - return DefaultClientHttpObservationConvention.class; + return DefaultClientRequestObservationConvention.class; } @Override diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationContext.java b/spring-web/src/main/java/org/springframework/http/client/observation/ClientRequestObservationContext.java similarity index 83% rename from spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationContext.java rename to spring-web/src/main/java/org/springframework/http/client/observation/ClientRequestObservationContext.java index d5957d1a54a..c84a45f7e89 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/ClientHttpObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/ClientRequestObservationContext.java @@ -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. *

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 { +public class ClientRequestObservationContext extends RequestReplySenderContext { @Nullable private String uriTemplate; @@ -40,8 +39,8 @@ public class ClientHttpObservationContext extends RequestReplySenderContext { +public interface ClientRequestObservationConvention extends ObservationConvention { @Override default boolean supportsContext(Observation.Context context) { - return context instanceof ClientHttpObservationContext; + return context instanceof ClientRequestObservationContext; } } diff --git a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientHttpObservationConvention.java b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java similarity index 68% rename from spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientHttpObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java index 9fcedf226f1..8c5b728dbc9 100644 --- a/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientHttpObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/client/observation/DefaultClientRequestObservationConvention.java @@ -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; + } + } + + } + } diff --git a/spring-web/src/main/java/org/springframework/http/observation/DefaultServerRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/observation/DefaultServerRequestObservationConvention.java new file mode 100644 index 00000000000..38ce54a4532 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/observation/DefaultServerRequestObservationConvention.java @@ -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; + } + } + + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/observation/HttpOutcome.java b/spring-web/src/main/java/org/springframework/http/observation/HttpOutcome.java deleted file mode 100644 index c9440669a8f..00000000000 --- a/spring-web/src/main/java/org/springframework/http/observation/HttpOutcome.java +++ /dev/null @@ -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. - *

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; - } -} diff --git a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationDocumentation.java b/spring-web/src/main/java/org/springframework/http/observation/ServerHttpObservationDocumentation.java similarity index 93% rename from spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationDocumentation.java rename to spring-web/src/main/java/org/springframework/http/observation/ServerHttpObservationDocumentation.java index a468da62f99..a8918e5f3ec 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationDocumentation.java +++ b/spring-web/src/main/java/org/springframework/http/observation/ServerHttpObservationDocumentation.java @@ -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. *

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> getDefaultConvention() { - return DefaultHttpRequestsObservationConvention.class; + return DefaultServerRequestObservationConvention.class; } @Override diff --git a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationContext.java b/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationContext.java similarity index 78% rename from spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationContext.java rename to spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationContext.java index 6007e88e18d..b2e288c67c8 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationContext.java @@ -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}. *

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 { +public class ServerRequestObservationContext extends RequestReplyReceiverContext { @Nullable private String pathPattern; - public HttpRequestsObservationContext(HttpServletRequest request, HttpServletResponse response) { + public ServerRequestObservationContext(HttpServletRequest request, HttpServletResponse response) { super(HttpServletRequest::getHeader); setCarrier(request); setResponse(response); diff --git a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationConvention.java b/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationConvention.java similarity index 70% rename from spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationConvention.java index bac16c6dd46..be2bf1d6e7d 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/observation/ServerRequestObservationConvention.java @@ -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 { +public interface ServerRequestObservationConvention extends ObservationConvention { @Override default boolean supportsContext(Observation.Context context) { - return context instanceof HttpRequestsObservationContext; + return context instanceof ServerRequestObservationContext; } } diff --git a/spring-web/src/main/java/org/springframework/http/observation/package-info.java b/spring-web/src/main/java/org/springframework/http/observation/package-info.java index 87a33f2d274..73b05998c39 100644 --- a/spring-web/src/main/java/org/springframework/http/observation/package-info.java +++ b/spring-web/src/main/java/org/springframework/http/observation/package-info.java @@ -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 diff --git a/spring-web/src/main/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConvention.java b/spring-web/src/main/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConvention.java new file mode 100644 index 00000000000..f7701ffee89 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConvention.java @@ -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; + } + } + + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationDocumentation.java b/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerHttpObservationDocumentation.java similarity index 93% rename from spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationDocumentation.java rename to spring-web/src/main/java/org/springframework/http/observation/reactive/ServerHttpObservationDocumentation.java index 2bd8485d81f..317ff30b25a 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationDocumentation.java +++ b/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerHttpObservationDocumentation.java @@ -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. *

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> getDefaultConvention() { - return DefaultHttpRequestsObservationConvention.class; + return DefaultServerRequestObservationConvention.class; } @Override diff --git a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationContext.java b/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationContext.java similarity index 78% rename from spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationContext.java rename to spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationContext.java index 0a98560f0e7..7e00e9a5aa8 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationContext.java +++ b/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationContext.java @@ -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}. *

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 { +public class ServerRequestObservationContext extends RequestReplyReceiverContext { @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; } diff --git a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationConvention.java b/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationConvention.java similarity index 69% rename from spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationConvention.java rename to spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationConvention.java index 4364d1a27ef..cbe51d15b4c 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationConvention.java +++ b/spring-web/src/main/java/org/springframework/http/observation/reactive/ServerRequestObservationConvention.java @@ -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 { +public interface ServerRequestObservationConvention extends ObservationConvention { @Override default boolean supportsContext(Observation.Context context) { - return context instanceof HttpRequestsObservationContext; + return context instanceof ServerRequestObservationContext; } } diff --git a/spring-web/src/main/java/org/springframework/web/observation/package-info.java b/spring-web/src/main/java/org/springframework/http/observation/reactive/package-info.java similarity index 66% rename from spring-web/src/main/java/org/springframework/web/observation/package-info.java rename to spring-web/src/main/java/org/springframework/http/observation/reactive/package-info.java index 4cc30c56be3..473d6d16164 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/package-info.java +++ b/spring-web/src/main/java/org/springframework/http/observation/reactive/package-info.java @@ -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; diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index bf68ae3cf40..01e1a164b28 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -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(); diff --git a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java similarity index 66% rename from spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationFilter.java rename to spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java index fc6fab63637..58b99aa129b 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/HttpRequestsObservationFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ServerHttpObservationFilter.java @@ -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}. - *

Web Frameworks can fetch the current {@link HttpRequestsObservationContext context} + * information gathered from the {@link ServerRequestObservationContext}. + *

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 findObservationContext(HttpServletRequest request) { - return Optional.ofNullable((HttpRequestsObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); + public static Optional 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()); diff --git a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationWebFilter.java b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java similarity index 66% rename from spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationWebFilter.java rename to spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java index 8e5bd463b5a..e34908f01c9 100644 --- a/spring-web/src/main/java/org/springframework/web/observation/reactive/HttpRequestsObservationWebFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java @@ -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}. - *

Web Frameworks can fetch the current {@link HttpRequestsObservationContext context} + * information gathered from the {@link ServerRequestObservationContext}. + *

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 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 findObservationContext(ServerWebExchange exchange) { + public static Optional findObservationContext(ServerWebExchange exchange) { return Optional.ofNullable(exchange.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); } @Override public Mono 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 filter(ServerWebExchange exchange, HttpRequestsObservationContext observationContext, Mono call) { - Observation observation = HttpRequestsObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention, + private Publisher filter(ServerWebExchange exchange, ServerRequestObservationContext observationContext, Mono call) { + Observation observation = ServerHttpObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry); observation.start(); return call.doOnEach(signal -> { diff --git a/spring-web/src/main/java/org/springframework/web/observation/DefaultHttpRequestsObservationConvention.java b/spring-web/src/main/java/org/springframework/web/observation/DefaultHttpRequestsObservationConvention.java deleted file mode 100644 index d4b0e17de62..00000000000 --- a/spring-web/src/main/java/org/springframework/web/observation/DefaultHttpRequestsObservationConvention.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-web/src/main/java/org/springframework/web/observation/reactive/DefaultHttpRequestsObservationConvention.java b/spring-web/src/main/java/org/springframework/web/observation/reactive/DefaultHttpRequestsObservationConvention.java deleted file mode 100644 index 259eb07bfc7..00000000000 --- a/spring-web/src/main/java/org/springframework/web/observation/reactive/DefaultHttpRequestsObservationConvention.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-web/src/main/java/org/springframework/web/observation/reactive/package-info.java b/spring-web/src/main/java/org/springframework/web/observation/reactive/package-info.java deleted file mode 100644 index 75a10dd5aa6..00000000000 --- a/spring-web/src/main/java/org/springframework/web/observation/reactive/package-info.java +++ /dev/null @@ -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; diff --git a/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientHttpObservationConventionTests.java b/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java similarity index 80% rename from spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientHttpObservationConventionTests.java rename to spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java index 8b1bc238f56..3373126a7dd 100644 --- a/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientHttpObservationConventionTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/observation/DefaultClientRequestObservationConventionTests.java @@ -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; } diff --git a/spring-web/src/test/java/org/springframework/web/observation/DefaultHttpRequestsObservationConventionTests.java b/spring-web/src/test/java/org/springframework/http/observation/DefaultServerRequestObservationConventionTests.java similarity index 91% rename from spring-web/src/test/java/org/springframework/web/observation/DefaultHttpRequestsObservationConventionTests.java rename to spring-web/src/test/java/org/springframework/http/observation/DefaultServerRequestObservationConventionTests.java index 14ac6c3abed..920c54e1d4a 100644 --- a/spring-web/src/test/java/org/springframework/web/observation/DefaultHttpRequestsObservationConventionTests.java +++ b/spring-web/src/test/java/org/springframework/http/observation/DefaultServerRequestObservationConventionTests.java @@ -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 diff --git a/spring-web/src/test/java/org/springframework/http/observation/HttpOutcomeTests.java b/spring-web/src/test/java/org/springframework/http/observation/HttpOutcomeTests.java deleted file mode 100644 index e30b6645da1..00000000000 --- a/spring-web/src/test/java/org/springframework/http/observation/HttpOutcomeTests.java +++ /dev/null @@ -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")); - } - -} diff --git a/spring-web/src/test/java/org/springframework/web/observation/reactive/DefaultHttpRequestsObservationConventionTests.java b/spring-web/src/test/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConventionTests.java similarity index 85% rename from spring-web/src/test/java/org/springframework/web/observation/reactive/DefaultHttpRequestsObservationConventionTests.java rename to spring-web/src/test/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConventionTests.java index fdd4728de5a..e2d5e8538dd 100644 --- a/spring-web/src/test/java/org/springframework/web/observation/reactive/DefaultHttpRequestsObservationConventionTests.java +++ b/spring-web/src/test/java/org/springframework/http/observation/reactive/DefaultServerRequestObservationConventionTests.java @@ -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); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java index dafb6295778..b85de7fc2f2 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java @@ -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 { + static class ContextAssertionObservationHandler implements ObservationHandler { @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(); } } diff --git a/spring-web/src/test/java/org/springframework/web/observation/HttpRequestsObservationFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java similarity index 79% rename from spring-web/src/test/java/org/springframework/web/observation/HttpRequestsObservationFilterTests.java rename to spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java index 8f2e59874da..a574faeac44 100644 --- a/spring-web/src/test/java/org/springframework/web/observation/HttpRequestsObservationFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/ServerHttpObservationFilterTests.java @@ -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"); } diff --git a/spring-web/src/test/java/org/springframework/web/observation/reactive/HttpRequestsObservationWebFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java similarity index 86% rename from spring-web/src/test/java/org/springframework/web/observation/reactive/HttpRequestsObservationWebFilterTests.java rename to spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java index b091f3b2190..79b2f3bef5f 100644 --- a/spring-web/src/test/java/org/springframework/web/observation/reactive/HttpRequestsObservationWebFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java @@ -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 observationContext = HttpRequestsObservationWebFilter.findObservationContext(filterExchange); + Optional 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 observationContext = HttpRequestsObservationWebFilter.findObservationContext(exchange); + Optional observationContext = ServerHttpObservationFilter.findObservationContext(exchange); assertThat(observationContext.get().getError()).isInstanceOf(IllegalArgumentException.class); assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR"); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientObservationDocumentation.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java similarity index 89% rename from spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientObservationDocumentation.java rename to spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java index 855578bf9d6..73248cae7bd 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientObservationDocumentation.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientHttpObservationDocumentation.java @@ -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. *

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> 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(); } }; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientObservationContext.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java similarity index 85% rename from spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientObservationContext.java rename to spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java index ea616891df8..36423eb381d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientObservationContext.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java @@ -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 { +public class ClientRequestObservationContext extends RequestReplySenderContext { @Nullable private String uriTemplate; @@ -35,8 +35,8 @@ public class ClientObservationContext extends RequestReplySenderContext { +public interface ClientRequestObservationConvention extends ObservationConvention { @Override default boolean supportsContext(Observation.Context context) { - return context instanceof ClientObservationContext; + return context instanceof ClientRequestObservationContext; } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientObservationConvention.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientObservationConvention.java deleted file mode 100644 index 080c2eea847..00000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientObservationConvention.java +++ /dev/null @@ -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; - } - -} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java new file mode 100644 index 00000000000..69c088e4763 --- /dev/null +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConvention.java @@ -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; + } + } + + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 9ce4e0b87a7..219f7167711 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -73,7 +73,7 @@ class DefaultWebClient implements WebClient { private static final Mono 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 defaultCookies, @Nullable Consumer> defaultRequest, @Nullable Map, Function>> 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 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)); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java index 43979e97b1a..a5493f2def1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java @@ -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; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index 95291810e45..e3998eade85 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -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. diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientObservationConventionTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java similarity index 79% rename from spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientObservationConventionTests.java rename to spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java index be3cf63beba..2e3d3003f04 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientObservationConventionTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestObservationConventionTests.java @@ -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; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java index 5376aee637f..5f349f8b4a4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/support/RouterFunctionMapping.java @@ -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); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java index 4c7009f1bb2..6c9dce05553 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java @@ -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); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index 727382096d2..8c0c3a4f369 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -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); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java index 28b8f91d760..ead0a1e538b 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/support/RouterFunctionMappingTests.java @@ -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; } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java index a3cf9695ec9..5dcf68759d3 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java @@ -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");