Datasource MicrometerでSpring BootアプリのJDBC操作を計測する

Note

2026-02-04 datasource-micrometer-opentelemetryの説明を追加。

Datasource Micrometer (ドキュメントリンク) は、JDBC の Proxy であり、JDBC の操作を Micrometer の Observation API で計測して、Observability を高められます。Trace、Metrics だけでなく、SQL ログやスローログも取得可能です。現在は Micrometer とは独立した開発になっていますが、Micrometer と近い位置で開発されています。

Datasource Micrometer は 2025 年末あたりから Spring Initializr からも選択可能になり、便利に利用できるようになりました。Datasource Micrometer 1 系は Spring Boot 3.5 系で、Datasource Micrometer 2 系は Spring Boot 4 系で利用可能です。
image

題材アプリとして、簡単な Counter API を作成します。次のコマンドでプロジェクトの雛形を作成します。

curl -s https://start.spring.io/starter.tgz \
       -d artifactId=counter-api\
       -d name=counter-api \
       -d baseDir=counter-api \
       -d packageName=com.example \
       -d dependencies=web,jdbc,postgresql,actuator,configuration-processor,opentelemetry,datasource-micrometer,testcontainers \
       -d type=maven-project \
       -d applicationName=CounterApiApplication | tar -xzvf -
cd counter-api

すでに Datasource Micrometer の dependency は設定済みです。dependenciesにdatasource-micrometerを含めない場合に比べて、以下の設定が追加されます。

30a31
> 		<datasource-micrometer.version>2.1.0</datasource-micrometer.version>
48a50,57
> 		<dependency>
> 			<groupId>net.ttddyy.observation</groupId>
> 			<artifactId>datasource-micrometer-opentelemetry</artifactId>
> 		</dependency>
> 		<dependency>
> 			<groupId>net.ttddyy.observation</groupId>
> 			<artifactId>datasource-micrometer-spring-boot</artifactId>
> 		</dependency>
95a105,115
> 	<dependencyManagement>
> 		<dependencies>
> 			<dependency>
> 				<groupId>net.ttddyy.observation</groupId>
> 				<artifactId>datasource-micrometer-bom</artifactId>
> 				<version>${datasource-micrometer.version}</version>
> 				<type>pom</type>
> 				<scope>import</scope>
> 			</dependency>
> 		</dependencies>
> 	</dependencyManagement>

dependenciesにopentelemetryも含まれる場合は、datasource-micrometer-opentelemetryも合わせて追加されます。

せっかくなので こちらの記事 で紹介した、OpenTelemetry Logback Appender の AutoConfiguration も追加します。

<dependency>
    <groupId>am.ik.spring.opentelemetry</groupId>
    <artifactId>otel-logs-autoconfigure</artifactId>
    <version>0.5.0</version>
</dependency>

簡単な API を実装します。

cat <<EOF > src/main/java/com/example/CounterController.java
package com.example;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CounterController {

	private final JdbcClient jdbcClient;

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	public CounterController(JdbcClient jdbcClient) {
		this.jdbcClient = jdbcClient;
	}

	@PostMapping(path = "/counter")
	@Transactional
	public CounterResponse increment(@RequestBody CounterRequest request) {
		CounterResponse counterResponse = this.jdbcClient.sql("""
				INSERT INTO counters (entry_id, counter)
				VALUES (?, 1)
				ON CONFLICT (entry_id)
				DO UPDATE
				  SET counter = counters.counter + 1
				RETURNING entry_id, counter
				""").param(request.entryId()).query(CounterResponse.class).single();
		logger.atInfo()
			.addKeyValue("entryId", counterResponse.entryId())
			.addKeyValue("counter", counterResponse.counter())
			.log("event=increment entryId={} counter={}", counterResponse.entryId(), counterResponse.counter());
		return counterResponse;
	}

	@GetMapping(path = "/counter")
	public List<CounterResponse> getAll() {
		return this.jdbcClient.sql("""
				SELECT entry_id, counter FROM counters ORDER BY counter DESC
				""").query(CounterResponse.class).list();
	}

	public record CounterRequest(int entryId) {
	}

	public record CounterResponse(int entryId, long counter) {
	}

}
EOF

application.properties を設定します。Datasource Micrometer による SQL ログとスロークエリログの設定を行います。

cat <<EOF >> src/main/resources/application.properties
jdbc.datasource-proxy.json-format=true
jdbc.datasource-proxy.logging=slf4j
jdbc.datasource-proxy.multiline=false
jdbc.datasource-proxy.query.enable-logging=true
jdbc.datasource-proxy.slow-query.enable-logging=true
jdbc.datasource-proxy.slow-query.threshold=5
logging.level.net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener=debug
management.opentelemetry.instrumentation.logback-appender.capture-experimental-attributes=true
management.opentelemetry.instrumentation.logback-appender.capture-key-value-pair-attributes=true
management.otlp.metrics.export.base-time-unit=seconds
management.otlp.metrics.export.step=30s
management.tracing.sampling.probability=1.0
spring.sql.init.mode=always
EOF

簡単なスキーマを定義します。

cat <<EOF > src/main/resources/schema.sql
CREATE TABLE IF NOT EXISTS counters
(
    entry_id BIGINT PRIMARY KEY,
    counter  BIGINT NOT NULL
);
EOF

次のコマンドを実行すると、Testcontainers を使った PostgreSQL と LGTM スタック のローカル開発用コンテナが立ち上がります (src/test/java/com/example/TestcontainersConfiguration.java を確認してみてください)。DataSource の設定や OTLP エンドポイントの設定は自動で行われます。

./mvnw spring-boot:test-run

起動時に次のようなログが出力され、Grafana の URL がわかります。

2026-01-26T13:08:41.512+09:00  INFO 28654 --- [counter-api] [           main] [                                                 ] tc.grafana/otel-lgtm:latest              : Creating container for image: grafana/otel-lgtm:latest
2026-01-26T13:08:41.579+09:00  INFO 28654 --- [counter-api] [           main] [                                                 ] tc.grafana/otel-lgtm:latest              : Container grafana/otel-lgtm:latest is starting: e0bb34b52593b45152a1236b6766c8ba2b1d3949b5ade2f4126dfbe8bf681790
2026-01-26T13:08:46.762+09:00  INFO 28654 --- [counter-api] [           main] [                                                 ] tc.grafana/otel-lgtm:latest              : Container grafana/otel-lgtm:latest started in PT5.249929S
2026-01-26T13:08:46.762+09:00  INFO 28654 --- [counter-api] [           main] [                                                 ] o.t.grafana.LgtmStackContainer           : Access to the Grafana dashboard: http://localhost:35507
2026-01-26T13:08:46.862+09:00  INFO 28654 --- [counter-api] [           main] [                                                 ] i.m.c.instrument.push.PushMeterRegistry  : Publishing metrics for OtlpMeterRegistry every 30s to http://localhost:35511/v1/metrics with resource attributes {service.name=counter-api}

適当にリクエストを送ります。

curl -s http://localhost:8080/counter --json '{"entryId":100}'
curl -s http://localhost:8080/counter 

次のような SQL ログを確認できます。

2026-01-26T14:11:44.165+09:00 DEBUG 28654 --- [counter-api] [nio-8080-exec-1] [382b35f274086c23f3a45d4c94cfb785-6c507dd2b93e2c20] n.t.d.l.l.SLF4JQueryLoggingListener      : {"name":"test", "connection":4, "time":2, "success":true, "type":"Prepared", "batch":false, "querySize":1, "batchSize":0, "query":["INSERT INTO counters (entry_id, counter)\nVALUES (?, 1)\nON CONFLICT (entry_id)\nDO UPDATE\n  SET counter = counters.counter + 1\nRETURNING entry_id, counter\n"], "params":[["100"]]}
2026-01-26T14:11:44.169+09:00  INFO 28654 --- [counter-api] [nio-8080-exec-1] [382b35f274086c23f3a45d4c94cfb785-25067aeeaad66229] com.example.CounterController            : event=increment entryId=100 counter=1
2026-01-26T14:11:45.923+09:00 DEBUG 28654 --- [counter-api] [nio-8080-exec-3] [1a250337750c5fbed850028f8fe5c636-72c7abbd0c14e831] n.t.d.l.l.SLF4JQueryLoggingListener      : {"name":"test", "connection":5, "time":0, "success":true, "type":"Prepared", "batch":false, "querySize":1, "batchSize":0, "query":["SELECT entry_id, counter FROM counters ORDER BY counter DESC\n"], "params":[[]]}

次に、以下のコマンドで vegeta を使い、負荷をかけてみます。

for round in $(seq 20); do
  echo "=== Round $round/20 ==="
  for i in $(seq 3000); do
    id=$((RANDOM % 50 + 1))
    if [ $((RANDOM % 3)) -eq 0 ]; then
      echo '{"method":"GET","url":"http://localhost:8080/counter"}'
    else
      echo '{"method":"POST","url":"http://localhost:8080/counter","header":{"Content-Type":["application/json"]},"body":"'$(echo -n "{\"entryId\":$id}" | base64)'"}'
    fi
  done | vegeta attack -rate=100 -duration=30s -format=json | vegeta report
done

Grafanaで確認

Grafana にアクセスします。Grafana の URL はログに出力されています。

image

Traces

左のメニューの Drilldown から "Traces" を選択します。

image

Span Rate の ◇ マーク (Exemplar、代表点) をクリックし、"View trace" リンクをクリックします。

image

そのリクエストの Trace View が表示されます。

image

"connection" Span をクリックすると、commitのタイミング/回数や時間を確認することができます。

image

"INSERT ..." Span をクリックすると、そのタイミングでの SQL を確認することができます。

image

datasource-micrometer-opentelemetryが追加されることにより、Attribute名は OpenTelemetry semantic conventions に準拠するようになりました。

"Log" ボタンをクリックしてみます。 image

この Trace におけるログを確認することができます。

image

Metrics

次に、左のメニューの Drilldown から "Metrics" を選択します。"Prefix filters" で "jdbc" を選択すると、表示を Datasource Micrometer が送信したメトリクスのみに絞ることができます。

image

取得できるメトリクスは こちらのドキュメント を参照してください。Connection の開始から終了までの時間、コミット・ロールバックの回数、クエリの実行時間、回数などが取得できます。

Logs

最後に、左のメニューの Drilldown から "Logs" を選択します。

image

"counter-api" で "Show logs" ボタンをクリックします。

image

特定のログをクリックし、"Links" の "Trace" ボタンをクリックすると、

image

また、Trace View に飛ぶことができ、このログに関する SQL などを確認することもできます。

image


Datasource Micrometer を使い、Spring Boot アプリの JDBC 操作を計測する方法を紹介しました。Spring Initializr から利用できるので、簡単に組み込むことができます。ぜひ、使ってみてください。