> Markdown version of [Grafana](https://vaadin.com/docs/next/tools/observability/integrations/grafana). Section index: [llms.txt](https://vaadin.com/docs/next/tools/llms.txt)

# Grafana Integration

Grafana is an observability stack with support for metrics, traces, and logs, viewed together in a single tool. A typical stack pairs Grafana with [Prometheus](https://grafana.com/oss/prometheus/) for metrics, [Tempo](https://grafana.com/oss/tempo/) for traces, and [Loki](https://grafana.com/oss/loki/) for logs. It can be self-hosted or used as the managed [Grafana Cloud](https://grafana.com/products/cloud/) service.

This page covers the application-side configuration — getting the kit’s telemetry into those backing stores. For standing up the stores themselves, follow the [Grafana documentation](https://grafana.com/docs/); Grafana Cloud also exposes a single OTLP gateway that accepts both metrics and traces.

## <a id="configure-the-application"></a>Configure the Application

Add Actuator and the Prometheus registry for metrics, and the OpenTelemetry starter for OTLP trace export:

`pom.xml`

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
```

`application.properties`

```properties
spring.application.name=vaadin

# Metrics: scraped by Prometheus from Actuator
management.endpoints.web.exposure.include=prometheus
# The OpenTelemetry starter also brings in the OTLP metrics registry; disable it so metrics go through Prometheus only
management.otlp.metrics.export.enabled=false

# Traces: pushed to Tempo over OTLP
management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4318/v1/traces
management.tracing.sampling.probability=1.0
```

Point `management.opentelemetry.tracing.export.otlp.endpoint` at your Tempo (or collector) OTLP endpoint, and configure Prometheus to scrape the application at `/actuator/prometheus` — see the [Prometheus](https://vaadin.com/docs/next/tools/observability/integrations/prometheus.md) page.

Logs reach Loki through your logging pipeline rather than the kit; see the [Loki documentation](https://grafana.com/docs/loki/latest/send-data/) for the available options.

## <a id="viewing-data"></a>Viewing Data

Data is explored through Grafana’s [Explore view](http://localhost:3000/explore), selecting the data source for each signal. See the [Grafana documentation](https://grafana.com/docs/grafana/latest/explore/) for details.

### <a id="viewing-traces"></a>Viewing Traces

Open the Explore view and select **Tempo** as the data source. Change the **Query type** to **Search**, select your service name from the **Service name** menu, then click the refresh button in the top-right to search for traces.

Use the **Tags** option to filter for specific span attributes. For example, to find traces that contain errors, filter on `outcome=error`.

Clicking a trace ID brings up a side panel with detailed information about the trace, such as its nested spans and their attributes.

### <a id="viewing-metrics"></a>Viewing Metrics

Open the Explore view and select **Prometheus** as the data source. Select a metric from the **Metric** drop-down — for example `vaadin_sessions_active` — then click the refresh button in the top-right to display it.

### <a id="viewing-logs"></a>Viewing Logs

Open the Explore view and select **Loki** as the data source. Under **Labels**, configure a label for `service_name` with your application’s service name, then click the refresh button in the top-right to search for logs.

## <a id="example-dashboard-queries"></a>Example Dashboard Queries

The following PromQL expressions cover the most useful panels for a Vaadin dashboard. Remember that Prometheus renders the dotted meter names in its own convention — `vaadin.request.duration` becomes `vaadin_request_duration_seconds`, and the `.created` counters become `vaadin_sessions_total` and `vaadin_ui_total`.

| Panel                              | Query                                                                                                                                    |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Active sessions                    | `sum(vaadin_sessions_active)`                                                                                                            |
| Active UIs (browser tabs)          | `sum(vaadin_ui_active)`                                                                                                                  |
| Request rate                       | `sum(rate(vaadin_request_duration_seconds_count[$__rate_interval]))`                                                                     |
| Server errors                      | `sum(rate(vaadin_errors_total[$__rate_interval]))`                                                                                       |
| Request latency, p95               | `histogram_quantile(0.95, sum by (le) (rate(vaadin_request_duration_seconds_bucket[$__rate_interval])))`                                 |
| RPC latency by type, p95           | `histogram_quantile(0.95, sum by (le, type) (rate(vaadin_rpc_duration_seconds_bucket[$__rate_interval])))`                               |
| Navigation rate by route           | `sum by (route) (rate(vaadin_navigation_seconds_count[$__rate_interval]))`                                                               |
| Session lock wait, p95             | `histogram_quantile(0.95, sum by (le) (rate(vaadin_session_lock_wait_seconds_bucket[$__rate_interval])))`                                |
| Retained UI state against the heap | `sum(vaadin_ui_state_size_bytes)` charted next to `sum(jvm_memory_used_bytes{area="heap"})` and `sum(jvm_memory_max_bytes{area="heap"})` |

The percentile queries read `_bucket` series, which the kit’s timers don’t publish by default — enable them per meter first; see [Percentiles and Histogram Buckets](https://vaadin.com/docs/next/tools/observability/integrations.md#percentiles). The UI state panel needs the opt-in `vaadin.observability.ui-state` feature, and a byte figure appears only when `vaadin.observability.ui-state-bytes-per-node` is set; see [UI State Size](https://vaadin.com/docs/next/tools/observability/reference.md#ui-state-size).

A complete dashboard built from these queries ships with the [Vaadin observability use-case application](https://github.com/vaadin/use-cases/tree/main/observability): a provisioned Grafana dashboard (`grafana/dashboards/vaadin.json`) together with a Docker Compose stack that runs Prometheus and Grafana against the application. Import the JSON into your own Grafana as a starting point, or run the stack to see the panels over live data.
