This is an automated email from the ASF dual-hosted git repository.
shishkovilja pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-extensions.git
The following commit(s) were added to refs/heads/master by this push:
new a4e7c11a IGNITE-27122 Enhance ConflictResolver logging (#329)
a4e7c11a is described below
commit a4e7c11ad939220b7147f971113af676b38180bd
Author: Ilya Shishkov <[email protected]>
AuthorDate: Tue Jan 13 18:22:04 2026 +0300
IGNITE-27122 Enhance ConflictResolver logging (#329)
---
.../CacheVersionConflictResolverImpl.java | 71 ++++++++++++----
.../ignite/cdc/CacheConflictOperationsTest.java | 98 ++++++++++++++++++++--
...heConflictOperationsWithCustomResolverTest.java | 26 +++++-
3 files changed, 170 insertions(+), 25 deletions(-)
diff --git
a/modules/cdc-ext/src/main/java/org/apache/ignite/cdc/conflictresolve/CacheVersionConflictResolverImpl.java
b/modules/cdc-ext/src/main/java/org/apache/ignite/cdc/conflictresolve/CacheVersionConflictResolverImpl.java
index 1e275a7b..ed10b336 100644
---
a/modules/cdc-ext/src/main/java/org/apache/ignite/cdc/conflictresolve/CacheVersionConflictResolverImpl.java
+++
b/modules/cdc-ext/src/main/java/org/apache/ignite/cdc/conflictresolve/CacheVersionConflictResolverImpl.java
@@ -17,6 +17,8 @@
package org.apache.ignite.cdc.conflictresolve;
+import java.util.Objects;
+import org.apache.ignite.IgniteCommonsSystemProperties;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.internal.processors.cache.CacheObjectValueContext;
@@ -113,7 +115,6 @@ public class CacheVersionConflictResolverImpl implements
CacheVersionConflictRes
* @param <V> Key type.
* @return {@code True} is should use new entry.
*/
- @SuppressWarnings({"unchecked", "rawtypes"})
protected <K, V> boolean isUseNew(
CacheObjectValueContext ctx,
GridCacheVersionedEntryEx<K, V> oldEntry,
@@ -147,28 +148,57 @@ public class CacheVersionConflictResolverImpl implements
CacheVersionConflictRes
return value(oldVal).compareTo(value(newVal)) < 0;
}
catch (Exception e) {
- log.error(
- "Error while resolving replication conflict. [field="
+ conflictResolveField + ", key=" + newEntry.key() + ']',
- e
- );
+ log.error("Error during field-based conflicts resolving " +
+ "[key=" + safeKeyToString(newEntry) +
+ ", field=" + conflictResolveField + ']',
+ e);
}
}
+ else {
+ log.warning(
+ "Field-based conflicts resolving is enabled, but at least
one of entries has null value " +
+ "[key=" + safeKeyToString(newEntry) +
+ ", oldValIsNull=" + (oldVal == null) +
+ ", newValIsNull=" + (newVal == null) + ']');
+ }
}
+ else
+ log.warning("Field-based conflicts resolving is not enabled: key="
+ safeKeyToString(newEntry));
log.error("Conflict can't be resolved, " + (newEntry.value(ctx) ==
null ? "remove" : "update") + " ignored " +
- "[key=" + newEntry.key() + ", fromCluster=" +
newEntry.dataCenterId() + ", toCluster=" + oldEntry.dataCenterId() + ']');
+ "[key=" + safeKeyToString(newEntry) +
+ ", fromCluster=" + newEntry.dataCenterId() +
+ ", toCluster=" + oldEntry.dataCenterId() + ']');
// Ignoring update.
return false;
}
/** @return Conflict resolve field value. */
- protected Comparable value(Object val) {
+ protected <T> Comparable<T> value(Object val) {
return (val instanceof BinaryObject)
? ((BinaryObject)val).field(conflictResolveField)
: U.field(val, conflictResolveField);
}
+ /** @return Sensitive-safe string representation of an entry key. */
+ private static <K, V> String safeKeyToString(GridCacheVersionedEntryEx<K,
V> entry) {
+ return safeToString(entry.key());
+ }
+
+ /**
+ * @param obj Object.
+ *
+ * @return Sensitive-safe string representation of an object.
+ * @see IgniteCommonsSystemProperties#IGNITE_TO_STRING_INCLUDE_SENSITIVE
+ */
+ private static String safeToString(Object obj) {
+ if (obj instanceof BinaryObject)
+ return Objects.toString(obj);
+
+ return S.includeSensitive() ? Objects.toString(obj) :
"[sensitiveDataHash=" + Objects.hashCode(obj) + ']';
+ }
+
/** */
private <K, V> void debugResolve(
CacheObjectValueContext ctx,
@@ -179,32 +209,39 @@ public class CacheVersionConflictResolverImpl implements
CacheVersionConflictRes
Object oldVal = conflictResolveFieldEnabled ? oldEntry.value(ctx) :
null;
Object newVal = conflictResolveFieldEnabled ? newEntry.value(ctx) :
null;
+ String keyStr = safeKeyToString(newEntry);
+
if (oldVal != null)
- oldVal = debugValue(oldVal);
+ oldVal = debugValue(keyStr, oldVal);
if (newVal != null)
- newVal = debugValue(newVal);
+ newVal = debugValue(keyStr, newVal);
- log.debug("isUseNew[" +
- "start=" + oldEntry.isStartVersion() +
+ log.debug("isUseNew [" +
+ "key=" + keyStr +
+ ", start=" + oldEntry.isStartVersion() +
", oldVer=" + oldEntry.version() +
", newVer=" + newEntry.version() +
", oldExpire=[" + oldEntry.ttl() + "," + oldEntry.expireTime() +
']' +
", newExpire=[" + newEntry.ttl() + "," + newEntry.expireTime() +
']' +
- ", old=" + oldVal +
- ", new=" + newVal +
+ ", oldResolveField=" + oldVal +
+ ", newResolveField=" + newVal +
", res=" + useNew + ']');
}
/** @return Conflict resolve field value, or specified {@code val} if the
field not found. */
- private Object debugValue(Object val) {
+ private Object debugValue(String keyStr, Object val) {
try {
- return value(val);
+ return safeToString(value(val));
}
catch (Exception e) {
- log.debug("Can't resolve field value [field=" +
conflictResolveField + ", val=" + val + ']');
+ log.error("Can't resolve field value " +
+ "[key=" + keyStr +
+ ", field=" + conflictResolveField +
+ ", val=" + safeToString(val) + ']',
+ e);
- return val;
+ return null;
}
}
diff --git
a/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsTest.java
b/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsTest.java
index 86ef675f..b37af4f2 100644
---
a/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsTest.java
+++
b/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsTest.java
@@ -24,8 +24,10 @@ import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.binary.BinaryObject;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CacheEntry;
@@ -44,9 +46,11 @@ import
org.apache.ignite.internal.processors.cache.dr.GridCacheDrInfo;
import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
import org.apache.ignite.testframework.ListeningTestLogger;
import org.apache.ignite.testframework.LogListener;
+import org.apache.ignite.testframework.junits.WithSystemProperty;
import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -134,6 +138,8 @@ public class CacheConflictOperationsTest extends
GridCommonAbstractTest {
cachex = client.cachex(DEFAULT_CACHE_NAME);
}
+
+ listeningLog.clearListeners();
}
/** {@inheritDoc} */
@@ -259,33 +265,113 @@ public class CacheConflictOperationsTest extends
GridCommonAbstractTest {
putConflict(key, 5, conflictResolveField() != null);
}
- /** Test switching debug log level for ConflictResolver during runtime */
+ /** Test switching debug log level for ConflictResolver during runtime. */
@Test
+ @WithSystemProperty(key =
IgniteSystemProperties.IGNITE_TO_STRING_INCLUDE_SENSITIVE, value = "true")
public void testResolveDebug() throws Exception {
- String key = key("UpdateClusterUpdateReorder", otherClusterId);
+ checkResolveDebug(true);
+ }
+
+ /**
+ * Test switching debug log level for ConflictResolver during runtime.
+ * Sensitive data should be hidden.
+ */
+ @Test
+ @WithSystemProperty(key =
IgniteSystemProperties.IGNITE_TO_STRING_INCLUDE_SENSITIVE, value = "false")
+ public void testResolveDebugExcludeSensitive() throws Exception {
+ checkResolveDebug(false);
+ }
+
+ /** */
+ private void checkResolveDebug(boolean includeSensitive) {
+ String key = key("test-debug-key", otherClusterId);
- LogListener lsnr = LogListener.matches("isUseNew").build();
+ String expKeyStr = includeSensitive ? key : "[sensitiveDataHash=" +
key.hashCode() + "]";
+ LogListener lsnr = LogListener.matches("isUseNew [key=" +
expKeyStr).build();
listeningLog.registerListener(lsnr);
+ LogListener resolveFieldLsnr =
LogListener.matches(newValueString(includeSensitive)).build();
+ listeningLog.registerListener(resolveFieldLsnr);
+
Configurator.setLevel(CacheVersionConflictResolverImpl.class.getName(),
Level.DEBUG);
try {
- putConflict(key, 1, true);
+ assertFalse(lsnr.check());
+ assertFalse(resolveFieldLsnr.check());
- putConflict(key, 1, false);
+ put(key);
assertTrue(lsnr.check());
+ assertTrue(resolveFieldLsnr.check());
}
finally {
Configurator.setLevel(CacheVersionConflictResolverImpl.class.getName(),
Level.INFO);
}
lsnr.reset();
+ resolveFieldLsnr.reset();
- putConflict(key, 1, false);
+ put(key);
assertFalse(lsnr.check());
+ assertFalse(resolveFieldLsnr.check());
+ }
+
+ /** Gets expected conflict resolvable field output in log. */
+ private String newValueString(boolean includeSensitive) {
+ String newValExpStr = null;
+
+ if (conflictResolveField() != null) {
+ // Incremented in ConflictResolvableTestData#create during put.
+ long expReqId = ConflictResolvableTestData.REQUEST_ID.get() + 1;
+
+ newValExpStr = includeSensitive ? String.valueOf(expReqId) :
"[sensitiveDataHash=" +
+ Objects.hashCode(expReqId);
+ }
+
+ return "newResolveField=" + newValExpStr;
+ }
+
+ /** Test log of resolving error. */
+ @Test
+ @WithSystemProperty(key =
IgniteSystemProperties.IGNITE_TO_STRING_INCLUDE_SENSITIVE, value = "true")
+ public void testResolveError() throws Exception {
+ checkResolveError("test-non-sensitive-key", true);
+ }
+
+ /** Test log of resolving error with hidden sensitive data. */
+ @Test
+ @WithSystemProperty(key =
IgniteSystemProperties.IGNITE_TO_STRING_INCLUDE_SENSITIVE, value = "false")
+ public void testResolveErrorExcludeSensitive() throws Exception {
+ checkResolveError("test-sensitive-key", false);
+ }
+
+ /** */
+ private void checkResolveError(String keyVal, boolean includeSensitive)
throws IgniteCheckedException {
+ Assume.assumeTrue("Should not run with enabled field",
conflictResolveField() == null);
+
+ String key = key(keyVal, otherClusterId);
+
+ String expKeyStr = includeSensitive ? key : "[sensitiveDataHash=" +
key.hashCode() + "]";
+
+ LogListener warnLsnr = LogListener.matches("Field-based conflicts
resolving is not enabled: key=" +
+ expKeyStr).build();
+
+ LogListener errLsnr = LogListener.matches("Conflict can't be resolved,
update ignored " +
+ "[key=" + expKeyStr + ", fromCluster=" + otherClusterId + ",
toCluster=" + SECOND_CLUSTER_ID + "]").build();
+
+ listeningLog.registerListener(warnLsnr);
+ listeningLog.registerListener(errLsnr);
+
+ put(key);
+ assertFalse(warnLsnr.check());
+ assertFalse(errLsnr.check());
+
+ putConflict(key, 1, false);
+
+ assertTrue(warnLsnr.check());
+ assertTrue(errLsnr.check());
}
/** */
diff --git
a/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsWithCustomResolverTest.java
b/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsWithCustomResolverTest.java
index 24bd8489..b7844336 100644
---
a/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsWithCustomResolverTest.java
+++
b/modules/cdc-ext/src/test/java/org/apache/ignite/cdc/CacheConflictOperationsWithCustomResolverTest.java
@@ -24,6 +24,7 @@ import
org.apache.ignite.internal.processors.cache.version.CacheVersionConflictR
import
org.apache.ignite.internal.processors.cache.version.GridCacheVersionConflictContext;
import
org.apache.ignite.internal.processors.cache.version.GridCacheVersionedEntryEx;
import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Ignore;
import org.junit.Test;
/** Cache conflict operations test with a custom resolver. */
@@ -53,9 +54,30 @@ public class CacheConflictOperationsWithCustomResolverTest
extends CacheConflict
/** {@inheritDoc} */
@Test
+ @Ignore("LwwConflictResolver does not have logging.")
@Override public void testResolveDebug() throws Exception {
- // LWW strategy resolves conflicts in unexpected way at versioned
resolve test.
- GridTestUtils.assertThrows(log, super::testResolveDebug,
AssertionError.class, "");
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("LwwConflictResolver does not have logging.")
+ @Override public void testResolveDebugExcludeSensitive() throws Exception {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("LwwConflictResolver does not have logging.")
+ @Override public void testResolveError() throws Exception {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("LwwConflictResolver does not have logging.")
+ @Override public void testResolveErrorExcludeSensitive() throws Exception {
+ // No-op.
}
/**