added support for 'custom level definitions' in LogFilePatternReceiver, which allows the user to map strings in the log file to Log4j levels
if there are extra spaces that would prevent a pattern from matching, strip them out
exclude empty lines (if appendNonMatches was false, would add an empty line to the log)

With these changes, redirection of Android logging via 'logcat -v time' will now render fine in Chainsaw, using:
<param name="logFormat" value="TIMESTAMP LEVEL/LOGGER(PROP(PID)):MESSAGE"/> 
<param name="customLevelDefinitions" value="V=TRACE,D=DEBUG,I=INFO,W=WARN,E=ERROR,F=FATAL,S=OFF"/>


git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/companions/receivers/trunk@941180 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
index 1f54b5e..0b4f45b 100644
--- a/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
+++ b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
@@ -31,6 +31,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.StringTokenizer;
 
 import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
@@ -158,11 +159,10 @@
   private static final String REGEXP_DEFAULT_WILDCARD = ".*?";
   private static final String REGEXP_GREEDY_WILDCARD = ".*";
   private static final String PATTERN_WILDCARD = "*";
-  private static final String NOSPACE_GROUP = "(\\S*?)";
+  private static final String NOSPACE_GROUP = "(\\S*\\s*?)";
   private static final String DEFAULT_GROUP = "(" + REGEXP_DEFAULT_WILDCARD + ")";
   private static final String GREEDY_GROUP = "(" + REGEXP_GREEDY_WILDCARD + ")";
   private static final String MULTIPLE_SPACES_REGEXP = "[ ]+";
-  
   private final String newLine = System.getProperty("line.separator");
 
   private final String[] emptyException = new String[] { "" };
@@ -170,6 +170,7 @@
   private SimpleDateFormat dateFormat;
   private String timestampFormat = "yyyy-MM-d HH:mm:ss,SSS";
   private String logFormat;
+  private String customLevelDefinitions;
   private String fileURL;
   private String host;
   private String path;
@@ -197,6 +198,7 @@
   private boolean useCurrentThread;
   public static final int MISSING_FILE_RETRY_MILLIS = 10000;
   private boolean appendNonMatches;
+  private final Map customLevelDefinitionMap = new HashMap();
 
     public LogFilePatternReceiver() {
     keywords.add(TIMESTAMP);
@@ -235,6 +237,20 @@
     this.fileURL = fileURL;
   }
 
+    /**
+     * If the log file contains non-log4j level strings, they can be mapped to log4j levels using the format (android example):
+     * V=TRACE,D=DEBUG,I=INFO,W=WARN,E=ERROR,F=FATAL,S=OFF
+     *
+     * @param customLevelDefinitions the level definition string
+     */
+  public void setCustomLevelDefinitions(String customLevelDefinitions) {
+    this.customLevelDefinitions = customLevelDefinitions;
+  }
+
+  public String getCustomLevelDefinitions() {
+    return customLevelDefinitions;
+  }
+
   /**
    * Accessor
    * @return append non matches
@@ -479,6 +495,8 @@
         Perl5Matcher eventMatcher = new Perl5Matcher();
         String line;
         while ((line = bufferedReader.readLine()) != null) {
+            //skip empty line entries
+            if (line.trim().equals("")) {continue;}
             if (eventMatcher.matches(line, regexpPattern)) {
                 //build an event from the previous match (held in current map)
                 LoggingEvent event = buildEvent();
@@ -631,7 +649,8 @@
       dateFormat = new SimpleDateFormat(timestampFormat);
       timestampPatternText = convertTimestamp();
     }
-
+    //if custom level definitions exist, parse them
+    updateCustomLevelDefinitionMap();
     try {
       if (filterExpression != null) {
         expressionRule = ExpressionRule.getRule(filterExpression);
@@ -729,6 +748,18 @@
     getLogger().debug("regexp is " + regexp);
   }
 
+    private void updateCustomLevelDefinitionMap() {
+        if (customLevelDefinitions != null) {
+            StringTokenizer entryTokenizer = new StringTokenizer(customLevelDefinitions, ",");
+
+            customLevelDefinitionMap.clear();
+            while (entryTokenizer.hasMoreTokens()) {
+                StringTokenizer innerTokenizer = new StringTokenizer(entryTokenizer.nextToken(), "=");
+                customLevelDefinitionMap.put(innerTokenizer.nextToken(), Level.toLevel(innerTokenizer.nextToken()));
+            }
+        }
+    }
+
     private boolean isInteger(String value) {
         try {
             Integer.parseInt(value);
@@ -847,11 +878,24 @@
     }
 
     level = (String) fieldMap.remove(LEVEL);
-    Level levelImpl = (level == null ? Level.DEBUG : Level.toLevel(level.trim()));
-    if (level != null && !level.equals(levelImpl.toString())) {
-        getLogger().debug("found unexpected level: " + level + ", logger: " + logger.getName() + ", msg: " + message);
-        //make sure the text that couldn't match a level is added to the message
-        message = level + " " + message;
+    Level levelImpl;
+    if (level == null) {
+        levelImpl = Level.DEBUG;
+    } else {
+        //first try to resolve against custom level definition map, then fall back to regular levels
+        levelImpl = (Level) customLevelDefinitionMap.get(level);
+        if (levelImpl == null) {
+            levelImpl = Level.toLevel(level.trim());
+            if (!level.equals(levelImpl.toString())) {
+                //check custom level map
+                if (levelImpl == null) {
+                    levelImpl = Level.DEBUG;
+                    getLogger().debug("found unexpected level: " + level + ", logger: " + logger.getName() + ", msg: " + message);
+                    //make sure the text that couldn't match a level is added to the message
+                    message = level + " " + message;
+                }
+            }
+        }
     }
 
     threadName = (String) fieldMap.remove(THREAD);
diff --git a/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java
index ba26f90..f99f289 100644
--- a/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java
+++ b/src/main/java/org/apache/log4j/varia/LogFilePatternReceiverBeanInfo.java
@@ -42,6 +42,7 @@
           "filterExpression", LogFilePatternReceiver.class),
         new PropertyDescriptor("waitMillis", LogFilePatternReceiver.class),
         new PropertyDescriptor("appendNonMatches", LogFilePatternReceiver.class),
+        new PropertyDescriptor("customLevelDefinitions", LogFilePatternReceiver.class),
         new PropertyDescriptor("useCurrentThread", LogFilePatternReceiver.class),
       };
     } catch (Exception e) {