This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.1.11! |
Observability Support
Micrometer defines an 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 holistic 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 configuring the observability infrastructure in Spring Boot.
List of produced Observations
Spring Framework instruments various features for observability. As outlined at the beginning of this section, observations can generate timer Metrics and/or Traces depending on the configuration.
Observation name | Description |
---|---|
Time spent for HTTP client exchanges |
|
Processing time for HTTP server exchanges at the Framework level |
Observations are using Micrometer’s official naming convention, but Metrics names will be automatically converted to the format preferred by the monitoring system backend (Prometheus, Atlas, Graphite, InfluxDB…). |
Micrometer Observation concepts
If you are not familiar with Micrometer Observation, here’s a quick summary of the concepts you should know about.
-
Observation
is the actual recording of something happening in your application. This is processed byObservationHandler
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, and so forth. -
Each
Observation
holdsKeyValues
metadata. In the case of an HTTP server observation, this could be the HTTP request method, the HTTP response status, and so forth. This metadata is contributed byObservationConvention
implementations which should declare the type ofObservationContext
they support. -
KeyValues
are said to be "low cardinality" if there is a low, bounded number of possible values for theKeyValue
tuple (HTTP method is a good example). Low cardinality values are contributed to metrics only. Conversely, "high cardinality" values are 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.
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 extractedKeyValues
Using custom Observation conventions
Let’s take the example of the Spring MVC "http.server.requests" metrics instrumentation with the ServerHttpObservationFilter
.
This observation uses 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:
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));
}
private KeyValue custom(ServerRequestObservationContext context) {
return KeyValue.of("custom.method", context.getCarrier().getMethod());
}
}
If you want full control, you can implement the entire convention contract for the observation you’re interested in:
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));
}
private 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());
}
// status(), exception(), httpUrl()...
private KeyValue status(ServerRequestObservationContext context) {
return KeyValue.of(ServerHttpObservationDocumentation.LowCardinalityKeyNames.STATUS, String.valueOf(context.getResponse().getStatus()));
}
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 httpUrl(ServerRequestObservationContext context) {
return KeyValue.of(ServerHttpObservationDocumentation.HighCardinalityKeyNames.HTTP_URL, context.getCarrier().getRequestURI());
}
}
You can also achieve 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.
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;
}
}
You can configure ObservationFilter
instances on the ObservationRegistry
.
HTTP Server instrumentation
HTTP server exchange observations are created with the name "http.server.requests"
for Servlet and Reactive applications.
Servlet applications
Applications need to configure the org.springframework.web.filter.ServerHttpObservationFilter
Servlet filter in their application.
It uses the org.springframework.http.server.observation.DefaultServerRequestObservationConvention
by default, backed by the ServerRequestObservationContext
.
This will only record an observation as an error if the Exception
has not been handled by the web framework and has bubbled up to the Servlet filter.
Typically, all exceptions handled by Spring MVC’s @ExceptionHandler
and ProblemDetail
support will not be recorded with the observation.
You can, at any point during request processing, set the error field on the ObservationContext
yourself:
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.filter.ServerHttpObservationFilter;
@Controller
public class UserController {
@ExceptionHandler(MissingUserException.class)
ResponseEntity<Void> handleMissingUser(HttpServletRequest request, MissingUserException exception) {
// We want to record this exception with the observation
ServerHttpObservationFilter.findObservationContext(request)
.ifPresent(context -> context.setError(exception));
return ResponseEntity.notFound().build();
}
static class MissingUserException extends RuntimeException {
}
}
Because the instrumentation is done at the Servlet Filter level, the observation scope only covers the filters ordered after this one as well as the handling of the request.
Typically, Servlet container error handling is performed at a lower level and won’t have any active observation or span.
For this use case, a container-specific implementation is required, such as a org.apache.catalina.Valve for Tomcat; this is outside of the scope of this project.
|
By default, the following KeyValues
are created:
Name |
Description |
|
Name of the exception thrown during the exchange, or |
|
Name of HTTP request method or |
|
Outcome of the HTTP server exchange. |
|
HTTP response raw status code, or |
|
URI pattern for the matching handler if available, falling back to |
Name |
Description |
|
HTTP request URI. |
Reactive applications
Applications need to configure the org.springframework.web.filter.reactive.ServerHttpObservationFilter
reactive WebFilter
in their application.
It uses the org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention
by default, backed by the ServerRequestObservationContext
.
This will only record an observation as an error if the Exception
has not been handled by the web framework and has bubbled up to the WebFilter
.
Typically, all exceptions handled by Spring WebFlux’s @ExceptionHandler
and ProblemDetail
support will not be recorded with the observation.
You can, at any point during request processing, set the error field on the ObservationContext
yourself:
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
import org.springframework.web.server.ServerWebExchange;
@Controller
public class UserController {
@ExceptionHandler(MissingUserException.class)
ResponseEntity<Void> handleMissingUser(ServerWebExchange exchange, MissingUserException exception) {
// We want to record this exception with the observation
ServerHttpObservationFilter.findObservationContext(exchange)
.ifPresent(context -> context.setError(exception));
return ResponseEntity.notFound().build();
}
static class MissingUserException extends RuntimeException {
}
}
By default, the following KeyValues
are created:
Name |
Description |
|
Name of the exception thrown during the exchange, or |
|
Name of HTTP request method or |
|
Outcome of the HTTP server exchange. |
|
HTTP response raw status code, or |
|
URI pattern for the matching handler if available, falling back to |
Name |
Description |
|
HTTP request URI. |
HTTP Client Instrumentation
HTTP client exchange 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.
RestTemplate
Applications must configure an ObservationRegistry
on RestTemplate
instances to enable the instrumentation; without that, observations are "no-ops".
Spring Boot will auto-configure RestTemplateBuilder
beans with the observation registry already set.
Instrumentation uses the org.springframework.http.client.observation.ClientRequestObservationConvention
by default, backed by the ClientRequestObservationContext
.
Name |
Description |
|
Name of HTTP request method or |
|
URI template used for HTTP request, or |
|
Client name derived from the request URI host. |
|
HTTP response raw status code, or |
|
Outcome of the HTTP client exchange. |
|
Name of the exception thrown during the exchange, or |
Name |
Description |
|
HTTP request URI. |
WebClient
Applications must configure an ObservationRegistry
on the WebClient
builder to enable the instrumentation; without that, observations are "no-ops".
Spring Boot will auto-configure WebClient.Builder
beans with the observation registry already set.
Instrumentation uses the org.springframework.web.reactive.function.client.ClientRequestObservationConvention
by default, backed by the ClientRequestObservationContext
.
Name |
Description |
|
Name of HTTP request method or |
|
URI template used for HTTP request, or |
|
Client name derived from the request URI host. |
|
HTTP response raw status code, or |
|
Outcome of the HTTP client exchange. |
|
Name of the exception thrown during the exchange, or |
Name |
Description |
|
HTTP request URI. |