SLING-6142 - Add grep like support in Log Tailer

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1764602 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/commons/log/logback/internal/FilteringListener.java b/src/main/java/org/apache/sling/commons/log/logback/internal/FilteringListener.java
new file mode 100644
index 0000000..162e40d
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/log/logback/internal/FilteringListener.java
@@ -0,0 +1,75 @@
+/*
+ * 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.sling.commons.log.logback.internal;
+
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import org.apache.sling.commons.log.logback.internal.Tailer.TailerListener;
+
+class FilteringListener implements TailerListener {
+    public static final String MATCH_ALL = "*";
+    private final Pattern pattern;
+    private final String regex;
+    private final PrintWriter pw;
+
+    /**
+     * Constructs a FilteringListener which uses regex pattern to
+     * determine which lines need to be included
+     *
+     * @param pw writer to write the tailed line
+     * @param regex pattern used to filter line. If null or "*"
+     *              then all lines would be included. Regex can be simple
+     *              string also. In that case search would be done in a
+     *              case insensitive way
+     */
+    public FilteringListener(PrintWriter pw, String regex){
+        this.pw = pw;
+        this.regex = regex != null ? regex.toLowerCase(Locale.ENGLISH) : null;
+        this.pattern = createPattern(regex);
+    }
+
+    @Override
+    public void handle(String line) {
+        if (include(line)){
+            pw.println(line);
+        }
+    }
+
+    private boolean include(String line) {
+        if (pattern == null) {
+            return true;
+        }
+
+        String lc = line.toLowerCase(Locale.ENGLISH);
+        if (lc.contains(regex)){
+            return true;
+        }
+        return pattern.matcher(line).matches();
+    }
+
+    private static Pattern createPattern(String regex) {
+        if (regex == null || MATCH_ALL.equals(regex)){
+            return null;
+        }
+        return Pattern.compile(regex);
+    }
+}
diff --git a/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java b/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java
index 2d80f36..b082a9d 100644
--- a/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java
+++ b/src/main/java/org/apache/sling/commons/log/logback/internal/SlingLogPanel.java
@@ -90,17 +90,11 @@
     private final LogbackManager logbackManager;
     private final BundleContext bundleContext;
 
-    private final String labelRes;
-
-    private final int labelResLen;
-
     private static final org.slf4j.Logger log = LoggerFactory.getLogger(SlingLogPanel.class);
 
     public SlingLogPanel(final LogbackManager logbackManager, final BundleContext bundleContext) {
         this.logbackManager = logbackManager;
         this.bundleContext = bundleContext;
-        this.labelRes = '/' + APP_ROOT + '/';
-        this.labelResLen = labelRes.length() - 1;
     }
 
     @Override
@@ -572,7 +566,7 @@
                         if (numOfLines == 0){
                             numOfLines = logbackManager.getLogConfigManager().getNumOfLines();
                         }
-                        new Tailer(pw, numOfLines).tail(file);
+                        new Tailer(new FilteringListener(pw, opts.getRegex()), numOfLines).tail(file);
                     }
                 }
                 return;
@@ -584,11 +578,13 @@
     private String getLinkedName(FileAppender<ILoggingEvent> appender) throws UnsupportedEncodingException {
         String fileName = appender.getFile();
         String name = appender.getName();
-        return String.format("File : [<a href=\"%s/%s?%s=%d&%s=%s\">%s</a>] %s",
+        return String.format("File : [<a href=\"%s/%s?%s=%d&%s=%s&%s=%s\">%s</a>] %s",
                 APP_ROOT,
                 PATH_TAILER,
-                PARAM_NUM_OF_LINES,
+                PARAM_TAIL_NUM_OF_LINES,
                 logbackManager.getLogConfigManager().getNumOfLines(),
+                PARAM_TAIL_GREP,
+                FilteringListener.MATCH_ALL,
                 PARAM_APPENDER_NAME,
                 URLEncoder.encode(name, "UTF-8"),
                 XmlUtil.escapeXml(name),
diff --git a/src/main/java/org/apache/sling/commons/log/logback/webconsole/LogPanel.java b/src/main/java/org/apache/sling/commons/log/logback/webconsole/LogPanel.java
index a33c2c0..50b2be3 100644
--- a/src/main/java/org/apache/sling/commons/log/logback/webconsole/LogPanel.java
+++ b/src/main/java/org/apache/sling/commons/log/logback/webconsole/LogPanel.java
@@ -29,11 +29,16 @@
     /**
      * Request param name to control number of lines to include in the log
      */
-    String PARAM_NUM_OF_LINES = "tail";
+    String PARAM_TAIL_NUM_OF_LINES = "tail";
     /**
      * Request param name for appender name
      */
     String PARAM_APPENDER_NAME = "name";
+
+    /**
+     * Request param capturing the regular expression to search
+     */
+    String PARAM_TAIL_GREP = "grep";
     /**
      * Let the path end with extension. In that case WebConsole logic would by pass this request's
      * response completely
diff --git a/src/main/java/org/apache/sling/commons/log/logback/webconsole/TailerOptions.java b/src/main/java/org/apache/sling/commons/log/logback/webconsole/TailerOptions.java
index 961f78e..d504c73 100644
--- a/src/main/java/org/apache/sling/commons/log/logback/webconsole/TailerOptions.java
+++ b/src/main/java/org/apache/sling/commons/log/logback/webconsole/TailerOptions.java
@@ -21,9 +21,11 @@
 
 public final class TailerOptions {
     private final int numOfLines;
+    private final String regex;
 
-    public TailerOptions(int numOfLines) {
+    public TailerOptions(int numOfLines, String regex) {
         this.numOfLines = numOfLines;
+        this.regex = regex;
     }
 
     public boolean tailAll() {
@@ -33,4 +35,8 @@
     public int getNumOfLines() {
         return numOfLines;
     }
+
+    public String getRegex() {
+        return regex;
+    }
 }
diff --git a/src/test/java/org/apache/sling/commons/log/logback/internal/FilteringListenerTest.java b/src/test/java/org/apache/sling/commons/log/logback/internal/FilteringListenerTest.java
new file mode 100644
index 0000000..21f4634
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/log/logback/internal/FilteringListenerTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.sling.commons.log.logback.internal;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.apache.sling.commons.log.logback.internal.Tailer.TailerListener;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.*;
+
+public class FilteringListenerTest {
+
+    private StringWriter sw = new StringWriter();
+    private PrintWriter pw = new PrintWriter(sw);
+
+    @Test
+    public void nullPattern() throws Exception{
+        TailerListener l = new FilteringListener(pw, null);
+        l.handle("foo");
+        assertThat(sw.toString(), containsString("foo"));
+    }
+
+    @Test
+    public void allPattern() throws Exception{
+        TailerListener l = new FilteringListener(pw, FilteringListener.MATCH_ALL);
+        l.handle("foo");
+        assertThat(sw.toString(), containsString("foo"));
+    }
+
+    @Test
+    public void basicPattern() throws Exception{
+        TailerListener l = new FilteringListener(pw, "foo");
+        l.handle("foo");
+        assertThat(sw.toString(), containsString("foo"));
+
+        l.handle("bar");
+        assertThat(sw.toString(), not(containsString("bar")));
+
+        l.handle("foo bar");
+        assertThat(sw.toString(), containsString("foo bar"));
+    }
+
+    @Test
+    public void regexPattern() throws Exception{
+        TailerListener l = new FilteringListener(pw, "foo.*bar");
+        l.handle("foo");
+        assertThat(sw.toString(), not(containsString("foo")));
+
+        l.handle("bar");
+        assertThat(sw.toString(), not(containsString("bar")));
+
+        l.handle("foo talks to bar");
+        assertThat(sw.toString(), containsString("bar"));
+        assertThat(sw.toString(), containsString("talks"));
+    }
+
+}
\ No newline at end of file