Changes to the audit logging implementation 11/34111/3
authorvempo <vitaliy.emporopulo@amdocs.com>
Mon, 5 Mar 2018 18:55:58 +0000 (20:55 +0200)
committerAvi Gaffa <avi.gaffa@amdocs.com>
Tue, 6 Mar 2018 15:14:51 +0000 (15:14 +0000)
Changes to the API and implementation, fixed
issues, added javadoc and unit tests, changed
scope of some dependencies to "provided" to
prevent transitive when not needed.

Change-Id: Ib76ae2f921846c2115fcc5cc61b93625307a7c65
Issue-ID: SDC-772
Signed-off-by: vempo <vitaliy.emporopulo@amdocs.com>
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/AuditData.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/StatusCode.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/AuditDataTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/LoggerFactoryTest.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/SpyAuditData.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/pom.xml
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggerWrapper.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggingServiceProvider.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/LogFileCreationTest.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggerWrapperTest.java [new file with mode: 0644]

index 7292762..fdef45a 100644 (file)
 package org.openecomp.sdc.logging.api;
 
 /**
- * @author KATYR
+ * Builder to populate audit data for logging according to
+ * <a href="https://wiki.onap.org/download/attachments/1015849/ONAP%20application%20logging%20guidelines.pdf?api=v2>
+ * ONAP application logging guidelines</a>. This includes only data known to an application, and not otherwise available
+ * to the logging framework.
+ *
+ * @author KATYR, evitaliy
  * @since February 15, 2018
- * This interface defines part of the Audit log application is responsible to provide.
- * Fields list is according to ONAP application logging guidelines
- * (https://wiki.onap.org/download/attachments/1015849/ONAP%20application%20logging%20guidelines.pdf?api=v2)
- * StartTime -> BeginTimestamp (Date-time that processing for the activities begins)
- * EndTime-> EndTimestamp (Date-time that processing for the activities being logged ends)
- * StatusCode -> StatusCode (indicate high level success or failure of the operation activities that is invoked)
- * ResponseCode -> ResponseCode(application-specific response code returned by the operation activities)
- * ResponseDescription - > ResponseDescription (human readable description of the response code)
- * ClientIpAddress -> ClientIpAddress (Requesting remote client application’s IP address)
  */
+public class AuditData {
+
+    // A concrete implementation interface was chosen over an interface to enable to gracefully
+    // add new fields without affecting client code
+
+    private final long startTime;
+    private final long endTime;
+    private final StatusCode statusCode;
+    private final String responseCode;
+    private final String responseDescription;
+    private final String clientIpAddress;
 
-public interface AuditData {
+    AuditData(final AuditDataBuilder builder) {
+        this.startTime = builder.startTime;
+        this.endTime = builder.endTime;
+        this.statusCode = builder.statusCode;
+        this.responseCode = builder.responseCode;
+        this.responseDescription = builder.responseDescription;
+        this.clientIpAddress = builder.clientIpAddress;
+    }
+
+    /**
+     * Begin timestamp of an API invocation
+     *
+     * @return timestamp
+     */
+    public long getStartTime() {
+        return startTime;
+    }
 
-    enum StatusCode {
-        COMPLETE, ERROR
+    /**
+     * End timestamp of an API invocation
+     *
+     * @return timestamp
+     */
+    public long getEndTime() {
+        return endTime;
     }
 
-    long getStartTime();
+    /**
+     * Result status of an API invocation
+     *
+     * @return protocol and application agnostic status code
+     */
+    public StatusCode getStatusCode() {
+        return statusCode;
+    }
 
-    long getEndTime();
+    /**
+     * Application/protocol specific response status of an API invocation
+     *
+     * @return response code
+     */
+    public String getResponseCode() {
+        return responseCode;
+    }
 
-    StatusCode getStatusCode();
+    /**
+     * Application/protocol specific response in a human-friendly way
+     *
+     * @return human-friendly response description
+     */
+    public String getResponseDescription() {
+        return responseDescription;
+    }
 
-    String getResponseCode();
+    /**
+     * IP address of the invoking client when available
+     *
+     * @return IP address
+     */
+    public String getClientIpAddress() {
+        return clientIpAddress;
+    }
 
-    String getResponseDescription();
+    @Override
+    public String toString() {
+        return "AuditData{startTime=" + startTime + ", endTime=" + endTime + ", statusCode=" + statusCode +
+            ", responseCode='" + responseCode + ", responseDescription=" + responseDescription + ", clientIpAddress="
+            + clientIpAddress + '}';
+    }
 
-    String getClientIpAddress();
+    public static AuditDataBuilder builder() {
+        return new AuditDataBuilder();
+    }
+
+    public static class AuditDataBuilder {
+
+        private long startTime;
+        private long endTime;
+        private StatusCode statusCode;
+        private String responseCode;
+        private String responseDescription;
+        private String clientIpAddress;
+
+        AuditDataBuilder() { /* package-private default constructor to hide the public one */ }
+
+        /**
+         * Begin timestamp of an activity being audited
+         *
+         * @param startTime local timestamp, usually received from {@link System#currentTimeMillis()}
+         * @return this builder for fluent API
+         */
+        public AuditDataBuilder startTime(final long startTime) {
+            this.startTime = startTime;
+            return this;
+        }
+
+        /**
+         * End timestamp of an activity being audited
+         *
+         * @param endTime local timestamp, usually received from {@link System#currentTimeMillis()}
+         * @return this builder for fluent API
+         */
+        public AuditDataBuilder endTime(final long endTime) {
+            this.endTime = endTime;
+            return this;
+        }
+
+        /**
+         * Indicate whether an invocation was successful. It is up the the application to decide if a particular result
+         * must be treated as a success or a failure.
+         *
+         * @param statusCode invocation status success/failure
+         * @return this builder for fluent API
+         */
+        public AuditDataBuilder statusCode(final StatusCode statusCode) {
+            this.statusCode = statusCode;
+            return this;
+        }
+
+        /**
+         * Application/protocol specific response code. For a Web API, it is likely a standard HTTP response code.
+         *
+         * @param responseCode response code that depends on application and invocation protocol
+         * @return this builder for fluent API
+         */
+        public AuditDataBuilder responseCode(final String responseCode) {
+            this.responseCode = responseCode;
+            return this;
+        }
+
+        /**
+         * Response description that explains {@link #responseCode(String)} in a human-friendly way. For a Web API, it
+         * is likely to be a standard HTTP response phrase.
+         *
+         * @param responseDescription human-friendly response description
+         * @return this builder for fluent API
+         */
+        public AuditDataBuilder responseDescription(final String responseDescription) {
+            this.responseDescription = responseDescription;
+            return this;
+        }
+
+        /**
+         * IP address of an invoking client.
+         *
+         * @param clientIpAddress IP address
+         * @return this builder for fluent API
+         */
+        public AuditDataBuilder clientIpAddress(final String clientIpAddress) {
+            this.clientIpAddress = clientIpAddress;
+            return this;
+        }
+
+        /**
+         * Create an instance of {@link AuditData}
+         *
+         * @return a populated instance of audit data
+         */
+        public AuditData build() {
+            return new AuditData(this);
+        }
+    }
 }
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/StatusCode.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/StatusCode.java
new file mode 100644 (file)
index 0000000..dd8bd57
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.api;
+
+/**
+ * Protocol-agnostic status codes to indicate the result status (success, failure) of an API invocation.
+ *
+ * @author EVITALIY
+ * @since 04 Mar 18
+ */
+public enum StatusCode {
+    COMPLETE, ERROR
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/AuditDataTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/AuditDataTest.java
new file mode 100644 (file)
index 0000000..08ce508
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.api;
+
+import static org.testng.Assert.*;
+
+import org.testng.annotations.Test;
+
+/**
+ * @author EVITALIY
+ * @since 04 Mar 18
+ */
+public class AuditDataTest {
+
+    @Test
+    public void allPropertiesReadWhenPopulated() {
+
+        final long start = System.currentTimeMillis();
+        final long end = start + 100;
+        final String responseCode = "Response-Code";
+        final String responseDescription = "Response-Description";
+        final String ipAddress = "10.56.20.70";
+
+        AuditData data = AuditData.builder().startTime(start).endTime(end).statusCode(StatusCode.COMPLETE)
+            .responseCode(responseCode).responseDescription(responseDescription).clientIpAddress(ipAddress).build();
+
+        assertEquals(data.getClientIpAddress(), ipAddress);
+        assertEquals(data.getEndTime(), end);
+        assertEquals(data.getStartTime(), start);
+        assertEquals(data.getResponseCode(), responseCode);
+        assertEquals(data.getResponseDescription(), responseDescription);
+        assertEquals(data.getStatusCode(), StatusCode.COMPLETE);
+    }
+
+    @Test
+    public void allPropertiesEmptyWhenUnpopulated() {
+        AuditData data = AuditData.builder().build();
+        assertEquals(data.getStartTime(), 0);
+        assertEquals(data.getEndTime(), 0);
+        assertNull(data.getClientIpAddress());
+        assertNull(data.getResponseCode());
+        assertNull(data.getResponseDescription());
+        assertNull(data.getStatusCode());
+    }
+}
\ No newline at end of file
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/SpyAuditData.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/SpyAuditData.java
deleted file mode 100644 (file)
index 8766f25..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright © 2016-2018 European Support Limited
- *
- * 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.
- */
-package org.openecomp.sdc.logging.api;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class SpyAuditData implements  AuditData{
-    private final Set<String> calledMethods = new HashSet<>();
-
-    @Override
-    public long getStartTime() {
-        calledMethods.add("getStartTime");
-        return 0;
-    }
-
-    @Override
-    public long getEndTime() {
-        calledMethods.add("getEndTime");
-        return 0;
-    }
-
-    @Override
-    public AuditData.StatusCode getStatusCode() {
-        calledMethods.add("getEndTime");
-        return null;
-    }
-
-    @Override
-    public String getResponseCode() {
-        calledMethods.add("getResponseCode");
-        return null;
-    }
-
-    @Override
-    public String getResponseDescription() {
-        calledMethods.add("getResponseDescription");
-        return null;
-    }
-
-    @Override
-    public String getClientIpAddress() {
-        calledMethods.add("getClientIpAddress");
-        return null;
-    }
-
-    public boolean wasCalled(String method) {
-        return calledMethods.contains(method);
-    }
-}
index c6f2578..2bbd0d2 100644 (file)
@@ -22,6 +22,7 @@
             <groupId>org.aspectj</groupId>
             <artifactId>aspectjrt</artifactId>
             <version>${aspectj.version}</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
             <version>${logback.version}</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
             <version>${servlet.version}</version>
+            <scope>provided</scope>
         </dependency>
                
                <!-- for testing -->
index 416af8f..a8ada87 100644 (file)
 
 package org.openecomp.sdc.logging.slf4j;
 
+import java.text.Format;
+import java.text.SimpleDateFormat;
 import org.openecomp.sdc.logging.api.AuditData;
 import org.openecomp.sdc.logging.api.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggingServiceProvider.PREFIX;
-
 /**
  * @author EVITALIY
  * @since 08 Jan 18
  */
 class SLF4JLoggerWrapper implements Logger {
 
-    private static final String BEGIN_TIMESTAMP = PREFIX + "BeginTimestamp";
-    private static final String END_TIMESTAMP = PREFIX + "EndTimestamp";
-    private static final String ELAPSED_TIME = PREFIX + "ElapsedTime";
-    private static final String STATUS_CODE = PREFIX + "StatusCode";
-    private static final String RESPONSE_CODE = PREFIX + "ResponseCode";
-    private static final String RESPONSE_DESCRIPTION = PREFIX + "ResponsDescription";
-    private static final String CLIENT_IP_ADDRESS = PREFIX + "ClientIpAddress";
-
     //The specified format presents time in UTC formatted per ISO 8601, as required by ONAP logging guidelines
-    private final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
+
+    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
+    private static final String PREFIX = "";
+
+    static final String BEGIN_TIMESTAMP = PREFIX + "BeginTimestamp";
+    static final String END_TIMESTAMP = PREFIX + "EndTimestamp";
+    static final String ELAPSED_TIME = PREFIX + "ElapsedTime";
+    static final String STATUS_CODE = PREFIX + "StatusCode";
+    static final String RESPONSE_CODE = PREFIX + "ResponseCode";
+    static final String RESPONSE_DESCRIPTION = PREFIX + "ResponseDescription";
+    static final String CLIENT_IP_ADDRESS = PREFIX + "ClientIpAddress";
+
+    private static final String[] ALL_MDC_FIELDS = {
+        BEGIN_TIMESTAMP, END_TIMESTAMP, ELAPSED_TIME, STATUS_CODE,
+        RESPONSE_CODE, RESPONSE_DESCRIPTION, CLIENT_IP_ADDRESS
+    };
+
     private final org.slf4j.Logger logger;
 
+    SLF4JLoggerWrapper(org.slf4j.Logger delegate) {
+        this.logger = delegate;
+    }
+
+    // May cause http://www.slf4j.org/codes.html#loggerNameMismatch
     SLF4JLoggerWrapper(Class<?> clazz) {
-        logger = LoggerFactory.getLogger(clazz);
+        this(LoggerFactory.getLogger(clazz));
     }
 
     SLF4JLoggerWrapper(String className) {
-        logger = LoggerFactory.getLogger(className);
+        this(LoggerFactory.getLogger(className));
     }
 
     @Override
@@ -96,42 +106,41 @@ class SLF4JLoggerWrapper implements Logger {
     public void audit(AuditData data) {
 
         if (data == null) {
-            return;
+            return; // not failing if null
         }
 
-        MDC.put(BEGIN_TIMESTAMP, DATE_FORMAT.format(new Date(data.getStartTime())));
-        MDC.put(END_TIMESTAMP,   DATE_FORMAT.format(new Date(data.getEndTime())));
-        MDC.put(ELAPSED_TIME,    String.valueOf(data.getEndTime() - data.getStartTime()));
+        putTimes(data);
+        putIfNotNull(RESPONSE_CODE, data.getResponseCode());
+        putIfNotNull(RESPONSE_DESCRIPTION, data.getResponseDescription());
+        putIfNotNull(CLIENT_IP_ADDRESS, data.getClientIpAddress());
 
         if (data.getStatusCode() != null) {
-            MDC.put(STATUS_CODE, data.getStatusCode() == AuditData.StatusCode.COMPLETE ? "COMPLETE" : "ERROR");
-        }
-
-        if (data.getResponseCode() != null) {
-            MDC.put(RESPONSE_CODE, data.getResponseCode());
-        }
-
-        if (data.getResponseDescription() != null) {
-            MDC.put(RESPONSE_DESCRIPTION, data.getResponseDescription());
-        }
-
-        if (data.getClientIpAddress() != null) {
-            MDC.put(CLIENT_IP_ADDRESS, data.getClientIpAddress());
+            MDC.put(STATUS_CODE, data.getStatusCode().name());
         }
 
         try {
             logger.info(Markers.AUDIT, "");
         } finally {
-            MDC.remove(BEGIN_TIMESTAMP);
-            MDC.remove(END_TIMESTAMP);
-            MDC.remove(ELAPSED_TIME);
-            MDC.remove(STATUS_CODE);
-            MDC.remove(RESPONSE_CODE);
-            MDC.remove(RESPONSE_DESCRIPTION);
-            MDC.remove(CLIENT_IP_ADDRESS);
+            for (String k : ALL_MDC_FIELDS) {
+                MDC.remove(k);
+            }
         }
     }
 
+    private void putIfNotNull(String key, String value) {
+        if (value != null) {
+            MDC.put(key, value);
+        }
+    }
+
+    private void putTimes(AuditData data) {
+        // SimpleDateFormat is not thread-safe and cannot be a constant
+        Format dateTimeFormat = new SimpleDateFormat(DATE_TIME_PATTERN);
+        MDC.put(BEGIN_TIMESTAMP, dateTimeFormat.format(data.getStartTime()));
+        MDC.put(END_TIMESTAMP, dateTimeFormat.format(data.getEndTime()));
+        MDC.put(ELAPSED_TIME, String.valueOf(data.getEndTime() - data.getStartTime()));
+    }
+
     @Override
     public boolean isDebugEnabled() {
         return logger.isDebugEnabled();
index 86b2297..fbda93c 100644 (file)
@@ -28,13 +28,7 @@ import org.slf4j.MDC;
  */
 public class SLF4JLoggingServiceProvider implements LoggingServiceProvider {
 
-    public static final String PREFIX = "";
     private static final String KEY_CANNOT_BE_NULL = "Key cannot be null";
-    private static final String REQUEST_ID = PREFIX + "RequestId";
-    private static final String SERVICE_NAME = PREFIX + "ServiceName";
-    private static final String PARTNER_NAME = PREFIX + "PartnerName";
-
-    private static final String[] ALL_FIELDS = { REQUEST_ID, SERVICE_NAME, PARTNER_NAME };
 
     @Override
     public Logger getLogger(String className) {
@@ -74,14 +68,12 @@ public class SLF4JLoggingServiceProvider implements LoggingServiceProvider {
     @Override
     public Runnable copyToRunnable(Runnable runnable) {
         Objects.requireNonNull(runnable, "Runnable cannot be null");
-        // TODO: Copy only the fields this service is responsible for
         return new MDCRunnableWrapper(runnable);
     }
 
     @Override
     public <V> Callable<V> copyToCallable(Callable<V> callable) {
         Objects.requireNonNull(callable, "Runnable cannot be null");
-        // TODO: Copy only the fields this service is responsible for
         return new MDCCallableWrapper<>(callable);
     }
 
index 4595e56..14dab13 100644 (file)
@@ -21,8 +21,17 @@ import org.openecomp.sdc.logging.api.Logger;
 import org.openecomp.sdc.logging.api.LoggerFactory;
 import org.testng.annotations.Test;
 
+/**
+ * This is only for manual testing to make sure that a log file is created as expected.
+ * To run change {@link #ENABLED} to 'true'
+ *
+ * @author evitaliy
+ * @since 13/09/2016.
+ */
 public class LogFileCreationTest {
+
     private static final boolean ENABLED = false; // for manual testing change to 'true'
+
     private static final Logger LOGGER = LoggerFactory.getLogger(LogFileCreationTest.class);
 
     @Test(enabled = ENABLED)
@@ -32,8 +41,7 @@ public class LogFileCreationTest {
 
     @Test(enabled = ENABLED)
     public void testAudit() {
-        SpyAuditData auditData = new SpyAuditData();
-        LOGGER.audit(auditData);
+        LOGGER.audit(AuditData.builder().build());
     }
 
     @Test(enabled = ENABLED)
@@ -55,47 +63,4 @@ public class LogFileCreationTest {
     public void testError() {
         LOGGER.error("This is error");
     }
-
-    private class SpyAuditData implements AuditData {
-        @Override
-        public long getStartTime() {
-
-            return 0;
-
-        }
-
-        @Override
-        public long getEndTime(){
-
-            return 0;
-        }
-
-        @Override
-        public StatusCode getStatusCode(){
-
-            return null;
-
-        }
-
-        @Override
-        public String getResponseCode(){
-
-            return null;
-
-        }
-
-        @Override
-        public String getResponseDescription(){
-
-            return null;
-
-        }
-
-        @Override
-        public String getClientIpAddress(){
-
-            return null;
-
-        }
-    }
 }
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggerWrapperTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggerWrapperTest.java
new file mode 100644 (file)
index 0000000..a4a5e5c
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+package org.openecomp.sdc.logging.slf4j;
+
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.BEGIN_TIMESTAMP;
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.CLIENT_IP_ADDRESS;
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.ELAPSED_TIME;
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.END_TIMESTAMP;
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.RESPONSE_CODE;
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.RESPONSE_DESCRIPTION;
+import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.STATUS_CODE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Map;
+import org.openecomp.sdc.logging.api.AuditData;
+import org.openecomp.sdc.logging.api.StatusCode;
+import org.slf4j.Logger;
+import org.slf4j.MDC;
+import org.testng.annotations.Test;
+
+/**
+ * @author EVITALIY
+ * @since 05 Mar 18
+ */
+public class SLF4JLoggerWrapperTest {
+
+    @Test
+    public void auditDoesNotFailWhenInputNull() {
+        new SLF4JLoggerWrapper(this.getClass()).audit(null);
+    }
+
+    @Test
+    public void beginTimeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        long start = System.currentTimeMillis();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().startTime(start).build());
+        assertNotNull(spy.mdc().get(BEGIN_TIMESTAMP));
+    }
+
+    @Test
+    public void entTimeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        long end = System.currentTimeMillis();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().endTime(end).build());
+        assertNotNull(spy.mdc().get(END_TIMESTAMP));
+    }
+
+    @Test
+    public void elapsedTimeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        long start = System.currentTimeMillis();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder()
+            .startTime(start).endTime(start).build());
+        assertNotNull(spy.mdc().get(ELAPSED_TIME));
+    }
+
+    @Test
+    public void statusCodeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().statusCode(StatusCode.COMPLETE).build());
+        assertEquals(spy.mdc().get(STATUS_CODE), StatusCode.COMPLETE.name());
+    }
+
+    @Test
+    public void statusCodeEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
+        assertNull(spy.mdc().get(STATUS_CODE));
+    }
+
+    @Test
+    public void responseCodeAvailableWhenPassed() {
+        final String responseCode = "SpyResponse";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().responseCode(responseCode).build());
+        assertEquals(spy.mdc().get(RESPONSE_CODE), responseCode);
+    }
+
+    @Test
+    public void responseCodeEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
+        assertNull(spy.mdc().get(RESPONSE_CODE));
+    }
+
+    @Test
+    public void responseDescriptionAvailableWhenPassed() {
+        final String responseDescription = "SpyDescription";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().responseDescription(responseDescription).build());
+        assertEquals(spy.mdc().get(RESPONSE_DESCRIPTION), responseDescription);
+    }
+
+    @Test
+    public void responseDescriptionEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
+        assertNull(spy.mdc().get(RESPONSE_DESCRIPTION));
+    }
+
+    @Test
+    public void clientIpAddressAvailableWhenPassed() {
+        final String ipAddress = "10.56.20.20";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().clientIpAddress(ipAddress).build());
+        assertEquals(spy.mdc().get(CLIENT_IP_ADDRESS), ipAddress);
+    }
+
+    @Test
+    public void clientIpAddressEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
+        assertNull(spy.mdc().get(CLIENT_IP_ADDRESS));
+    }
+
+    @Test
+    public void elapsedTimeEqualsDifferenceBetweenStartAndEnd() {
+        SpyLogger spy = createSpy();
+        final long diff = 1024;
+        long start = System.currentTimeMillis();
+        long end = start + diff;
+        new SLF4JLoggerWrapper(spy).audit(AuditData.builder().startTime(start).endTime(end).build());
+        assertEquals(spy.mdc().get(ELAPSED_TIME), Long.toString(diff));
+    }
+
+    interface SpyLogger extends Logger {
+
+        Map<String, String> mdc();
+    }
+
+    /**
+     * Creates a in instance of Logger that can be used to track MDC changes as part of an invocation of
+     * Logger.audit().
+     *
+     * @return object that implements {@link SpyLogger}
+     */
+    private static SpyLogger createSpy() {
+
+        // build a dynamic proxy to avoid implementing the long list of Logger methods
+        // when we actually need just Logger.info() with the audit marker
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        return SpyLogger.class.cast(
+                Proxy.newProxyInstance(classLoader, new Class<?>[]{SpyLogger.class}, new SpyingInvocationHandler()));
+    }
+
+    private static class SpyingInvocationHandler implements InvocationHandler {
+
+        private Map<String, String> mdc;
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) {
+
+            // return the remembered MDC for spying
+            if (method.getName().equals("mdc")) {
+                return mdc;
+            }
+
+            // filter out everything that's not related to audit
+            if (!method.getName().equals("info") || args.length == 0 || !args[0].equals(Markers.AUDIT)) {
+                throw new UnsupportedOperationException("Method " + method.getName() + " with arguments " +
+                    Arrays.toString(args) + " wasn't supposed to be called");
+            }
+
+            // remember the MDC that was active during the invocation
+            mdc = MDC.getCopyOfContextMap();
+
+            return null;
+        }
+    }
+}
\ No newline at end of file