Merge pull request #295 from jbonofre/KARAF-7260

[KARAF-7260] Add included.categories property
diff --git a/collector/log/src/main/cfg/org.apache.karaf.decanter.collector.log.cfg b/collector/log/src/main/cfg/org.apache.karaf.decanter.collector.log.cfg
index 527f805..7e3ea65 100644
--- a/collector/log/src/main/cfg/org.apache.karaf.decanter.collector.log.cfg
+++ b/collector/log/src/main/cfg/org.apache.karaf.decanter.collector.log.cfg
@@ -21,9 +21,12 @@
 # Decanter Log collector configuration
 #
 
-# logger categories that the log collector should ignore
-# By default, we ignored all messages coming from Decanter Appenders
-ignored.categories=org.apache.karaf.decanter.appender.*
+# logger categories that the log collector should exclude
+# By default, we exclude all messages coming from Decanter appenders
+excluded.categories=org.apache.karaf.decanter.appender.*
+
+# include only some logger categories, others categories will be excluded by default
+#included.categories=my.logger,another.one
 
 # custom fields
 #fields.add.eventType=LOGEvent
diff --git a/collector/log/src/main/java/org/apache/karaf/decanter/collector/log/LogCollector.java b/collector/log/src/main/java/org/apache/karaf/decanter/collector/log/LogCollector.java
index 5ca4ff2..054bff0 100644
--- a/collector/log/src/main/java/org/apache/karaf/decanter/collector/log/LogCollector.java
+++ b/collector/log/src/main/java/org/apache/karaf/decanter/collector/log/LogCollector.java
@@ -56,7 +56,8 @@
     private final static Pattern PATTERN = Pattern.compile("[^A-Za-z0-9]");
 
     private Dictionary<String, Object> properties;
-    protected String[] ignoredCategories;
+    protected String[] excludedCategories;
+    protected String[] includedCategories;
     protected String[] locationDisabledCategories;
 
     @SuppressWarnings("unchecked")
@@ -64,7 +65,13 @@
     public void activate(ComponentContext context) {
         this.properties = context.getProperties();
         if (this.properties.get("ignored.categories") != null) {
-            ignoredCategories = ((String)this.properties.get("ignored.categories")).split(",");
+            excludedCategories = ((String)this.properties.get("ignored.categories")).split(",");
+        }
+        if (this.properties.get("excluded.categories") != null) {
+            excludedCategories = ((String)this.properties.get("excluded.categories")).split(",");
+        }
+        if (this.properties.get("included.categories") != null) {
+            includedCategories = ((String)this.properties.get("included.categories")).split(",");
         }
         if (this.properties.get("location.disabled") != null) {
             locationDisabledCategories = ((String) this.properties.get("location.disabled")).split(",");
@@ -86,8 +93,12 @@
         }
     }
 
-    private void appendInternal(PaxLoggingEvent event) throws Exception {
-        if (isIgnored(event.getLoggerName(), ignoredCategories)) {
+    protected void appendInternal(PaxLoggingEvent event) throws Exception {
+        if (includedCategories != null && !filterCategory(event.getLoggerName(), includedCategories)) {
+            LOGGER.debug("{} logger is not in the included list", event.getLoggerName());
+            return;
+        }
+        if (filterCategory(event.getLoggerName(), excludedCategories)) {
             LOGGER.debug("{} logger is ignored by the log collector", event.getLoggerName());
             return;
         }
@@ -100,10 +111,10 @@
         data.put("loggerName", event.getLoggerName());
         data.put("threadName", event.getThreadName());
         data.put("message", event.getMessage());
-        data.put("level", event.getLevel().toString());
+        data.put("level", (event.getLevel() != null) ? event.getLevel().toString() : "default");
         data.put("renderedMessage", event.getRenderedMessage());
         data.put("MDC", event.getProperties());
-        if (locationDisabledCategories == null || !isIgnored(event.getLoggerName(), locationDisabledCategories)) {
+        if (locationDisabledCategories == null || !filterCategory(event.getLoggerName(), locationDisabledCategories)) {
             putLocation(data, event.getLocationInformation());
         }
         String[] throwableAr = event.getThrowableStrRep();
@@ -135,10 +146,12 @@
     }
 
     private void putLocation(Map<String, Object> data, PaxLocationInfo loc) {
-        data.put("loc.class", loc.getClassName());
-        data.put("loc.file", loc.getFileName());
-        data.put("loc.line", loc.getLineNumber());
-        data.put("loc.method", loc.getMethodName());
+        if (loc != null) {
+            data.put("loc.class", loc.getClassName());
+            data.put("loc.file", loc.getFileName());
+            data.put("loc.line", loc.getLineNumber());
+            data.put("loc.method", loc.getMethodName());
+        }
     }
 
     private Object join(String[] throwableAr) {
@@ -149,12 +162,12 @@
         return builder.toString();
     }
 
-    protected boolean isIgnored(String loggerName, String[] ignoreList) {
+    protected boolean filterCategory(String loggerName, String[] filterCategories) {
         if (loggerName == null) {
             return true;
         }
-        if (ignoreList != null) {
-            for (String cat : ignoreList) {
+        if (filterCategories != null) {
+            for (String cat : filterCategories) {
                 if (loggerName.matches(cat)) {
                     return true;
                 }
diff --git a/collector/log/src/test/java/org/apache/karaf/decanter/collector/log/LogCollectorTest.java b/collector/log/src/test/java/org/apache/karaf/decanter/collector/log/LogCollectorTest.java
index ca109b9..4e3a5ad 100644
--- a/collector/log/src/test/java/org/apache/karaf/decanter/collector/log/LogCollectorTest.java
+++ b/collector/log/src/test/java/org/apache/karaf/decanter/collector/log/LogCollectorTest.java
@@ -16,7 +16,13 @@
  */
 package org.apache.karaf.decanter.collector.log;
 
+import org.apache.log4j.Category;
+import org.apache.log4j.spi.LoggingEvent;
 import org.junit.Test;
+import org.ops4j.pax.logging.service.internal.spi.PaxLoggingEventImpl;
+import org.ops4j.pax.logging.spi.PaxLevel;
+import org.ops4j.pax.logging.spi.PaxLocationInfo;
+import org.ops4j.pax.logging.spi.PaxLoggingEvent;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -25,10 +31,7 @@
 import org.osgi.service.event.Event;
 import org.osgi.service.event.EventAdmin;
 
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.Properties;
+import java.util.*;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
@@ -38,10 +41,10 @@
 
     @Test
     public void testCleanLoggerName() {
-        LogCollector appender = new LogCollector();
+        LogCollector collector = new LogCollector();
         
         String loggerName = "wrong$Pattern%For&event!Name";
-        String cleanedLoggerName = appender.cleanLoggerName(loggerName);
+        String cleanedLoggerName = collector.cleanLoggerName(loggerName);
 
         assertThat(cleanedLoggerName, not(containsString("%")));
         assertThat(cleanedLoggerName, not(containsString("$")));
@@ -52,56 +55,175 @@
 
     @Test
     public void testIgnoredLoggerCategories() {
-        LogCollector appender = new LogCollector();
+        LogCollector collector = new LogCollector();
 
         ComponentContext componentContext = new ComponentContextMock();
         componentContext.getProperties().put("ignored.categories", "org.apache.karaf.decanter.collector.log.*,test,other");
 
-        appender.activate(componentContext);
+        collector.activate(componentContext);
 
-        assertEquals("org.apache.karaf.decanter.collector.log.*", appender.ignoredCategories[0]);
-        assertEquals("test", appender.ignoredCategories[1]);
-        assertEquals("other", appender.ignoredCategories[2]);
+        assertEquals("org.apache.karaf.decanter.collector.log.*", collector.excludedCategories[0]);
+        assertEquals("test", collector.excludedCategories[1]);
+        assertEquals("other", collector.excludedCategories[2]);
 
-        assertFalse(appender.isIgnored("org.apache.karaf.decanter.other", appender.ignoredCategories));
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.collector.log", appender.ignoredCategories));
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.collector.log.LogEvent", appender.ignoredCategories));
+        assertFalse(collector.filterCategory("org.apache.karaf.decanter.other", collector.excludedCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log", collector.excludedCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log.LogEvent", collector.excludedCategories));
+    }
+
+    @Test
+    public void testExcludedLoggerCategories() {
+        LogCollector collector = new LogCollector();
+
+        ComponentContext componentContext = new ComponentContextMock();
+        componentContext.getProperties().put("excluded.categories", "org.apache.karaf.decanter.collector.log.*,test,other");
+
+        collector.activate(componentContext);
+
+        assertEquals("org.apache.karaf.decanter.collector.log.*", collector.excludedCategories[0]);
+        assertEquals("test", collector.excludedCategories[1]);
+        assertEquals("other", collector.excludedCategories[2]);
+
+        assertFalse(collector.filterCategory("org.apache.karaf.decanter.other", collector.excludedCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log", collector.excludedCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log.LogEvent", collector.excludedCategories));
+    }
+
+    @Test
+    public void testIncludedLoggerCategories() {
+        LogCollector collector = new LogCollector();
+
+        ComponentContext componentContext = new ComponentContextMock();
+        componentContext.getProperties().put("included.categories", "foo,bar");
+
+        collector.activate(componentContext);
+
+        assertEquals("foo", collector.includedCategories[0]);
+        assertEquals("bar", collector.includedCategories[1]);
+
+        assertTrue(collector.filterCategory("foo", collector.includedCategories));
+        assertTrue(collector.filterCategory("bar", collector.includedCategories));
+        assertFalse(collector.filterCategory("other", collector.includedCategories));
+    }
+
+    @Test
+    public void testCollectorProcess() throws Exception {
+        DispatcherMock dispatcher = new DispatcherMock();
+
+        LogCollector collector = new LogCollector();
+        collector.dispatcher = dispatcher;
+        collector.activate(new ComponentContextMock());
+
+        collector.appendInternal(new PaxLoggingEventMock("foo", "This is a test"));
+        collector.appendInternal(new PaxLoggingEventMock("bar", "Another test"));
+
+        assertEquals(2, dispatcher.postEvents.size());
+
+        assertEquals("foo", dispatcher.postEvents.get(0).getProperty("loggerName"));
+        assertEquals("This is a test", dispatcher.postEvents.get(0).getProperty("message"));
+
+        assertEquals("bar", dispatcher.postEvents.get(1).getProperty("loggerName"));
+        assertEquals("Another test", dispatcher.postEvents.get(1).getProperty("message"));
+    }
+
+    @Test
+    public void testExcludedCategoriesCollectorProcess() throws Exception {
+        DispatcherMock dispatcher = new DispatcherMock();
+
+        ComponentContextMock componentContext = new ComponentContextMock();
+        componentContext.getProperties().put("excluded.categories", "my.excluded.logger");
+
+        LogCollector collector = new LogCollector();
+        collector.dispatcher = dispatcher;
+        collector.activate(componentContext);
+
+        collector.appendInternal(new PaxLoggingEventMock("foo", "This is a test"));
+        collector.appendInternal(new PaxLoggingEventMock("my.excluded.logger", "excluded"));
+
+        assertEquals(1, dispatcher.postEvents.size());
+
+        assertEquals("foo", dispatcher.postEvents.get(0).getProperty("loggerName"));
+        assertEquals("This is a test", dispatcher.postEvents.get(0).getProperty("message"));
+    }
+
+    @Test
+    public void testIncludedCategoriesCollectorProcess() throws Exception {
+        DispatcherMock dispatcherMock = new DispatcherMock();
+
+        ComponentContextMock componentContext = new ComponentContextMock();
+        componentContext.getProperties().put("included.categories", "my.logger");
+
+        LogCollector collector = new LogCollector();
+        collector.dispatcher = dispatcherMock;
+        collector.activate(componentContext);
+
+        collector.appendInternal(new PaxLoggingEventMock("my.logger", "This is a test"));
+        collector.appendInternal(new PaxLoggingEventMock("other.logger", "another"));
+        collector.appendInternal(new PaxLoggingEventMock("bar", "bar"));
+
+        assertEquals(1, dispatcherMock.postEvents.size());
+
+        assertEquals("my.logger", dispatcherMock.postEvents.get(0).getProperty("loggerName"));
+        assertEquals("This is a test", dispatcherMock.postEvents.get(0).getProperty("message"));
+    }
+
+    @Test
+    public void testExcludedIncludedCategoriesCollectorProcess() throws Exception {
+        DispatcherMock dispatcherMock = new DispatcherMock();
+
+        ComponentContextMock componentContextMock = new ComponentContextMock();
+        componentContextMock.getProperties().put("included.categories", "my.included.logger,my.includedexcluded.logger");
+        componentContextMock.getProperties().put("excluded.categories", "my.includedexcluded.logger");
+
+        LogCollector collector = new LogCollector();
+        collector.dispatcher = dispatcherMock;
+        collector.activate(componentContextMock);
+
+        collector.appendInternal(new PaxLoggingEventMock("foo", "This is foo"));
+        collector.appendInternal(new PaxLoggingEventMock("bar", "This is bar"));
+        collector.appendInternal(new PaxLoggingEventMock("my.included.logger", "This should be included"));
+        collector.appendInternal(new PaxLoggingEventMock("my.includedexcluded.logger", "This should be excluded"));
+
+        assertEquals(1, dispatcherMock.postEvents.size());
+
+        assertEquals("my.included.logger", dispatcherMock.postEvents.get(0).getProperty("loggerName"));
+        assertEquals("This should be included", dispatcherMock.postEvents.get(0).getProperty("message"));
     }
 
     @Test
     public void testDisabledLocationCategories() {
-        LogCollector appender = new LogCollector();
+        LogCollector collector = new LogCollector();
 
         ComponentContext componentContext = new ComponentContextMock();
         componentContext.getProperties().put("location.disabled", "org.apache.karaf.decanter.collector.log.*,test,other");
 
-        appender.activate(componentContext);
+        collector.activate(componentContext);
 
-        assertEquals("org.apache.karaf.decanter.collector.log.*", appender.locationDisabledCategories[0]);
-        assertEquals("test", appender.locationDisabledCategories[1]);
-        assertEquals("other", appender.locationDisabledCategories[2]);
+        assertEquals("org.apache.karaf.decanter.collector.log.*", collector.locationDisabledCategories[0]);
+        assertEquals("test", collector.locationDisabledCategories[1]);
+        assertEquals("other", collector.locationDisabledCategories[2]);
 
-        assertFalse(appender.isIgnored("org.apache.karaf.decanter.other", appender.locationDisabledCategories));
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.collector.log", appender.locationDisabledCategories));
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.collector.log.LogEvent", appender.locationDisabledCategories));
+        assertFalse(collector.filterCategory("org.apache.karaf.decanter.other", collector.locationDisabledCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log", collector.locationDisabledCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log.LogEvent", collector.locationDisabledCategories));
     }
 
     @Test
     public void testDisabledLocationCategoriesAllWildcard() {
-        LogCollector appender = new LogCollector();
+        LogCollector collector = new LogCollector();
 
         ComponentContext componentContext = new ComponentContextMock();
         componentContext.getProperties().put("location.disabled", ".*,test,other");
 
-        appender.activate(componentContext);
+        collector.activate(componentContext);
 
-        assertEquals(".*", appender.locationDisabledCategories[0]);
-        assertEquals("test", appender.locationDisabledCategories[1]);
-        assertEquals("other", appender.locationDisabledCategories[2]);
+        assertEquals(".*", collector.locationDisabledCategories[0]);
+        assertEquals("test", collector.locationDisabledCategories[1]);
+        assertEquals("other", collector.locationDisabledCategories[2]);
 
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.other", appender.locationDisabledCategories));
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.collector.log", appender.locationDisabledCategories));
-        assertTrue(appender.isIgnored("org.apache.karaf.decanter.collector.log.LogEvent", appender.locationDisabledCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.other", collector.locationDisabledCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log", collector.locationDisabledCategories));
+        assertTrue(collector.filterCategory("org.apache.karaf.decanter.collector.log.LogEvent", collector.locationDisabledCategories));
     }
 
     private class ComponentContextMock implements ComponentContext {
@@ -187,4 +309,70 @@
         }
     }
 
+    private class PaxLoggingEventMock implements PaxLoggingEvent {
+
+        public String loggerName;
+        public String message;
+
+        public PaxLoggingEventMock(String loggerName, String message) {
+            this.loggerName = loggerName;
+            this.message = message;
+        }
+
+        @Override
+        public PaxLocationInfo getLocationInformation() {
+            return null;
+        }
+
+        @Override
+        public PaxLevel getLevel() {
+            return null;
+        }
+
+        @Override
+        public String getLoggerName() {
+            return this.loggerName;
+        }
+
+        @Override
+        public String getFQNOfLoggerClass() {
+            return null;
+        }
+
+        @Override
+        public String getMessage() {
+            return this.message;
+        }
+
+        @Override
+        public String getRenderedMessage() {
+            return null;
+        }
+
+        @Override
+        public String getThreadName() {
+            return null;
+        }
+
+        @Override
+        public String[] getThrowableStrRep() {
+            return new String[0];
+        }
+
+        @Override
+        public boolean locationInformationExists() {
+            return false;
+        }
+
+        @Override
+        public long getTimeStamp() {
+            return 0;
+        }
+
+        @Override
+        public Map<String, Object> getProperties() {
+            return null;
+        }
+    }
+
 }
diff --git a/collector/utils/src/main/java/org/apache/karaf/decanter/collector/utils/PropertiesPreparator.java b/collector/utils/src/main/java/org/apache/karaf/decanter/collector/utils/PropertiesPreparator.java
index 6c24a3f..0724839 100644
--- a/collector/utils/src/main/java/org/apache/karaf/decanter/collector/utils/PropertiesPreparator.java
+++ b/collector/utils/src/main/java/org/apache/karaf/decanter/collector/utils/PropertiesPreparator.java
@@ -61,31 +61,33 @@
         data.put("hostName", hostName);
 
         // custom fields
-        Enumeration<String> keys = properties.keys();
-        while (keys.hasMoreElements()) {
-            String key = keys.nextElement();
-            if (key.startsWith(FIELDS_ADD)) {
-                if ("UUID".equals(properties.get(key).toString().trim())) {
-                    String uuid = UUID.randomUUID().toString();
-                    data.put(key.substring(FIELDS_ADD.length()), uuid);
-                } else if ("TIMESTAMP".equals(properties.get(key).toString().trim())) {
-                    Date date = new Date();
-                    data.put(key.substring(FIELDS_ADD.length()), tsFormat.format(date));
+        if (properties != null) {
+            Enumeration<String> keys = properties.keys();
+            while (keys.hasMoreElements()) {
+                String key = keys.nextElement();
+                if (key.startsWith(FIELDS_ADD)) {
+                    if ("UUID".equals(properties.get(key).toString().trim())) {
+                        String uuid = UUID.randomUUID().toString();
+                        data.put(key.substring(FIELDS_ADD.length()), uuid);
+                    } else if ("TIMESTAMP".equals(properties.get(key).toString().trim())) {
+                        Date date = new Date();
+                        data.put(key.substring(FIELDS_ADD.length()), tsFormat.format(date));
+                    } else {
+                        data.put(key.substring(FIELDS_ADD.length()), properties.get(key));
+                    }
+                } else if (key.startsWith(FIELDS_RENAME)) {
+                    if (data.containsKey(key.substring(FIELDS_RENAME.length()))) {
+                        Object value = data.get(key.substring(FIELDS_RENAME.length()));
+                        data.remove(key.substring(FIELDS_RENAME.length()));
+                        data.put(properties.get(key).toString().trim(), value);
+                    }
+                } else if (key.startsWith(FIELDS_REMOVE)) {
+                    if (data.containsKey(key.substring(FIELDS_REMOVE.length()))) {
+                        data.remove(key.substring(FIELDS_REMOVE.length()));
+                    }
                 } else {
-                    data.put(key.substring(FIELDS_ADD.length()), properties.get(key));
+                    data.put(key, properties.get(key));
                 }
-            } else if (key.startsWith(FIELDS_RENAME)) {
-                if (data.containsKey(key.substring(FIELDS_RENAME.length()))) {
-                    Object value = data.get(key.substring(FIELDS_RENAME.length()));
-                    data.remove(key.substring(FIELDS_RENAME.length()));
-                    data.put(properties.get(key).toString().trim(), value);
-                }
-            } else if (key.startsWith(FIELDS_REMOVE)) {
-                if (data.containsKey(key.substring(FIELDS_REMOVE.length()))) {
-                    data.remove(key.substring(FIELDS_REMOVE.length()));
-                }
-            } else {
-                data.put(key, properties.get(key));
             }
         }
     }
diff --git a/manual/src/main/asciidoc/user-guide/collectors.adoc b/manual/src/main/asciidoc/user-guide/collectors.adoc
index 0a986de..1c69523 100644
--- a/manual/src/main/asciidoc/user-guide/collectors.adoc
+++ b/manual/src/main/asciidoc/user-guide/collectors.adoc
@@ -54,6 +54,24 @@
 
 =====================================================================
 
+Optionally, you can select the log categories you want to exclude or include in the logger.
+
+In the `etc/org.apache.karaf.collector.log.cfg` configuration file, you can set `excluded.categories` (comma separated list of loggers):
+
+----
+excluded.categories=ignored.logger
+----
+
+With this property, the log collector will ignore any log events/messages coming from `ignored.logger` logger.
+
+On the other hand, you can include only some logger with the `included.categories` (comma separated list of loggers):
+
+----
+included.categories=my.logger
+----
+
+With this property, the log collector will only consider log events/messages coming from `included.categories` logger, other loggers will be ignored.
+
 ==== CXF Logging feature integration
 
 The link:http://cxf.apache.org/docs/message-logging.html[CXF message logging] nicely integrates with Decanter. Simply add the link:https://github.com/apache/cxf/blob/master/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/LoggingFeature.java[org.apache.cxf.ext.logging.LoggingFeature] to your service.