diff --git a/module/spring-boot-neo4j/build.gradle b/module/spring-boot-neo4j/build.gradle index 35066a730f1..7f846c191d3 100644 --- a/module/spring-boot-neo4j/build.gradle +++ b/module/spring-boot-neo4j/build.gradle @@ -36,6 +36,7 @@ dependencies { optional(project(":core:spring-boot-testcontainers")) optional(project(":module:spring-boot-health")) optional("org.testcontainers:neo4j") + optional("org.neo4j.driver:neo4j-java-driver-observation-micrometer") dockerTestImplementation(project(":core:spring-boot-test")) dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) diff --git a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java index 4dde54f423f..97d108eac2d 100644 --- a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java +++ b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfiguration.java @@ -21,9 +21,12 @@ import java.net.URI; import java.time.Duration; import java.util.List; import java.util.Locale; +import java.util.ServiceLoader; import java.util.concurrent.TimeUnit; +import io.micrometer.observation.ObservationRegistry; import org.jspecify.annotations.Nullable; +import org.neo4j.bolt.connection.BoltConnectionProviderFactory; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokenManager; import org.neo4j.driver.AuthTokens; @@ -32,6 +35,7 @@ import org.neo4j.driver.Config.TrustStrategy; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; import org.neo4j.driver.internal.Scheme; +import org.neo4j.driver.observation.micrometer.MicrometerObservationProvider; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -63,6 +67,20 @@ import org.springframework.util.StringUtils; @EnableConfigurationProperties(Neo4jProperties.class) public final class Neo4jAutoConfiguration { + private static final boolean HAS_DRIVER_METRICS; + + static { + boolean metricsObservationProviderFound = true; + try { + Class.forName("org.neo4j.driver.observation.micrometer.MicrometerObservationProvider", false, + Neo4jAutoConfiguration.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + metricsObservationProviderFound = false; + } + HAS_DRIVER_METRICS = metricsObservationProviderFound; + } + @Bean @ConditionalOnMissingBean(Neo4jConnectionDetails.class) PropertiesNeo4jConnectionDetails neo4jConnectionDetails(Neo4jProperties properties, @@ -73,10 +91,11 @@ public final class Neo4jAutoConfiguration { @Bean @ConditionalOnMissingBean Driver neo4jDriver(Neo4jProperties properties, Environment environment, Neo4jConnectionDetails connectionDetails, - ObjectProvider configBuilderCustomizers) { + ObjectProvider configBuilderCustomizers, + ObjectProvider observationRegistryProvider) { Config config = mapDriverConfig(properties, connectionDetails, - configBuilderCustomizers.orderedStream().toList()); + configBuilderCustomizers.orderedStream().toList(), observationRegistryProvider); AuthTokenManager authTokenManager = connectionDetails.getAuthTokenManager(); if (authTokenManager != null) { return GraphDatabase.driver(connectionDetails.getUri(), authTokenManager, config); @@ -86,29 +105,29 @@ public final class Neo4jAutoConfiguration { } Config mapDriverConfig(Neo4jProperties properties, Neo4jConnectionDetails connectionDetails, - List customizers) { + List customizers, + ObjectProvider observationRegistryProvider) { Config.ConfigBuilder builder = Config.builder(); - configurePoolSettings(builder, properties.getPool()); + configurePoolSettings(builder, properties.getPool(), observationRegistryProvider); URI uri = connectionDetails.getUri(); String scheme = (uri != null) ? uri.getScheme() : "bolt"; configureDriverSettings(builder, properties, isSimpleScheme(scheme)); - builder.withLogging(new Neo4jSpringJclLogging()); customizers.forEach((customizer) -> customizer.customize(builder)); return builder.build(); } private boolean isSimpleScheme(String scheme) { String lowerCaseScheme = scheme.toLowerCase(Locale.ENGLISH); - try { - Scheme.validateScheme(lowerCaseScheme); - } - catch (IllegalArgumentException ex) { + if (!ServiceLoader.load(BoltConnectionProviderFactory.class) + .stream() + .anyMatch((p) -> p.get().supports(lowerCaseScheme))) { throw new IllegalArgumentException(String.format("'%s' is not a supported scheme.", scheme)); } - return lowerCaseScheme.equals("bolt") || lowerCaseScheme.equals("neo4j"); + return !Scheme.isSecurityScheme(lowerCaseScheme); } - private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool) { + private void configurePoolSettings(Config.ConfigBuilder builder, Pool pool, + ObjectProvider observationRegistryProvider) { if (pool.isLogLeakedSessions()) { builder.withLeakedSessionsLogging(); } @@ -120,12 +139,11 @@ public final class Neo4jAutoConfiguration { builder.withMaxConnectionLifetime(pool.getMaxConnectionLifetime().toMillis(), TimeUnit.MILLISECONDS); builder.withConnectionAcquisitionTimeout(pool.getConnectionAcquisitionTimeout().toMillis(), TimeUnit.MILLISECONDS); - if (pool.isMetricsEnabled()) { - builder.withDriverMetrics(); - } - else { - builder.withoutDriverMetrics(); - } + observationRegistryProvider.ifAvailable((orp) -> { + if (pool.isMetricsEnabled() && HAS_DRIVER_METRICS) { + builder.withObservationProvider(MicrometerObservationProvider.builder(orp).build()); + } + }); } private void configureDriverSettings(Config.ConfigBuilder builder, Neo4jProperties properties, diff --git a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jSpringJclLogging.java b/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jSpringJclLogging.java deleted file mode 100644 index 822433d1611..00000000000 --- a/module/spring-boot-neo4j/src/main/java/org/springframework/boot/neo4j/autoconfigure/Neo4jSpringJclLogging.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-present 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.boot.neo4j.autoconfigure; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.neo4j.driver.Logger; -import org.neo4j.driver.Logging; - -/** - * Shim to use Spring JCL implementation, delegating all the hard work of deciding the - * underlying system to Spring and Spring Boot. - * - * @author Michael J. Simons - */ -class Neo4jSpringJclLogging implements Logging { - - /** - * This prefix gets added to the log names the driver requests to add some namespace - * around it in a bigger application scenario. - */ - private static final String AUTOMATIC_PREFIX = "org.neo4j.driver."; - - @Override - public Logger getLog(String name) { - String requestedLog = name; - if (!requestedLog.startsWith(AUTOMATIC_PREFIX)) { - requestedLog = AUTOMATIC_PREFIX + name; - } - Log springJclLog = LogFactory.getLog(requestedLog); - return new SpringJclLogger(springJclLog); - } - - private static final class SpringJclLogger implements Logger { - - private final Log delegate; - - SpringJclLogger(Log delegate) { - this.delegate = delegate; - } - - @Override - public void error(String message, Throwable cause) { - this.delegate.error(message, cause); - } - - @Override - public void info(String format, Object... params) { - this.delegate.info(String.format(format, params)); - } - - @Override - public void warn(String format, Object... params) { - this.delegate.warn(String.format(format, params)); - } - - @Override - public void warn(String message, Throwable cause) { - this.delegate.warn(message, cause); - } - - @Override - public void debug(String format, Object... params) { - if (isDebugEnabled()) { - this.delegate.debug(String.format(format, params)); - } - } - - @Override - public void debug(String message, Throwable throwable) { - if (isDebugEnabled()) { - this.delegate.debug(message, throwable); - } - } - - @Override - public void trace(String format, Object... params) { - if (isTraceEnabled()) { - this.delegate.trace(String.format(format, params)); - } - } - - @Override - public boolean isTraceEnabled() { - return this.delegate.isTraceEnabled(); - } - - @Override - public boolean isDebugEnabled() { - return this.delegate.isDebugEnabled(); - } - - } - -} diff --git a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java index e5bbdc20998..83298f89c25 100644 --- a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java +++ b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jAutoConfigurationTests.java @@ -21,7 +21,9 @@ import java.io.IOException; import java.net.URI; import java.time.Duration; import java.util.Arrays; +import java.util.stream.Stream; +import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -32,6 +34,7 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Config.ConfigBuilder; import org.neo4j.driver.Driver; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration.PropertiesNeo4jConnectionDetails; @@ -217,13 +220,6 @@ class Neo4jAutoConfigurationTests { .withMessage("Cannot specify both username ('Farin') and kerberos ticket ('AABBCCDDEE')"); } - @Test - void poolWithMetricsEnabled() { - Neo4jProperties properties = new Neo4jProperties(); - properties.getPool().setMetricsEnabled(true); - assertThat(mapDriverConfig(properties).isMetricsEnabled()).isTrue(); - } - @Test void poolWithLogLeakedSessions() { Neo4jProperties properties = new Neo4jProperties(); @@ -322,14 +318,15 @@ class Neo4jAutoConfigurationTests { .isEqualTo(Config.TrustStrategy.Strategy.TRUST_SYSTEM_CA_SIGNED_CERTIFICATES); } - @Test - void driverConfigShouldBeConfiguredToUseUseSpringJclLogging() { - assertThat(mapDriverConfig(new Neo4jProperties()).logging()).isInstanceOf(Neo4jSpringJclLogging.class); - } - private Config mapDriverConfig(Neo4jProperties properties, ConfigBuilderCustomizer... customizers) { return new Neo4jAutoConfiguration().mapDriverConfig(properties, - new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers)); + new PropertiesNeo4jConnectionDetails(properties, null), Arrays.asList(customizers), + new ObjectProvider<>() { + @Override + public Stream stream() { + return Stream.empty(); + } + }); } } diff --git a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jPropertiesTests.java b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jPropertiesTests.java index 3e73edfe457..3a6be9ba8fe 100644 --- a/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jPropertiesTests.java +++ b/module/spring-boot-neo4j/src/test/java/org/springframework/boot/neo4j/autoconfigure/Neo4jPropertiesTests.java @@ -38,7 +38,6 @@ class Neo4jPropertiesTests { void poolSettingsHaveConsistentDefaults() { Config defaultConfig = Config.defaultConfig(); Pool pool = new Neo4jProperties().getPool(); - assertThat(pool.isMetricsEnabled()).isEqualTo(defaultConfig.isMetricsEnabled()); assertThat(pool.isLogLeakedSessions()).isEqualTo(defaultConfig.logLeakedSessions()); assertThat(pool.getMaxConnectionPoolSize()).isEqualTo(defaultConfig.maxConnectionPoolSize()); assertDuration(pool.getIdleTimeBeforeConnectionTest(), defaultConfig.idleTimeBeforeConnectionTest()); diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index 20fceb10085..592e29a7c5f 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -1621,7 +1621,7 @@ bom { ] } } - library("Neo4j Java Driver", "5.28.9") { + library("Neo4j Java Driver", "6.0.0") { alignWith { version { from "org.springframework.data:spring-data-neo4j" @@ -1630,7 +1630,8 @@ bom { } group("org.neo4j.driver") { modules = [ - "neo4j-java-driver" + "neo4j-java-driver", + "neo4j-java-driver-observation-micrometer" ] } links {