Reorganize server observability packages

Prior to this commit, the server observability support would create a
cycle in Java packages.

This commit refactors the current arrangement to solve this by:

* "flattening" the reactive HTTP instrumentation; this removes the
  dependency to the `ServerWebExchange` and `PathPattern` types
* moving the `observation` package under
  `org.springframework.http.server` and
  `org.springframework.http.server.reactive`

See gh-29477
This commit is contained in:
Brian Clozel
2022-11-14 12:59:36 +01:00
parent 91c6fac18a
commit 1ad7cc3702
24 changed files with 87 additions and 94 deletions

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
package org.springframework.http.observation;
package org.springframework.http.server.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.ServerHttpObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.http.observation.ServerHttpObservationDocumentation.LowCardinalityKeyNames;
import org.springframework.http.server.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.http.server.observation.ServerHttpObservationDocumentation.LowCardinalityKeyNames;
import org.springframework.util.StringUtils;
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.http.observation;
package org.springframework.http.server.observation;
import io.micrometer.common.KeyValue;
import io.micrometer.common.docs.KeyName;
@@ -33,9 +33,9 @@ import io.micrometer.observation.docs.ObservationDocumentation;
public enum ServerHttpObservationDocumentation implements ObservationDocumentation {
/**
* HTTP exchanges observations for Servlet-based servers.
* HTTP request observations for Servlet-based servers.
*/
HTTP_SERVLET_SERVER_EXCHANGES {
HTTP_SERVLET_SERVER_REQUESTS {
@Override
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
return DefaultServerRequestObservationConvention.class;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.http.observation;
package org.springframework.http.server.observation;
import io.micrometer.observation.transport.RequestReplyReceiverContext;
import jakarta.servlet.http.HttpServletRequest;
@@ -23,10 +23,10 @@ import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
/**
* Context that holds information for metadata collection during observations
* for {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_EXCHANGES Servlet HTTP exchanges}.
* Context that holds information for metadata collection regarding
* {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_REQUESTS Servlet HTTP requests} observations.
* <p>This context also extends {@link RequestReplyReceiverContext} for propagating
* tracing information with the HTTP server exchange.
* tracing information during HTTP request processing.
*
* @author Brian Clozel
* @since 6.0

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package org.springframework.http.observation;
package org.springframework.http.server.observation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
/**
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_EXCHANGES Servlet HTTP exchanges}.
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_SERVLET_SERVER_REQUESTS Servlet HTTP requests}.
*
* @author Brian Clozel
* @since 6.0

View File

@@ -1,9 +1,9 @@
/**
* Instrumentation for {@link io.micrometer.observation.Observation observing} HTTP applications.
* Instrumentation for {@link io.micrometer.observation.Observation observing} HTTP server applications.
*/
@NonNullApi
@NonNullFields
package org.springframework.http.observation;
package org.springframework.http.server.observation;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -14,17 +14,16 @@
* limitations under the License.
*/
package org.springframework.http.observation.reactive;
package org.springframework.http.server.reactive.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.reactive.ServerHttpObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.http.observation.reactive.ServerHttpObservationDocumentation.LowCardinalityKeyNames;
import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation.LowCardinalityKeyNames;
import org.springframework.util.StringUtils;
import org.springframework.web.util.pattern.PathPattern;
/**
* Default {@link ServerRequestObservationConvention}.
@@ -117,12 +116,12 @@ public class DefaultServerRequestObservationConvention implements ServerRequestO
protected KeyValue uri(ServerRequestObservationContext context) {
if (context.getCarrier() != null) {
PathPattern pattern = context.getPathPattern();
String pattern = context.getPathPattern();
if (pattern != null) {
if (pattern.toString().isEmpty()) {
if (pattern.isEmpty()) {
return URI_ROOT;
}
return KeyValue.of(LowCardinalityKeyNames.URI, pattern.toString());
return KeyValue.of(LowCardinalityKeyNames.URI, pattern);
}
if (context.getResponse() != null && context.getResponse().getStatusCode() != null) {
HttpStatus status = HttpStatus.resolve(context.getResponse().getStatusCode().value());

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.http.observation.reactive;
package org.springframework.http.server.reactive.observation;
import io.micrometer.common.KeyValue;
import io.micrometer.common.docs.KeyName;
@@ -33,9 +33,9 @@ import io.micrometer.observation.docs.ObservationDocumentation;
public enum ServerHttpObservationDocumentation implements ObservationDocumentation {
/**
* HTTP exchanges observations for reactive servers.
* HTTP request observations for reactive servers.
*/
HTTP_REACTIVE_SERVER_EXCHANGES {
HTTP_REACTIVE_SERVER_REQUESTS {
@Override
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
return DefaultServerRequestObservationConvention.class;

View File

@@ -14,46 +14,47 @@
* limitations under the License.
*/
package org.springframework.http.observation.reactive;
package org.springframework.http.server.reactive.observation;
import java.util.Collections;
import java.util.Map;
import io.micrometer.observation.transport.RequestReplyReceiverContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
/**
* Context that holds information for metadata collection during observations
* for {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_EXCHANGES reactive HTTP exchanges}.
* Context that holds information for metadata collection regarding
* {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_REQUESTS reactive HTTP requests} observations.
* <p>This context also extends {@link RequestReplyReceiverContext} for propagating
* tracing information with the HTTP server exchange.
* tracing information during HTTP request processing.
*
* @author Brian Clozel
* @since 6.0
*/
public class ServerRequestObservationContext extends RequestReplyReceiverContext<ServerHttpRequest, ServerHttpResponse> {
private final ServerWebExchange serverWebExchange;
private final Map<String, Object> attributes;
@Nullable
private PathPattern pathPattern;
private String pathPattern;
private boolean connectionAborted;
public ServerRequestObservationContext(ServerWebExchange exchange) {
super((request, key) -> request.getHeaders().getFirst(key));
this.serverWebExchange = exchange;
setCarrier(exchange.getRequest());
setResponse(exchange.getResponse());
public ServerRequestObservationContext(ServerHttpRequest request, ServerHttpResponse response, Map<String, Object> attributes) {
super((req, key) -> req.getHeaders().getFirst(key));
setCarrier(request);
setResponse(response);
this.attributes = Collections.unmodifiableMap(attributes);
}
/**
* Return the current {@link ServerWebExchange HTTP exchange}.
* Return an immutable map of the current request attributes.
*/
public ServerWebExchange getServerWebExchange() {
return this.serverWebExchange;
public Map<String, Object> getAttributes() {
return this.attributes;
}
/**
@@ -63,7 +64,7 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext
* @return the path pattern, or {@code null} if none found
*/
@Nullable
public PathPattern getPathPattern() {
public String getPathPattern() {
return this.pathPattern;
}
@@ -72,7 +73,7 @@ public class ServerRequestObservationContext extends RequestReplyReceiverContext
* <p>Path patterns must have a low cardinality for the entire application.
* @param pathPattern the path pattern, for example {@code "/projects/{name}"}.
*/
public void setPathPattern(@Nullable PathPattern pathPattern) {
public void setPathPattern(@Nullable String pathPattern) {
this.pathPattern = pathPattern;
}

View File

@@ -14,13 +14,13 @@
* limitations under the License.
*/
package org.springframework.http.observation.reactive;
package org.springframework.http.server.reactive.observation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
/**
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_EXCHANGES reactive HTTP exchanges}.
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REACTIVE_SERVER_REQUESTS reactive HTTP requests}.
*
* @author Brian Clozel
* @since 6.0

View File

@@ -1,9 +1,9 @@
/**
* Instrumentation for {@link io.micrometer.observation.Observation observing} reactive HTTP applications.
* Instrumentation for {@link io.micrometer.observation.Observation observing} reactive HTTP server applications.
*/
@NonNullApi
@NonNullFields
package org.springframework.http.observation.reactive;
package org.springframework.http.server.reactive.observation;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;

View File

@@ -27,10 +27,10 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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.http.server.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.http.server.observation.ServerRequestObservationConvention;
import org.springframework.lang.Nullable;
@@ -127,7 +127,7 @@ public class ServerHttpObservationFilter extends OncePerRequestFilter {
Observation observation = (Observation) request.getAttribute(CURRENT_OBSERVATION_ATTRIBUTE);
if (observation == null) {
ServerRequestObservationContext context = new ServerRequestObservationContext(request, response);
observation = ServerHttpObservationDocumentation.HTTP_SERVLET_SERVER_EXCHANGES.observation(this.observationConvention,
observation = ServerHttpObservationDocumentation.HTTP_SERVLET_SERVER_REQUESTS.observation(this.observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> context, this.observationRegistry).start();
request.setAttribute(CURRENT_OBSERVATION_ATTRIBUTE, observation);
if (!observation.isNoop()) {

View File

@@ -25,11 +25,11 @@ import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccess
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.http.server.reactive.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation;
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
@@ -96,13 +96,14 @@ public class ServerHttpObservationFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange);
ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(),
exchange.getResponse(), exchange.getAttributes());
exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
return chain.filter(exchange).transformDeferred(call -> filter(exchange, observationContext, call));
}
private Publisher<Void> filter(ServerWebExchange exchange, ServerRequestObservationContext observationContext, Mono<Void> call) {
Observation observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_EXCHANGES.observation(this.observationConvention,
Observation observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_REQUESTS.observation(this.observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
observation.start();
return call.doOnEach(signal -> {