Add Network Logging Feature

This is part 1 of introducing the mdc filter feature. Network
logging has to have interception points in order to pre/post
process messages coming in from a network endpoint.

The OrderedService interface and OrderedServiceImpl of the
drools-pdp project have now been migrated to common but is
also left in drools-pdp to mitigate disruption to existing
features. However for features that need to use common's
OrderedServiceImpl and drools-pdp version of OrderedServiceImpl
there will be a conflict with getSequenceNumber(). So a migration
of the other features is suggested.

Network logging is moved to its own util class so that feature
hooks can be invoked any time an event is being logged by a
network logger. This util class will also be accessible to
drools-applications in the case where drools-applications
would like to invoke network logging features for REST events.

Change-Id: I83d7c46e5abb351486f841c3be4d9009f7992476
Issue-ID: POLICY-1499
Signed-off-by: Daniel Cruz <dc443y@att.com>
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicEndpoint.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicEndpoint.java
index a2ad4d3..ef002f5 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicEndpoint.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicEndpoint.java
@@ -23,7 +23,8 @@
 
 import java.util.List;
 import org.onap.policy.common.endpoints.event.comm.bus.internal.TopicBase;
-import org.onap.policy.common.utils.slf4j.LoggerFactoryWrapper;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,11 +39,6 @@
     private static Logger logger = LoggerFactory.getLogger(NoopTopicEndpoint.class);
 
     /**
-     * Network logger.
-     */
-    private static final Logger netLogger = LoggerFactoryWrapper.getNetworkLogger();
-
-    /**
      * {@inheritDoc}.
      */
     public NoopTopicEndpoint(List<String> servers, String topic) {
@@ -52,10 +48,11 @@
     /**
      *  I/O.
      *
+     * @param type "IN" or "OUT".
      * @param message message.
-     * @return true if sucessful.
+     * @return true if successful.
      */
-    protected boolean io(String message) {
+    protected boolean io(EventType type, String message) {
 
         if (message == null || message.isEmpty()) {
             throw new IllegalArgumentException("Message is empty");
@@ -70,8 +67,7 @@
                 this.recentEvents.add(message);
             }
 
-            netLogger.info("[OUT|{}|{}]{}{}", this.getTopicCommInfrastructure(), this.topic, System.lineSeparator(),
-                    message);
+            NetLoggerUtil.log(type, this.getTopicCommInfrastructure(), this.topic, message);
 
             broadcast(message);
         } catch (Exception e) {
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSink.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSink.java
index f6ad433..d374594 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSink.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSink.java
@@ -7,9 +7,9 @@
  * Licensed 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.
@@ -22,6 +22,7 @@
 
 import java.util.List;
 import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 
 /**
  * No Operation Topic Sink.
@@ -45,7 +46,7 @@
      */
     @Override
     public boolean send(String message) {
-        return super.io(message);
+        return super.io(EventType.OUT, message);
     }
 
     /**
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSource.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSource.java
index c3215e0..95ed0fe 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSource.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/NoopTopicSource.java
@@ -22,6 +22,7 @@
 
 import java.util.List;
 import org.onap.policy.common.endpoints.event.comm.TopicSource;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 
 /**
  * No Operation Topic Source.
@@ -45,7 +46,7 @@
      */
     @Override
     public boolean offer(String event) {
-        return super.io(event);
+        return super.io(EventType.IN, event);
     }
 
     /**
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/InlineBusTopicSink.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/InlineBusTopicSink.java
index d6ca824..e94bdff 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/InlineBusTopicSink.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/InlineBusTopicSink.java
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * policy-endpoints
  * ================================================================================
- * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
  * Modifications Copyright (C) 2018-2019 Samsung Electronics Co., Ltd.
 * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,8 @@
 import java.util.UUID;
 
 import org.onap.policy.common.endpoints.event.comm.bus.BusTopicSink;
-import org.onap.policy.common.utils.slf4j.LoggerFactoryWrapper;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,7 +40,6 @@
      * Loggers.
      */
     private static Logger logger = LoggerFactory.getLogger(InlineBusTopicSink.class);
-    private static final Logger netLogger = LoggerFactoryWrapper.getNetworkLogger();
 
     /**
      * The partition key to publish to.
@@ -140,8 +140,7 @@
                 this.recentEvents.add(message);
             }
 
-            netLogger.info("[OUT|{}|{}]{}{}", this.getTopicCommInfrastructure(), this.topic, System.lineSeparator(),
-                    message);
+            NetLoggerUtil.log(EventType.OUT, this.getTopicCommInfrastructure(), this.topic, message);
 
             publisher.send(this.partitionId, message);
             broadcast(message);
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/SingleThreadedBusTopicSource.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/SingleThreadedBusTopicSource.java
index 83f3760..98e30e2 100644
--- a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/SingleThreadedBusTopicSource.java
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/event/comm/bus/internal/SingleThreadedBusTopicSource.java
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * policy-endpoints
  * ================================================================================
- * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
  * Modifications Copyright (C) 2018-2019 Samsung Electronics Co., Ltd.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,8 +28,9 @@
 import org.onap.policy.common.endpoints.event.comm.TopicListener;
 import org.onap.policy.common.endpoints.event.comm.bus.BusTopicSource;
 import org.onap.policy.common.endpoints.event.comm.bus.internal.BusConsumer.FilterableBusConsumer;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 import org.onap.policy.common.utils.network.NetworkUtil;
-import org.onap.policy.common.utils.slf4j.LoggerFactoryWrapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -45,7 +46,6 @@
      * that in a single file in a concise format.
      */
     private static Logger logger = LoggerFactory.getLogger(InlineBusTopicSink.class);
-    private static final Logger netLogger = LoggerFactoryWrapper.getNetworkLogger();
 
     /**
      * Bus consumer group.
@@ -228,8 +228,7 @@
                         this.recentEvents.add(event);
                     }
 
-                    netLogger.info("[IN|{}|{}]{}{}", this.getTopicCommInfrastructure(), this.topic,
-                            System.lineSeparator(), event);
+                    NetLoggerUtil.log(EventType.IN, this.getTopicCommInfrastructure(), this.topic, event);
 
                     broadcast(event);
 
@@ -255,8 +254,7 @@
             this.recentEvents.add(event);
         }
 
-        netLogger.info("[IN|{}|{}]{}{}", this.getTopicCommInfrastructure(), this.topic, System.lineSeparator(), event);
-
+        NetLoggerUtil.log(EventType.IN, this.getTopicCommInfrastructure(), this.topic, event);
 
         return broadcast(event);
     }
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/features/NetLoggerFeatureApi.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/features/NetLoggerFeatureApi.java
new file mode 100644
index 0000000..4e7d444
--- /dev/null
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/features/NetLoggerFeatureApi.java
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ * policy-endpoints
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.endpoints.features;
+
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.services.OrderedService;
+import org.onap.policy.common.utils.services.OrderedServiceImpl;
+import org.slf4j.Logger;
+
+/**
+ * Logging Feature API. Provides interception points before and after logging a message.
+ */
+public interface NetLoggerFeatureApi extends OrderedService {
+
+    /**
+     * Feature providers implementing this interface.
+     */
+    OrderedServiceImpl<NetLoggerFeatureApi> providers =
+                    new OrderedServiceImpl<>(NetLoggerFeatureApi.class);
+
+    /**
+     * Intercepts a message before it is logged.
+     *
+     * @return true if this feature intercepts and takes ownership of the operation
+     *         preventing the invocation of lower priority features. False, otherwise.
+     */
+    default boolean beforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+                    String message) {
+        return false;
+    }
+
+    /**
+     * Intercepts a message after it is logged.
+     *
+     * @return true if this feature intercepts and takes ownership of the operation
+     *         preventing the invocation of lower priority features. False, otherwise.
+     */
+    default boolean afterLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+                    String message) {
+        return false;
+    }
+
+}
diff --git a/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/utils/NetLoggerUtil.java b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/utils/NetLoggerUtil.java
new file mode 100644
index 0000000..f82e6a8
--- /dev/null
+++ b/policy-endpoints/src/main/java/org/onap/policy/common/endpoints/utils/NetLoggerUtil.java
@@ -0,0 +1,153 @@
+/*
+ * ============LICENSE_START=======================================================
+ * policy-endpoints
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.endpoints.utils;
+
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.features.NetLoggerFeatureApi;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A network logging utility class that allows drools applications code to access the
+ * network log (or other specified loggers) and logging features.
+ *
+ */
+public class NetLoggerUtil {
+
+    /**
+     * Loggers.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(NetLoggerUtil.class);
+    private static final Logger netLogger = LoggerFactory.getLogger("network");
+
+    /**
+     * Constant for the system line separator.
+     */
+    public static final String SYSTEM_LS = System.lineSeparator();
+
+    /**
+     * Specifies if the message is coming in or going out.
+     */
+    public enum EventType {
+        IN, OUT
+    }
+
+    /**
+     * Get Network Logger.
+     *
+     * @return logger instance
+     */
+    public static Logger getNetworkLogger() {
+        return netLogger;
+    }
+
+    /**
+     * Logs a message to the network logger.
+     *
+     * @param type can either be IN or OUT
+     * @param protocol the protocol used to receive/send the message
+     * @param topic the topic the message came from or null if the type is REST
+     * @param message message to be logged
+     */
+    public static void log(EventType type, CommInfrastructure protocol, String topic, String message) {
+        log(netLogger, type, protocol, topic, message);
+    }
+
+    /**
+     * Logs a message to the specified logger (i.e. a controller logger).
+     *
+     * @param eventLogger the logger that will have the message appended
+     * @param type can either be IN or OUT
+     * @param protocol the protocol used to receive/send the message
+     * @param topic the topic the message came from or null if the type is REST
+     * @param message message to be logged
+     */
+    public static void log(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+                    String message) {
+        if (eventLogger == null) {
+            logger.debug("the logger is null, defaulting to network logger");
+            eventLogger = netLogger;
+        }
+
+        if (featureBeforeLog(eventLogger, type, protocol, topic, message)) {
+            return;
+        }
+
+        eventLogger.info("[{}|{}|{}]{}{}", type, protocol, topic, SYSTEM_LS, message);
+
+        featureAfterLog(eventLogger, type, protocol, topic, message);
+    }
+
+    /**
+     * Executes features that pre-process a message before it is logged.
+     *
+     * @param eventLogger the logger that will have the message appended
+     * @param type can either be IN or OUT
+     * @param protocol the protocol used to receive/send the message
+     * @param topic the topic the message came from or null if the type is REST
+     * @param message message to be logged
+     *
+     * @return true if this feature intercepts and takes ownership of the operation
+     *         preventing the invocation of lower priority features. False, otherwise
+     */
+    private static boolean featureBeforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol,
+                    String topic, String message) {
+        for (NetLoggerFeatureApi feature : NetLoggerFeatureApi.providers.getList()) {
+            try {
+                if (feature.beforeLog(eventLogger, type, protocol, topic, message)) {
+                    return true;
+                }
+            } catch (Exception e) {
+                logger.error("feature {} before-log failure because of {}", feature.getClass().getName(),
+                                e.getMessage(), e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Executes features that post-process a message after it is logged.
+     *
+     * @param eventLogger the logger that will have the message appended
+     * @param type can either be IN or OUT
+     * @param protocol the protocol used to receive/send the message
+     * @param topic the topic the message came from or null if the type is rest
+     * @param message message to be logged
+     *
+     * @return true if this feature intercepts and takes ownership of the operation
+     *         preventing the invocation of lower priority features. False, otherwise
+     */
+    private static boolean featureAfterLog(Logger eventLogger, EventType type, CommInfrastructure protocol,
+                    String topic, String message) {
+        for (NetLoggerFeatureApi feature : NetLoggerFeatureApi.providers.getList()) {
+            try {
+                if (feature.afterLog(eventLogger, type, protocol, topic, message)) {
+                    return true;
+                }
+            } catch (Exception e) {
+                logger.error("feature {} after-log failure because of {}", feature.getClass().getName(), e.getMessage(),
+                                e);
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/utils/NetLoggerUtilTest.java b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/utils/NetLoggerUtilTest.java
new file mode 100644
index 0000000..6e84c13
--- /dev/null
+++ b/policy-endpoints/src/test/java/org/onap/policy/common/endpoints/utils/NetLoggerUtilTest.java
@@ -0,0 +1,268 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * policy-endpoints
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.endpoints.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.features.NetLoggerFeatureApi;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.slf4j.Logger;
+
+/**
+ * Test class for network log utilities such as logging and feature invocation.
+ */
+public class NetLoggerUtilTest {
+
+    private static final String MESSAGE = "hello world!";
+    /**
+     * Test feature used for junits.
+     */
+    private static NetLoggerFeature netLoggerFeature;
+
+    /**
+     * Obtains the test implementation of NetLoggerFeatureApi.
+     */
+    @BeforeClass
+    public static void setUp() {
+        netLoggerFeature = (NetLoggerFeature) NetLoggerFeatureApi.providers.getList().get(0);
+    }
+
+    /**
+     * Clears events list and resets return/exceptions flags before invoking every unit test.
+     */
+    @Before
+    public void reset() {
+        TestAppender.clear();
+        netLoggerFeature.setReturnValue(false, false);
+        netLoggerFeature.setExceptions(false, false);
+    }
+
+    /**
+     * Tests obtaining the network logger instance.
+     */
+    @Test
+    public void getNetworkLoggerTest() {
+        assertEquals("network", NetLoggerUtil.getNetworkLogger().getName());
+    }
+
+    /**
+     * Tests logging a message to the network logger and invoking features before/after logging.
+     */
+    @Test
+    public void logTest() {
+        NetLoggerUtil.log(EventType.IN, CommInfrastructure.NOOP, "test-topic", MESSAGE);
+        assertEquals(3, TestAppender.events.size());
+    }
+
+    /**
+     * Tests that the network logger is used to log messages if a logger is not passed in.
+     */
+    @Test
+    public void logDefaultTest() {
+        NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, "test-topic", MESSAGE);
+        assertEquals(3, TestAppender.events.size());
+        assertEquals("network", TestAppender.events.get(0).getLoggerName());
+    }
+
+    /**
+     * Tests a NetLoggerFeature that replaces base implementation before logging.
+     */
+    @Test
+    public void beforeLogReturnTrueTest() {
+        netLoggerFeature.setReturnValue(true, false);
+        NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, "test-topic", MESSAGE);
+        assertEquals(1, TestAppender.events.size());
+    }
+
+    /**
+     * Tests a NetLoggerFeature that post processes a logged message.
+     */
+    @Test
+    public void afterLogReturnTrueTest() {
+        netLoggerFeature.setReturnValue(false, true);
+        NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, "test-topic", MESSAGE);
+        assertEquals(3, TestAppender.events.size());
+    }
+
+    /**
+     * Tests throwing an exception in the before hook.
+     */
+    @Test
+    public void beforeLogExceptionTest() {
+        netLoggerFeature.setExceptions(true, false);
+        NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, "test-topic", MESSAGE);
+        assertEquals(2, TestAppender.events.size());
+    }
+
+    /**
+     * Tests throwing an exception in the after hook.
+     */
+    @Test
+    public void afterLogExceptionTest() {
+        netLoggerFeature.setExceptions(false, true);
+        NetLoggerUtil.log(null, EventType.IN, CommInfrastructure.NOOP, "test-topic", MESSAGE);
+        assertEquals(2, TestAppender.events.size());
+    }
+
+    /**
+     * A custom list appender to track messages being logged to the network logger.
+     * NOTE: Check src/test/resources/logback-test.xml for network logger configurations.
+     */
+    public static class TestAppender extends AppenderBase<ILoggingEvent> {
+
+        /**
+         * List of logged events.
+         */
+        public static List<ILoggingEvent> events = new ArrayList<>();
+
+        /**
+         * Called after every unit test to clear list of events.
+         */
+        public static void clear() {
+            events.clear();
+        }
+
+        /**
+         * Appends each event to the event list.
+         */
+        @Override
+        protected void append(ILoggingEvent event) {
+            events.add(event);
+        }
+
+    }
+
+    /**
+     * Test implementation of NetLoggerFeatureApi to be used by junits.
+     */
+    public static class NetLoggerFeature implements NetLoggerFeatureApi {
+
+        /**
+         * Used for setting the return values of before/after hooks.
+         */
+        private boolean beforeReturn = false;
+        private boolean afterReturn = false;
+
+        /**
+         * Used for throwing an exception in the before/after hooks.
+         */
+        private boolean beforeException = false;
+        private boolean afterException = false;
+
+
+        /**
+         * Gets sequence number.
+         */
+        @Override
+        public int getSequenceNumber() {
+            return 0;
+        }
+
+        /**
+         * Get beforeLog return value.
+         */
+        public boolean getBeforeReturn() {
+            return this.beforeReturn;
+        }
+
+        /**
+         * Get afterLog return value.
+         */
+        public boolean getAfterReturn() {
+            return this.afterReturn;
+        }
+
+        /**
+         * Sets the return value for the before/after hooks.
+         *
+         * @param beforeVal beforeLog() return value
+         * @param afterVal afterLog() return value
+         */
+        public void setReturnValue(boolean beforeVal, boolean afterVal) {
+            this.beforeReturn = beforeVal;
+            this.afterReturn = afterVal;
+        }
+
+        /**
+         * Gets beforeException boolean.
+         */
+        public boolean getBeforeException() {
+            return this.beforeException;
+        }
+
+        /**
+         * Gets afterException boolean.
+         */
+        public boolean getAfterException() {
+            return this.afterException;
+        }
+
+        /**
+         * Sets before/after flags to determine if the feature should throw an exception.
+         */
+        public void setExceptions(boolean beforeException, boolean afterException) {
+            this.beforeException = beforeException;
+            this.afterException = afterException;
+        }
+
+        /**
+         * Simple beforeLog message.
+         */
+        @Override
+        public boolean beforeLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+                        String message) {
+
+            if (beforeException) {
+                throw new RuntimeException("beforeLog exception");
+            }
+
+            eventLogger.info("before feature test");
+
+            return this.beforeReturn;
+        }
+
+        /**
+         * Simple afterLog message.
+         */
+        @Override
+        public boolean afterLog(Logger eventLogger, EventType type, CommInfrastructure protocol, String topic,
+                        String message) {
+
+            if (afterException) {
+                throw new RuntimeException("afterLog exception");
+            }
+
+            eventLogger.info("after feature test");
+
+            return this.afterReturn;
+        }
+
+    }
+
+}
diff --git a/policy-endpoints/src/test/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi b/policy-endpoints/src/test/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi
new file mode 100644
index 0000000..c861ad1
--- /dev/null
+++ b/policy-endpoints/src/test/resources/META-INF/services/org.onap.policy.common.endpoints.features.NetLoggerFeatureApi
@@ -0,0 +1 @@
+org.onap.policy.common.endpoints.utils.NetLoggerUtilTest$NetLoggerFeature
diff --git a/policy-endpoints/src/test/resources/logback-test.xml b/policy-endpoints/src/test/resources/logback-test.xml
index b3feef9..4edcc9d 100644
--- a/policy-endpoints/src/test/resources/logback-test.xml
+++ b/policy-endpoints/src/test/resources/logback-test.xml
@@ -2,7 +2,7 @@
   ============LICENSE_START=======================================================
   ONAP
   ================================================================================
-  Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+  Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
   ================================================================================
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
@@ -27,8 +27,14 @@
         </encoder>
     </appender>
     
+    <appender name="testAppender" class="org.onap.policy.common.endpoints.utils.NetLoggerUtilTest$TestAppender"/>
+    
     <logger name="org.onap.policy.drools.http.server.test" level="INFO"/>
-
+    
+    <logger name="network" level="INFO">
+        <appender-ref ref="testAppender" />
+    </logger>
+    
     <root level="WARN">
         <appender-ref ref="STDOUT"/>
     </root>
diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/OrderedService.java b/utils/src/main/java/org/onap/policy/common/utils/services/OrderedService.java
new file mode 100644
index 0000000..c5050c8
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/services/OrderedService.java
@@ -0,0 +1,47 @@
+/*
+ * ============LICENSE_START=======================================================
+ * utils
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+/**
+ * This is a base interface that is used to control the order of a list
+ * of services (features) discovered via 'ServiceLoader'. See
+ * 'OrderedServiceImpl' for more details.
+ */
+@FunctionalInterface
+public interface OrderedService {
+    /**
+     * Get sequence number.
+     *
+     * @return an integer sequence number, which determines the order of a list
+     *     of objects implementing this interface
+     */
+    public int getSequenceNumber();
+
+
+    /**
+     * Get the name.
+     *
+     * @return the name of the ordered service
+     */
+    public default String getName() {
+        return this.getClass().getName();
+    }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/services/OrderedServiceImpl.java b/utils/src/main/java/org/onap/policy/common/utils/services/OrderedServiceImpl.java
new file mode 100644
index 0000000..bbd3022
--- /dev/null
+++ b/utils/src/main/java/org/onap/policy/common/utils/services/OrderedServiceImpl.java
@@ -0,0 +1,135 @@
+/*
+ * ============LICENSE_START=======================================================
+ * utils
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is a template for building a sorted list of service instances,
+ * which are discovered and created using 'ServiceLoader'. 
+ */
+public class OrderedServiceImpl<T extends OrderedService> {
+    // logger
+    private static Logger  logger = LoggerFactory.getLogger(OrderedServiceImpl.class); 
+
+    // sorted list of instances implementing the service
+    private List<T> implementers = null;
+
+    // 'ServiceLoader' that is used to discover and create the services
+    private ServiceLoader<T> serviceLoader = null;
+
+    // use this to ensure that we only use one unique instance of each class
+    @SuppressWarnings("rawtypes")
+    private static HashMap<Class,OrderedService> classToSingleton = new HashMap<>();
+
+    /**
+     * Constructor - create the 'ServiceLoader' instance.
+     *
+     * @param clazz the class object associated with 'T' (I supposed it could
+     *     be a subclass, but I'm not sure this is useful)
+     */
+    public OrderedServiceImpl(Class<T> clazz) {
+        // This constructor wouldn't be needed if 'T.class' was legal
+        serviceLoader = ServiceLoader.load(clazz);
+    }
+
+    /**
+     * Get List of implementers.
+     * 
+     * @return the sorted list of services implementing interface 'T' discovered
+     *     by 'ServiceLoader'.
+     */
+    public synchronized List<T> getList() {
+        if (implementers == null) {
+            rebuildList();
+        }
+        return implementers;
+    }
+
+    /**
+     * This method is called by 'getList', but could also be called directly if
+     * we were running with a 'ClassLoader' that supported the dynamic addition
+     * of JAR files. In this case, it could be invoked in order to discover any
+     * new services implementing interface 'T'. This is probably a relatively
+     * expensive operation in terms of CPU and elapsed time, so it is best if it
+     * isn't invoked too frequently.
+     *
+     * @return the sorted list of services implementing interface 'T' discovered
+     *      by 'ServiceLoader'.
+     */
+    @SuppressWarnings("unchecked")
+    public synchronized List<T> rebuildList() {
+        // build a list of all of the current implementors
+        List<T> tmp = new LinkedList<>();
+        for (T service : serviceLoader) {
+            tmp.add((T)getSingleton(service));
+        }
+
+        // Sort the list according to sequence number, and then alphabetically
+        // according to full class name.
+        Collections.sort(tmp, (o1, o2) -> {
+            int s1 = o1.getSequenceNumber();
+            int s2 = o2.getSequenceNumber();
+            if (s1 < s2) {
+                return -1;
+            } else if (s1 > s2) {
+                return 1;
+            } else {
+                return o1.getClass().getName().compareTo(o2.getClass().getName());
+            }
+        });
+
+        // create an unmodifiable version of this list
+        implementers = Collections.unmodifiableList(tmp);
+        logger.info("***** OrderedServiceImpl implementers:\n {}", implementers);
+        return implementers;
+    }
+
+    /**
+     * If a service implements multiple APIs managed by 'ServiceLoader', a
+     * separate instance is created for each API. This method ensures that
+     * the first instance is used in all of the lists.
+     *
+     * @param service this is the object created by ServiceLoader
+     * @return the object to use in place of 'service'. If 'service' is the first
+     *     object of this class created by ServiceLoader, it is returned. If not,
+     *     the object of this class that was initially created is returned
+     *     instead.
+     */
+    private static synchronized OrderedService getSingleton(OrderedService service) {
+        // see if we already have an instance of this class
+        OrderedService rval = classToSingleton.get(service.getClass());
+        if (rval == null) {
+            // No previous instance of this class exists -- use the supplied
+            // instance, and place it in the table.
+            rval = service;
+            classToSingleton.put(service.getClass(), service);
+        }
+        return rval;
+    }
+}
diff --git a/utils/src/main/java/org/onap/policy/common/utils/slf4j/LoggerFactoryWrapper.java b/utils/src/main/java/org/onap/policy/common/utils/slf4j/LoggerFactoryWrapper.java
deleted file mode 100644
index 6f80001..0000000
--- a/utils/src/main/java/org/onap/policy/common/utils/slf4j/LoggerFactoryWrapper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * ONAP Policy Engine - Common Modules
- * ================================================================================
- * Copyright (C) 2019 Samsung Electronics. All rights reserved.
- * Modifications Copyright (C) 2019 AT&T Intellectual Property.
- * ================================================================================
- * Licensed 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.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.common.utils.slf4j;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Helper class to retrieve particular types of loggers without storing the logger name everywhere in ths code.
- */
-public class LoggerFactoryWrapper {
-
-    /**
-     * Constructs the object.
-     */
-    private LoggerFactoryWrapper() {
-        super();
-    }
-
-    /**
-     * Get Network Logger.
-     *
-     * @return logger instance
-     */
-    public static Logger getNetworkLogger() {
-        return LoggerFactory.getLogger("network");
-    }
-}
diff --git a/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java b/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
index eb918d3..e5a79bc 100644
--- a/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
+++ b/utils/src/test/java/org/onap/policy/common/utils/resources/ResourceUtilsTest.java
@@ -250,7 +250,9 @@
         assertNull(theString);
 
         theString = ResourceUtils.getResourceAsString("");
-        assertEquals("logback-test.xml\norg\ntestdir\n", theString);
+
+        assertEquals("logback-test.xml\nMETA-INF\norg\ntestdir\n", theString);
+
     }
 
     @Test
diff --git a/utils/src/test/java/org/onap/policy/common/utils/services/OrderedServiceImplTest.java b/utils/src/test/java/org/onap/policy/common/utils/services/OrderedServiceImplTest.java
new file mode 100644
index 0000000..39c8a2b
--- /dev/null
+++ b/utils/src/test/java/org/onap/policy/common/utils/services/OrderedServiceImplTest.java
@@ -0,0 +1,205 @@
+/*
+ * ============LICENSE_START=======================================================
+ * utils
+ * ================================================================================
+ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.services;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class OrderedServiceImplTest {
+
+    private static final int HIGH_PRIORITY_NUM = -1000;
+    private static final int LOW_PRIORITY_NUM = 1000;
+
+    private static GenericService highPrioService;
+    private static GenericService lowPrioService;
+
+    /**
+     * Saves the original state of the ordered service list to restore after each test.
+     */
+    @BeforeClass
+    public static void setup() {
+        List<GenericService> implementers = GenericService.providers.getList();
+        highPrioService = implementers.get(0);
+        lowPrioService = implementers.get(1);
+    }
+
+    /**
+     * Restores original state after each test.
+     */
+    @Before
+    public void resetOrder() {
+        highPrioService.setSequenceNumber(HIGH_PRIORITY_NUM);
+        lowPrioService.setSequenceNumber(LOW_PRIORITY_NUM);
+    }
+
+    /**
+     * Tests obtaining a list of service implementers.
+     */
+    @Test
+    public void getListTest() {
+        List<GenericService> implementers = GenericService.providers.getList();
+        assertEquals(2, implementers.size());
+
+        assertEquals(highPrioService, implementers.get(0));
+        assertEquals(HIGH_PRIORITY_NUM, highPrioService.getSequenceNumber());
+
+        assertEquals(lowPrioService, implementers.get(1));
+        assertEquals(LOW_PRIORITY_NUM, lowPrioService.getSequenceNumber());
+    }
+
+    /**
+     * Tests inverting the priority of two services to ensure the list is rebuilt
+     * with the new order.
+     */
+    @Test
+    public void rebuildListInvertedPriorityTest() {
+
+        List<GenericService> implementers = GenericService.providers.getList();
+        assertEquals(2, implementers.size());
+
+        assertEquals(highPrioService, implementers.get(0));
+        assertEquals(HIGH_PRIORITY_NUM, highPrioService.getSequenceNumber());
+
+        assertEquals(lowPrioService, implementers.get(1));
+        assertEquals(LOW_PRIORITY_NUM, lowPrioService.getSequenceNumber());
+
+        highPrioService.setSequenceNumber(LOW_PRIORITY_NUM);
+        lowPrioService.setSequenceNumber(HIGH_PRIORITY_NUM);
+
+        implementers = GenericService.providers.rebuildList();
+        assertEquals(2, implementers.size());
+
+        assertEquals(lowPrioService, implementers.get(0));
+        assertEquals(HIGH_PRIORITY_NUM, lowPrioService.getSequenceNumber());
+
+        assertEquals(highPrioService, implementers.get(1));
+        assertEquals(LOW_PRIORITY_NUM, highPrioService.getSequenceNumber());
+
+    }
+
+    /**
+     * Tests that the service list is ordered alphabetically by class names
+     * if the priorities are equivalent.
+     */
+    @Test
+    public void rebuildListEqualPriorityTest() {
+
+        List<GenericService> implementers = GenericService.providers.getList();
+        assertEquals(2, implementers.size());
+
+        assertEquals(highPrioService, implementers.get(0));
+        assertEquals(HIGH_PRIORITY_NUM, highPrioService.getSequenceNumber());
+
+        assertEquals(lowPrioService, implementers.get(1));
+        assertEquals(LOW_PRIORITY_NUM, lowPrioService.getSequenceNumber());
+
+        highPrioService.setSequenceNumber(LOW_PRIORITY_NUM);
+        lowPrioService.setSequenceNumber(LOW_PRIORITY_NUM);
+
+        implementers = GenericService.providers.rebuildList();
+        assertEquals(2, implementers.size());
+
+        assertEquals(highPrioService, implementers.get(0));
+        assertEquals(LOW_PRIORITY_NUM, highPrioService.getSequenceNumber());
+
+        assertEquals(lowPrioService, implementers.get(1));
+        assertEquals(LOW_PRIORITY_NUM, lowPrioService.getSequenceNumber());
+
+    }
+
+    /**
+     * Test interface that extends OrderedService to allow changing the sequence number.
+     */
+    public static interface GenericService extends OrderedService {
+
+        /**
+         * Providers of the GenericService interface.
+         */
+        OrderedServiceImpl<GenericService> providers = new OrderedServiceImpl<>(GenericService.class);
+
+        /**
+         * Sets the sequence number of the service.
+         */
+        public void setSequenceNumber(int seqNum);
+
+    }
+
+    /**
+     * A high priority service class.
+     */
+    public static class HighPriorityService implements GenericService {
+
+        /**
+         * Defaults to a high priority.
+         */
+        private int seqNum = HIGH_PRIORITY_NUM;
+
+        /**
+         * {@inheritDoc}.
+         */
+        @Override
+        public int getSequenceNumber() {
+            return this.seqNum;
+        }
+
+        /**
+         * {@inheritDoc}.
+         */
+        @Override
+        public void setSequenceNumber(int seqNum) {
+            this.seqNum = seqNum;
+        }
+
+    }
+
+    /**
+     * A low priority service class.
+     */
+    public static class LowPriorityService implements GenericService {
+
+        /**
+         * Defaults to a low priority.
+         */
+        private int seqNum = LOW_PRIORITY_NUM;
+
+        /**
+         * {@inheritDoc}.
+         */
+        @Override
+        public int getSequenceNumber() {
+            return this.seqNum;
+        }
+
+        /**
+         * {@inheritDoc}.
+         */
+        @Override
+        public void setSequenceNumber(int seqNum) {
+            this.seqNum = seqNum;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/utils/src/test/java/org/onap/policy/common/utils/slf4j/LoggerFactoryWrapperTest.java b/utils/src/test/java/org/onap/policy/common/utils/slf4j/LoggerFactoryWrapperTest.java
deleted file mode 100644
index 4bf8db3..0000000
--- a/utils/src/test/java/org/onap/policy/common/utils/slf4j/LoggerFactoryWrapperTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * ONAP Policy Engine - Common Modules
- * ================================================================================
- * Copyright (C) 2019 Samsung Electronics. All rights reserved.
- * Modifications Copyright (C) 2019 AT&T Intellectual Property.
- * ================================================================================
- * Licensed 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.
- * ============LICENSE_END=========================================================
- */
-
-
-package org.onap.policy.common.utils.slf4j;
-
-import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest(LoggerFactory.class)
-public class LoggerFactoryWrapperTest {
-    @Test
-    public void test_get_network_logger() {
-        String netLoggerName = "network";
-        Logger mockLogger = mock(Logger.class);
-
-        PowerMockito.mockStatic(LoggerFactory.class);
-
-        PowerMockito.when(LoggerFactory.getLogger(netLoggerName)).thenReturn(mockLogger);
-        assertSame(mockLogger, LoggerFactoryWrapper.getNetworkLogger());
-    }
-}
diff --git a/utils/src/test/resources/META-INF/services/org.onap.policy.common.utils.services.OrderedServiceImplTest$GenericService b/utils/src/test/resources/META-INF/services/org.onap.policy.common.utils.services.OrderedServiceImplTest$GenericService
new file mode 100644
index 0000000..1e920f4
--- /dev/null
+++ b/utils/src/test/resources/META-INF/services/org.onap.policy.common.utils.services.OrderedServiceImplTest$GenericService
@@ -0,0 +1,2 @@
+org.onap.policy.common.utils.services.OrderedServiceImplTest$HighPriorityService
+org.onap.policy.common.utils.services.OrderedServiceImplTest$LowPriorityService
\ No newline at end of file