This is an automated email from the ASF dual-hosted git repository.

adelapena pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 143a5e8  Add diagnostic events for guardrails
143a5e8 is described below

commit 143a5e8b064e442970182cfb349b4f0826683e85
Author: Andrés de la Peña <[email protected]>
AuthorDate: Thu Mar 3 18:17:38 2022 +0000

    Add diagnostic events for guardrails
    
    patch by Andrés de la Peña; reviewed by Berenguer Blasi and Stefan 
Miklosovic for CASSANDRA-17197
    
    Co-authored-by: Andrés de la Peña <[email protected]>
    Co-authored-by: Aleksandr Sorokoumov <[email protected]>
---
 CHANGES.txt                                        |   1 +
 conf/cassandra.yaml                                |   2 +-
 src/java/org/apache/cassandra/cql3/Lists.java      |   8 +-
 src/java/org/apache/cassandra/cql3/Maps.java       |   8 +-
 src/java/org/apache/cassandra/cql3/Sets.java       |   8 +-
 .../restrictions/ClusteringColumnRestrictions.java |   2 +-
 .../PartitionKeySingleRestrictionSet.java          |   2 +-
 .../cassandra/cql3/statements/SelectStatement.java |   4 +-
 .../statements/schema/AlterTableStatement.java     |   2 +-
 .../statements/schema/CreateIndexStatement.java    |   1 +
 .../statements/schema/CreateKeyspaceStatement.java |   2 +-
 .../statements/schema/CreateTableStatement.java    |   4 +-
 .../statements/schema/CreateViewStatement.java     |   1 +
 .../apache/cassandra/db/guardrails/Guardrail.java  |  13 ++
 .../cassandra/db/guardrails/GuardrailEvent.java    |  73 +++++++
 .../db/guardrails/GuardrailsDiagnostics.java       |  63 ++++++
 .../apache/cassandra/db/guardrails/Threshold.java  |  38 ++--
 .../cassandra/io/sstable/format/SSTableWriter.java |   4 +-
 .../db/guardrails/GuardrailCollectionSizeTest.java |  17 ++
 .../GuardrailInSelectCartesianProductTest.java     |   5 +-
 .../GuardrailItemsPerCollectionTest.java           |  17 ++
 .../guardrails/GuardrailTablePropertiesTest.java   |   5 +
 .../cassandra/db/guardrails/GuardrailTester.java   | 220 +++++++++++++++++++--
 .../guardrails/GuardrailsConfigProviderTest.java   |  19 +-
 .../cassandra/db/guardrails/GuardrailsTest.java    |  92 +++++----
 .../cassandra/db/guardrails/ThresholdTester.java   |  40 +++-
 26 files changed, 545 insertions(+), 106 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index db8d987..d0d021c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.1
+ * Add diagnostic events for guardrails (CASSANDRA-17197)
  * Increase cqlsh version (CASSANDRA-17432)
  * Update SUPPORTED_UPGRADE_PATHS to include 3.0 and 3.x to 4.1 paths and 
remove obsolete tests (CASSANDRA-17362)
  * Support DELETE in CQLSSTableWriter (CASSANDRA-14797)
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index ce79767..a2c4db8 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -1583,7 +1583,7 @@ drop_compact_storage_enabled: false
 #         abort_threshold_kb: 0
 
 # Whether guardrails are enabled or not. Guardrails are disabled by default.
-# guardrails_enabled: true
+# guardrails_enabled: false
 # Guardrail to warn or fail when creating more user keyspaces than threshold.
 # The two thresholds default to -1 to disable.
 # keyspaces_warn_threshold: -1
diff --git a/src/java/org/apache/cassandra/cql3/Lists.java 
b/src/java/org/apache/cassandra/cql3/Lists.java
index dba9ba6..bdac046 100644
--- a/src/java/org/apache/cassandra/cql3/Lists.java
+++ b/src/java/org/apache/cassandra/cql3/Lists.java
@@ -513,7 +513,7 @@ public abstract class Lists
                 // Guardrails about collection size are only checked for the 
added elements without considering
                 // already existent elements. This is done so to avoid 
read-before-write, having additional checks
                 // during SSTable write.
-                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), params.clientState);
+                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), false, params.clientState);
 
                 int dataSize = 0;
                 for (ByteBuffer buffer : elements)
@@ -522,13 +522,13 @@ public abstract class Lists
                     Cell<?> cell = params.addCell(column, 
CellPath.create(uuid), buffer);
                     dataSize += cell.dataSize();
                 }
-                Guardrails.collectionSize.guard(dataSize, 
column.name.toString(), params.clientState);
+                Guardrails.collectionSize.guard(dataSize, 
column.name.toString(), false, params.clientState);
             }
             else
             {
-                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), params.clientState);
+                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), false, params.clientState);
                 Cell<?> cell = params.addCell(column, 
value.get(ProtocolVersion.CURRENT));
-                Guardrails.collectionSize.guard(cell.dataSize(), 
column.name.toString(), params.clientState);
+                Guardrails.collectionSize.guard(cell.dataSize(), 
column.name.toString(), false, params.clientState);
             }
         }
     }
diff --git a/src/java/org/apache/cassandra/cql3/Maps.java 
b/src/java/org/apache/cassandra/cql3/Maps.java
index b5ee2ec..8bb7ae1 100644
--- a/src/java/org/apache/cassandra/cql3/Maps.java
+++ b/src/java/org/apache/cassandra/cql3/Maps.java
@@ -441,7 +441,7 @@ public abstract class Maps
                 // Guardrails about collection size are only checked for the 
added elements without considering
                 // already existent elements. This is done so to avoid 
read-before-write, having additional checks
                 // during SSTable write.
-                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), params.clientState);
+                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), false, params.clientState);
 
                 int dataSize = 0;
                 for (Map.Entry<ByteBuffer, ByteBuffer> entry : 
elements.entrySet())
@@ -449,13 +449,13 @@ public abstract class Maps
                     Cell<?> cell = params.addCell(column, 
CellPath.create(entry.getKey()), entry.getValue());
                     dataSize += cell.dataSize();
                 }
-                Guardrails.collectionSize.guard(dataSize, 
column.name.toString(), params.clientState);
+                Guardrails.collectionSize.guard(dataSize, 
column.name.toString(), false, params.clientState);
             }
             else
             {
-                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), params.clientState);
+                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), false, params.clientState);
                 Cell<?> cell = params.addCell(column, 
value.get(ProtocolVersion.CURRENT));
-                Guardrails.collectionSize.guard(cell.dataSize(), 
column.name.toString(), params.clientState);
+                Guardrails.collectionSize.guard(cell.dataSize(), 
column.name.toString(), false, params.clientState);
             }
         }
     }
diff --git a/src/java/org/apache/cassandra/cql3/Sets.java 
b/src/java/org/apache/cassandra/cql3/Sets.java
index 146f7b7..05ddf0a 100644
--- a/src/java/org/apache/cassandra/cql3/Sets.java
+++ b/src/java/org/apache/cassandra/cql3/Sets.java
@@ -368,7 +368,7 @@ public abstract class Sets
                 // Guardrails about collection size are only checked for the 
added elements without considering
                 // already existent elements. This is done so to avoid 
read-before-write, having additional checks
                 // during SSTable write.
-                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), params.clientState);
+                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), false, params.clientState);
 
                 int dataSize = 0;
                 for (ByteBuffer bb : elements)
@@ -379,13 +379,13 @@ public abstract class Sets
                     Cell<?> cell = params.addCell(column, CellPath.create(bb), 
ByteBufferUtil.EMPTY_BYTE_BUFFER);
                     dataSize += cell.dataSize();
                 }
-                Guardrails.collectionSize.guard(dataSize, 
column.name.toString(), params.clientState);
+                Guardrails.collectionSize.guard(dataSize, 
column.name.toString(), false, params.clientState);
             }
             else
             {
-                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), params.clientState);
+                Guardrails.itemsPerCollection.guard(elements.size(), 
column.name.toString(), false, params.clientState);
                 Cell<?> cell = params.addCell(column, 
value.get(ProtocolVersion.CURRENT));
-                Guardrails.collectionSize.guard(cell.dataSize(), 
column.name.toString(), params.clientState);
+                Guardrails.collectionSize.guard(cell.dataSize(), 
column.name.toString(), false, params.clientState);
             }
         }
     }
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
 
b/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
index bcf080e..c1d0c52 100644
--- 
a/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
+++ 
b/src/java/org/apache/cassandra/cql3/restrictions/ClusteringColumnRestrictions.java
@@ -111,7 +111,7 @@ final class ClusteringColumnRestrictions extends 
RestrictionSetWrapper
             r.appendTo(builder, options);
 
             if (hasIN() && Guardrails.inSelectCartesianProduct.enabled(state))
-                Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), 
"clustering key", state);
+                Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), 
"clustering key", false, state);
 
             if (builder.hasMissingElements())
                 break;
diff --git 
a/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
 
b/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
index a137175..a6f227a 100644
--- 
a/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
+++ 
b/src/java/org/apache/cassandra/cql3/restrictions/PartitionKeySingleRestrictionSet.java
@@ -88,7 +88,7 @@ final class PartitionKeySingleRestrictionSet extends 
RestrictionSetWrapper imple
             r.appendTo(builder, options);
 
             if (hasIN() && Guardrails.inSelectCartesianProduct.enabled(state))
-                Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), 
"partition key", state);
+                Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), 
"partition key", false, state);
 
             if (builder.hasMissingElements())
                 break;
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 54ea8c8..1637ba5 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -394,7 +394,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement
                                        int userLimit,
                                        long queryStartNanoTime) throws 
RequestValidationException, RequestExecutionException
     {
-        Guardrails.pageSize.guard(pageSize, table(), state.getClientState());
+        Guardrails.pageSize.guard(pageSize, table(), false, 
state.getClientState());
 
         if (aggregationSpec != null)
         {
@@ -579,7 +579,7 @@ public class SelectStatement implements 
CQLStatement.SingleKeyspaceCqlStatement
 
         if (restrictions.keyIsInRelation())
         {
-            Guardrails.partitionKeysInSelect.guard(keys.size(), table.name, 
state);
+            Guardrails.partitionKeysInSelect.guard(keys.size(), table.name, 
false, state);
         }
 
         ClusteringIndexFilter filter = makeClusteringIndexFilter(options, 
state, columnFilter);
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
index 61cac8a..f6411c4 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
@@ -187,7 +187,7 @@ public abstract class AlterTableStatement extends 
AlterSchemaStatement
             Views.Builder viewsBuilder = keyspace.views.unbuild();
             newColumns.forEach(c -> addColumn(keyspace, table, c, 
tableBuilder, viewsBuilder));
 
-            Guardrails.columnsPerTable.guard(tableBuilder.numColumns(), 
tableName, state);
+            Guardrails.columnsPerTable.guard(tableBuilder.numColumns(), 
tableName, false, state);
 
             TableMetadata tableMetadata = tableBuilder.build();
             tableMetadata.validate();
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
index 56cc2f9..686a9cd 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
@@ -117,6 +117,7 @@ public final class CreateIndexStatement extends 
AlterSchemaStatement
                                                   
Strings.isNullOrEmpty(indexName)
                                                   ? String.format("on table 
%s", table.name)
                                                   : String.format("%s on table 
%s", indexName, table.name),
+                                                  false,
                                                   state);
 
         List<IndexTarget> indexTargets = 
Lists.newArrayList(transform(rawIndexTargets, t -> t.prepare(table)));
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
index a6036a3..256b33b 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
@@ -117,7 +117,7 @@ public final class CreateKeyspaceStatement extends 
AlterSchemaStatement
         super.validate(state);
 
         // Guardrail on number of keyspaces
-        Guardrails.keyspaces.guard(Schema.instance.getUserKeyspaces().size() + 
1, keyspaceName, state);
+        Guardrails.keyspaces.guard(Schema.instance.getUserKeyspaces().size() + 
1, keyspaceName, false, state);
     }
 
     @Override
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
index 8ab8383..6d65c5a 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
@@ -133,7 +133,7 @@ public final class CreateTableStatement extends 
AlterSchemaStatement
             Guardrails.tableProperties.guard(attrs.updatedProperties(), 
attrs::removeProperty, state);
 
             // Guardrail on columns per table
-            Guardrails.columnsPerTable.guard(rawColumns.size(), tableName, 
state);
+            Guardrails.columnsPerTable.guard(rawColumns.size(), tableName, 
false, state);
 
             // Guardrail on number of tables
             if (Guardrails.tables.enabled(state))
@@ -144,7 +144,7 @@ public final class CreateTableStatement extends 
AlterSchemaStatement
                                                      .map(Keyspace::open)
                                                      .mapToInt(keyspace -> 
keyspace.getColumnFamilyStores().size())
                                                      .sum();
-                Guardrails.tables.guard(totalUserTables + 1, tableName, state);
+                Guardrails.tables.guard(totalUserTables + 1, tableName, false, 
state);
             }
         }
     }
diff --git 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
index 4297d9c..e368d24 100644
--- 
a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
+++ 
b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
@@ -157,6 +157,7 @@ public final class CreateViewStatement extends 
AlterSchemaStatement
         Iterable<ViewMetadata> tableViews = keyspace.views.forTable(table.id);
         Guardrails.materializedViewsPerTable.guard(Iterables.size(tableViews) 
+ 1,
                                                    String.format("%s on table 
%s", viewName, table.name),
+                                                   false,
                                                    state);
 
         if (table.params.gcGraceSeconds == 0)
diff --git a/src/java/org/apache/cassandra/db/guardrails/Guardrail.java 
b/src/java/org/apache/cassandra/db/guardrails/Guardrail.java
index 334d956..d49131d 100644
--- a/src/java/org/apache/cassandra/db/guardrails/Guardrail.java
+++ b/src/java/org/apache/cassandra/db/guardrails/Guardrail.java
@@ -44,6 +44,7 @@ public abstract class Guardrail
 {
     protected static final NoSpamLogger logger = 
NoSpamLogger.getLogger(LoggerFactory.getLogger(Guardrail.class),
                                                                         10, 
TimeUnit.MINUTES);
+    protected static final String REDACTED = "<redacted>";
 
     /** A name identifying the guardrail (mainly for shipping with diagnostic 
events). */
     public final String name;
@@ -69,6 +70,11 @@ public abstract class Guardrail
 
     protected void warn(String message)
     {
+        warn(message, message);
+    }
+
+    protected void warn(String message, String redactedMessage)
+    {
         message = decorateMessage(message);
 
         logger.warn(message);
@@ -77,10 +83,16 @@ public abstract class Guardrail
         ClientWarn.instance.warn(message);
         // Similarly, tracing will also ignore the message if we're not 
running tracing on the current thread.
         Tracing.trace(message);
+        GuardrailsDiagnostics.warned(name, decorateMessage(redactedMessage));
     }
 
     protected void fail(String message, @Nullable ClientState state)
     {
+        fail(message, message, state);
+    }
+
+    protected void fail(String message, String redactedMessage, @Nullable 
ClientState state)
+    {
         message = decorateMessage(message);
 
         logger.error(message);
@@ -89,6 +101,7 @@ public abstract class Guardrail
         ClientWarn.instance.warn(message);
         // Similarly, tracing will also ignore the message if we're not 
running tracing on the current thread.
         Tracing.trace(message);
+        GuardrailsDiagnostics.failed(name, decorateMessage(redactedMessage));
 
         if (state != null)
             throw new GuardrailViolatedException(message);
diff --git a/src/java/org/apache/cassandra/db/guardrails/GuardrailEvent.java 
b/src/java/org/apache/cassandra/db/guardrails/GuardrailEvent.java
new file mode 100644
index 0000000..30eaafa
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/guardrails/GuardrailEvent.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *     http://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.apache.cassandra.db.guardrails;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+import org.apache.cassandra.diag.DiagnosticEvent;
+
+/**
+ * {@link DiagnosticEvent} implementation for guardrail activation events.
+ */
+final class GuardrailEvent extends DiagnosticEvent
+{
+    enum GuardrailEventType
+    {
+        WARNED, FAILED
+    }
+
+    /** The type of activation, which is a warning or a failure. */
+    private final GuardrailEventType type;
+
+    /** The name that identifies the activated guardrail. */
+    private final String name;
+
+    /** The warn/fail message emitted by the activated guardrail. */
+    private final String message;
+
+    /**
+     * Creates new guardrail activation event.
+     *
+     * @param type    The type of activation, which is warning or a failure.
+     * @param name    The name that identifies the activated guardrail.
+     * @param message The warn/fail message emitted by the activated guardrail.
+     */
+    GuardrailEvent(GuardrailEventType type, String name, String message)
+    {
+        this.type = type;
+        this.name = name;
+        this.message = message;
+    }
+
+    @Override
+    public Enum<GuardrailEventType> getType()
+    {
+        return type;
+    }
+
+    @Override
+    public HashMap<String, Serializable> toMap()
+    {
+        HashMap<String, Serializable> ret = new HashMap<>();
+        ret.put("name", name);
+        ret.put("message", message);
+        return ret;
+    }
+}
diff --git 
a/src/java/org/apache/cassandra/db/guardrails/GuardrailsDiagnostics.java 
b/src/java/org/apache/cassandra/db/guardrails/GuardrailsDiagnostics.java
new file mode 100644
index 0000000..2905afa
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/guardrails/GuardrailsDiagnostics.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *     http://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.apache.cassandra.db.guardrails;
+
+import org.apache.cassandra.db.guardrails.GuardrailEvent.GuardrailEventType;
+import org.apache.cassandra.diag.DiagnosticEventService;
+
+/**
+ * Utility methods for {@link GuardrailEvent} activities.
+ */
+final class GuardrailsDiagnostics
+{
+    private static final DiagnosticEventService service = 
DiagnosticEventService.instance();
+
+    private GuardrailsDiagnostics()
+    {
+    }
+
+    /**
+     * Creates a new diagnostic event for the activation of the soft/warn 
limit of a guardrail.
+     *
+     * @param name    The name that identifies the activated guardrail.
+     * @param message The warning message emitted by the activated guardrail.
+     */
+    static void warned(String name, String message)
+    {
+        if (isEnabled(GuardrailEventType.WARNED))
+            service.publish(new GuardrailEvent(GuardrailEventType.WARNED, 
name, message));
+    }
+
+    /**
+     * Creates a new diagnostic event for the activation of the hard/fail 
limit of a guardrail.
+     *
+     * @param name    The name that identifies the activated guardrail.
+     * @param message The failure message emitted by the activated guardrail.
+     */
+    static void failed(String name, String message)
+    {
+        if (isEnabled(GuardrailEventType.FAILED))
+            service.publish(new GuardrailEvent(GuardrailEventType.FAILED, 
name, message));
+    }
+
+    private static boolean isEnabled(GuardrailEventType type)
+    {
+        return service.isEnabled(GuardrailEvent.class, type);
+    }
+}
diff --git a/src/java/org/apache/cassandra/db/guardrails/Threshold.java 
b/src/java/org/apache/cassandra/db/guardrails/Threshold.java
index 3e63cb3..f88d1e0 100644
--- a/src/java/org/apache/cassandra/db/guardrails/Threshold.java
+++ b/src/java/org/apache/cassandra/db/guardrails/Threshold.java
@@ -64,6 +64,11 @@ public class Threshold extends Guardrail
                                              thresholdValue);
     }
 
+    private String redactedErrMsg(boolean isWarning, long value, long 
thresholdValue)
+    {
+        return errMsg(isWarning, REDACTED, value, thresholdValue);
+    }
+
     private long failValue(ClientState state)
     {
         long failValue = failThreshold.applyAsLong(state);
@@ -106,17 +111,16 @@ public class Threshold extends Guardrail
     /**
      * Apply the guardrail to the provided value, warning or failing if 
appropriate.
      *
-     * @param value The value to check.
-     * @param what  A string describing what {@code value} is a value of. This 
is used in the error message if the
-     *              guardrail is triggered. For instance, say the guardrail 
guards the size of column values, then this
-     *              argument must describe which column of which row is 
triggering the guardrail for convenience.
-     * @param state The client state, used to skip the check if the query is 
internal or is done by a superuser.
-     *              A {@code null} value means that the check should be done 
regardless of the query, although it won't
-     *              throw any exception if the failure threshold is exceeded. 
This is so because checks without an
-     *              associated client come from asynchronous processes such as 
compaction, and we don't want to
-     *              interrupt such processes.
+     * @param value            The value to check.
+     * @param what             A string describing what {@code value} is a 
value of. This is used in the error message
+     *                         if the guardrail is triggered. For instance, 
say the guardrail guards the size of column
+     *                         values, then this argument must describe which 
column of which row is triggering the
+     *                         guardrail for convenience.
+     * @param containsUserData whether the {@code what} contains user data 
that should be redacted on external systems.
+     * @param state            The client state, used to skip the check if the 
query is internal or is done by a superuser.
+     *                         A {@code null} value means that the check 
should be done regardless of the query.
      */
-    public void guard(long value, String what, @Nullable ClientState state)
+    public void guard(long value, String what, boolean containsUserData, 
@Nullable ClientState state)
     {
         if (!enabled(state))
             return;
@@ -124,23 +128,25 @@ public class Threshold extends Guardrail
         long failValue = failValue(state);
         if (value > failValue)
         {
-            triggerFail(value, failValue, what, state);
+            triggerFail(value, failValue, what, containsUserData, state);
             return;
         }
 
         long warnValue = warnValue(state);
         if (value > warnValue)
-            triggerWarn(value, warnValue, what);
+            triggerWarn(value, warnValue, what, containsUserData);
     }
 
-    private void triggerFail(long value, long failValue, String what, 
ClientState state)
+    private void triggerFail(long value, long failValue, String what, boolean 
containsUserData, ClientState state)
     {
-        fail(errMsg(false, what, value, failValue), state);
+        String fullMessage = errMsg(false, what, value, failValue);
+        fail(fullMessage, containsUserData ? redactedErrMsg(false, value, 
failValue) : fullMessage, state);
     }
 
-    private void triggerWarn(long value, long warnValue, String what)
+    private void triggerWarn(long value, long warnValue, String what, boolean 
containsUserData)
     {
-        warn(errMsg(true, what, value, warnValue));
+        String fullMessage = errMsg(true, what, value, warnValue);
+        warn(fullMessage, containsUserData ? redactedErrMsg(true, value, 
warnValue) : fullMessage);
     }
 
     /**
diff --git a/src/java/org/apache/cassandra/io/sstable/format/SSTableWriter.java 
b/src/java/org/apache/cassandra/io/sstable/format/SSTableWriter.java
index 0ff3e46..93447c2 100644
--- a/src/java/org/apache/cassandra/io/sstable/format/SSTableWriter.java
+++ b/src/java/org/apache/cassandra/io/sstable/format/SSTableWriter.java
@@ -430,8 +430,8 @@ public abstract class SSTableWriter extends SSTable 
implements Transactional
                                        column.name.toString(),
                                        keyString,
                                        metadata);
-            Guardrails.collectionSize.guard(cellsSize, msg, null);
-            Guardrails.itemsPerCollection.guard(cellsCount, msg, null);
+            Guardrails.collectionSize.guard(cellsSize, msg, true, null);
+            Guardrails.itemsPerCollection.guard(cellsCount, msg, true, null);
         }
     }
 }
diff --git 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailCollectionSizeTest.java 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailCollectionSizeTest.java
index f867783..482e37b 100644
--- 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailCollectionSizeTest.java
+++ 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailCollectionSizeTest.java
@@ -25,6 +25,7 @@ import java.util.Collections;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import org.junit.After;
 import org.junit.Test;
 
 import org.apache.cassandra.db.marshal.BytesType;
@@ -55,6 +56,14 @@ public class GuardrailCollectionSizeTest extends 
ThresholdTester
               Guardrails::getCollectionSizeFailThresholdInKiB);
     }
 
+    @After
+    public void after()
+    {
+        // immediately drop the created table so its async cleanup doesn't 
interfere with the next tests
+        if (currentTable() != null)
+            dropTable("DROP TABLE %s");
+    }
+
     @Test
     public void testSetSize() throws Throwable
     {
@@ -206,6 +215,14 @@ public class GuardrailCollectionSizeTest extends 
ThresholdTester
         assertWarns("UPDATE %s SET v = v + ? WHERE k = 6", 
map(allocate(FAIL_THRESHOLD / 4 + 1), allocate(FAIL_THRESHOLD / 4)));
     }
 
+    @Override
+    protected String createTable(String query)
+    {
+        String table = super.createTable(query);
+        disableCompaction();
+        return table;
+    }
+
     private void assertValid(String query, ByteBuffer... values) throws 
Throwable
     {
         assertValid(execute(query, values));
diff --git 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailInSelectCartesianProductTest.java
 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailInSelectCartesianProductTest.java
index 6575084..ef61ee4 100644
--- 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailInSelectCartesianProductTest.java
+++ 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailInSelectCartesianProductTest.java
@@ -19,6 +19,7 @@
 package org.apache.cassandra.db.guardrails;
 
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -167,9 +168,9 @@ public class GuardrailInSelectCartesianProductTest extends 
ThresholdTester
         else if (keys > WARN_THRESHOLD)
         {
             if (clusterings > FAIL_THRESHOLD)
-                assertFails(function, keysWarnMessage, clusteringsFailMessage);
+                assertFails(function, Arrays.asList(keysWarnMessage, 
clusteringsFailMessage));
             else if (clusterings > WARN_THRESHOLD)
-                assertWarns(function, keysWarnMessage, clusteringsWarnMessage);
+                assertWarns(function, Arrays.asList(keysWarnMessage, 
clusteringsWarnMessage));
             else
                 assertWarns(function, keysWarnMessage);
         }
diff --git 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailItemsPerCollectionTest.java
 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailItemsPerCollectionTest.java
index 66424aa..a13e9b3 100644
--- 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailItemsPerCollectionTest.java
+++ 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailItemsPerCollectionTest.java
@@ -24,6 +24,7 @@ import java.util.stream.Collector;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
+import org.junit.After;
 import org.junit.Test;
 
 import org.apache.cassandra.db.marshal.Int32Type;
@@ -52,6 +53,14 @@ public class GuardrailItemsPerCollectionTest extends 
ThresholdTester
               Guardrails::getItemsPerCollectionFailThreshold);
     }
 
+    @After
+    public void after()
+    {
+        // immediately drop the created table so its async cleanup doesn't 
interfere with the next tests
+        if (currentTable() != null)
+            dropTable("DROP TABLE %s");
+    }
+
     @Test
     public void testSetSize() throws Throwable
     {
@@ -208,6 +217,14 @@ public class GuardrailItemsPerCollectionTest extends 
ThresholdTester
         assertWarns("UPDATE %s SET v = v + ? WHERE k = 4", map(1, 
FAIL_THRESHOLD + 1), FAIL_THRESHOLD);
     }
 
+    @Override
+    protected String createTable(String query)
+    {
+        String table = super.createTable(query);
+        disableCompaction();
+        return table;
+    }
+
     private void assertValid(String query, ByteBuffer collection) throws 
Throwable
     {
         assertValid(execute(query, collection));
diff --git 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailTablePropertiesTest.java
 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailTablePropertiesTest.java
index 018700a..5754c51 100644
--- 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailTablePropertiesTest.java
+++ 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailTablePropertiesTest.java
@@ -49,6 +49,11 @@ public class GuardrailTablePropertiesTest extends 
GuardrailTester
     private static final String IGNORED_PROPERTY_NAME = 
"table_properties_ignored";
     private static final String DISALLOWED_PROPERTY_NAME = 
"table_properties_disallowed";
 
+    public GuardrailTablePropertiesTest()
+    {
+        super(Guardrails.tableProperties);
+    }
+
     @Before
     public void before()
     {
diff --git a/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java
index 2592f85..4540a51 100644
--- a/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java
+++ b/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java
@@ -18,11 +18,15 @@
 
 package org.apache.cassandra.db.guardrails;
 
+import java.io.Serializable;
 import java.net.InetSocketAddress;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -31,18 +35,22 @@ import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
 import com.google.common.collect.ImmutableSet;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 
 import org.apache.cassandra.auth.AuthenticatedUser;
 import org.apache.cassandra.auth.CassandraRoleManager;
+import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.cql3.CQLStatement;
 import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.cql3.QueryProcessor;
 import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.guardrails.GuardrailEvent.GuardrailEventType;
 import org.apache.cassandra.db.view.View;
+import org.apache.cassandra.diag.DiagnosticEventService;
 import org.apache.cassandra.index.sasi.SASIIndex;
 import org.apache.cassandra.service.ClientState;
 import org.apache.cassandra.service.ClientWarn;
@@ -55,6 +63,7 @@ import org.assertj.core.api.Assertions;
 import static java.lang.String.format;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -70,12 +79,13 @@ public abstract class GuardrailTester extends CQLTester
 
     protected static ClientState systemClientState, userClientState, 
superClientState;
 
-    /**
-     * The tested guardrail, if we are testing a specific one.
-     */
+    /** The tested guardrail, if we are testing a specific one. */
     @Nullable
     protected final Guardrail guardrail;
 
+    /** A listener for emitted diagnostic events. */
+    protected final Listener listener;
+
     public GuardrailTester()
     {
         this(null);
@@ -84,6 +94,7 @@ public abstract class GuardrailTester extends CQLTester
     public GuardrailTester(@Nullable Guardrail guardrail)
     {
         this.guardrail = guardrail;
+        this.listener = new Listener();
     }
 
     @BeforeClass
@@ -93,6 +104,7 @@ public abstract class GuardrailTester extends CQLTester
         requireAuthentication();
         requireNetwork();
         guardrails().setEnabled(true);
+        DatabaseDescriptor.setDiagnosticEventsEnabled(true);
 
         systemClientState = ClientState.forInternalCalls();
         userClientState = 
ClientState.forExternalCalls(InetSocketAddress.createUnresolved("127.0.0.1", 
123));
@@ -115,6 +127,14 @@ public abstract class GuardrailTester extends CQLTester
         execute(userClientState, useKeyspaceQuery);
         execute(systemClientState, useKeyspaceQuery);
         execute(superClientState, useKeyspaceQuery);
+
+        DiagnosticEventService.instance().subscribe(GuardrailEvent.class, 
listener);
+    }
+
+    @After
+    public void afterGuardrailTest() throws Throwable
+    {
+        DiagnosticEventService.instance().unsubscribe(listener);
     }
 
     static Guardrails guardrails()
@@ -176,6 +196,8 @@ public abstract class GuardrailTester extends CQLTester
         {
             function.apply();
             assertEmptyWarnings();
+            listener.assertNotWarned();
+            listener.assertNotFailed();
         }
         catch (GuardrailViolatedException e)
         {
@@ -184,6 +206,7 @@ public abstract class GuardrailTester extends CQLTester
         finally
         {
             ClientWarn.instance.resetWarnings();
+            listener.clear();
         }
     }
 
@@ -192,7 +215,42 @@ public abstract class GuardrailTester extends CQLTester
         assertValid(() -> execute(userClientState, query));
     }
 
-    protected void assertWarns(CheckedFunction function, String... messages) 
throws Throwable
+    protected void assertWarns(String query, String message) throws Throwable
+    {
+        assertWarns(query, message, message);
+    }
+
+    protected void assertWarns(String query, String message, String 
redactedMessage) throws Throwable
+    {
+        assertWarns(query, Collections.singletonList(message), 
Collections.singletonList(redactedMessage));
+    }
+
+    protected void assertWarns(String query, List<String> messages) throws 
Throwable
+    {
+        assertWarns(() -> execute(userClientState, query), messages, messages);
+    }
+
+    protected void assertWarns(String query, List<String> messages, 
List<String> redactedMessages) throws Throwable
+    {
+        assertWarns(() -> execute(userClientState, query), messages, 
redactedMessages);
+    }
+
+    protected void assertWarns(CheckedFunction function, String message) 
throws Throwable
+    {
+        assertWarns(function, message, message);
+    }
+
+    protected void assertWarns(CheckedFunction function, String message, 
String redactedMessage) throws Throwable
+    {
+        assertWarns(function, Collections.singletonList(message), 
Collections.singletonList(redactedMessage));
+    }
+
+    protected void assertWarns(CheckedFunction function, List<String> 
messages) throws Throwable
+    {
+        assertWarns(function, messages, messages);
+    }
+
+    protected void assertWarns(CheckedFunction function, List<String> 
messages, List<String> redactedMessages) throws Throwable
     {
         // We use client warnings to check we properly warn as this is the 
most convenient. Technically,
         // this doesn't validate we also log the warning, but that's probably 
fine ...
@@ -201,24 +259,67 @@ public abstract class GuardrailTester extends CQLTester
         {
             function.apply();
             assertWarnings(messages);
+            listener.assertWarned(redactedMessages);
+            listener.assertNotFailed();
         }
         finally
         {
             ClientWarn.instance.resetWarnings();
+            listener.clear();
         }
     }
 
-    protected void assertWarns(String query, String... messages) throws 
Throwable
+    protected void assertFails(String query, String message) throws Throwable
+    {
+        assertFails(query, message, message);
+    }
+
+    protected void assertFails(String query, String message, String 
redactedMessage) throws Throwable
+    {
+        assertFails(query, Collections.singletonList(message), 
Collections.singletonList(redactedMessage));
+    }
+
+    protected void assertFails(String query, List<String> messages) throws 
Throwable
+    {
+        assertFails(query, messages, messages);
+    }
+
+    protected void assertFails(String query, List<String> messages, 
List<String> redactedMessages) throws Throwable
+    {
+        assertFails(() -> execute(userClientState, query), messages, 
redactedMessages);
+    }
+
+    protected void assertFails(CheckedFunction function, String message) 
throws Throwable
+    {
+        assertFails(function, message, message);
+    }
+
+    protected void assertFails(CheckedFunction function, String message, 
String redactedMessage) throws Throwable
+    {
+        assertFails(function, true, message, redactedMessage);
+    }
+
+    protected void assertFails(CheckedFunction function, boolean thrown, 
String message) throws Throwable
+    {
+        assertFails(function, thrown, message, message);
+    }
+
+    protected void assertFails(CheckedFunction function, boolean thrown, 
String message, String redactedMessage) throws Throwable
     {
-        assertWarns(() -> execute(userClientState, query), messages);
+        assertFails(function, thrown, Collections.singletonList(message), 
Collections.singletonList(redactedMessage));
     }
 
-    protected void assertFails(CheckedFunction function, String... messages) 
throws Throwable
+    protected void assertFails(CheckedFunction function, List<String> 
messages) throws Throwable
     {
-        assertFails(function, true, messages);
+        assertFails(function, messages, messages);
     }
 
-    protected void assertFails(CheckedFunction function, boolean thrown, 
String... messages) throws Throwable
+    protected void assertFails(CheckedFunction function, List<String> 
messages, List<String> redactedMessages) throws Throwable
+    {
+        assertFails(function, true, messages, redactedMessages);
+    }
+
+    protected void assertFails(CheckedFunction function, boolean thrown, 
List<String> messages, List<String> redactedMessages) throws Throwable
     {
         ClientWarn.instance.captureWarnings();
         try
@@ -233,7 +334,7 @@ public abstract class GuardrailTester extends CQLTester
             assertTrue("Expect no exception thrown", thrown);
 
             // the last message is the one raising the guardrail failure, the 
previous messages are warnings
-            String failMessage = messages[messages.length - 1];
+            String failMessage = messages.get(messages.size() - 1);
 
             if (guardrail != null)
             {
@@ -246,16 +347,22 @@ public abstract class GuardrailTester extends CQLTester
                        e.getMessage().contains(failMessage));
 
             assertWarnings(messages);
+            if (messages.size() > 1)
+                listener.assertWarned(redactedMessages.subList(0, 
messages.size() - 1));
+            else
+                listener.assertNotWarned();
+            listener.assertFailed(redactedMessages.get(messages.size() - 1));
         }
         finally
         {
             ClientWarn.instance.resetWarnings();
+            listener.clear();
         }
     }
 
     protected void assertFails(String query, String... messages) throws 
Throwable
     {
-        assertFails(() -> execute(userClientState, query), messages);
+        assertFails(() -> execute(userClientState, query), 
Arrays.asList(messages));
     }
 
     protected void assertThrows(CheckedFunction function, Class<? extends 
Throwable> exception, String message)
@@ -275,20 +382,19 @@ public abstract class GuardrailTester extends CQLTester
         }
     }
 
-    private void assertWarnings(String... messages)
+    private void assertWarnings(List<String> messages)
     {
         List<String> warnings = getWarnings();
 
         assertFalse("Expected to warn, but no warning was received", warnings 
== null || warnings.isEmpty());
-        assertEquals(format("Expected %d warnings but got %d: %s", 
messages.length, warnings.size(), warnings),
-                     messages.length,
+        assertEquals(format("Expected %d warnings but got %d: %s", 
messages.size(), warnings.size(), warnings),
+                     messages.size(),
                      warnings.size());
 
-        for (int i = 0; i < messages.length; i++)
+        for (int i = 0; i < messages.size(); i++)
         {
+            String message = messages.get(i);
             String warning = warnings.get(i);
-
-            String message = messages[i];
             if (guardrail != null)
             {
                 String prefix = guardrail.decorateMessage("");
@@ -383,4 +489,84 @@ public abstract class GuardrailTester extends CQLTester
     {
         return String.join(",", (new 
TreeSet<>(ImmutableSet.copyOf((csv.split(","))))));
     }
+
+    /**
+     * A listener for guardrails diagnostic events.
+     */
+    public class Listener implements Consumer<GuardrailEvent>
+    {
+        private final List<String> warnings = new CopyOnWriteArrayList<>();
+        private final List<String> failures = new CopyOnWriteArrayList<>();
+
+        @Override
+        public void accept(GuardrailEvent event)
+        {
+            assertNotNull(event);
+            Map<String, Serializable> map = event.toMap();
+
+            if (guardrail != null)
+                assertEquals(guardrail.name, map.get("name"));
+
+            GuardrailEventType type = (GuardrailEventType) event.getType();
+            String message = map.toString();
+
+            switch (type)
+            {
+                case WARNED:
+                    warnings.add(message);
+                    break;
+                case FAILED:
+                    failures.add(message);
+                    break;
+                default:
+                    fail("Unexpected diagnostic event:" + type);
+            }
+        }
+
+        public void clear()
+        {
+            warnings.clear();
+            failures.clear();
+        }
+
+        public void assertNotWarned()
+        {
+            assertTrue(format("Expect no warning diagnostic events but got 
%s", warnings), warnings.isEmpty());
+        }
+
+        public void assertWarned(List<String> messages)
+        {
+            assertFalse("Expected to emit warning diagnostic event, but no 
warning was emitted", warnings.isEmpty());
+            assertEquals(format("Expected %d warning diagnostic events but got 
%d: %s)", messages.size(), warnings.size(), warnings),
+                         messages.size(), warnings.size());
+
+            for (int i = 0; i < messages.size(); i++)
+            {
+                String message = messages.get(i);
+                String warning = warnings.get(i);
+                assertTrue(format("Warning diagnostic event '%s' does not 
contain expected message '%s'", warning, message),
+                           warning.contains(message));
+            }
+        }
+
+        public void assertNotFailed()
+        {
+            assertTrue(format("Expect no failure diagnostic events but got 
%s", failures), failures.isEmpty());
+        }
+
+        public void assertFailed(String... messages)
+        {
+            assertFalse("Expected to emit failure diagnostic event, but no 
failure was emitted", failures.isEmpty());
+            assertEquals(format("Expected %d failure diagnostic events but got 
%d: %s)", messages.length, failures.size(), failures),
+                         messages.length, failures.size());
+
+            for (int i = 0; i < messages.length; i++)
+            {
+                String message = messages[i];
+                String failure = failures.get(i);
+                assertTrue(format("Failure diagnostic event '%s' does not 
contain expected message '%s'", failure, message),
+                           failure.contains(message));
+            }
+        }
+    }
 }
diff --git 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailsConfigProviderTest.java
 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailsConfigProviderTest.java
index cf2e40f..991dcf7 100644
--- 
a/test/unit/org/apache/cassandra/db/guardrails/GuardrailsConfigProviderTest.java
+++ 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailsConfigProviderTest.java
@@ -42,12 +42,19 @@ public class GuardrailsConfigProviderTest extends 
GuardrailTester
                                         (isWarn, what, v, t) -> format("%s: 
for %s, %s > %s",
                                                                        isWarn 
? "Warning" : "Aborting", what, v, t));
 
-        assertValid(() -> guard.guard(5, "Z", userClientState));
-        assertWarns(() -> guard.guard(25, "A", userClientState), "Warning: for 
A, 25 > 10");
-        assertWarns(() -> guard.guard(100, "B", userClientState), "Warning: 
for B, 100 > 10");
-        assertFails(() -> guard.guard(101, "X", userClientState), "Aborting: 
for X, 101 > 100");
-        assertFails(() -> guard.guard(200, "Y", userClientState), "Aborting: 
for Y, 200 > 100");
-        assertValid(() -> guard.guard(5, "Z", userClientState));
+        assertValid(() -> guard.guard(5, "Z", false, userClientState));
+        assertWarns(() -> guard.guard(25, "A", false, userClientState), 
"Warning: for A, 25 > 10");
+        assertWarns(() -> guard.guard(100, "B", false, userClientState), 
"Warning: for B, 100 > 10");
+        assertFails(() -> guard.guard(101, "X", false, userClientState), 
"Aborting: for X, 101 > 100");
+        assertFails(() -> guard.guard(200, "Y", false, userClientState), 
"Aborting: for Y, 200 > 100");
+        assertValid(() -> guard.guard(5, "Z", false, userClientState));
+
+        assertValid(() -> guard.guard(5, "Z", true, userClientState));
+        assertWarns(() -> guard.guard(25, "A", true, userClientState), 
"Warning: for A, 25 > 10", "Warning: for <redacted>, 25 > 10");
+        assertWarns(() -> guard.guard(100, "B", true, userClientState), 
"Warning: for B, 100 > 10", "Warning: for <redacted>, 100 > 10");
+        assertFails(() -> guard.guard(101, "X", true, userClientState), 
"Aborting: for X, 101 > 100", "Aborting: for <redacted>, 101 > 100");
+        assertFails(() -> guard.guard(200, "Y", true, userClientState), 
"Aborting: for Y, 200 > 100", "Aborting: for <redacted>, 200 > 100");
+        assertValid(() -> guard.guard(5, "Z", true, userClientState));
 
         Assertions.assertThatThrownBy(() -> 
GuardrailsConfigProvider.build("unexistent_class"))
                   .isInstanceOf(ConfigurationException.class)
diff --git a/test/unit/org/apache/cassandra/db/guardrails/GuardrailsTest.java 
b/test/unit/org/apache/cassandra/db/guardrails/GuardrailsTest.java
index 70c4cfc..b98659e 100644
--- a/test/unit/org/apache/cassandra/db/guardrails/GuardrailsTest.java
+++ b/test/unit/org/apache/cassandra/db/guardrails/GuardrailsTest.java
@@ -50,17 +50,20 @@ public class GuardrailsTest extends GuardrailTester
     {
         assertFalse(guard.enabled(userClientState));
 
-        assertValid(() -> guard.guard(5, "Z", null));
-        assertValid(() -> guard.guard(25, "A", userClientState));
-        assertValid(() -> guard.guard(100, "B", userClientState));
-        assertValid(() -> guard.guard(101, "X", userClientState));
-        assertValid(() -> guard.guard(200, "Y", userClientState));
+        for (boolean containsUserData : Arrays.asList(true, false))
+        {
+            assertValid(() -> guard.guard(5, "Z", containsUserData, null));
+            assertValid(() -> guard.guard(25, "A", containsUserData, 
userClientState));
+            assertValid(() -> guard.guard(100, "B", containsUserData, 
userClientState));
+            assertValid(() -> guard.guard(101, "X", containsUserData, 
userClientState));
+            assertValid(() -> guard.guard(200, "Y", containsUserData, 
userClientState));
+        }
     }
 
     @Test
     public void testThreshold() throws Throwable
     {
-        Threshold guard = new Threshold("x", 
+        Threshold guard = new Threshold("x",
                                         state -> 10,
                                         state -> 100,
                                         (isWarn, what, v, t) -> format("%s: 
for %s, %s > %s",
@@ -68,12 +71,19 @@ public class GuardrailsTest extends GuardrailTester
 
         assertTrue(guard.enabled(userClientState));
 
-        assertValid(() -> guard.guard(5, "Z", userClientState));
-        assertWarns(() -> guard.guard(25, "A", userClientState), "Warning: for 
A, 25 > 10");
-        assertWarns(() -> guard.guard(100, "B", userClientState), "Warning: 
for B, 100 > 10");
-        assertFails(() -> guard.guard(101, "X", userClientState), "Aborting: 
for X, 101 > 100");
-        assertFails(() -> guard.guard(200, "Y", userClientState), "Aborting: 
for Y, 200 > 100");
-        assertValid(() -> guard.guard(5, "Z", userClientState));
+        assertValid(() -> guard.guard(5, "Z", false, userClientState));
+        assertWarns(() -> guard.guard(25, "A", false, userClientState), 
"Warning: for A, 25 > 10");
+        assertWarns(() -> guard.guard(100, "B", false, userClientState), 
"Warning: for B, 100 > 10");
+        assertFails(() -> guard.guard(101, "X", false, userClientState), 
"Aborting: for X, 101 > 100");
+        assertFails(() -> guard.guard(200, "Y", false, userClientState), 
"Aborting: for Y, 200 > 100");
+        assertValid(() -> guard.guard(5, "Z", false, userClientState));
+
+        assertValid(() -> guard.guard(5, "Z", true, userClientState));
+        assertWarns(() -> guard.guard(25, "A", true, userClientState), 
"Warning: for A, 25 > 10", "Warning: for <redacted>, 25 > 10");
+        assertWarns(() -> guard.guard(100, "B", true, userClientState), 
"Warning: for B, 100 > 10", "Warning: for <redacted>, 100 > 10");
+        assertFails(() -> guard.guard(101, "X", true, userClientState), 
"Aborting: for X, 101 > 100", "Aborting: for <redacted>, 101 > 100");
+        assertFails(() -> guard.guard(200, "Y", true, userClientState), 
"Aborting: for Y, 200 > 100", "Aborting: for <redacted>, 200 > 100");
+        assertValid(() -> guard.guard(5, "Z", true, userClientState));
     }
 
     @Test
@@ -87,8 +97,11 @@ public class GuardrailsTest extends GuardrailTester
 
         assertTrue(guard.enabled(userClientState));
 
-        assertValid(() -> guard.guard(5, "Z", userClientState));
-        assertWarns(() -> guard.guard(11, "A", userClientState), "Warning: for 
A, 11 > 10");
+        assertValid(() -> guard.guard(5, "Z", false, userClientState));
+        assertWarns(() -> guard.guard(11, "A", false, userClientState), 
"Warning: for A, 11 > 10");
+
+        assertValid(() -> guard.guard(5, "Z", true, userClientState));
+        assertWarns(() -> guard.guard(11, "A", true, userClientState), 
"Warning: for A, 11 > 10", "Warning: for <redacted>, 11 > 10");
     }
 
     @Test
@@ -102,8 +115,11 @@ public class GuardrailsTest extends GuardrailTester
 
         assertTrue(guard.enabled(userClientState));
 
-        assertValid(() -> guard.guard(5, "Z", userClientState));
-        assertFails(() -> guard.guard(11, "A", userClientState), "Aborting: 
for A, 11 > 10");
+        assertValid(() -> guard.guard(5, "Z", false, userClientState));
+        assertFails(() -> guard.guard(11, "A", false, userClientState), 
"Aborting: for A, 11 > 10");
+
+        assertValid(() -> guard.guard(5, "Z", true, userClientState));
+        assertFails(() -> guard.guard(11, "A", true, userClientState), 
"Aborting: for A, 11 > 10", "Aborting: for <redacted>, 11 > 10");
     }
 
     @Test
@@ -116,23 +132,23 @@ public class GuardrailsTest extends GuardrailTester
                                                                        isWarn 
? "Warning" : "Failure", what, v, t));
 
         // value under both thresholds
-        assertValid(() -> guard.guard(5, "x", null));
-        assertValid(() -> guard.guard(5, "x", userClientState));
-        assertValid(() -> guard.guard(5, "x", systemClientState));
-        assertValid(() -> guard.guard(5, "x", superClientState));
+        assertValid(() -> guard.guard(5, "x", false, null));
+        assertValid(() -> guard.guard(5, "x", false, userClientState));
+        assertValid(() -> guard.guard(5, "x", false, systemClientState));
+        assertValid(() -> guard.guard(5, "x", false, superClientState));
 
         // value over warning threshold
-        assertWarns(() -> guard.guard(100, "y", null), "Warning: for y, 100 > 
10");
-        assertWarns(() -> guard.guard(100, "y", userClientState), "Warning: 
for y, 100 > 10");
-        assertValid(() -> guard.guard(100, "y", systemClientState));
-        assertValid(() -> guard.guard(100, "y", superClientState));
-
-        // value over fail threshold. An undefined user means that the check 
comes from a background process,
-        // so we warn instead of failing to prevent interrupting that process.
-        assertWarns(() -> guard.guard(101, "z", null), "Failure: for z, 101 > 
100");
-        assertFails(() -> guard.guard(101, "z", userClientState), "Failure: 
for z, 101 > 100");
-        assertValid(() -> guard.guard(101, "z", systemClientState));
-        assertValid(() -> guard.guard(101, "z", superClientState));
+        assertWarns(() -> guard.guard(100, "y", false, null), "Warning: for y, 
100 > 10");
+        assertWarns(() -> guard.guard(100, "y", false, userClientState), 
"Warning: for y, 100 > 10");
+        assertValid(() -> guard.guard(100, "y", false, systemClientState));
+        assertValid(() -> guard.guard(100, "y", false, superClientState));
+
+        // value over fail threshold. An undefined user means that the check 
comes from a background process, so we
+        // still emit failure messages and events, but we don't throw an 
exception to prevent interrupting that process.
+        assertFails(() -> guard.guard(101, "z", false, null), false, "Failure: 
for z, 101 > 100");
+        assertFails(() -> guard.guard(101, "z", false, userClientState), 
"Failure: for z, 101 > 100");
+        assertValid(() -> guard.guard(101, "z", false, systemClientState));
+        assertValid(() -> guard.guard(101, "z", false, superClientState));
     }
 
     @Test
@@ -164,7 +180,7 @@ public class GuardrailsTest extends GuardrailTester
     public void testValuesWarned() throws Throwable
     {
         // Using a sorted set below to ensure the order in the warning message 
checked below is not random
-        Values<Integer> warned = new Values<>("x", 
+        Values<Integer> warned = new Values<>("x",
                                               state -> insertionOrderedSet(4, 
6, 20),
                                               state -> Collections.emptySet(),
                                               state -> Collections.emptySet(),
@@ -184,7 +200,7 @@ public class GuardrailsTest extends GuardrailTester
     public void testValuesIgnored() throws Throwable
     {
         // Using a sorted set below to ensure the order in the error message 
checked below are not random
-        Values<Integer> ignored = new Values<>("x", 
+        Values<Integer> ignored = new Values<>("x",
                                                state -> Collections.emptySet(),
                                                state -> insertionOrderedSet(4, 
6, 20),
                                                state -> Collections.emptySet(),
@@ -218,7 +234,7 @@ public class GuardrailsTest extends GuardrailTester
     public void testValuesDisallowed() throws Throwable
     {
         // Using a sorted set below to ensure the order in the error message 
checked below are not random
-        Values<Integer> disallowed = new Values<>("x", 
+        Values<Integer> disallowed = new Values<>("x",
                                                   state -> 
Collections.emptySet(),
                                                   state -> 
Collections.emptySet(),
                                                   state -> 
insertionOrderedSet(4, 6, 20),
@@ -234,16 +250,16 @@ public class GuardrailsTest extends GuardrailTester
         assertValid(() -> disallowed.guard(set(200), action, userClientState));
         assertValid(() -> disallowed.guard(set(1, 2, 3), action, 
userClientState));
 
-        assertWarns(() -> disallowed.guard(set(4, 6), action, null),
+        assertFails(() -> disallowed.guard(set(4, 6), action, null), false,
                     "Provided values [4, 6] are not allowed for integer 
(disallowed values are: [4, 6, 20])");
-        assertWarns(() -> disallowed.guard(set(4, 5, 6, 7), action, null),
+        assertFails(() -> disallowed.guard(set(4, 5, 6, 7), action, null), 
false,
                     "Provided values [4, 6] are not allowed for integer 
(disallowed values are: [4, 6, 20])");
     }
 
     @Test
     public void testValuesUsers() throws Throwable
     {
-        Values<Integer> disallowed = new Values<>("x", 
+        Values<Integer> disallowed = new Values<>("x",
                                                   state -> 
Collections.singleton(2),
                                                   state -> 
Collections.singleton(3),
                                                   state -> 
Collections.singleton(4),
@@ -271,7 +287,7 @@ public class GuardrailsTest extends GuardrailTester
         Assert.assertEquals(list(3, 3), triggeredOn);
 
         message = "Provided values [4] are not allowed for integer (disallowed 
values are: [4])";
-        assertWarns(() -> disallowed.guard(set(4), action, null), message);
+        assertFails(() -> disallowed.guard(set(4), action, null), false, 
message);
         assertFails(() -> disallowed.guard(set(4), action, userClientState), 
message);
         assertValid(() -> disallowed.guard(set(4), action, systemClientState));
         assertValid(() -> disallowed.guard(set(4), action, superClientState));
diff --git a/test/unit/org/apache/cassandra/db/guardrails/ThresholdTester.java 
b/test/unit/org/apache/cassandra/db/guardrails/ThresholdTester.java
index a6ce3a1..885f626 100644
--- a/test/unit/org/apache/cassandra/db/guardrails/ThresholdTester.java
+++ b/test/unit/org/apache/cassandra/db/guardrails/ThresholdTester.java
@@ -18,6 +18,8 @@
 
 package org.apache.cassandra.db.guardrails;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.ToIntFunction;
 import java.util.function.ToLongFunction;
@@ -125,18 +127,48 @@ public abstract class ThresholdTester extends 
GuardrailTester
                   .isLessThanOrEqualTo(failGetter.applyAsLong(guardrails()));
     }
 
-    protected void assertThresholdWarns(String query, String... messages) 
throws Throwable
+    protected void assertThresholdWarns(String query, String message) throws 
Throwable
     {
-        assertWarns(query, messages);
+        assertThresholdWarns(query, message, message);
+    }
+
+    protected void assertThresholdWarns(String query, String message, String 
redactedMessage) throws Throwable
+    {
+        assertThresholdWarns(query, Collections.singletonList(message), 
Collections.singletonList(redactedMessage));
+    }
+
+    protected void assertThresholdWarns(String query, List<String> messages) 
throws Throwable
+    {
+        assertThresholdWarns(query, messages, messages);
+    }
+
+    protected void assertThresholdWarns(String query, List<String> messages, 
List<String> redactedMessages) throws Throwable
+    {
+        assertWarns(query, messages, redactedMessages);
 
         Assertions.assertThat(currentValue())
                   .isGreaterThan(warnGetter.applyAsLong(guardrails()))
                   .isLessThanOrEqualTo(failGetter.applyAsLong(guardrails()));
     }
 
-    protected void assertThresholdFails(String query, String... messages) 
throws Throwable
+    protected void assertThresholdFails(String query, String message) throws 
Throwable
+    {
+        assertThresholdFails(query, message, message);
+    }
+
+    protected void assertThresholdFails(String query, String message, String 
redactedMessage) throws Throwable
+    {
+        assertThresholdFails(query, Collections.singletonList(message), 
Collections.singletonList(redactedMessage));
+    }
+
+    protected void assertThresholdFails(String query, List<String> messages) 
throws Throwable
+    {
+        assertThresholdFails(query, messages, messages);
+    }
+
+    protected void assertThresholdFails(String query, List<String> messages, 
List<String> redactedMessages) throws Throwable
     {
-        assertFails(query, messages);
+        assertFails(query, messages, redactedMessages);
 
         Assertions.assertThat(currentValue())
                   .isGreaterThanOrEqualTo(warnGetter.applyAsLong(guardrails()))

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to