Skip to content

Commit 57d841b

Browse files
feat: Suppress logging of deprecation warning for id() by default.
Signed-off-by: Michael Simons <[email protected]>
1 parent 341fc7d commit 57d841b

File tree

5 files changed

+189
-38
lines changed

5 files changed

+189
-38
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Collection;
1919
import java.util.Map;
2020
import java.util.Optional;
21+
import java.util.concurrent.atomic.AtomicBoolean;
2122
import java.util.function.BiFunction;
2223
import java.util.function.Function;
2324
import java.util.function.Supplier;
@@ -44,6 +45,12 @@
4445
@API(status = API.Status.STABLE, since = "6.0")
4546
public interface Neo4jClient {
4647

48+
/**
49+
* This is a public API introduced to turn the logging of the infamous warning back on.
50+
* {@code The query used a deprecated function: `id`.}
51+
*/
52+
AtomicBoolean SUPPRESS_ID_DEPRECATIONS = new AtomicBoolean(true);
53+
4754
LogAccessor cypherLog = new LogAccessor(LogFactory.getLog("org.springframework.data.neo4j.cypher"));
4855
LogAccessor log = new LogAccessor(LogFactory.getLog(Neo4jClient.class));
4956

src/main/java/org/springframework/data/neo4j/core/ResultSummaries.java

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@
1616
package org.springframework.data.neo4j.core;
1717

1818
import java.util.function.Consumer;
19+
import java.util.function.Predicate;
20+
import java.util.regex.Pattern;
1921
import java.util.stream.Collectors;
2022
import java.util.stream.Stream;
2123

2224
import org.apache.commons.logging.LogFactory;
23-
import org.neo4j.driver.NotificationCategory;
25+
import org.neo4j.driver.NotificationClassification;
26+
import org.neo4j.driver.NotificationSeverity;
2427
import org.neo4j.driver.summary.InputPosition;
2528
import org.neo4j.driver.summary.Notification;
2629
import org.neo4j.driver.summary.Plan;
2730
import org.neo4j.driver.summary.ResultSummary;
2831
import org.springframework.core.log.LogAccessor;
32+
import org.springframework.lang.Nullable;
2933

3034
/**
3135
* Utility class for dealing with result summaries.
@@ -46,6 +50,8 @@ final class ResultSummaries {
4650
private static final LogAccessor cypherSecurityNotificationLog = new LogAccessor(LogFactory.getLog("org.springframework.data.neo4j.cypher.security"));
4751
private static final LogAccessor cypherTopologyNotificationLog = new LogAccessor(LogFactory.getLog("org.springframework.data.neo4j.cypher.topology"));
4852

53+
private static final Pattern DEPRECATED_ID_PATTERN = Pattern.compile("(?im)The query used a deprecated function: `id`\\.");
54+
4955
/**
5056
* Does some post-processing on the giving result summary, especially logging all notifications
5157
* and potentially query plans.
@@ -65,48 +71,55 @@ private static void logNotifications(ResultSummary resultSummary) {
6571
return;
6672
}
6773

74+
boolean supressIdDeprecations = Neo4jClient.SUPPRESS_ID_DEPRECATIONS.getAcquire();
75+
Predicate<Notification> isDeprecationWarningForId;
76+
try {
77+
isDeprecationWarningForId = notification -> supressIdDeprecations
78+
&& notification.classification().orElse(NotificationClassification.UNRECOGNIZED)
79+
== NotificationClassification.DEPRECATION && DEPRECATED_ID_PATTERN.matcher(notification.description())
80+
.matches();
81+
} finally {
82+
Neo4jClient.SUPPRESS_ID_DEPRECATIONS.setRelease(supressIdDeprecations);
83+
}
84+
6885
String query = resultSummary.query().text();
6986
resultSummary.notifications()
70-
.forEach(notification -> {
71-
LogAccessor log = notification.category()
72-
.map(ResultSummaries::getLogAccessor)
73-
.orElse(Neo4jClient.cypherLog);
74-
Consumer<String> logFunction =
75-
switch (notification.severity()) {
76-
case "WARNING" -> log::warn;
77-
case "INFORMATION" -> log::info;
78-
default -> log::debug;
79-
};
87+
.stream().filter(Predicate.not(isDeprecationWarningForId))
88+
.forEach(notification -> notification.severityLevel().ifPresent(severityLevel -> {
89+
var category = notification.classification().orElse(null);
90+
91+
var logger = getLogAccessor(category);
92+
Consumer<String> logFunction;
93+
if (severityLevel == NotificationSeverity.WARNING) {
94+
logFunction = logger::warn;
95+
} else if (severityLevel == NotificationSeverity.INFORMATION) {
96+
logFunction = logger::info;
97+
} else if (severityLevel == NotificationSeverity.OFF) {
98+
logFunction = (String message) -> {
99+
};
100+
} else {
101+
logFunction = logger::debug;
102+
}
103+
80104
logFunction.accept(ResultSummaries.format(notification, query));
81-
});
105+
}));
82106
}
83107

84-
private static LogAccessor getLogAccessor(NotificationCategory category) {
85-
if (category == NotificationCategory.HINT) {
86-
return cypherHintNotificationLog;
87-
}
88-
if (category == NotificationCategory.DEPRECATION) {
89-
return cypherDeprecationNotificationLog;
90-
}
91-
if (category == NotificationCategory.PERFORMANCE) {
92-
return cypherPerformanceNotificationLog;
93-
}
94-
if (category == NotificationCategory.GENERIC) {
95-
return cypherGenericNotificationLog;
96-
}
97-
if (category == NotificationCategory.UNSUPPORTED) {
98-
return cypherUnsupportedNotificationLog;
99-
}
100-
if (category == NotificationCategory.UNRECOGNIZED) {
101-
return cypherUnrecognizedNotificationLog;
102-
}
103-
if (category == NotificationCategory.SECURITY) {
104-
return cypherSecurityNotificationLog;
105-
}
106-
if (category == NotificationCategory.TOPOLOGY) {
107-
return cypherTopologyNotificationLog;
108+
private static LogAccessor getLogAccessor(@Nullable NotificationClassification category) {
109+
if (category == null) {
110+
return Neo4jClient.cypherLog;
108111
}
109-
return Neo4jClient.cypherLog;
112+
return switch (category) {
113+
case HINT -> cypherHintNotificationLog;
114+
case DEPRECATION -> cypherDeprecationNotificationLog;
115+
case PERFORMANCE -> cypherPerformanceNotificationLog;
116+
case GENERIC -> cypherGenericNotificationLog;
117+
case UNSUPPORTED -> cypherUnsupportedNotificationLog;
118+
case UNRECOGNIZED -> cypherUnrecognizedNotificationLog;
119+
case SECURITY -> cypherSecurityNotificationLog;
120+
case TOPOLOGY -> cypherTopologyNotificationLog;
121+
default -> Neo4jClient.cypherLog;
122+
};
110123
}
111124

112125
/**

src/test/java/org/springframework/data/neo4j/integration/issues/pure_element_id/AbstractElementIdTestBase.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ static void assertThatLogMessageDoNotIndicateIDUsage(LogbackCapture logbackCaptu
8080
assertThat(formattedMessages)
8181
.noneMatch(s -> s.contains("Neo.ClientNotification.Statement.FeatureDeprecationWarning") ||
8282
s.contains("The query used a deprecated function. ('id' is no longer supported)") ||
83+
s.contains("The query used a deprecated function: `id`.") ||
8384
s.matches("(?s).*toString\\(id\\(.*")); // No deprecations are logged when deprecated function call is nested. Anzeige ist raus.
8485
}
8586
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2011-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.misc;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatCode;
20+
21+
import java.util.function.Predicate;
22+
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.condition.EnabledIf;
25+
import org.junit.jupiter.api.extension.ExtendWith;
26+
import org.neo4j.driver.Driver;
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.ComponentScan;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.data.neo4j.core.Neo4jClient;
32+
import org.springframework.data.neo4j.integration.bookmarks.DatabaseInitializer;
33+
import org.springframework.data.neo4j.test.LogbackCapture;
34+
import org.springframework.data.neo4j.test.LogbackCapturingExtension;
35+
import org.springframework.data.neo4j.test.Neo4jExtension;
36+
import org.springframework.data.neo4j.test.Neo4jImperativeTestConfiguration;
37+
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
38+
import org.springframework.data.neo4j.test.ServerVersion;
39+
import org.springframework.transaction.annotation.EnableTransactionManagement;
40+
41+
import ch.qos.logback.classic.Level;
42+
import ch.qos.logback.classic.Logger;
43+
44+
@Neo4jIntegrationTest
45+
@ExtendWith(LogbackCapturingExtension.class)
46+
class IdLoggingIT {
47+
48+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
49+
50+
@Configuration
51+
@EnableTransactionManagement
52+
@ComponentScan
53+
static class Config extends Neo4jImperativeTestConfiguration {
54+
55+
@Bean
56+
DatabaseInitializer databaseInitializer(Driver driver) {
57+
return new DatabaseInitializer(driver);
58+
}
59+
60+
@Bean
61+
public Driver driver() {
62+
return neo4jConnectionSupport.getDriver();
63+
}
64+
65+
@Override
66+
public boolean isCypher5Compatible() {
67+
return neo4jConnectionSupport.isCypher5SyntaxCompatible();
68+
}
69+
}
70+
71+
static boolean isGreaterThanOrEqualNeo4j5() {
72+
return neo4jConnectionSupport.getServerVersion().greaterThanOrEqual(ServerVersion.v5_0_0);
73+
}
74+
75+
@EnabledIf("isGreaterThanOrEqualNeo4j5")
76+
@Test
77+
void idWarningShouldBeSuppressed(LogbackCapture logbackCapture, @Autowired Neo4jClient neo4jClient) {
78+
79+
// Was not able to combine the autowiring of capture and the client here
80+
for (Boolean enabled : new Boolean[] {true, false, null}) {
81+
82+
Logger logger = (Logger) org.slf4j.LoggerFactory.getLogger("org.springframework.data.neo4j.cypher.deprecation");
83+
Level originalLevel = logger.getLevel();
84+
logger.setLevel(Level.DEBUG);
85+
86+
Boolean oldValue = null;
87+
if (enabled != null) {
88+
oldValue = Neo4jClient.SUPPRESS_ID_DEPRECATIONS.getAndSet(enabled);
89+
}
90+
91+
try {
92+
assertThatCode(() -> neo4jClient.query(
93+
"CREATE (n:XXXIdTest) RETURN id(n)").fetch().all()).doesNotThrowAnyException();
94+
Predicate<String> stringPredicate = msg -> msg.contains(
95+
"Neo.ClientNotification.Statement.FeatureDeprecationWarning");
96+
97+
if (enabled == null || enabled) {
98+
assertThat(logbackCapture.getFormattedMessages()).noneMatch(stringPredicate);
99+
} else {
100+
assertThat(logbackCapture.getFormattedMessages()).anyMatch(stringPredicate);
101+
}
102+
} finally {
103+
logbackCapture.clear();
104+
logger.setLevel(originalLevel);
105+
if (oldValue != null) {
106+
Neo4jClient.SUPPRESS_ID_DEPRECATIONS.set(oldValue);
107+
}
108+
}
109+
}
110+
}
111+
112+
@EnabledIf("isGreaterThanOrEqualNeo4j5")
113+
@Test
114+
void otherDeprecationsWarningsShouldNotBeSuppressed(LogbackCapture logbackCapture, @Autowired Neo4jClient neo4jClient) {
115+
116+
Logger logger = (Logger) org.slf4j.LoggerFactory.getLogger("org.springframework.data.neo4j.cypher.deprecation");
117+
Level originalLevel = logger.getLevel();
118+
logger.setLevel(Level.DEBUG);
119+
120+
try {
121+
assertThatCode(() -> neo4jClient.query(
122+
"MATCH (n) CALL {WITH n RETURN count(n) AS cnt} RETURN *").fetch().all()).doesNotThrowAnyException();
123+
assertThat(logbackCapture.getFormattedMessages())
124+
.anyMatch(msg -> msg.contains("Neo.ClientNotification.Statement.FeatureDeprecationWarning"))
125+
.anyMatch(msg -> msg.contains("CALL subquery without a variable scope clause is now deprecated. Use CALL (n) { ... }"));
126+
} finally {
127+
logger.setLevel(originalLevel);
128+
}
129+
}
130+
}

src/test/java/org/springframework/data/neo4j/test/LogbackCapture.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void start() {
6161
this.listAppender.start();
6262
}
6363

64-
void clear() {
64+
public void clear() {
6565
this.resetLogLevel();
6666
this.listAppender.list.clear();
6767
}

0 commit comments

Comments
 (0)