Reorganize HTTP Observation types

Closes gh-29334
This commit is contained in:
Brian Clozel
2022-10-17 21:54:04 +02:00
parent 4d44aaf81b
commit 001b2636d6
42 changed files with 762 additions and 849 deletions

View File

@@ -25,8 +25,9 @@ import org.springframework.http.client.ClientHttpRequestFactory;
/**
* Documented {@link io.micrometer.common.KeyValue KeyValues} for {@link ClientHttpRequestFactory HTTP client observations}.
* Documented {@link io.micrometer.common.KeyValue KeyValues} for {@link ClientHttpRequestFactory HTTP client} observations.
* <p>This class is used by automated tools to document KeyValues attached to the HTTP client observations.
*
* @author Brian Clozel
* @since 6.0
*/
@@ -38,7 +39,7 @@ public enum ClientHttpObservationDocumentation implements ObservationDocumentati
HTTP_REQUEST {
@Override
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
return DefaultClientHttpObservationConvention.class;
return DefaultClientRequestObservationConvention.class;
}
@Override

View File

@@ -19,19 +19,18 @@ package org.springframework.http.client.observation;
import io.micrometer.observation.transport.RequestReplySenderContext;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
/**
* Context that holds information for metadata collection
* during the {@link ClientHttpRequestFactory client HTTP} observations.
* during the {@link ClientHttpObservationDocumentation#HTTP_REQUEST client HTTP exchanges} observations.
* <p>This context also extends {@link RequestReplySenderContext} for propagating tracing
* information with the HTTP client exchange.
* @author Brian Clozel
* @since 6.0
*/
public class ClientHttpObservationContext extends RequestReplySenderContext<ClientHttpRequest, ClientHttpResponse> {
public class ClientRequestObservationContext extends RequestReplySenderContext<ClientHttpRequest, ClientHttpResponse> {
@Nullable
private String uriTemplate;
@@ -40,8 +39,8 @@ public class ClientHttpObservationContext extends RequestReplySenderContext<Clie
* Create an observation context for {@link ClientHttpRequest} observations.
* @param request the HTTP client request
*/
public ClientHttpObservationContext(ClientHttpRequest request) {
super(ClientHttpObservationContext::setRequestHeader);
public ClientRequestObservationContext(ClientHttpRequest request) {
super(ClientRequestObservationContext::setRequestHeader);
this.setCarrier(request);
}

View File

@@ -20,15 +20,17 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
/**
* Interface for an {@link ObservationConvention} related to RestTemplate HTTP exchanges.
* Interface for an {@link ObservationConvention} for
* {@link ClientHttpObservationDocumentation#HTTP_REQUEST client HTTP exchanges}.
*
* @author Brian Clozel
* @since 6.0
*/
public interface ClientHttpObservationConvention extends ObservationConvention<ClientHttpObservationContext> {
public interface ClientRequestObservationConvention extends ObservationConvention<ClientRequestObservationContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof ClientHttpObservationContext;
return context instanceof ClientRequestObservationContext;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.observation;
import io.micrometer.common.KeyValue;
import org.springframework.http.HttpStatusCode;
/**
* The outcome of an HTTP request.
* <p>Used as the {@code "outcome"} {@link io.micrometer.common.KeyValue}
* for HTTP {@link io.micrometer.observation.Observation observations}.
*
* @author Brian Clozel
* @author Andy Wilkinson
* @since 6.0
*/
public enum HttpOutcome {
/**
* Outcome of the request was informational.
*/
INFORMATIONAL,
/**
* Outcome of the request was success.
*/
SUCCESS,
/**
* Outcome of the request was redirection.
*/
REDIRECTION,
/**
* Outcome of the request was client error.
*/
CLIENT_ERROR,
/**
* Outcome of the request was server error.
*/
SERVER_ERROR,
/**
* Outcome of the request was unknown.
*/
UNKNOWN;
private final KeyValue keyValue;
HttpOutcome() {
this.keyValue = KeyValue.of("outcome", name());
}
/**
* Returns the {@code Outcome} as a {@link KeyValue} named {@code outcome}.
* @return the {@code outcome} {@code KeyValue}
*/
public KeyValue asKeyValue() {
return this.keyValue;
}
/**
* Return the {@code HttpOutcome} for the given HTTP {@code status} code.
* @param status the HTTP status code
* @return the matching HttpOutcome
*/
public static HttpOutcome forStatus(HttpStatusCode status) {
if (status.is1xxInformational()) {
return INFORMATIONAL;
}
else if (status.is2xxSuccessful()) {
return SUCCESS;
}
else if (status.is3xxRedirection()) {
return REDIRECTION;
}
else if (status.is4xxClientError()) {
return CLIENT_ERROR;
}
else if (status.is5xxServerError()) {
return SERVER_ERROR;
}
return UNKNOWN;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.observation;
package org.springframework.http.observation;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.Observation;
@@ -25,10 +25,11 @@ import io.micrometer.observation.docs.ObservationDocumentation;
* Documented {@link io.micrometer.common.KeyValue KeyValues} for the HTTP server observations
* for Servlet-based web applications.
* <p>This class is used by automated tools to document KeyValues attached to the HTTP server observations.
*
* @author Brian Clozel
* @since 6.0
*/
public enum HttpRequestsObservationDocumentation implements ObservationDocumentation {
public enum ServerHttpObservationDocumentation implements ObservationDocumentation {
/**
* HTTP server request observations.
@@ -36,7 +37,7 @@ public enum HttpRequestsObservationDocumentation implements ObservationDocumenta
HTTP_REQUESTS {
@Override
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
return DefaultHttpRequestsObservationConvention.class;
return DefaultServerRequestObservationConvention.class;
}
@Override

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.observation;
package org.springframework.http.observation;
import io.micrometer.observation.transport.RequestReplyReceiverContext;
import jakarta.servlet.http.HttpServletRequest;
@@ -23,18 +23,20 @@ import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
/**
* Context that holds information for metadata collection during observations for Servlet web application.
* Context that holds information for metadata collection during observations
* for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS Servlet HTTP exchanges}.
* <p>This context also extends {@link RequestReplyReceiverContext} for propagating
* tracing information with the HTTP server exchange.
*
* @author Brian Clozel
* @since 6.0
*/
public class HttpRequestsObservationContext extends RequestReplyReceiverContext<HttpServletRequest, HttpServletResponse> {
public class ServerRequestObservationContext extends RequestReplyReceiverContext<HttpServletRequest, HttpServletResponse> {
@Nullable
private String pathPattern;
public HttpRequestsObservationContext(HttpServletRequest request, HttpServletResponse response) {
public ServerRequestObservationContext(HttpServletRequest request, HttpServletResponse response) {
super(HttpServletRequest::getHeader);
setCarrier(request);
setResponse(response);

View File

@@ -14,21 +14,22 @@
* limitations under the License.
*/
package org.springframework.web.observation;
package org.springframework.http.observation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
/**
* Interface for an {@link ObservationConvention} related to Servlet HTTP exchanges.
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS Servlet HTTP exchanges}.
*
* @author Brian Clozel
* @since 6.0
*/
public interface HttpRequestsObservationConvention extends ObservationConvention<HttpRequestsObservationContext> {
public interface ServerRequestObservationConvention extends ObservationConvention<ServerRequestObservationContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof HttpRequestsObservationContext;
return context instanceof ServerRequestObservationContext;
}
}

View File

@@ -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

View File

@@ -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;
}
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.observation.reactive;
package org.springframework.http.observation.reactive;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.Observation;
@@ -25,10 +25,11 @@ import io.micrometer.observation.docs.ObservationDocumentation;
* Documented {@link io.micrometer.common.KeyValue KeyValues} for the HTTP server observations
* for reactive web applications.
* <p>This class is used by automated tools to document KeyValues attached to the HTTP server observations.
*
* @author Brian Clozel
* @since 6.0
*/
public enum HttpRequestsObservationDocumentation implements ObservationDocumentation {
public enum ServerHttpObservationDocumentation implements ObservationDocumentation {
/**
* HTTP server request observations.
@@ -36,7 +37,7 @@ public enum HttpRequestsObservationDocumentation implements ObservationDocumenta
HTTP_REQUESTS {
@Override
public Class<? extends ObservationConvention<? extends Observation.Context>> getDefaultConvention() {
return DefaultHttpRequestsObservationConvention.class;
return DefaultServerRequestObservationConvention.class;
}
@Override

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.observation.reactive;
package org.springframework.http.observation.reactive;
import io.micrometer.observation.transport.RequestReplyReceiverContext;
@@ -25,20 +25,22 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
/**
* Context that holds information for metadata collection during observations for reactive web applications.
* Context that holds information for metadata collection during observations
* for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS reactive HTTP exchanges}.
* <p>This context also extends {@link RequestReplyReceiverContext} for propagating
* tracing information with the HTTP server exchange.
*
* @author Brian Clozel
* @since 6.0
*/
public class HttpRequestsObservationContext extends RequestReplyReceiverContext<ServerHttpRequest, ServerHttpResponse> {
public class ServerRequestObservationContext extends RequestReplyReceiverContext<ServerHttpRequest, ServerHttpResponse> {
@Nullable
private PathPattern pathPattern;
private boolean connectionAborted;
public HttpRequestsObservationContext(ServerWebExchange exchange) {
public ServerRequestObservationContext(ServerWebExchange exchange) {
super((request, key) -> request.getHeaders().getFirst(key));
setCarrier(exchange.getRequest());
setResponse(exchange.getResponse());
@@ -74,7 +76,13 @@ public class HttpRequestsObservationContext extends RequestReplyReceiverContext<
return this.connectionAborted;
}
void setConnectionAborted(boolean connectionAborted) {
/**
* Set whether the current connection was aborted by the client, resulting
* in a {@link reactor.core.publisher.SignalType#CANCEL cancel signal} on the reactive chain,
* or an {@code AbortedException} when reading the request.
* @param connectionAborted if the connection has been aborted
*/
public void setConnectionAborted(boolean connectionAborted) {
this.connectionAborted = connectionAborted;
}

View File

@@ -14,21 +14,22 @@
* limitations under the License.
*/
package org.springframework.web.observation.reactive;
package org.springframework.http.observation.reactive;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
/**
* Interface for an {@link ObservationConvention} related to reactive HTTP exchanges.
* Interface for an {@link ObservationConvention} for {@link ServerHttpObservationDocumentation#HTTP_REQUESTS reactive HTTP exchanges}.
*
* @author Brian Clozel
* @since 6.0
*/
public interface HttpRequestsObservationConvention extends ObservationConvention<HttpRequestsObservationContext> {
public interface ServerRequestObservationConvention extends ObservationConvention<ServerRequestObservationContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof HttpRequestsObservationContext;
return context instanceof ServerRequestObservationContext;
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.observation;
package org.springframework.web.filter;
import java.io.IOException;
import java.util.Optional;
@@ -28,45 +28,49 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.observation.ServerHttpObservationDocumentation;
import org.springframework.http.observation.ServerRequestObservationContext;
import org.springframework.http.observation.ServerRequestObservationConvention;
import org.springframework.lang.Nullable;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* {@link jakarta.servlet.Filter} that creates {@link Observation observations}
* for HTTP exchanges. This collects information about the execution time and
* information gathered from the {@link HttpRequestsObservationContext}.
* <p>Web Frameworks can fetch the current {@link HttpRequestsObservationContext context}
* information gathered from the {@link ServerRequestObservationContext}.
* <p>Web Frameworks can fetch the current {@link ServerRequestObservationContext context}
* as a {@link #CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE request attribute} and contribute
* additional information to it.
* The configured {@link HttpRequestsObservationConvention} will use this context to collect
* The configured {@link ServerRequestObservationConvention} will use this context to collect
* {@link io.micrometer.common.KeyValue metadata} and attach it to the observation.
*
* @author Brian Clozel
* @since 6.0
*/
public class HttpRequestsObservationFilter extends OncePerRequestFilter {
public class ServerHttpObservationFilter extends OncePerRequestFilter {
/**
* Name of the request attribute holding the {@link HttpRequestsObservationContext context} for the current observation.
* Name of the request attribute holding the {@link ServerRequestObservationContext context} for the current observation.
*/
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = HttpRequestsObservationFilter.class.getName() + ".context";
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".context";
private static final HttpRequestsObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultHttpRequestsObservationConvention();
private static final ServerRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultServerRequestObservationConvention();
private static final String CURRENT_OBSERVATION_ATTRIBUTE = HttpRequestsObservationFilter.class.getName() + ".observation";
private static final String CURRENT_OBSERVATION_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".observation";
private final ObservationRegistry observationRegistry;
private final HttpRequestsObservationConvention observationConvention;
private final ServerRequestObservationConvention observationConvention;
/**
* Create an {@code HttpRequestsObservationFilter} that records observations
* against the given {@link ObservationRegistry}. The default
* {@link DefaultHttpRequestsObservationConvention convention} will be used.
* {@link DefaultServerRequestObservationConvention convention} will be used.
* @param observationRegistry the registry to use for recording observations
*/
public HttpRequestsObservationFilter(ObservationRegistry observationRegistry) {
public ServerHttpObservationFilter(ObservationRegistry observationRegistry) {
this(observationRegistry, DEFAULT_OBSERVATION_CONVENTION);
}
@@ -76,18 +80,18 @@ public class HttpRequestsObservationFilter extends OncePerRequestFilter {
* @param observationRegistry the registry to use for recording observations
* @param observationConvention the convention to use for all recorded observations
*/
public HttpRequestsObservationFilter(ObservationRegistry observationRegistry, HttpRequestsObservationConvention observationConvention) {
public ServerHttpObservationFilter(ObservationRegistry observationRegistry, ServerRequestObservationConvention observationConvention) {
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
}
/**
* Get the current {@link HttpRequestsObservationContext observation context} from the given request, if available.
* Get the current {@link ServerRequestObservationContext observation context} from the given request, if available.
* @param request the current request
* @return the current observation context
*/
public static Optional<HttpRequestsObservationContext> findObservationContext(HttpServletRequest request) {
return Optional.ofNullable((HttpRequestsObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
public static Optional<ServerRequestObservationContext> findObservationContext(HttpServletRequest request) {
return Optional.ofNullable((ServerRequestObservationContext) request.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
}
@Override
@@ -124,8 +128,8 @@ public class HttpRequestsObservationFilter extends OncePerRequestFilter {
private Observation createOrFetchObservation(HttpServletRequest request, HttpServletResponse response) {
Observation observation = (Observation) request.getAttribute(CURRENT_OBSERVATION_ATTRIBUTE);
if (observation == null) {
HttpRequestsObservationContext context = new HttpRequestsObservationContext(request, response);
observation = HttpRequestsObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
ServerRequestObservationContext context = new ServerRequestObservationContext(request, response);
observation = ServerHttpObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> context, this.observationRegistry).start();
request.setAttribute(CURRENT_OBSERVATION_ATTRIBUTE, observation);
request.setAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observation.getContext());

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.web.observation.reactive;
package org.springframework.web.filter.reactive;
import java.util.Optional;
import java.util.Set;
@@ -24,6 +24,10 @@ import io.micrometer.observation.ObservationRegistry;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import org.springframework.http.observation.reactive.DefaultServerRequestObservationConvention;
import org.springframework.http.observation.reactive.ServerHttpObservationDocumentation;
import org.springframework.http.observation.reactive.ServerRequestObservationContext;
import org.springframework.http.observation.reactive.ServerRequestObservationConvention;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
@@ -33,39 +37,39 @@ import org.springframework.web.server.WebFilterChain;
/**
* {@link org.springframework.web.server.WebFilter} that creates {@link Observation observations}
* for HTTP exchanges. This collects information about the execution time and
* information gathered from the {@link HttpRequestsObservationContext}.
* <p>Web Frameworks can fetch the current {@link HttpRequestsObservationContext context}
* information gathered from the {@link ServerRequestObservationContext}.
* <p>Web Frameworks can fetch the current {@link ServerRequestObservationContext context}
* as a {@link #CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE request attribute} and contribute
* additional information to it.
* The configured {@link HttpRequestsObservationConvention} will use this context to collect
* The configured {@link ServerRequestObservationConvention} will use this context to collect
* {@link io.micrometer.common.KeyValue metadata} and attach it to the observation.
*
* @author Brian Clozel
* @since 6.0
*/
public class HttpRequestsObservationWebFilter implements WebFilter {
public class ServerHttpObservationFilter implements WebFilter {
/**
* Name of the request attribute holding the {@link HttpRequestsObservationContext context} for the current observation.
* Name of the request attribute holding the {@link ServerRequestObservationContext context} for the current observation.
*/
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = HttpRequestsObservationWebFilter.class.getName() + ".context";
public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".context";
private static final HttpRequestsObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultHttpRequestsObservationConvention();
private static final ServerRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultServerRequestObservationConvention();
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS = Set.of("AbortedException",
"ClientAbortException", "EOFException", "EofException");
private final ObservationRegistry observationRegistry;
private final HttpRequestsObservationConvention observationConvention;
private final ServerRequestObservationConvention observationConvention;
/**
* Create an {@code HttpRequestsObservationWebFilter} that records observations
* against the given {@link ObservationRegistry}. The default
* {@link DefaultHttpRequestsObservationConvention convention} will be used.
* {@link DefaultServerRequestObservationConvention convention} will be used.
* @param observationRegistry the registry to use for recording observations
*/
public HttpRequestsObservationWebFilter(ObservationRegistry observationRegistry) {
public ServerHttpObservationFilter(ObservationRegistry observationRegistry) {
this(observationRegistry, DEFAULT_OBSERVATION_CONVENTION);
}
@@ -75,29 +79,29 @@ public class HttpRequestsObservationWebFilter implements WebFilter {
* @param observationRegistry the registry to use for recording observations
* @param observationConvention the convention to use for all recorded observations
*/
public HttpRequestsObservationWebFilter(ObservationRegistry observationRegistry, HttpRequestsObservationConvention observationConvention) {
public ServerHttpObservationFilter(ObservationRegistry observationRegistry, ServerRequestObservationConvention observationConvention) {
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
}
/**
* Get the current {@link HttpRequestsObservationContext observation context} from the given request, if available.
* Get the current {@link ServerRequestObservationContext observation context} from the given request, if available.
* @param exchange the current exchange
* @return the current observation context
*/
public static Optional<HttpRequestsObservationContext> findObservationContext(ServerWebExchange exchange) {
public static Optional<ServerRequestObservationContext> findObservationContext(ServerWebExchange exchange) {
return Optional.ofNullable(exchange.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE));
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
HttpRequestsObservationContext observationContext = new HttpRequestsObservationContext(exchange);
ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange);
exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext);
return chain.filter(exchange).transformDeferred(call -> filter(exchange, observationContext, call));
}
private Publisher<Void> filter(ServerWebExchange exchange, HttpRequestsObservationContext observationContext, Mono<Void> call) {
Observation observation = HttpRequestsObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
private Publisher<Void> filter(ServerWebExchange exchange, ServerRequestObservationContext observationContext, Mono<Void> call) {
Observation observation = ServerHttpObservationDocumentation.HTTP_REQUESTS.observation(this.observationConvention,
DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
observation.start();
return call.doOnEach(signal -> {

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;