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

tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 0bd5b0329a [ZEPPELIN-6322] Filter download messages in ProcessData 
error stream
0bd5b0329a is described below

commit 0bd5b0329ad4dd50ed006a2fac904e67e072dd04
Author: YeonKyung Ryu <[email protected]>
AuthorDate: Tue Jul 21 00:18:21 2026 +0900

    [ZEPPELIN-6322] Filter download messages in ProcessData error stream
    
    ### What is this PR for?
    This PR improves error stream output filtering in the `ProcessData` class 
to exclude download-related messages. Currently, Maven/npm download progress 
information clutters the error stream during integration tests, making it 
difficult to identify actual errors.
    This change filters out download messages (e.g., "Downloading:", "Progress: 
45%", "1024/2048 KB") while preserving real error messages.
    
    
    ### What type of PR is it?
    Improvement
    
    
      ### Todos
      * [x] - Add DOWNLOAD_PATTERNS array with 6 regex patterns
      * [x] - Implement isDownloadMessage() method
      * [x] - Apply filtering logic in buildOutputAndErrorStreamData()
    
    
    ### What is the Jira issue?
    [ZEPPELIN-6322](https://issues.apache.org/jira/browse/ZEPPELIN-6322)
    
    ### How should this be tested?
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions? No
    * Does this needs documentation? No
    
    
    Closes #5104 from celinayk/ZEPPELIN-6322.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../test/java/org/apache/zeppelin/ProcessData.java | 91 ++++++++++++++++++++--
 .../java/org/apache/zeppelin/ProcessDataTest.java  | 63 +++++++++++++++
 2 files changed, 147 insertions(+), 7 deletions(-)

diff --git 
a/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessData.java 
b/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessData.java
index f4a578a32b..a7ad3fc3d9 100644
--- a/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessData.java
+++ b/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessData.java
@@ -25,6 +25,7 @@ import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,6 +40,19 @@ public class ProcessData {
 
   private static final Logger LOGGER = 
LoggerFactory.getLogger(ProcessData.class);
 
+  // Patterns to identify download-related messages in the error stream.
+  // Anchored to the start of a (single, already-split) line to avoid matching
+  // error messages that merely mention a size or percentage figure.
+  private static final Pattern[] DOWNLOAD_PATTERNS = {
+    Pattern.compile("^\\[INFO\\]\\s+(Downloading|Downloaded):.*"),  // Maven 
download messages
+    Pattern.compile("^Downloading\\s+.*"),                          // Generic 
downloading messages
+    Pattern.compile("^Downloaded\\s+.*"),                           // Generic 
downloaded messages
+    Pattern.compile("^Progress\\s*(\\(\\d+\\))?:?\\s*\\d+%.*",
+        Pattern.CASE_INSENSITIVE),                                  // 
Progress indicators, e.g. "Progress (1): 45%"
+    Pattern.compile("^[\\d.]+/[\\d.]+\\s*(KB|MB|GB|bytes|kB)\\b.*",
+        Pattern.CASE_INSENSITIVE)                                   // Size 
progress (e.g., "1024/2048 KB")
+  };
+
   private Process checked_process;
   private boolean printToConsole = false;
 
@@ -149,6 +163,51 @@ public class ProcessData {
     return this.errorStream;
   }
 
+  /**
+   * Checks whether a single (already trimmed) line looks like a 
download-related
+   * message (Maven downloads, progress indicators, size information, etc.).
+   *
+   * <p>This is only used to decide the console log level for a line - a line 
that
+   * matches is still logged (at TRACE), never dropped, so a misclassified 
line is
+   * merely quieter rather than lost. Callers must check for "error"/"failed" 
content
+   * before calling this, since that check always takes precedence.
+   *
+   * @param line The single line to check
+   * @return true if the line looks like a download message, false otherwise
+   */
+  boolean isDownloadMessage(String line) {
+    if (line == null || line.isEmpty()) {
+      return false;
+    }
+
+    for (Pattern pattern : DOWNLOAD_PATTERNS) {
+      if (pattern.matcher(line).matches()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Classifies and logs a single, complete line of error-stream output to the 
console.
+   * A line is never dropped: real error/failed lines always win at WARN, and 
only lines
+   * that look like download noise are downgraded to TRACE instead of being 
discarded.
+   */
+  private void logErrorLine(String line) {
+    String trimmedLine = line.trim();
+    if (trimmedLine.isEmpty()) {
+      return;
+    }
+    String lowerLine = trimmedLine.toLowerCase();
+    if (lowerLine.contains("error") || lowerLine.contains("failed")) {
+      LOGGER.warn(trimmedLine);
+    } else if (isDownloadMessage(trimmedLine)) {
+      LOGGER.trace(trimmedLine);
+    } else {
+      LOGGER.debug(trimmedLine);
+    }
+  }
+
   @Override
   public String toString() {
     StringBuilder result = new StringBuilder();
@@ -161,6 +220,10 @@ public class ProcessData {
   private void buildOutputAndErrorStreamData() throws IOException {
     StringBuilder sbInStream = new StringBuilder();
     StringBuilder sbErrorStream = new StringBuilder();
+    // Carries an incomplete trailing line across chunk reads, since a single 
line of
+    // output can be split across two BUFFER_LEN-sized reads (or even two 
outer-loop
+    // iterations). Only complete lines are ever classified/logged from this 
buffer.
+    StringBuilder pendingErrorLine = new StringBuilder();
 
     try {
       InputStream in = this.checked_process.getInputStream();
@@ -203,19 +266,25 @@ public class ProcessData {
             break;
           }
           tempSB.append(charBuffer, 0, readCount);
+          // The full, unfiltered chunk is always kept here, so 
getErrorStream() always
+          // returns the complete error output. Download-message filtering 
below only
+          // affects the verbosity of the console log, not the returned stream 
content.
           sbErrorStream.append(tempSB);
           if (tempSB.length() > 0) {
             outputProduced = true;
             String temp = new String(tempSB);
             temp = temp.replaceAll("Pseudo-terminal will not be allocated 
because stdin is not a terminal.", "");
-            //TODO : error stream output need to be improved, because it 
outputs downloading information.
             if (printToConsole) {
-              if (!temp.trim().equals("")) {
-                if (temp.toLowerCase().contains("error") || 
temp.toLowerCase().contains("failed")) {
-                  LOGGER.warn(temp.trim());
-                } else {
-                  LOGGER.debug(temp.trim());
-                }
+              // Buffer chunks can hold several lines, and a line can itself 
be split
+              // across chunks/iterations, so accumulate into pendingErrorLine 
and only
+              // classify/log complete lines. The trailing remainder (no 
newline yet)
+              // stays buffered until more data (or stream close) completes it.
+              pendingErrorLine.append(temp);
+              int newlineIndex;
+              while ((newlineIndex = pendingErrorLine.indexOf("\n")) >= 0) {
+                String line = pendingErrorLine.substring(0, newlineIndex);
+                pendingErrorLine.delete(0, newlineIndex + 1);
+                logErrorLine(line);
               }
             }
           }
@@ -246,6 +315,14 @@ public class ProcessData {
         }
       }
 
+      // Stream ended (or we gave up waiting) - the process will send no more 
data, so
+      // whatever is left in pendingErrorLine is a final, unterminated line. 
Flush it
+      // rather than silently dropping it.
+      if (printToConsole && pendingErrorLine.length() > 0) {
+        logErrorLine(pendingErrorLine.toString());
+        pendingErrorLine.setLength(0);
+      }
+
       in.close();
       inErrors.close();
     } finally {
diff --git 
a/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessDataTest.java 
b/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessDataTest.java
new file mode 100644
index 0000000000..a7df1dca30
--- /dev/null
+++ 
b/zeppelin-integration/src/test/java/org/apache/zeppelin/ProcessDataTest.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.zeppelin;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class ProcessDataTest {
+
+  private final ProcessData processData = new ProcessData(null, false);
+
+  @Test
+  public void detectsMavenDownloadMessages() {
+    assertTrue(processData.isDownloadMessage(
+        "[INFO] Downloading: https://repo1.maven.org/maven2/foo/foo.jar";));
+    assertTrue(processData.isDownloadMessage(
+        "[INFO] Downloaded: https://repo1.maven.org/maven2/foo/foo.jar (12 kB 
at 45 kB/s)"));
+    assertTrue(processData.isDownloadMessage("Downloading from central: 
https://example.com/foo.jar";));
+    assertTrue(processData.isDownloadMessage("Downloaded from central: 
https://example.com/foo.jar";));
+  }
+
+  @Test
+  public void detectsProgressAndSizeMessages() {
+    assertTrue(processData.isDownloadMessage("Progress (1): 45%"));
+    assertTrue(processData.isDownloadMessage("progress: 45%"));
+    assertTrue(processData.isDownloadMessage("1024/2048 KB"));
+    assertTrue(processData.isDownloadMessage("1.5/12 kB"));
+  }
+
+  @Test
+  public void doesNotFilterOutRealErrorLines() {
+    // These lines contain download-shaped figures but are genuine failures 
and must
+    // never be classified as a download message, otherwise they would only 
ever be
+    // reachable via the download-noise path instead of being surfaced as 
warnings.
+    assertFalse(processData.isDownloadMessage("Task failed after writing 
1024/2048 MB"));
+    assertFalse(processData.isDownloadMessage("Build failed at progress: 
50%"));
+    assertFalse(processData.isDownloadMessage("ERROR: could not resolve 
dependency foo:bar:1.0"));
+    assertFalse(processData.isDownloadMessage("Compilation error in 
Main.java"));
+  }
+
+  @Test
+  public void ignoresBlankOrNullInput() {
+    assertFalse(processData.isDownloadMessage(""));
+    assertFalse(processData.isDownloadMessage(null));
+  }
+}
\ No newline at end of file

Reply via email to