Observability with Spring Boot 3
Discription

The Spring Observability Team has been working on adding observability support for Spring Applications for quite some time, and we are pleased to inform you that this feature will be generally available with Spring Framework 6 and Spring Boot 3!

What is observability? In our understanding, it is _”how well you can understand the internals of your system by examining its outputs”_. We believe that the interconnection between metrics, logging, and distributed tracing gives you the ability to reason about the state of your system in order to debug exceptions and latency in your applications. You can watch more about what we think observability is in [this episode of Enlightning with Jonatan Ivanov]().

The upcoming Spring Boot `3.0.0-RC1` release will contain numerous autoconfigurations for improved metrics with [Micrometer]() and new distributed tracing support with [Micrometer Tracing]() (formerly [Spring Cloud Sleuth]()). The most notable changes are that it will contain built-in support for log correlation, [W3C context propagation]() will be the default propagation type, and we will support automatic propagation of metadata to be used by the tracing infrastructure (called “remote baggage”) that helps to label the observations.

We have been changing the Micrometer API a lot over the course of this year. The most important change is that we have introduced a new API: the Observation API.

> The idea of its founding was that we want the users to instrument their code once using a single API and have multiple benefits out of it (e.g. metrics, tracing, logging).

This blog post details what you need to know to about that API and how you can use it to provide more insights into your application.

# How Does Micrometer Observation Work?

For any observation to happen, you need to register `ObservationHandler` objects through an `ObservationRegistry`. An `ObservationHandler` reacts only to supported implementations of an `Observation.Context` and can create, for example, timers, spans, and logs by reacting to the lifecycle events of an observation, such as:

* `start` – Observation has been started. Happens when the `Observation#start()` method gets called.

* `stop` – Observation has been stopped. Happens when the `Observation#stop()` method gets called.

* `error` – An error occurred while observing. Happens when the `Observation#error(exception)` method gets called.

* `event` – An event happened when observing. Happens when the `Observation#event(event)` method gets called.

* `scope started` – Observation opens a scope. The scope must be closed when no longer used. Handlers can create thread local variables on start that are cleared upon closing of the scope. Happens when the `Observation#openScope()` method gets called.

* `scope stopped` – Observation stops a scope. Happens when the `Observation.Scope#close()` method gets called.

Whenever any of these methods is called, an `ObservationHandler` method (such as `onStart(T extends Observation.Context ctx)`, `onStop(T extends Observation.Context ctx)`, and so on) are called. To pass state between the handler methods, you can use the `Observation.Context`.

The observation state diagram looks like this:

Observation Observation
Context Context
Created ———-> Started ———-> Stopped

The observation Scope state diagram looks like this:

Observation
Context
Scope Started ———-> Scope Closed

To make it possible to debug production problems, an observation needs additional metadata, such as key-value pairs (also known as tags). You can then query your metrics or distributed tracing backend by using those tags to find the required data. Tags can be of either high or low cardinality.

This is an example of the Micrometer Observation API.

// Create an ObservationRegistry
ObservationRegistry registry = ObservationRegistry.create();
// Register an ObservationHandler
registry.observationConfig().observationHandler(new MyHandler());

// Create an Observation and observe your code!
Observation.createNotStarted(“user.name”, registry)
.contextualName(“getting-user-name”)
.lowCardinalityKeyValue(“userType”, “userType1”) // let’s assume that you can have 3 user types
.highCardinalityKeyValue(“userId”, “1234”) // let’s assume that this is an arbitrary number
.observe(() -> log.info(“Hello”)); // this is a shortcut for starting an observation, opening a scope, running user’s code, closing the scope and stopping the observation

Important

| **High cardinality** means that a pair will have an unbounded number of possible values. An HTTP URL is a good example of such a key value (for example, `/user/user1234`, `/user/user2345`, and so on). **Low cardinality** means that a key value will have a bounded number of possible values. A **templated** HTTP URL (such as `/user/{userId}`) is a good example of such a key value.
—|—

To separate observation lifecycle operations from an observation configuration (such as names and low and high cardinality tags), you can use the `ObservationConvention` that provides an easy way of overriding the default naming conventions.

# Building Your First Observed Application

The easiest way to get started is to create a new project from . Make sure to select Spring Boot 3.0.0-SNAPSHOT (you can switch to RC1 once [it’s available]()) and your favorite build tool.

We will build a Spring WebMvc server application and a client to call the server using RestTemplate. We start with the server side.

## WebMvc Server Setup

Since we want to start an HTTP server, we have to pick the `org.springframework.boot:spring-boot-starter-web` dependency.

To create observations by using the `@Observed` aspect, we need to add the `org.springframework.boot:spring-boot-starter-aop` dependency.

To add observation features to your application, choose `spring-boot-starter-actuator` (to add [Micrometer]() to the classpath).

Now it is time to add observability related features!

* **Metrics**

* For Micrometer metrics with Prometheus, we need to add the `io.micrometer:micrometer-registry-prometheus` dependency.

* **Tracing**

* For **Tracing Context Propagation** with Micrometer Tracing, we need to pick a **tracer** bridge (**tracer** is a library that is used to handle the lifecycle of a span). We pick [Zipkin Brave]() by adding the `io.micrometer:micrometer-tracing-bridge-brave`.

* The client application that we will create for this demo will use another tracer library to show an interop between tracers.

* For **Latency Visualization**, we need to send the finished spans in some format to a server. In our case, we produce an Zipkin-compliant span. To achieve that, we need to add the `io.zipkin.reporter2:zipkin-reporter-brave` dependency.

* **Logs**

* Since we have Micrometer Tracing on the classpath, the logs are automatically correlated (that is, they contain a unique trace identifier). Now we need to ship the logs. For this demo, we ship them to [Grafana Loki](). We can achieve that by adding the `com.github.loki4j:loki-logback-appender:latest.release` dependency.

Important

| If you are new to tracing, we need to quickly define a couple of basic terms. You can wrap any operation in a `span`. It has a unique `span id` and contains timing information and some additional metadata (key-value pairs). Because you can produce child spans from spans, the whole tree of spans forms a `trace` that shares the same `trace id` (that is, a correlation identifier).
—|—

Now we need to add some configuration. We set up `actuator` and `metrics` to publish percentiles histograms, and we redefine the logging pattern to include the trace and span identifiers. We set the sampling probability to `1.0` to send all traces to latency analysis tool.

/src/main/resources/application.properties

server.port=7654
spring.application.name=server

# All traces should be sent to latency analysis tool
management.tracing.sampling.probability=1.0
management.endpoints.web.exposure.include=prometheus

# For Exemplars to work we need histogram buckets
management.metrics.distribution.percentiles-histogram.http.server.requests=true

# traceID and spanId are predefined MDC keys – we want the logs to include them
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

Since we are running the [Grafana]() stack with [Loki]() and [Tempo]() locally, we configure the `loki-logback-appender` to send logs to the local instance of Loki.

/src/main/resources/logback-spring.xml

https://localhost:3100/loki/api/v1/push

${FILE_LOG_PATTERN}

true

## WebMvc Server Code

Time to write some server-side code! We want to achieve full observability of our application, including metrics, tracing, and additional logging.

To begin with, we write a controller that logs a message to the console and delegate work to a service.

MyController.java

@RestController
class MyController {

private static final Logger log = LoggerFactory.getLogger(MyController.class);
private final MyUserService myUserService;

MyController(MyUserService myUserService) {
this.myUserService = myUserService;
}

@GetMapping(“/user/{userId}”)
String userName(@PathVariable(“userId”) String userId) {
log.info(“Got a request”);
return myUserService.userName(userId);
}
}

We want to have some detailed observation of the `MyUserService#userName` method. Thanks to having added AOP support, we can use the `@Observed` annotation. To do so, we can register a `ObservedAspect` bean.

MyConfiguration.java

@Configuration(proxyBeanMethods = false)
class MyConfiguration {
// To have the @Observed support we need to register this aspect
@Bean
ObservedAspect observedAspect(ObservationRegistry observationRegistry) {
return new ObservedAspect(observationRegistry);
}
}

MyUserService.java

@Service
class MyUserService {

private static final Logger log = LoggerFactory.getLogger(MyUserService.class);

private final Random random = new Random();

// Example of using an annotation to observe methods
// will be used as a metric name
// will be used as a span name
// will be set as a tag for both metric & span
@Observed(name = “user.name”,
contextualName = “getting-user-name”,
lowCardinalityKeyValues = {“userType”, “userType2”})
String userName(String userId) {
log.info(“Getting user name for user with id “, userId);
try {
Thread.sleep(random.nextLong(200L)); // simulates latency
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
return “foo”;
}
}

With metrics and tracing on the classpath, having this annotation leads to the creation of a `timer`, a `long task timer`, and a `span`. The timer would be named `user.name`, the long task timer would be named `user.name.active`, and the span would be named `getting-user-name`.

What about logs? We do not want to write the logging statements manually whenever an observation takes place. What we can do is to create a dedicated handler that logs some text for each observation.

MyHandler.java

// Example of plugging in a custom handler that in this case will print a statement before and after all observations take place
@Component
class MyHandler implements ObservationHandler {

private static final Logger log = LoggerFactory.getLogger(MyHandler.class);

@Override
public void onStart(Observation.Context context) {
log.info(“Before running the observation for context [{}], userType [{}]”, context.getName(), getUserTypeFromContext(context));
}

@Override
public void onStop(Observation.Context context) {
log.info(“After running the observation for context [{}], userType [{}]”, context.getName(), getUserTypeFromContext(context));
}

@Override
public boolean supportsContext(Observation.Context context) {
return true;
}

private String getUserTypeFromContext(Observation.Context context) {
return StreamSupport.stream(context.getLowCardinalityKeyValues().spliterator(), false)
.filter(keyValue -> “userType”.equals(keyValue.getKey()))
.map(KeyValue::getValue)
.findFirst()
.orElse(“UNKNOWN”);
}
}

You might wonder what you should do to have the observability turned on for the controllers. You can register one bean and be ready to go. Once [this issue]() is fixed, this configuration will not need to be manually defined.

MyConfiguration.java

@Configuration(proxyBeanMethods = false)
class MyConfiguration {
// You must set this manually until this is registered in Boot
@Bean
FilterRegistrationBean observationWebFilter(ObservationRegistry observationRegistry) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new HttpRequestsObservationFilter(observationRegistry));
filterRegistrationBean.setDispatcherTypes(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.FORWARD,
DispatcherType.INCLUDE, DispatcherType.REQUEST);
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
// We provide a list of URLs that we want to create observations for
filterRegistrationBean.setUrlPatterns(Collections.singletonList(“/user/*”));
return filterRegistrationBean;
}
}

That is it! Time for the client side.

## RestTemplate Client Application Setup

As before, we add the `spring-boot-starter-web` and `spring-boot-starter-actuator` dependencies to have a web server running and Micrometer support added.

Time to add observability related features!

* **Metrics**

* For Micrometer metrics with Prometheus, we need to add the `io.micrometer:micrometer-registry-prometheus` dependency.

* **Tracing**

* For **Tracing Context Propagation** with Micrometer Tracing, we need to pick a **tracer** bridge. We pick [OpenTelemetry]() by adding the `io.micrometer:micrometer-tracing-bridge-otel`.

* For **Latency Visualization**, we need to send the finished spans in some format to a server. In our case, we produce an OpenZipkin compliant span. To achieve that, we need to add the `io.opentelemetry:opentelemetry-exporter-zipkin` dependency.

* **Logs**

* As previously, we add the `com.github.loki4j:loki-logback-appender:latest.release` dependency to ship logs to Loki.

Now we need to add some configuration. We add almost identical configuration as we did on the server side.

/src/main/resources/application.properties

server.port=6543
spring.application.name=client

# All traces should be sent to latency analysis tool
management.tracing.sampling.probability=1.0
management.endpoints.web.exposure.include=prometheus

# traceID and spanId are predefined MDC keys – we want the logs to include them
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]

The Loki Appender configuration looks exactly the same.

/src/main/resources/logback-spring.xml

https://localhost:3100/loki/api/v1/push

${FILE_LOG_PATTERN}

true

## RestTemplate Application Client Code

Now it is time to write some client-side code! We send a request with `RestTemplate` to the server side, and we want to achieve the full observability of our application, including metrics and tracing.

To begin, we need a `RestTemplate` bean that is automatically instrumented by Spring Boot. Remember to inject the `RestTemplateBuilder` and to construct a `RestTemplate` instance from the builder.

MyConfiguration.java

@Configuration(proxyBeanMethods = false)
class MyConfiguration {
// IMPORTANT! To instrument RestTemplate you must inject the RestTemplateBuilder
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}

Now we can write a `CommandLineRunner` bean that is wrapped by using the Observation API and that sends a request to the server side. All parts of the API are described in more detail in the following snippet.

MyConfiguration.java

@Configuration(proxyBeanMethods = false)
class MyConfiguration {
@Bean
CommandLineRunner myCommandLineRunner(ObservationRegistry registry, RestTemplate restTemplate) {
Random highCardinalityValues = new Random(); // Simulates potentially large number of values
List lowCardinalityValues = Arrays.asList(“userType1”, “userType2”, “userType3”); // Simulates low number of values
return args -> {
String highCardinalityUserId = String.valueOf(highCardinalityValues.nextLong(100_000));
// Example of using the Observability API manually
// is a “technical” name that does not depend on the context. It will be used to name e.g. Metrics
Observation.createNotStarted(“my.observation”, registry)
// Low cardinality means that the number of potential values won’t be big. Low cardinality entries will end up in e.g. Metrics
.lowCardinalityKeyValue(“userType”, randomUserTypePicker(lowCardinalityValues))
// High cardinality means that the number of potential values can be large. High cardinality entries will end up in e.g. Spans
.highCardinalityKeyValue(“userId”, highCardinalityUserId)
// is a “contextual” name that gives more details within the provided context. It will be used to name e.g. Spans
.contextualName(“command-line-runner”)
// The following lambda will be executed with an observation scope (e.g. all the MDC entries will be populated with tracing information). Also the observation will be started, stopped and if an error occurred it will be recorded on the observation
.observe(() -> {
log.info(“Will send a request to the server”); // Since we’re in an observation scope – this log line will contain tracing MDC entries …
String response = restTemplate.getForObject(“https://localhost:7654/user/{userId}”, String.class, highCardinalityUserId); // Boot’s RestTemplate instrumentation creates a child span here
log.info(“Got response [{}]”, response); // … so will this line
});

};
}
}

### Limitations

The Spring Boot AutoConfiguration for WebMvc Observability is not yet ready. As a result, we need to set things up manually. For more information, see this [issue]().

For the Spring Boot [Exemplars]() AutoConfiguration to work properly, we need to wait for [this PR]() and [this PR]() to be merged. Until then, we need to create [our configuration]() manually.

## Running It All Together

We have prepared a Docker setup of the whole observability infrastructure under [this link](). Follow these steps to run the infrastructure and both applications.

### Running the samples

To run the samples:

1. Start up the observability stack (for demonstration purposes, you can use the provided Grafana, Tempo, and Loki stack) and wait for it to start.

$ docker compose up

* To access Prometheus go to

* To access Grafana go to

2. Run the server side application (this will block your current terminal window).

$ ./mvnw spring-boot:run -pl :server

3. Run the client side application (this will block your current terminal window)

$ ./mvnw spring-boot:run -pl :client

You should see log statements similar to these:

2022-10-04T15:04:55.345+02:00 INFO [client,bbe3aea006077640b66d40f3e62f04b9,93b7a150b7e293ef] 92556 — [ main] com.example.client.ClientApplication : Will send a request to the server
2022-10-04T15:04:55.385+02:00 INFO [client,bbe3aea006077640b66d40f3e62f04b9,93b7a150b7e293ef] 92556 — [ main] com.example.client.ClientApplication : Got response [foo]

4. Go to [Grafana](), go to dashboards, and click on the `Logs, Traces, Metrics` dashboard. There you can pick a trace ID value (for example, `bbe3aea006077640b66d40f3e62f04b9`) to find all logs and traces from both applications that correspond to that trace ID. You should see a following correlated view of logs and traces related to the same trace identifier, and you will see metrics taking place at the same time range. The metrics are related to HTTP request processing latency. These come from the automated Spring Boot WebMvc instrumentation that uses the Micrometer API.

![logs metrics traces](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/logs-metrics-traces.png)

Notice a diamond shape in the metrics. These are [`Exemplars`](). Those are “specific trace representative of measurement taken in a given time interval”. If you click on the shape, you can jump to the trace ID view to see the corresponding trace.

![exemplar](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/exemplar.png)

5. Either click on the trace ID to `Query it with Tempo` or go to Tempo and pick the trace identifier yourself. You will see the following screen.

![trace view](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/trace-view.png)

Each bar represents a `span`. You can see how much time it took for each operation to complete. If you click on a given span, you can see tags (key-value metadata) and timing information related to that particular operation.

![span tags](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/span-tags.png)

This is how the correlated logs view would look in Loki.

![correlated logs](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/correlated-logs.png)

If you want to see the `@Observed` annotated method metrics, you can go to the `Prometheus` view and find the `user_name` Timer.

![annotation metric](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/annotation-metric.png)

If you want to see the metrics from your Observation that you have manually created, go to the `Prometheus` view and find the `my_observation` Timer.

![my observation](https://raw.githubusercontent.com/marcingrzejszczak/observability-boot-blog-post/main/docs/src/main/asciidoc/img/my-observation.png)

## Running It All Together with AOT Support

To better understand how Spring Boot supports Native, please read [this excellent blog post](). We reuse that knowledge to run the previously created applications using Spring Native.

### Building

To build the applications, you need GraalVM on your path. If you use `SDKMan`, invoke the following:

sdk install java 22.2.r17-nik

See also [GraalVM Quickstart]().

To build the application with Maven, you need to enable the `native` profile:

$ ./mvnw -Pnative clean package

### Running

Run the server side application first

$ ./server/target/server

Next, run the client side application.

$ ./client/target/client

You should get output similar to this:

Client side logs

2022-10-10T12:57:17.712+02:00 INFO [client,,] 82009 — [ main] com.example.client.ClientApplication : Starting ClientApplication using Java 17.0.4 on marcin-precision5560 with PID 82009 (/home/marcin/repo/observability/blogs/bootRc1/client/target/client started by marcin in /home/marcin/repo/observability/blogs/bootRc1)
2022-10-10T12:57:17.712+02:00 INFO [client,,] 82009 — [ main] com.example.client.ClientApplication : No active profile set, falling back to 1 default profile: “default”
2022-10-10T12:57:17.723+02:00 INFO [client,,] 82009 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 6543 (http)
2022-10-10T12:57:17.723+02:00 INFO [client,,] 82009 — [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-10-10T12:57:17.723+02:00 INFO [client,,] 82009 — [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.0.23]
2022-10-10T12:57:17.727+02:00 INFO [client,,] 82009 — [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-10-10T12:57:17.727+02:00 INFO [client,,] 82009 — [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 15 ms
2022-10-10T12:57:17.731+02:00 WARN [client,,] 82009 — [ main] i.m.c.i.binder.jvm.JvmGcMetrics : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2022-10-10T12:57:17.781+02:00 INFO [client,,] 82009 — [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 15 endpoint(s) beneath base path ‘/actuator’
2022-10-10T12:57:17.783+02:00 INFO [client,,] 82009 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 6543 (http) with context path ”
2022-10-10T12:57:17.783+02:00 INFO [client,,] 82009 — [ main] com.example.client.ClientApplication : Started ClientApplication in 0.077 seconds (process running for 0.079)
2022-10-10T12:57:17.784+02:00 INFO [client,27c1113e4276c4173daec3675f536bf4,e0f2db8b983607d8] 82009 — [ main] com.example.client.ClientApplication : Will send a request to the server
2022-10-10T12:57:17.820+02:00 INFO [client,27c1113e4276c4173daec3675f536bf4,e0f2db8b983607d8] 82009 — [ main] com.example.client.ClientApplication : Got response [foo]
2022-10-10T12:57:18.966+02:00 INFO [client,,] 82009 — [nio-6543-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’
2022-10-10T12:57:18.966+02:00 INFO [client,,] 82009 — [nio-6543-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet ‘dispatcherServlet’
2022-10-10T12:57:18.966+02:00 INFO [client,,] 82009 — [nio-6543-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms

Server side logs

2022-10-10T12:57:07.200+02:00 INFO [server,,] 81760 — [ main] com.example.server.ServerApplication : Starting ServerApplication using Java 17.0.4 on marcin-precision5560 with PID 81760 (/home/marcin/repo/observability/blogs/bootRc1/server/target/server started by marcin in /home/marcin/repo/observability/blogs/bootRc1)
2022-10-10T12:57:07.201+02:00 INFO [server,,] 81760 — [ main] com.example.server.ServerApplication : No active profile set, falling back to 1 default profile: “default”
2022-10-10T12:57:07.213+02:00 INFO [server,,] 81760 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 7654 (http)
2022-10-10T12:57:07.213+02:00 INFO [server,,] 81760 — [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-10-10T12:57:07.213+02:00 INFO [server,,] 81760 — [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.0.23]
2022-10-10T12:57:07.217+02:00 INFO [server,,] 81760 — [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-10-10T12:57:07.217+02:00 INFO [server,,] 81760 — [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 16 ms
2022-10-10T12:57:07.222+02:00 WARN [server,,] 81760 — [ main] i.m.c.i.binder.jvm.JvmGcMetrics : GC notifications will not be available because MemoryPoolMXBeans are not provided by the JVM
2022-10-10T12:57:07.278+02:00 INFO [server,,] 81760 — [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 15 endpoint(s) beneath base path ‘/actuator’
2022-10-10T12:57:07.280+02:00 INFO [server,,] 81760 — [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 7654 (http) with context path ”
2022-10-10T12:57:07.281+02:00 INFO [server,,] 81760 — [ main] com.example.server.ServerApplication : Started ServerApplication in 0.086 seconds (process running for 0.088)
2022-10-10T12:57:07.639+02:00 INFO [server,,] 81760 — [nio-7654-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’
2022-10-10T12:57:07.639+02:00 INFO [server,,] 81760 — [nio-7654-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet ‘dispatcherServlet’
2022-10-10T12:57:07.640+02:00 INFO [server,,] 81760 — [nio-7654-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2022-10-10T12:57:17.785+02:00 INFO [server,,] 81760 — [nio-7654-exec-8] com.example.server.MyHandler : Before running the observation for context [http.server.requests]
2022-10-10T12:57:17.785+02:00 INFO [server,27c1113e4276c4173daec3675f536bf4,9affba5698490e2d] 81760 — [nio-7654-exec-8] com.example.server.MyController : Got a request
2022-10-10T12:57:17.820+02:00 INFO [server,,] 81760 — [nio-7654-exec-8] com.example.server.MyHandler : After running the observation for context [http.server.requests]

You can check Grafana for metrics and traces. Read the Native Support Limitations section on why you won’t find any pushed logs to Loki.

### Native Support Limitations

You will not see logs being pushed to Loki just yet. For more information, look into [this issue]().

On the client side, we need to provide the `reflect-config.js` configuration manually. For more information, see [this PR]().

# Summary

In this blog post, we have managed to give you an introduction of the main concepts behind the Micrometer Observability API. We have also shown you how you can create observations by using the Observation API and annotations. You can also visualize the latency, see the correlated logs, and check the metrics that come from your Spring Boot applications.

You could also observe your applications by using native images with Spring Native.

# Acknowledgments

Work on the Micrometer Observability would not be possible without the extensive support of the whole Spring team, [Tadaya Tsuyukubo](), [Johnny Lim](), and all the other contributors and reviewers.

# Next Steps

Based on community feedback, we will continue to improve our Observability story. We intend to go [GA in November this year]().

This is an exciting time for us. We would again like to thank everyone who has already contributed and reported feedback, and we look forward to further feedback! Check out Spring Boot’s latest snapshots! Check out the documentation of our projects: [Micrometer Context Propagation](), [Micrometer](), [Micrometer Observation](), [Micrometer Tracing]() and [Micrometer Docs Generator]()! Click [here]() to see the code used for this blog post.Read More

Back to Main

Subscribe for the latest news: