Document Observability support in reference docs
Closes gh-29524
This commit is contained in:
parent
9a3cb628af
commit
8ce1f14554
|
|
@ -13,6 +13,7 @@ configurations {
|
|||
dependencies {
|
||||
api(project(":spring-context"))
|
||||
api(project(":spring-web"))
|
||||
api("jakarta.servlet:jakarta.servlet-api")
|
||||
|
||||
implementation(project(":spring-core-test"))
|
||||
implementation("org.assertj:assertj-core")
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ STOMP Messaging.
|
|||
<<web-reactive.adoc#spring-webflux, Web Reactive>> :: Spring WebFlux, WebClient,
|
||||
WebSocket, RSocket.
|
||||
<<integration.adoc#spring-integration, Integration>> :: REST Clients, JMS, JCA, JMX,
|
||||
Email, Tasks, Scheduling, Caching.
|
||||
Email, Tasks, Scheduling, Caching and Observability.
|
||||
<<languages.adoc#languages, Languages>> :: Kotlin, Groovy, Dynamic Languages.
|
||||
<<appendix.adoc#appendix, Appendix>> :: Spring properties.
|
||||
https://github.com/spring-projects/spring-framework/wiki[*Wiki*] :: What's New,
|
||||
|
|
|
|||
|
|
@ -18,4 +18,6 @@ include::integration/scheduling.adoc[leveloffset=+1]
|
|||
|
||||
include::integration/cache.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/observability.adoc[leveloffset=+1]
|
||||
|
||||
include::integration/integration-appendix.adoc[leveloffset=+1]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
[[integration.observability]]
|
||||
= Observability Support
|
||||
|
||||
Micrometer defines an https://micrometer.io/docs/observation[Observation concept that enables both Metrics and Traces] in applications.
|
||||
Metrics support offers a way to create timers, gauges or counters for collecting statistics about the runtime behavior of your application.
|
||||
Metrics can help you to track error rates, usage patterns, performance and more.
|
||||
Traces provide a holisitic view of an entire system, crossing application boundaries; you can zoom in on particular user requests and follow their entire completion across applications.
|
||||
|
||||
Spring Framework instruments various parts of its own codebase to publish observations if an `ObservationRegistry` is configured.
|
||||
You can learn more about {docs-spring-boot}/html/actuator.html#actuator.metrics[configuring the observability infrastructure in Spring Boot].
|
||||
|
||||
[[integration.observability.concepts]]
|
||||
== Micrometer Observation concepts
|
||||
|
||||
If you are not familiar with Micrometer Observation, here's a quick summary of the new concepts you should know about.
|
||||
|
||||
* `Observation` is the actual recording of something happening in your application. This is processed by `ObservationHandler` implementations to produce metrics or traces.
|
||||
* Each observation has a corresponding `ObservationContext` implementation; this type holds all the relevant information for extracting metadata for it.
|
||||
In the case of an HTTP server observation, the context implementation could hold the HTTP request, the HTTP response, any Exception thrown during processing...
|
||||
* Each `Observation` holds `KeyValues` metadata. In the case of an server HTTP observation, this could be the HTTP request method, the HTTP response status...
|
||||
This metadata is contributed by `ObservationConvention` implementations which should declare the type of `ObservationContext` they support.
|
||||
* `KeyValues` are said to be "low cardinality" if there is a low, bounded number of possible values for the `KeyValue` tuple (HTTP methods is a good example).
|
||||
Low cardinality values are contributed to metrics only.
|
||||
"High cardinality" are on the other hand unbounded (for example, HTTP request URIs) and are only contributed to Traces.
|
||||
* An `ObservationDocumentation` documents all observations in a particular domain, listing the expected key names and their meaning.
|
||||
|
||||
|
||||
[[integration.observability.config]]
|
||||
== Configuring Observations
|
||||
|
||||
Global configuration options are available at the `ObservationRegistry#observationConfig()` level.
|
||||
Each instrumented component will provide two extension points:
|
||||
|
||||
* setting the `ObservationRegistry`; if not set, observations will not be recorded and will be no-ops
|
||||
* providing a custom `ObservationConvention` to change the default observation name and extracted `KeyValues`
|
||||
|
||||
|
||||
[[integration.observability.config.conventions]]
|
||||
=== Using custom Observation conventions
|
||||
|
||||
Let's take the example of the Spring MVC "http.server.requests" metrics instrumentation with the `ServerHttpObservationFilter`.
|
||||
This observation is using a `ServerRequestObservationConvention` with a `ServerRequestObservationContext`; custom conventions can be configured on the Servlet filter.
|
||||
If you would like to customize the metadata produced with the observation, you can extend the `DefaultServerRequestObservationConvention` for your requirements:
|
||||
|
||||
include::code:ExtendedServerRequestObservationConvention[]
|
||||
|
||||
If you want full control, you can then implement the entire convention contract for the observation you're interested in:
|
||||
|
||||
include::code:CustomServerRequestObservationConvention[]
|
||||
|
||||
You can also similar goals using a custom `ObservationFilter` - adding or removing key values for an observation.
|
||||
Filters do not replace the default convention and are used as a post-processing component.
|
||||
|
||||
include::code:ServerRequestObservationFilter[]
|
||||
|
||||
You can configure `ObservationFilter` instances on the `ObservationRegistry`.
|
||||
|
||||
|
||||
[[integration.observability.http-server]]
|
||||
== HTTP Server instrumentation
|
||||
|
||||
HTTP server exchanges observations are created with the name `"http.server.requests"` for Servlet and Reactive applications.
|
||||
|
||||
[[integration.observability.http-server.servlet]]
|
||||
=== Servlet applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.ServerHttpObservationFilter` Servlet filter in their application.
|
||||
It is using the `org.springframework.http.server.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE`} if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `KeyValue#NONE_VALUE` if the request was not received properly.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
[[integration.observability.http-server.reactive]]
|
||||
=== Reactive applications
|
||||
|
||||
Applications need to configure the `org.springframework.web.filter.reactive.ServerHttpObservationFilter` reactive `WebFilter` in their application.
|
||||
It is using the `org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention` by default, backed by the `ServerRequestObservationContext`.
|
||||
|
||||
By default, the following `KeyValues` are created:
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `KeyValue#NONE_VALUE` if the request was not received properly.
|
||||
|`outcome` _(required)_|Outcome of the HTTP server exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"UNKNOWN"` if no response was created.
|
||||
|`uri` _(required)_|URI pattern for the matching handler if available, falling back to `REDIRECTION` for 3xx responses, `NOT_FOUND` for 404 responses, `root` for requests with no path info, and `UNKNOWN` for all other requests.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[integration.observability.http-client]]
|
||||
== HTTP Client instrumentation
|
||||
|
||||
HTTP client exchanges observations are created with the name `"http.client.requests"` for blocking and reactive clients.
|
||||
Unlike their server counterparts, the instrumentation is implemented directly in the client so the only required step is to configure an `ObservationRegistry` on the client.
|
||||
|
||||
[[integration.observability.http-server.resttemplate]]
|
||||
=== RestTemplate
|
||||
|
||||
Instrumentation is using the `org.springframework.http.client.observation.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `KeyValue#NONE_VALUE` if the request could not be created.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `KeyValue#NONE_VALUE` if none was provided.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[integration.observability.http-server.webclient]]
|
||||
=== WebClient
|
||||
|
||||
Instrumentation is using the `org.springframework.web.reactive.function.client.ClientRequestObservationConvention` by default, backed by the `ClientRequestObservationContext`.
|
||||
|
||||
.Low cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`exception` _(required)_|Name of the exception thrown during the exchange, or `KeyValue#NONE_VALUE` if no exception happened.
|
||||
|`method` _(required)_|Name of HTTP request method or `KeyValue#NONE_VALUE` if the request could not be created.
|
||||
|`outcome` _(required)_|Outcome of the HTTP client exchange.
|
||||
|`status` _(required)_|HTTP response raw status code, or `"IO_ERROR"` in case of `IOException`, or `"CLIENT_ERROR"` if no response was received.
|
||||
|`uri` _(required)_|URI template used for HTTP request, or `KeyValue#NONE_VALUE` if none was provided.
|
||||
|===
|
||||
|
||||
.High cardinality Keys
|
||||
[cols="a,a"]
|
||||
|===
|
||||
|Name | Description
|
||||
|`client.name` _(required)_|Client name derived from the request URI host.
|
||||
|`http.url` _(required)_|HTTP request URI.
|
||||
|===
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.docs.integration.observability.config.conventions;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.server.observation.ServerHttpObservationDocumentation;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationConvention;
|
||||
|
||||
public class CustomServerRequestObservationConvention implements ServerRequestObservationConvention {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
// will be used as the metric name
|
||||
return "http.server.requests";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContextualName(ServerRequestObservationContext context) {
|
||||
// will be used for the trace name
|
||||
return "http " + context.getCarrier().getMethod().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(method(context), status(context), exception(context));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
return KeyValues.of(httpUrl(context));
|
||||
}
|
||||
|
||||
|
||||
protected KeyValue method(ServerRequestObservationContext context) {
|
||||
// You should reuse as much as possible the corresponding ObservationDocumentation for key names
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.METHOD, context.getCarrier().getMethod());
|
||||
}
|
||||
|
||||
// @fold:on // status(), exception(), httpUrl()...
|
||||
private KeyValue exception(ServerRequestObservationContext context) {
|
||||
String exception = (context.getError() != null) ? context.getError().getClass().getSimpleName() : KeyValue.NONE_VALUE;
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.EXCEPTION, exception);
|
||||
}
|
||||
|
||||
private KeyValue status(ServerRequestObservationContext context) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
|
||||
}
|
||||
|
||||
private KeyValue httpUrl(ServerRequestObservationContext context) {
|
||||
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
|
||||
}
|
||||
// @fold:off
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.docs.integration.observability.config.conventions;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.common.KeyValues;
|
||||
|
||||
import org.springframework.http.server.observation.DefaultServerRequestObservationConvention;
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
|
||||
public class ExtendedServerRequestObservationConvention extends DefaultServerRequestObservationConvention {
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
|
||||
// here, we just want to have an additional KeyValue to the observation, keeping the default values
|
||||
return super.getLowCardinalityKeyValues(context).and(custom(context));
|
||||
}
|
||||
|
||||
protected KeyValue custom(ServerRequestObservationContext context) {
|
||||
return KeyValue.of("custom.method", context.getCarrier().getMethod());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.docs.integration.observability.config.conventions;
|
||||
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.Observation;
|
||||
import io.micrometer.observation.ObservationFilter;
|
||||
|
||||
import org.springframework.http.server.observation.ServerRequestObservationContext;
|
||||
|
||||
public class ServerRequestObservationFilter implements ObservationFilter {
|
||||
|
||||
@Override
|
||||
public Observation.Context map(Observation.Context context) {
|
||||
if (context instanceof ServerRequestObservationContext serverContext) {
|
||||
context.setName("custom.observation.name");
|
||||
context.addLowCardinalityKeyValue(KeyValue.of("project", "spring"));
|
||||
String customAttribute = (String) serverContext.getCarrier().getAttribute("customAttribute");
|
||||
context.addLowCardinalityKeyValue(KeyValue.of("custom.attribute", customAttribute));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue