Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Document Observability support in reference docs
Closes gh-29524
- Loading branch information
Showing
7 changed files
with
326 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 175 additions & 0 deletions
175
framework-docs/src/docs/asciidoc/integration/observability.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|=== | ||
|
||
|
72 changes: 72 additions & 0 deletions
72
...ntegration/observability/config/conventions/CustomServerRequestObservationConvention.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
||
} |
37 changes: 37 additions & 0 deletions
37
...egration/observability/config/conventions/ExtendedServerRequestObservationConvention.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
} | ||
|
||
} |
38 changes: 38 additions & 0 deletions
38
...ork/docs/integration/observability/config/conventions/ServerRequestObservationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |