ctubbsii commented on code in PR #3124:
URL: https://github.com/apache/accumulo/pull/3124#discussion_r1052513407


##########
core/src/main/java/org/apache/accumulo/core/client/ScannerBase.java:
##########
@@ -390,4 +390,21 @@ default Stream<Entry<Key,Value>> stream() {
     return StreamSupport.stream(this.spliterator(), false);
   }
 
+  /**
+   * Set user data on the Scanner. This data will be added to server side 
information and logs for
+   * correlation.
+   *
+   * @param userData meaningful data that can be used to correlate server side 
information
+   * @since 3.0.0
+   */
+  void setUserData(String userData);

Review Comment:
   Would calling this a "tag" help? Also, the javadoc should mention that this 
should be unique for each scan, in order to be useful.



##########
assemble/conf/log4j2-service.properties:
##########
@@ -84,6 +99,11 @@ logger.accumulo.level = debug
 #logger.audit.additivity = false
 #logger.audit.appenderRef.audit.ref = AuditLogFiles
 
+logger.userdata.name = org.apache.accumulo.core.logging.ScanUserDataLogger
+logger.userdata.level = off

Review Comment:
   If we don't want to use a class name, we can use a logger name like 
`org.apache.accumulo.userdata` (or `org.apache.accumulo.tags`), similar to what 
we did with the audit logs. We still include `org.apache.accumulo.` as a 
namespace prefix, but we don't need to use an actual class name for the logger 
name. It can be any meaningful name.



##########
core/src/main/java/org/apache/accumulo/core/clientImpl/ThriftScanner.java:
##########
@@ -118,7 +118,7 @@ public static boolean getBatchFromServer(ClientContext 
context, Range range, Key
         ScanState scanState = new ScanState(context, extent.tableId(), 
authorizations, range,
             fetchedColumns, size, serverSideIteratorList, 
serverSideIteratorOptions, false,
             Constants.SCANNER_DEFAULT_READAHEAD_THRESHOLD, null, batchTimeOut, 
classLoaderContext,
-            null, false);
+            null, false, null);

Review Comment:
   Should we use an empty string, or some default data for here?



##########
assemble/conf/log4j2-service.properties:
##########
@@ -72,6 +72,21 @@ appender.monitor.name = MonitorLog
 appender.monitor.filter.threshold.type = ThresholdFilter
 appender.monitor.filter.threshold.level = warn
 
+#appender.userdata.type = RollingFile
+#appender.userdata.name = UserDataLogFiles
+#appender.userdata.fileName = ${filename}.scan-user-data
+#appender.userdata.filePattern = 
${filename}-%d{yyyy-MM-dd}-%i.scan-user-data.gz
+#appender.userdata.layout.type = PatternLayout
+#appender.userdata.layout.pattern = %d{ISO8601} [%-8c{2}] %-5p: %m%n
+#appender.userdata.policies.type = Policies
+#appender.userdata.policies.time.type = TimeBasedTriggeringPolicy
+#appender.userdata.policies.time.interval = 1
+#appender.userdata.policies.time.modulate = true
+#appender.userdata.policies.size.type = SizeBasedTriggeringPolicy
+#appender.userdata.policies.size.size=512MB
+#appender.userdata.strategy.type = DefaultRolloverStrategy
+#appender.userdata.strategy.max = 10
+

Review Comment:
   In order to keep our example config file smaller, I don't think we need to 
create a new appender for these. We can just include them by default in the 
regular logs.



##########
core/src/main/java/org/apache/accumulo/core/logging/ScanUserDataLogger.java:
##########
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.accumulo.core.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+import org.slf4j.event.Level;
+import org.slf4j.helpers.AbstractLogger;
+import org.slf4j.spi.LoggingEventBuilder;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+public class ScanUserDataLogger extends AbstractLogger {

Review Comment:
   Until this PR, we've only ever depended upon `org.slf4j.Logger` and 
`org.slf4j.LoggerFactory` from the SLF4J API. This has kept us in a very stable 
position across SLF4J versions. This PR would depend upon other classes in the 
SLF4J API, that I'm not sure are as stable. Given the importance of our use of 
this dependency being widely compatible, because of its popularity as a 
transitive dependency from a lot of our direct dependencies, I think we'd want 
to try to find some other way to do this, without adding so much dependency on 
other SLF4J APIs beyond the main two classes that we use today.



##########
core/src/main/java/org/apache/accumulo/core/logging/ScanUserDataLogger.java:
##########
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.accumulo.core.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+import org.slf4j.event.Level;
+import org.slf4j.helpers.AbstractLogger;
+import org.slf4j.spi.LoggingEventBuilder;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+public class ScanUserDataLogger extends AbstractLogger {
+
+  private static final long serialVersionUID = 1L;
+  private static final Logger LOG = 
LoggerFactory.getLogger(ScanUserDataLogger.class);
+  private static final ThreadLocal<String> USER_DATA = new ThreadLocal<>();
+
+  public static void logTrace(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.trace(msg, objects);
+    }
+    if (!LOG.isTraceEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.trace(msg, objects);
+  }
+
+  public static void logDebug(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.debug(msg, objects);
+    }
+    if (!LOG.isDebugEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.debug(msg, objects);
+  }
+
+  public static void logInfo(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.info(msg, objects);
+    }
+    if (!LOG.isInfoEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.info(msg, objects);
+  }
+
+  public static void logWarn(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.warn(msg, objects);
+    }
+    if (!LOG.isWarnEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.warn(msg, objects);
+  }
+
+  public static void logError(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.error(msg, objects);
+    }
+    USER_DATA.set(userData);
+    LOG.error(msg, objects);
+  }
+
+  @Override
+  public boolean isTraceEnabled() {
+    return LOG.isTraceEnabled();
+  }
+
+  @Override
+  public boolean isTraceEnabled(Marker marker) {
+    return LOG.isTraceEnabled(marker);
+  }
+
+  @Override
+  public boolean isDebugEnabled() {
+    return LOG.isDebugEnabled();
+  }
+
+  @Override
+  public boolean isDebugEnabled(Marker marker) {
+    return LOG.isDebugEnabled(marker);
+  }
+
+  @Override
+  public boolean isInfoEnabled() {
+    return LOG.isInfoEnabled();
+  }
+
+  @Override
+  public boolean isInfoEnabled(Marker marker) {
+    return LOG.isInfoEnabled(marker);
+  }
+
+  @Override
+  public boolean isWarnEnabled() {
+    return LOG.isWarnEnabled();
+  }
+
+  @Override
+  public boolean isWarnEnabled(Marker marker) {
+    return LOG.isWarnEnabled(marker);
+  }
+
+  @Override
+  public boolean isErrorEnabled() {
+    return LOG.isErrorEnabled();
+  }
+
+  @Override
+  public boolean isErrorEnabled(Marker marker) {
+    return LOG.isErrorEnabled(marker);
+  }
+
+  @Override
+  protected String getFullyQualifiedCallerName() {
+    return null;
+  }
+
+  @Override
+  @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED",
+      justification = "Return value is self, using fluent object in non-fluent 
manner")

Review Comment:
   I'm assuming the warning being suppressed here is the builder, whose methods 
return `this`.
   
   This could be worded slightly better:
   ```suggestion
     @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED",
         justification = "fluent builder methods return itself, which we don't 
need to use")
   ```
   
   But, given that the implementation could change to have immutable 
intermediates rather than return `this`, it would probably be better to just 
use the builder API as intended.



##########
core/src/main/java/org/apache/accumulo/core/logging/ScanUserDataLogger.java:
##########
@@ -0,0 +1,169 @@
+/*
+ * 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
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.accumulo.core.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+import org.slf4j.event.Level;
+import org.slf4j.helpers.AbstractLogger;
+import org.slf4j.spi.LoggingEventBuilder;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+public class ScanUserDataLogger extends AbstractLogger {
+
+  private static final long serialVersionUID = 1L;
+  private static final Logger LOG = 
LoggerFactory.getLogger(ScanUserDataLogger.class);
+  private static final ThreadLocal<String> USER_DATA = new ThreadLocal<>();
+
+  public static void logTrace(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.trace(msg, objects);
+    }
+    if (!LOG.isTraceEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.trace(msg, objects);
+  }
+
+  public static void logDebug(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.debug(msg, objects);
+    }
+    if (!LOG.isDebugEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.debug(msg, objects);
+  }
+
+  public static void logInfo(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.info(msg, objects);
+    }
+    if (!LOG.isInfoEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.info(msg, objects);
+  }
+
+  public static void logWarn(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.warn(msg, objects);
+    }
+    if (!LOG.isWarnEnabled()) {
+      return;
+    }
+    USER_DATA.set(userData);
+    LOG.warn(msg, objects);
+  }
+
+  public static void logError(Logger classLogger, String userData, String msg, 
Object... objects) {
+    if (classLogger != null) {
+      classLogger.error(msg, objects);
+    }
+    USER_DATA.set(userData);
+    LOG.error(msg, objects);
+  }
+
+  @Override
+  public boolean isTraceEnabled() {
+    return LOG.isTraceEnabled();
+  }
+
+  @Override
+  public boolean isTraceEnabled(Marker marker) {
+    return LOG.isTraceEnabled(marker);
+  }
+
+  @Override
+  public boolean isDebugEnabled() {
+    return LOG.isDebugEnabled();
+  }
+
+  @Override
+  public boolean isDebugEnabled(Marker marker) {
+    return LOG.isDebugEnabled(marker);
+  }
+
+  @Override
+  public boolean isInfoEnabled() {
+    return LOG.isInfoEnabled();
+  }
+
+  @Override
+  public boolean isInfoEnabled(Marker marker) {
+    return LOG.isInfoEnabled(marker);
+  }
+
+  @Override
+  public boolean isWarnEnabled() {
+    return LOG.isWarnEnabled();
+  }
+
+  @Override
+  public boolean isWarnEnabled(Marker marker) {
+    return LOG.isWarnEnabled(marker);
+  }
+
+  @Override
+  public boolean isErrorEnabled() {
+    return LOG.isErrorEnabled();
+  }
+
+  @Override
+  public boolean isErrorEnabled(Marker marker) {
+    return LOG.isErrorEnabled(marker);
+  }
+
+  @Override
+  protected String getFullyQualifiedCallerName() {
+    return null;
+  }
+
+  @Override
+  @SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED",
+      justification = "Return value is self, using fluent object in non-fluent 
manner")
+  protected void handleNormalizedLoggingCall(Level level, Marker marker, 
String messagePattern,
+      Object[] arguments, Throwable throwable) {
+
+    final String userData = USER_DATA.get();
+    USER_DATA.set(null);
+
+    LoggingEventBuilder builder = LOG.atLevel(level);
+    if (marker != null) {
+      builder.addMarker(marker);
+    }
+    if (throwable != null) {
+      builder.setCause(throwable);
+    }
+    builder.setMessage("({}) " + messagePattern);
+    builder.addArgument(userData);
+    if (arguments != null) {
+      for (Object arg : arguments) {
+        builder.addArgument(arg);
+      }
+    }

Review Comment:
   It's not clear the message pattern this is intending to construct. A simple 
example in inline comments could help with debugging this.



##########
shell/src/main/java/org/apache/accumulo/shell/commands/ActiveScanIterator.java:
##########
@@ -71,9 +71,9 @@ private void readNext() {
 
     final String header = String.format(
         " %-21s| %-21s| %-9s| %-9s| %-7s| %-6s|"
-            + " %-8s| %-8s| %-10s| %-20s| %-10s| %-10s | %-20s | %s",
+            + " %-8s| %-8s| %-10s| %-20s| %-10s| %-10s | %-20s | %s | %s",

Review Comment:
   Because these are being printed... I'm wondering if it's better to use empty 
strings instead of nulls wherever null is being passed for the default here. 
This also makes me wonder if metadata scans internal to Accumulo should have a 
unique, generated user data.



##########
core/src/main/java/org/apache/accumulo/core/spi/scan/HintScanPrioritizer.java:
##########
@@ -80,6 +81,8 @@ private static int getPriority(ScanInfo si, int 
defaultPriority, HintProblemActi
     String prio = si.getExecutionHints().get("priority");
     if (prio != null) {
       try {
+        ScanUserDataLogger.logTrace(null, si.getUserData(),

Review Comment:
   Why are we passing `null` for the logger? Under what circumstances would we 
want to pass `null` vs. something else?



##########
core/src/main/java/org/apache/accumulo/core/spi/scan/ScanInfo.java:
##########
@@ -125,4 +125,9 @@ enum Type {
    * @return Hints set by a scanner using {@link 
ScannerBase#setExecutionHints(Map)}
    */
   Map<String,String> getExecutionHints();
+
+  /**
+   * @return user data set by a scanner using {@link 
ScannerBase#setUserData(String)}
+   */
+  String getUserData();

Review Comment:
   Since this is adding a method to an API/SPI interface, it would be nice to 
have a default method here, so it doesn't immediately break all users who 
implemented this SPI in the previous release.



##########
shell/src/main/java/org/apache/accumulo/shell/commands/ScanCommand.java:
##########
@@ -95,6 +96,14 @@ protected ConsistencyLevel getConsistency(CommandLine cl) {
     }
   }
 
+  protected String getUserData(CommandLine cl) {
+    if (cl.hasOption(userDataOpt.getOpt())) {
+      return cl.getOptionValue(userDataOpt.getOpt());
+    } else {
+      return "Accumulo Shell";

Review Comment:
   The shell could have a unique ID it uses by default here, so individual 
shell instances can be distinguished from one another.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to