Introduced metrics to logging API 17/38617/5
authorvempo <vitaliy.emporopulo@amdocs.com>
Mon, 26 Mar 2018 17:37:06 +0000 (20:37 +0300)
committerOren Kleks <orenkle@amdocs.com>
Sun, 1 Apr 2018 07:57:35 +0000 (07:57 +0000)
Metrics data can now be passed to logger. Also general cleanup,
refactoring, simpler implementation, javadocs.

Change-Id: I037101aa9626b3e011737ec2e3497ab348319e4c
Issue-ID: SDC-772
Signed-off-by: vempo <vitaliy.emporopulo@amdocs.com>
25 files changed:
openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/OrchestrationTemplateCandidateManagerImpl.java
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/ContextData.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/Logger.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/LoggerFactory.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/MetricsData.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/package-info.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/MetricsDataTest.java [new file with mode: 0644]
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/GlobalLoggingContext.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/aspects/MetricsAspect.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/context/HostAddress.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/context/InstanceId.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/AuditField.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/ContextField.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/GlobalContextProvider.java
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/MDCField.java [moved from openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/annotations/Metrics.java with 68% similarity]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/MetricsField.java [new file with mode: 0644]
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/test/java/org/openecomp/sdc/logging/LogFileCreationTest.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/aspects/MetricsAspectTest.java [deleted file]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/context/HostAddressTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/context/InstanceIdTest.java [new file with mode: 0644]
openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/slf4j/SLF4JLoggerWrapperTest.java

index 9d36ad4..e5b953f 100644 (file)
@@ -25,7 +25,6 @@ import org.openecomp.sdc.common.errors.CoreException;
 import org.openecomp.sdc.common.utils.CommonUtil;
 import org.openecomp.sdc.common.utils.SdcCommon;
 import org.openecomp.sdc.datatypes.error.ErrorMessage;
-import org.openecomp.sdc.logging.api.annotations.Metrics;
 import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManager;
 import org.openecomp.sdc.vendorsoftwareproduct.dao.VendorSoftwareProductInfoDao;
 import org.openecomp.sdc.vendorsoftwareproduct.dao.type.OrchestrationTemplateCandidateData;
@@ -63,7 +62,6 @@ public class OrchestrationTemplateCandidateManagerImpl
   }
 
   @Override
-  @Metrics
   public UploadFileResponse upload(String vspId, Version version, InputStream fileToUpload,
                                    String fileSuffix, String networkPackageName) {
     OrchestrationTemplateFileHandler orchestrationTemplateFileHandler =
index ecf795b..b7c4e0d 100644 (file)
 package org.openecomp.sdc.logging.api;
 
 /**
- * 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.
+ * Builder to populate <i>audit</i> data. This includes only data known to an application, and not otherwise available
+ * to the logging framework. As opposed, for example, to local runtime, host address, etc.
  *
  * @author KATYR, evitaliy
  * @since February 15, 2018
  */
 public class AuditData {
 
-    // A concrete implementation interface was chosen over an interface to enable to gracefully add new fields without
-    // affecting client code. Also, the builder can be implemented differently if needed, without affecting client code.
-    // For instance, it may delegate the population and instantiation to a service provider.
+    // don't inherit from MetricsData because it has a very different meaning
 
     private final long startTime;
     private final long endTime;
index ea777d5..43f0143 100644 (file)
@@ -17,8 +17,9 @@
 package org.openecomp.sdc.logging.api;
 
 /**
- * Builder to populate logging context data. This includes only data known to an application, and not otherwise
- * available to the logging framework.
+ * Builder to populate logging <i>context</i> data, i.e. data that should be available to any log writing event
+ * throughout an application. This includes only data known at some point to the application (e.g. at an API call),
+ * and not otherwise available to the logging framework (e.g. information about local runtime, machine, etc.).
  *
  * @author evitaliy
  * @since Mar 22, 2018
index d02e99f..d100a2d 100644 (file)
@@ -31,16 +31,16 @@ public interface Logger {
 
     boolean isMetricsEnabled();
 
+    void metrics(MetricsData data);
+
+    /**
+     * Kept for backward compatibility.
+     *
+     * @deprecated will be removed soon, {@link #metrics(MetricsData)} must be used instead
+     */
+    @Deprecated
     void metrics(String msg);
 
-    void metrics(String msg, Object arg);
-
-    void metrics(String msg, Object arg1, Object arg2);
-
-    void metrics(String msg, Object... arguments);
-
-    void metrics(String msg, Throwable t);
-
     boolean isAuditEnabled();
 
     void audit(AuditData data);
index 399fd37..1f3df8b 100644 (file)
@@ -16,9 +16,8 @@
 
 package org.openecomp.sdc.logging.api;
 
-import org.openecomp.sdc.logging.spi.LoggerCreationService;
-
 import java.util.Objects;
+import org.openecomp.sdc.logging.spi.LoggerCreationService;
 
 /**
  * <a>Factory to hide a concrete, framework-specific implementation of logger creation.</a>
@@ -69,27 +68,12 @@ public class LoggerFactory {
             }
 
             @Override
-            public void metrics(String msg) {
+            public void metrics(MetricsData msg) {
                 // no-op
             }
 
             @Override
-            public void metrics(String msg, Object arg) {
-                // no-op
-            }
-
-            @Override
-            public void metrics(String msg, Object arg1, Object arg2) {
-                // no-op
-            }
-
-            @Override
-            public void metrics(String msg, Object... arguments) {
-                // no-op
-            }
-
-            @Override
-            public void metrics(String msg, Throwable t) {
+            public void metrics(String msg) {
                 // no-op
             }
 
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/MetricsData.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/MetricsData.java
new file mode 100644 (file)
index 0000000..c44ab42
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * 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;
+
+/**
+ * Builder to populate <i>metrics</i> data. This includes only data known to an application, and not otherwise available
+ * to the logging framework.
+ *
+ * @author evitaliy
+ * @since 26 Mar 2018
+ */
+public class MetricsData {
+
+    // don't inherit from AuditData because it has a very different meaning
+
+    private final long startTime;
+    private final long endTime;
+    private final StatusCode statusCode;
+    private final String responseCode;
+    private final String responseDescription;
+    private final String clientIpAddress;
+    private final String targetVirtualEntity;
+    private final String targetEntity;
+
+    private MetricsData(final MetricsDataBuilder 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;
+        this.targetEntity = builder.targetEntity;
+        this.targetVirtualEntity = builder.targetVirtualEntity;
+    }
+
+    /**
+     * Begin timestamp of an API invocation.
+     *
+     * @return timestamp
+     */
+    public long getStartTime() {
+        return startTime;
+    }
+
+    /**
+     * End timestamp of an API invocation.
+     *
+     * @return timestamp
+     */
+    public long getEndTime() {
+        return endTime;
+    }
+
+    /**
+     * Result status of an API invocation.
+     *
+     * @return protocol and application agnostic status code
+     */
+    public StatusCode getStatusCode() {
+        return statusCode;
+    }
+
+    /**
+     * Application/protocol specific response status of an API invocation.
+     *
+     * @return response code
+     */
+    public String getResponseCode() {
+        return responseCode;
+    }
+
+    /**
+     * Application/protocol specific response in a human-friendly way.
+     *
+     * @return human-friendly response description
+     */
+    public String getResponseDescription() {
+        return responseDescription;
+    }
+
+    /**
+     * IP address of the invoking client when available.
+     *
+     * @return IP address
+     */
+    public String getClientIpAddress() {
+        return clientIpAddress;
+    }
+
+    /**
+     * External entity invoked by the local system.
+     *
+     * @return identifier of an external entity (system, component, sub-component)
+     */
+    public String getTargetEntity() {
+        return targetEntity;
+    }
+
+    /**
+     * External API invoked by the local system.
+     *
+     * @return name of an external API
+     */
+    public String getTargetVirtualEntity() {
+        return targetVirtualEntity;
+    }
+
+    @Override
+    public String toString() {
+        return "AuditData{startTime=" + startTime + ", endTime=" + endTime + ", statusCode=" + statusCode
+                + ", responseCode=" + responseCode + ", responseDescription=" + responseDescription
+                + ", clientIpAddress=" + clientIpAddress + '}';
+    }
+
+    public static MetricsDataBuilder builder() {
+        return new MetricsDataBuilder();
+    }
+
+    /**
+     * Fluent API for building metrics data.
+     */
+    public static class MetricsDataBuilder {
+
+        private long startTime;
+        private long endTime;
+        private StatusCode statusCode;
+        private String responseCode;
+        private String responseDescription;
+        private String clientIpAddress;
+        private String targetEntity;
+        private String targetVirtualEntity;
+
+        MetricsDataBuilder() { /* 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 MetricsDataBuilder 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 MetricsDataBuilder 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 MetricsDataBuilder 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 MetricsDataBuilder 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 MetricsDataBuilder 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 MetricsDataBuilder clientIpAddress(final String clientIpAddress) {
+            this.clientIpAddress = clientIpAddress;
+            return this;
+        }
+
+        /**
+         * External entity at which the operation is invoked.
+         *
+         * @param targetEntity external entity identifier
+         * @return this builder for fluent API
+         */
+        public MetricsDataBuilder targetEntity(String targetEntity) {
+            this.targetEntity = targetEntity;
+            return this;
+        }
+
+        /**
+         * Name of the API or operation activities invoked at the external entity.
+         *
+         * @param targetVirtualEntity invoked external API
+         * @return this builder for fluent API
+         */
+        public MetricsDataBuilder targetVirtualEntity(String targetVirtualEntity) {
+            this.targetVirtualEntity = targetVirtualEntity;
+            return this;
+        }
+
+        /**
+         * Create an instance of {@link MetricsData}.
+         *
+         * @return a populated instance of audit data
+         */
+        public MetricsData build() {
+            return new MetricsData(this);
+        }
+    }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/package-info.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/package-info.java
new file mode 100644 (file)
index 0000000..a8ecad0
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+/**
+ * <p>Client-visible API for logging, implemented according to
+ * <a href="https://wiki.onap.org/download/attachments/1015849/ONAP%20application%20logging%20guidelines.pdf?api=v2">
+ * ONAP application logging guidelines</a>. The actual implementation is delegated to a service provider bound through
+ * the <a href="https://docs.oracle.com/javase/tutorial/ext/basics/spi.html">Java SPI</a> mechanism. The provider must
+ * implement {@link org.openecomp.sdc.logging.spi.LoggingServiceProvider}.</p>
+ * <p>The logging API collects the following types of data:</p>
+ * <ol>
+ *     <li>Context that must be propagated throughout the application, and available at any point for debug and error
+ *     reporting.</li>
+ *     <li>Audit data, reflecting the invocation of a local, usually REST, API.</li>
+ *     <li>Metrics data, reflecting the invocation of a remote API by current system (component).</li>
+ * </ol>
+ * <p>The construction of all three types of data follows the same pattern for consistency. The builder pattern has
+ * been chosen over an interface to enable to gracefully add new fields without affecting the client code. Also, the
+ * builder can be implemented differently if needed, also without affecting client code. For instance, it may delegate
+ * the instantiation and population of a data object to the service provider.</p>
+ *
+ * @author evitaliy
+ * @since 26 Mar 2018
+ */
+package org.openecomp.sdc.logging.api;
\ No newline at end of file
index 1889f3e..a1fe8c2 100644 (file)
 
 package org.openecomp.sdc.logging.api;
 
-import org.testng.annotations.Test;
-
-import java.lang.reflect.Field;
-
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
+import java.lang.reflect.Field;
+import org.testng.annotations.Test;
+
 /**
+ * Unit-test creation of a logger via factory, assuming not default binding.
+ *
  * @author evitaliy
  * @since 14/09/2016.
  */
@@ -71,6 +72,6 @@ public class LoggerFactoryTest {
         logger.info("");
         logger.debug("");
         logger.audit(null);
-        logger.metrics("");
+        logger.metrics(MetricsData.builder().build());
     }
 }
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/MetricsDataTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/test/java/org/openecomp/sdc/logging/api/MetricsDataTest.java
new file mode 100644 (file)
index 0000000..a3c8b10
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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.assertEquals;
+import static org.testng.Assert.assertNull;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit-testing metrics builder and structure.
+ *
+ * @author evitaliy
+ * @since 04 Mar 18
+ */
+public class MetricsDataTest {
+
+    @Test
+    public void allMetricsPropertiesReadWhenPopulated() {
+
+        final long start = System.currentTimeMillis();
+        final long end = start + 1000;
+        final String responseCode = "Metrics-Response-Code";
+        final String responseDescription = "Metrics-Response-Description";
+        final String ipAddress = "10.56.20.72";
+        final String targetEntity = "Metrics-Target-Entity";
+        final String targetVirtualEntity = "Metrics-Target-Virtual-Entity";
+
+        MetricsData data = MetricsData.builder().startTime(start).endTime(end).statusCode(StatusCode.COMPLETE)
+                                      .responseCode(responseCode).responseDescription(responseDescription)
+                                      .clientIpAddress(ipAddress).targetEntity(targetEntity)
+                                      .targetVirtualEntity(targetVirtualEntity).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);
+        assertEquals(data.getTargetEntity(), targetEntity);
+        assertEquals(data.getTargetVirtualEntity(), targetVirtualEntity);
+
+    }
+
+    @Test
+    public void allMetricsPropertiesEmptyWhenUnpopulated() {
+        MetricsData data = MetricsData.builder().build();
+        assertEquals(data.getStartTime(), 0);
+        assertEquals(data.getEndTime(), 0);
+        assertNull(data.getClientIpAddress());
+        assertNull(data.getResponseCode());
+        assertNull(data.getResponseDescription());
+        assertNull(data.getStatusCode());
+        assertNull(data.getTargetEntity());
+        assertNull(data.getTargetVirtualEntity());
+    }
+}
\ No newline at end of file
index dbaef2a..e3fb799 100644 (file)
             <artifactId>openecomp-sdc-logging-api</artifactId>
             <version>${project.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.aspectj</groupId>
-            <artifactId>aspectjrt</artifactId>
-            <version>${aspectj.version}</version>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/GlobalLoggingContext.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/GlobalLoggingContext.java
deleted file mode 100644 (file)
index a708ed6..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright © 2016-2017 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;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.Preferences;
-
-/**
- * Collect information the is required for logging, but should not concern the business code of an application. For
- * example, host name and IP address.
- *
- * @author evitaliy
- * @since 04 Mar 2018
- */
-@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace", "squid:S106", "squid:S1148"})
-public class GlobalLoggingContext {
-
-    // should be cashed to avoid low-level call, but with a timeout to account for IP or FQDN changes
-    private static final HostAddressCache HOST_ADDRESS_CACHE = new HostAddressCache();
-
-    @SuppressWarnings("squid:S1075")
-    private static final String INSTANCE_UUID_PREFERENCES_PATH = "/logging/instance/uuid";
-
-    private static final String INSTANCE_ID;
-
-    static {
-        INSTANCE_ID = readInstanceId();
-    }
-
-    private GlobalLoggingContext() {
-        // prevent instantiation
-    }
-
-    /**
-     * A unique ID of the logging entity. Is useful to distinguish between different nodes of the same application. It
-     * is assumed, that the node can be re-started, in which case the unique ID must be retained.
-     *
-     * @return unique logging entity ID
-     */
-    public static String getInstanceId() {
-        return INSTANCE_ID;
-    }
-
-    /**
-     * Local host address as returned by Java runtime. A value of host address will be cached for the interval specified
-     * in {@link HostAddressCache#REFRESH_TIME}
-     *
-     * @return local host address, may be null if could not be read for some reason
-     */
-    public static InetAddress getHostAddress() {
-        return HOST_ADDRESS_CACHE.get();
-    }
-
-    private static String readInstanceId() {
-
-        try {
-
-            // On Linux, by default this will be ~/.java/.userPrefs/prefs.xml
-            final Preferences preferences = Preferences.userRoot();
-            String existingId = preferences.get(INSTANCE_UUID_PREFERENCES_PATH, null);
-            if (existingId != null) {
-                return existingId;
-            }
-
-            String newId = UUID.randomUUID().toString();
-            preferences.put(INSTANCE_UUID_PREFERENCES_PATH, newId);
-            preferences.flush();
-            return newId;
-
-        } catch (BackingStoreException e) {
-            e.printStackTrace();
-            // don't fail if there's a problem to use the store for some unexpected reason
-            return UUID.randomUUID().toString();
-        }
-    }
-
-    private static class HostAddressCache {
-
-        private static final long REFRESH_TIME = 60000L;
-
-        private final AtomicLong lastUpdated = new AtomicLong(0L);
-        private InetAddress hostAddress;
-
-        InetAddress get() {
-
-            long current = System.currentTimeMillis();
-            if (current - lastUpdated.get() > REFRESH_TIME) {
-
-                synchronized (this) {
-
-                    try {
-                        // set now to register the attempt even if failed
-                        lastUpdated.set(current);
-                        hostAddress = InetAddress.getLocalHost();
-                    } catch (UnknownHostException e) {
-                        e.printStackTrace(); // can't really use logging
-                        hostAddress = null;
-                    }
-                }
-            }
-
-            return hostAddress;
-        }
-    }
-}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/aspects/MetricsAspect.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/aspects/MetricsAspect.java
deleted file mode 100644 (file)
index a99a97d..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright © 2016-2017 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.aspects;
-
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.openecomp.sdc.logging.api.Logger;
-import org.openecomp.sdc.logging.api.LoggerFactory;
-import org.openecomp.sdc.logging.api.annotations.Metrics;
-
-/**
- * <p>Wraps around any method annotated with {@link Metrics} to measure and log its execution time
- * in milliseconds.</p>
- * <p>In order for the aspect to be used, AspectJ annotation processing must be tuned on and this
- * particular aspect enabled. Conversely, it can be disabled completely if the application does not
- * need to log metrics.</p>
- * <p>See, for example, <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html">
- * Aspect Oriented Programming with Spring</a>.</p>
- *
- * @author evitaliy
- * @see Metrics
- * @since 27/07/2016.
- */
-@Aspect
-public class MetricsAspect {
-
-  private static final String MESSAGE_TEMPLATE = "'{}' took {} milliseconds";
-
-  @Around("@annotation(org.openecomp.sdc.logging.api.annotations.Metrics)")
-  public Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
-
-    final Logger logger = LoggerFactory.getLogger(pjp.getSignature().getDeclaringTypeName());
-    // measure and log only if the logger for this class is enabled
-    if (logger.isMetricsEnabled()) {
-
-      final String method = pjp.getSignature().getName();
-      final long start = System.currentTimeMillis();
-
-      try {
-        return pjp.proceed();
-      } finally {
-        logger.metrics(MESSAGE_TEMPLATE, method, System.currentTimeMillis() - start);
-      }
-
-    } else {
-      return pjp.proceed();
-    }
-  }
-}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/context/HostAddress.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/context/HostAddress.java
new file mode 100644 (file)
index 0000000..bcfef6c
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.context;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Holds a reference to local host address as returned by Java runtime. A value of host address will be cached for the
+ * interval specified in the constructor or {@link #DEFAULT_REFRESH_INTERVAL}. The caching helps to avoid many low-level
+ * calls, but at the same time pick up any IP or FQDN changes. Although the underlying JDK implementation uses caching
+ * too, the refresh interval for logging may be much longer due to the nature of the use.
+ *
+ * @author evitaliy
+ * @since 26 Mar 2018
+ */
+@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace", "squid:S106", "squid:S1148"})
+public class HostAddress {
+
+    private static final long DEFAULT_REFRESH_INTERVAL = 60000L; // 1 min
+
+    private final long interval;
+
+    private CacheEntry cachedAddress;
+
+    public HostAddress() {
+        this(DEFAULT_REFRESH_INTERVAL);
+    }
+
+    /**
+     * Creates a cache for host address with a custom refresh interval.
+     */
+    public HostAddress(long refreshInterval) {
+        this.interval = refreshInterval;
+        this.cachedAddress = new CacheEntry(System.currentTimeMillis(), read());
+    }
+
+    /**
+     * Returns an address (host name and IP address) of the local system.
+     *
+     * @return local host address or <code>null</code> if it could not be read for some reason
+     */
+    public synchronized InetAddress get() {
+
+        long current = System.currentTimeMillis();
+        if (current - cachedAddress.lastUpdated < interval) {
+            return cachedAddress.address;
+        }
+
+        InetAddress address = read();
+        cachedAddress = new CacheEntry(current, address);
+        return address;
+    }
+
+    private InetAddress read() {
+
+        try {
+            return InetAddress.getLocalHost();
+        } catch (UnknownHostException e) {
+            e.printStackTrace(); // can't really use logging
+            return null; // let register the attempt even if failed
+        }
+    }
+
+    private static class CacheEntry {
+
+        private final long lastUpdated;
+        private final InetAddress address;
+
+        private CacheEntry(long lastUpdated, InetAddress address) {
+            this.lastUpdated = lastUpdated;
+            this.address = address;
+        }
+    }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/context/InstanceId.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/context/InstanceId.java
new file mode 100644 (file)
index 0000000..73544e1
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.context;
+
+import java.util.UUID;
+
+/**
+ * Holds a unique ID of the logging entity. Is useful to distinguish between different nodes of the same application. If
+ * it can be assumed, that the node can be re-started, then the unique ID must be retained on the disk.
+ *
+ * @author evitaliy
+ * @since 04 Mar 2018
+ */
+@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace", "squid:S106", "squid:S1148"})
+public class InstanceId {
+
+    private static final String INSTANCE_ID;
+
+    static {
+        // for some reason Java Preferences API
+        // https://docs.oracle.com/javase/8/docs/technotes/guides/preferences/overview.html
+        // didn't work in a Docker container, so for now just generate an ID every time
+        INSTANCE_ID = UUID.randomUUID().toString();
+    }
+
+    private InstanceId() {
+        // prevent instantiation
+    }
+
+    /**
+     * A unique ID of the logging entity.
+     *
+     * @return unique logging entity ID
+     */
+    public static String get() {
+        return INSTANCE_ID;
+    }
+}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/AuditField.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/AuditField.java
new file mode 100644 (file)
index 0000000..3e44a34
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+/**
+ * MDC fields that represent audit data.
+ *
+ * @author evitaliy
+ * @since 25 Mar 2018
+ */
+enum AuditField implements MDCField {
+
+    BEGIN_TIMESTAMP("BeginTimestamp"),
+    END_TIMESTAMP("EndTimestamp"),
+    ELAPSED_TIME("ElapsedTime"),
+    STATUS_CODE("StatusCode"),
+    RESPONSE_CODE("ResponseCode"),
+    RESPONSE_DESCRIPTION("ResponseDescription"),
+    CLIENT_IP_ADDRESS("ClientIpAddress");
+
+    private final String key;
+
+    AuditField(String key) {
+        this.key = key;
+    }
+
+    public String asKey() {
+        return key;
+    }
+}
index 6aa689b..cce5f44 100644 (file)
 package org.openecomp.sdc.logging.slf4j;
 
 /**
- * MDC fields to work with - populate, clear, copy.
+ * MDC fields that represent context data.
  *
  * @author evitaliy
  * @since 23 Mar 2018
  */
-enum ContextField {
+enum ContextField implements MDCField {
 
     REQUEST_ID("RequestId"),
     SERVICE_NAME("ServiceName"),
index a415e22..5f2963e 100644 (file)
@@ -19,7 +19,8 @@ package org.openecomp.sdc.logging.slf4j;
 import java.net.InetAddress;
 import java.util.EnumMap;
 import java.util.Map;
-import org.openecomp.sdc.logging.GlobalLoggingContext;
+import org.openecomp.sdc.logging.context.HostAddress;
+import org.openecomp.sdc.logging.context.InstanceId;
 
 /**
  * Maps global logging context to corresponding MDC fields.
@@ -29,13 +30,15 @@ import org.openecomp.sdc.logging.GlobalLoggingContext;
  */
 class GlobalContextProvider implements ContextProvider {
 
+    private static final HostAddress HOST_ADDRESS_CACHE = new HostAddress();
+
     @Override
     public Map<ContextField, String> values() {
 
         Map<ContextField, String> values = new EnumMap<>(ContextField.class);
-        values.put(ContextField.INSTANCE_ID, GlobalLoggingContext.getInstanceId());
+        values.put(ContextField.INSTANCE_ID, InstanceId.get());
 
-        InetAddress hostAddress = GlobalLoggingContext.getHostAddress();
+        InetAddress hostAddress = HOST_ADDRESS_CACHE.get();
         if (hostAddress != null) {
             values.put(ContextField.SERVER, hostAddress.getHostName());
             values.put(ContextField.SERVER_IP_ADDRESS, hostAddress.getHostAddress());
@@ -1,12 +1,12 @@
 /*
- * Copyright © 2016-2017 European Support Limited
+ * 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.
  * limitations under the License.
  */
 
-package org.openecomp.sdc.logging.api.annotations;
+package org.openecomp.sdc.logging.slf4j;
 
 /**
- * Indicates a method whose execution time should be measured and logged as required for Open OPENECOMP metrics.
+ * Represents an MDC field. Maps a Java type to an MDC key that can be referred to in logger configuration.
  *
  * @author evitaliy
- * @since 27/07/2016.
+ * @since 25 Mar 2018
  */
+interface MDCField {
+
+    String asKey();
 
-public @interface Metrics {
 }
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/MetricsField.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/main/java/org/openecomp/sdc/logging/slf4j/MetricsField.java
new file mode 100644 (file)
index 0000000..fda0c8b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+/**
+ * DC fields that represent metrics data.
+ *
+ * @author evitaliy
+ * @since 26 Mar 2018
+ */
+public enum MetricsField implements MDCField {
+
+    BEGIN_TIMESTAMP("BeginTimestamp"),
+    END_TIMESTAMP("EndTimestamp"),
+    ELAPSED_TIME("ElapsedTime"),
+    STATUS_CODE("StatusCode"),
+    RESPONSE_CODE("ResponseCode"),
+    RESPONSE_DESCRIPTION("ResponseDescription"),
+    CLIENT_IP_ADDRESS("ClientIpAddress"),
+    TARGET_VIRTUAL_ENTITY("TargetVirtualEntity"),
+    TARGET_ENTITY("TargetEntity");
+
+    private final String key;
+
+    MetricsField(String key) {
+        this.key = key;
+    }
+
+    public String asKey() {
+        return key;
+    }
+
+}
index 783dac4..fbb5fbf 100644 (file)
 
 package org.openecomp.sdc.logging.slf4j;
 
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.BEGIN_TIMESTAMP;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.CLIENT_IP_ADDRESS;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.ELAPSED_TIME;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.END_TIMESTAMP;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.RESPONSE_CODE;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.RESPONSE_DESCRIPTION;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.STATUS_CODE;
-
-import java.text.Format;
 import java.text.SimpleDateFormat;
 import org.openecomp.sdc.logging.api.AuditData;
 import org.openecomp.sdc.logging.api.Logger;
+import org.openecomp.sdc.logging.api.MetricsData;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
 /**
+ * Delegates log calls to SLF4J API and MDC.
+ *
  * @author evitaliy
  * @since 08 Jan 18
  */
@@ -40,40 +34,17 @@ class SLF4JLoggerWrapper implements Logger {
     //The specified format presents time in UTC formatted per ISO 8601, as required by ONAP logging guidelines
     private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
 
-    private static final String PREFIX = "";
-
-    enum AuditField {
-
-        BEGIN_TIMESTAMP(PREFIX + "BeginTimestamp"),
-        END_TIMESTAMP(PREFIX + "EndTimestamp"),
-        ELAPSED_TIME(PREFIX + "ElapsedTime"),
-        STATUS_CODE(PREFIX + "StatusCode"),
-        RESPONSE_CODE(PREFIX + "ResponseCode"),
-        RESPONSE_DESCRIPTION(PREFIX + "ResponseDescription"),
-        CLIENT_IP_ADDRESS(PREFIX + "ClientIpAddress");
-
-        private final String key;
-
-        AuditField(String key) {
-            this.key = key;
-        }
-
-        public String asKey() {
-            return key;
-        }
-    }
-
     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) {
         this(LoggerFactory.getLogger(clazz));
     }
 
+    SLF4JLoggerWrapper(org.slf4j.Logger delegate) {
+        this.logger = delegate;
+    }
+
     SLF4JLoggerWrapper(String className) {
         this(LoggerFactory.getLogger(className));
     }
@@ -90,27 +61,55 @@ class SLF4JLoggerWrapper implements Logger {
 
     @Override
     public void metrics(String msg) {
-        logger.info(Markers.METRICS, msg);
+        // do nothing, left for backward compatibility
     }
 
     @Override
-    public void metrics(String msg, Object arg) {
-        logger.info(Markers.METRICS, msg, arg);
+    public void metrics(MetricsData data) {
+
+        if (data == null) {
+            return; // not going to fail because of null
+        }
+
+        try {
+            putMetricsOnMdc(data);
+            logger.info(Markers.METRICS, "");
+        } finally {
+            clearMetricsFromMdc();
+        }
     }
 
-    @Override
-    public void metrics(String msg, Object arg1, Object arg2) {
-        logger.info(Markers.METRICS, msg, arg1, arg2);
+    private void putMetricsOnMdc(MetricsData metrics) {
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_PATTERN);
+        unsafePutOnMdc(MetricsField.BEGIN_TIMESTAMP, dateFormat.format(metrics.getStartTime()));
+        unsafePutOnMdc(MetricsField.END_TIMESTAMP, dateFormat.format(metrics.getEndTime()));
+        unsafePutOnMdc(MetricsField.ELAPSED_TIME, String.valueOf(metrics.getEndTime() - metrics.getStartTime()));
+        safePutOnMdc(MetricsField.RESPONSE_CODE, metrics.getResponseCode());
+        safePutOnMdc(MetricsField.RESPONSE_DESCRIPTION, metrics.getResponseDescription());
+        safePutOnMdc(MetricsField.CLIENT_IP_ADDRESS, metrics.getClientIpAddress());
+        safePutOnMdc(MetricsField.TARGET_ENTITY, metrics.getTargetEntity());
+        safePutOnMdc(MetricsField.TARGET_VIRTUAL_ENTITY, metrics.getTargetVirtualEntity());
+
+        if (metrics.getStatusCode() != null) {
+            unsafePutOnMdc(MetricsField.STATUS_CODE, metrics.getStatusCode().name());
+        }
     }
 
-    @Override
-    public void metrics(String msg, Object... arguments) {
-        logger.info(Markers.METRICS, msg, arguments);
+    private void clearMetricsFromMdc() {
+        for (MetricsField f : MetricsField.values()) {
+            MDC.remove(f.asKey());
+        }
     }
 
-    @Override
-    public void metrics(String msg, Throwable t) {
-        logger.info(Markers.METRICS, msg, t);
+    private static void unsafePutOnMdc(MDCField field, String value) {
+        MDC.put(field.asKey(), value);
+    }
+
+    private static void safePutOnMdc(MDCField field, String value) {
+        if (value != null) {
+            unsafePutOnMdc(field, value);
+        }
     }
 
     @Override
@@ -125,36 +124,33 @@ class SLF4JLoggerWrapper implements Logger {
             return; // not failing if null
         }
 
-        putTimes(data);
-        putIfNotNull(RESPONSE_CODE.key, data.getResponseCode());
-        putIfNotNull(RESPONSE_DESCRIPTION.key, data.getResponseDescription());
-        putIfNotNull(CLIENT_IP_ADDRESS.key, data.getClientIpAddress());
-
-        if (data.getStatusCode() != null) {
-            MDC.put(STATUS_CODE.key, data.getStatusCode().name());
-        }
-
         try {
+            putAuditOnMdc(data);
             logger.info(Markers.AUDIT, "");
         } finally {
-            for (AuditField f : AuditField.values()) {
-                MDC.remove(f.key);
-            }
+            clearAuditFromMdc();
         }
     }
 
-    private void putIfNotNull(String key, String value) {
-        if (value != null) {
-            MDC.put(key, value);
+    private void putAuditOnMdc(AuditData audit) {
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_TIME_PATTERN);
+        unsafePutOnMdc(AuditField.BEGIN_TIMESTAMP, dateFormat.format(audit.getStartTime()));
+        unsafePutOnMdc(AuditField.END_TIMESTAMP, dateFormat.format(audit.getEndTime()));
+        unsafePutOnMdc(AuditField.ELAPSED_TIME, String.valueOf(audit.getEndTime() - audit.getStartTime()));
+        safePutOnMdc(AuditField.RESPONSE_CODE, audit.getResponseCode());
+        safePutOnMdc(AuditField.RESPONSE_DESCRIPTION, audit.getResponseDescription());
+        safePutOnMdc(AuditField.CLIENT_IP_ADDRESS, audit.getClientIpAddress());
+
+        if (audit.getStatusCode() != null) {
+            unsafePutOnMdc(AuditField.STATUS_CODE, audit.getStatusCode().name());
         }
     }
 
-    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.key, dateTimeFormat.format(data.getStartTime()));
-        MDC.put(END_TIMESTAMP.key, dateTimeFormat.format(data.getEndTime()));
-        MDC.put(ELAPSED_TIME.key, String.valueOf(data.getEndTime() - data.getStartTime()));
+    private void clearAuditFromMdc() {
+        for (AuditField f : AuditField.values()) {
+            MDC.remove(f.asKey());
+        }
     }
 
     @Override
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/LogFileCreationTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/LogFileCreationTest.java
deleted file mode 100644 (file)
index 14dab13..0000000
+++ /dev/null
@@ -1,66 +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;
-
-import org.openecomp.sdc.logging.api.AuditData;
-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)
-    public void testMetrics() {
-        LOGGER.metrics("This is metrics");
-    }
-
-    @Test(enabled = ENABLED)
-    public void testAudit() {
-        LOGGER.audit(AuditData.builder().build());
-    }
-
-    @Test(enabled = ENABLED)
-    public void testDebug() {
-        LOGGER.debug("This is debug");
-    }
-
-    @Test(enabled = ENABLED)
-    public void testInfo() {
-        LOGGER.info("This is info");
-    }
-
-    @Test(enabled = ENABLED)
-    public void testWarn() {
-        LOGGER.warn("This is warning");
-    }
-
-    @Test(enabled = ENABLED)
-    public void testError() {
-        LOGGER.error("This is error");
-    }
-}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/aspects/MetricsAspectTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/aspects/MetricsAspectTest.java
deleted file mode 100644 (file)
index e4cd379..0000000
+++ /dev/null
@@ -1,429 +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.aspects;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Predicate;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.Signature;
-import org.aspectj.lang.reflect.SourceLocation;
-import org.aspectj.runtime.internal.AroundClosure;
-import org.easymock.EasyMock;
-import org.openecomp.sdc.logging.api.AuditData;
-import org.openecomp.sdc.logging.api.Logger;
-import org.openecomp.sdc.logging.api.LoggerFactory;
-import org.powermock.api.easymock.PowerMock;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.testng.PowerMockTestCase;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-/**
- * Unit-tests metrics aspect (AOP) behavior.
- *
- * @author evitaliy
- * @since 17 Aug 2016
- */
-@PrepareForTest(LoggerFactory.class)
-public class MetricsAspectTest extends PowerMockTestCase {
-
-    private static final Object OBJ_TO_RETURN = new Object();
-    private static final String EXPECTED_MESSAGE = "'{}' took {} milliseconds";
-
-    @Test
-    public void testLogExecutionTime() throws Throwable {
-
-        String className = UUID.randomUUID().toString();
-        String methodName = UUID.randomUUID().toString();
-
-        TestLogger logger = initLogging(className, true);
-
-        MetricsAspect aspect = new MetricsAspect();
-        MockProceedingJoinPoint pjp = new MockProceedingJoinPoint(className, methodName);
-        Object returned = aspect.logExecutionTime(pjp);
-
-        Assert.assertEquals(OBJ_TO_RETURN, returned);
-        assertExecution(methodName, pjp, logger);
-    }
-
-    private TestLogger initLogging(String className, boolean enabled) {
-        TestLogger logger = new TestLogger(enabled);
-        PowerMock.mockStatic(LoggerFactory.class);
-        EasyMock.expect(LoggerFactory.getLogger(className)).andReturn(logger);
-        PowerMock.replay(LoggerFactory.class);
-        return logger;
-    }
-
-    private void assertExecution(String methodName, MockProceedingJoinPoint pjp, TestLogger logger) {
-
-        Assert.assertEquals(1, pjp.getCount());
-        Assert.assertTrue(logger.contains(
-                (event) -> (event != null) && (event.length == 3) && EXPECTED_MESSAGE.equals(event[0]) && methodName
-                        .equals(event[1]) && (event[2] instanceof Long)));
-    }
-
-    @Test
-    public void testMetricsDisabled() throws Throwable {
-
-        String className = UUID.randomUUID().toString();
-        String methodName = UUID.randomUUID().toString();
-
-        TestLogger logger = initLogging(className, false);
-
-        MetricsAspect aspect = new MetricsAspect();
-        MockProceedingJoinPoint pjp = new MockProceedingJoinPoint(className, methodName);
-        Object returned = aspect.logExecutionTime(pjp);
-
-        Assert.assertEquals(OBJ_TO_RETURN, returned);
-        Assert.assertEquals(1, pjp.getCount());
-        // return any event - must be empty
-        Assert.assertFalse(logger.contains((event) -> true));
-    }
-
-    @Test(expectedExceptions = IllegalArgumentException.class)
-    public void testThrowingError() throws Throwable {
-
-        String className = UUID.randomUUID().toString();
-        String methodName = UUID.randomUUID().toString();
-
-        final TestLogger logger = initLogging(className, true);
-
-        MetricsAspect aspect = new MetricsAspect();
-        MockProceedingJoinPoint pjp = new MockProceedingJoinPointWithException(className, methodName);
-
-        try {
-            aspect.logExecutionTime(pjp);
-        } finally {
-            assertExecution(methodName, pjp, logger);
-        }
-    }
-
-    private static class MockSignature implements Signature {
-
-        private final String className;
-        private final String methodName;
-
-        private MockSignature(String className, String methodName) {
-            this.className = className;
-            this.methodName = methodName;
-        }
-
-        @Override
-        public String toShortString() {
-            return null;
-        }
-
-        @Override
-        public String toLongString() {
-            return null;
-        }
-
-        @Override
-        public String getName() {
-            return methodName;
-        }
-
-        @Override
-        public int getModifiers() {
-            return 0;
-        }
-
-        @Override
-        public Class getDeclaringType() {
-            return null;
-        }
-
-        @Override
-        public String getDeclaringTypeName() {
-            return className;
-        }
-    }
-
-    private static class MockProceedingJoinPoint implements ProceedingJoinPoint {
-
-        private final AtomicInteger count = new AtomicInteger(0);
-        private final Signature signature;
-
-        MockProceedingJoinPoint(String className, String methodName) {
-            this.signature = new MockSignature(className, methodName);
-        }
-
-        int getCount() {
-            return count.get();
-        }
-
-        @Override
-        public void set$AroundClosure(AroundClosure aroundClosure) {
-
-        }
-
-        @Override
-        public Object proceed() throws Throwable {
-            count.incrementAndGet();
-            return OBJ_TO_RETURN;
-        }
-
-        @Override
-        public Object proceed(Object[] objects) {
-            return null;
-        }
-
-        @Override
-        public String toShortString() {
-            return null;
-        }
-
-        @Override
-        public String toLongString() {
-            return null;
-        }
-
-        @Override
-        public Object getThis() {
-            return null;
-        }
-
-        @Override
-        public Object getTarget() {
-            return null;
-        }
-
-        @Override
-        public Object[] getArgs() {
-            return new Object[0];
-        }
-
-        @Override
-        public Signature getSignature() {
-            return this.signature;
-        }
-
-        @Override
-        public SourceLocation getSourceLocation() {
-            return null;
-        }
-
-        @Override
-        public String getKind() {
-            return null;
-        }
-
-        @Override
-        public StaticPart getStaticPart() {
-            return null;
-        }
-    }
-
-    private static class MockProceedingJoinPointWithException extends MockProceedingJoinPoint {
-
-        MockProceedingJoinPointWithException(String className, String methodName) {
-            super(className, methodName);
-        }
-
-        @Override
-        public Object proceed() throws Throwable {
-            super.proceed();
-            throw new IllegalArgumentException();
-        }
-    }
-
-    private class TestLogger implements Logger {
-
-        private final boolean enabled;
-        private final List<Object[]> events = Collections.synchronizedList(new ArrayList<>(10));
-
-        TestLogger(boolean enabled) {
-            this.enabled = enabled;
-        }
-
-        @Override
-        public String getName() {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public boolean isMetricsEnabled() {
-            return this.enabled;
-        }
-
-        @Override
-        public void metrics(String var1) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void metrics(String var1, Object var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void metrics(String var1, Object var2, Object var3) {
-
-            if (this.enabled) {
-                events.add(new Object[] {var1, var2, var3});
-            }
-        }
-
-        @Override
-        public void metrics(String var1, Object... var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void metrics(String var1, Throwable throwable) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public boolean isAuditEnabled() {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void audit(AuditData var1) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public boolean isDebugEnabled() {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void debug(String var1) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void debug(String var1, Object var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void debug(String var1, Object var2, Object var3) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void debug(String var1, Object... var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void debug(String var1, Throwable throwable) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public boolean isInfoEnabled() {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void info(String var1) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void info(String var1, Object var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void info(String var1, Object var2, Object var3) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void info(String var1, Object... var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void info(String var1, Throwable throwable) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public boolean isWarnEnabled() {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void warn(String var1) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void warn(String var1, Object var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void warn(String var1, Object... var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void warn(String var1, Object var2, Object var3) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void warn(String var1, Throwable throwable) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public boolean isErrorEnabled() {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void error(String var1) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void error(String var1, Object var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void error(String var1, Object var2, Object var3) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void error(String var1, Object... var2) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        @Override
-        public void error(String var1, Throwable throwable) {
-            throw new RuntimeException("Not implemented");
-        }
-
-        boolean contains(Predicate<Object[]> predicate) {
-            return events.stream().anyMatch(predicate);
-        }
-    }
-}
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/context/HostAddressTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/context/HostAddressTest.java
new file mode 100644 (file)
index 0000000..319bf19
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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.context;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import org.easymock.EasyMock;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.testng.PowerMockTestCase;
+import org.testng.annotations.Test;
+
+/**
+ * Retrieval and caching of host address.
+ *
+ * @author evitaliy
+ * @since 28 Mar 2018
+ */
+@PrepareForTest(InetAddress.class)
+public class HostAddressTest extends PowerMockTestCase {
+
+    @Test
+    public void hostAddressIsAlwaysPopulated() {
+        assertNotNull(new HostAddress().get());
+    }
+
+    @Test
+    public void cachedAddressRemainsTheSameWhenGotWithingRefreshInterval() throws UnknownHostException {
+        mockInetAddress(1);
+        HostAddress addressCache = new HostAddress(1000);
+        addressCache.get();
+        addressCache.get();
+    }
+
+    @Test
+    public void cachedAddressReplacedWhenGotAfterRefreshInterval() throws UnknownHostException {
+        mockInetAddress(2);
+        HostAddress addressCache = new HostAddress(-1);
+        addressCache.get();
+        addressCache.get();
+    }
+
+    private void mockInetAddress(int times) throws UnknownHostException {
+        InetAddress inetAddress = EasyMock.mock(InetAddress.class);
+        EasyMock.replay(inetAddress);
+        PowerMock.mockStatic(InetAddress.class);
+        //noinspection ResultOfMethodCallIgnored
+        InetAddress.getLocalHost();
+        PowerMock.expectLastCall().andReturn(inetAddress).times(times);
+        PowerMock.replay(InetAddress.class);
+    }
+}
\ No newline at end of file
diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/context/InstanceIdTest.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-core/src/test/java/org/openecomp/sdc/logging/context/InstanceIdTest.java
new file mode 100644 (file)
index 0000000..984cb3b
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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.context;
+
+import static org.testng.Assert.assertNotNull;
+
+import org.testng.annotations.Test;
+
+/**
+ * Retrieval of instance ID.
+ *
+ * @author evitaliy
+ * @since 28 Mar 2018
+ */
+public class InstanceIdTest {
+
+    @Test
+    public void makeSureInstanceIdNotNull() {
+        assertNotNull(InstanceId.get());
+    }
+
+}
\ No newline at end of file
index 2c5233f..f4a29ef 100644 (file)
 
 package org.openecomp.sdc.logging.slf4j;
 
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.BEGIN_TIMESTAMP;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.CLIENT_IP_ADDRESS;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.ELAPSED_TIME;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.END_TIMESTAMP;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.RESPONSE_CODE;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.RESPONSE_DESCRIPTION;
-import static org.openecomp.sdc.logging.slf4j.SLF4JLoggerWrapper.AuditField.STATUS_CODE;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
@@ -33,9 +26,11 @@ 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.MetricsData;
 import org.openecomp.sdc.logging.api.StatusCode;
 import org.slf4j.Logger;
 import org.slf4j.MDC;
+import org.slf4j.Marker;
 import org.testng.annotations.Test;
 
 /**
@@ -53,101 +48,209 @@ public class SLF4JLoggerWrapperTest {
     }
 
     @Test
-    public void beginTimeAvailableWhenPassed() {
+    public void metricsDoesNotFailWhenInputNull() {
+        new SLF4JLoggerWrapper(this.getClass()).metrics((MetricsData) null);
+    }
+
+    @Test
+    public void auditBeginTimeAvailableWhenPassed() {
         SpyLogger spy = createSpy();
         long start = System.currentTimeMillis();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().startTime(start).build());
-        assertNotNull(spy.mdc().get(BEGIN_TIMESTAMP.asKey()));
+        assertNotNull(spy.mdc().get(AuditField.BEGIN_TIMESTAMP.asKey()));
     }
 
     @Test
-    public void entTimeAvailableWhenPassed() {
+    public void metricsBeginTimeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        long start = System.currentTimeMillis();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().startTime(start).build());
+        assertNotNull(spy.mdc().get(MetricsField.BEGIN_TIMESTAMP.asKey()));
+    }
+
+    @Test
+    public void auditEndTimeAvailableWhenPassed() {
         SpyLogger spy = createSpy();
         long end = System.currentTimeMillis();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().endTime(end).build());
-        assertNotNull(spy.mdc().get(END_TIMESTAMP.asKey()));
+        assertNotNull(spy.mdc().get(AuditField.END_TIMESTAMP.asKey()));
     }
 
     @Test
-    public void elapsedTimeAvailableWhenPassed() {
+    public void metricsEndTimeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        long end = System.currentTimeMillis();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().endTime(end).build());
+        assertNotNull(spy.mdc().get(MetricsField.END_TIMESTAMP.asKey()));
+    }
+
+    @Test
+    public void auditElapsedTimeAvailableWhenPassed() {
         SpyLogger spy = createSpy();
         long start = System.currentTimeMillis();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder()
-            .startTime(start).endTime(start).build());
-        assertNotNull(spy.mdc().get(ELAPSED_TIME.asKey()));
+                                                   .startTime(start).endTime(start + 777).build());
+        assertEquals("777", spy.mdc().get(AuditField.ELAPSED_TIME.asKey()));
+    }
+
+    @Test
+    public void metricsElapsedTimeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        long start = System.currentTimeMillis();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder()
+                                                       .startTime(start).endTime(start + 1024).build());
+        assertEquals("1024", spy.mdc().get(MetricsField.ELAPSED_TIME.asKey()));
     }
 
     @Test
-    public void statusCodeAvailableWhenPassed() {
+    public void auditStatusCodeAvailableWhenPassed() {
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().statusCode(StatusCode.COMPLETE).build());
-        assertEquals(spy.mdc().get(STATUS_CODE.asKey()), StatusCode.COMPLETE.name());
+        assertEquals(spy.mdc().get(AuditField.STATUS_CODE.asKey()), StatusCode.COMPLETE.name());
+    }
+
+    @Test
+    public void metricsStatusCodeAvailableWhenPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().statusCode(StatusCode.COMPLETE).build());
+        assertEquals(spy.mdc().get(MetricsField.STATUS_CODE.asKey()), StatusCode.COMPLETE.name());
     }
 
     @Test
-    public void statusCodeEmptyWhenNotPassed() {
+    public void auditStatusCodeEmptyWhenNotPassed() {
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
-        assertNull(spy.mdc().get(STATUS_CODE.asKey()));
+        assertNull(spy.mdc().get(AuditField.STATUS_CODE.asKey()));
+    }
+
+    @Test
+    public void metricsStatusCodeEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().build());
+        assertNull(spy.mdc().get(MetricsField.STATUS_CODE.asKey()));
     }
 
     @Test
-    public void responseCodeAvailableWhenPassed() {
-        final String responseCode = "SpyResponse";
+    public void auditResponseCodeAvailableWhenPassed() {
+        final String responseCode = "AuditSpyResponse";
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().responseCode(responseCode).build());
-        assertEquals(spy.mdc().get(RESPONSE_CODE.asKey()), responseCode);
+        assertEquals(spy.mdc().get(AuditField.RESPONSE_CODE.asKey()), responseCode);
     }
 
     @Test
-    public void responseCodeEmptyWhenNotPassed() {
+    public void metricsResponseCodeAvailableWhenPassed() {
+        final String responseCode = "MetricsSpyResponse";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().responseCode(responseCode).build());
+        assertEquals(spy.mdc().get(MetricsField.RESPONSE_CODE.asKey()), responseCode);
+    }
+
+    @Test
+    public void auditResponseCodeEmptyWhenNotPassed() {
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
-        assertNull(spy.mdc().get(RESPONSE_CODE.asKey()));
+        assertNull(spy.mdc().get(AuditField.RESPONSE_CODE.asKey()));
+    }
+
+    @Test
+    public void metricsResponseCodeEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().build());
+        assertNull(spy.mdc().get(MetricsField.RESPONSE_CODE.asKey()));
     }
 
     @Test
-    public void responseDescriptionAvailableWhenPassed() {
-        final String responseDescription = "SpyDescription";
+    public void auditResponseDescriptionAvailableWhenPassed() {
+        final String responseDescription = "AuditSpyDescription";
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().responseDescription(responseDescription).build());
-        assertEquals(spy.mdc().get(RESPONSE_DESCRIPTION.asKey()), responseDescription);
+        assertEquals(spy.mdc().get(AuditField.RESPONSE_DESCRIPTION.asKey()), responseDescription);
     }
 
     @Test
-    public void responseDescriptionEmptyWhenNotPassed() {
+    public void metricsResponseDescriptionAvailableWhenPassed() {
+        final String responseDescription = "MetricsSpyDescription";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().responseDescription(responseDescription).build());
+        assertEquals(spy.mdc().get(MetricsField.RESPONSE_DESCRIPTION.asKey()), responseDescription);
+    }
+
+    @Test
+    public void auditResponseDescriptionEmptyWhenNotPassed() {
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
-        assertNull(spy.mdc().get(RESPONSE_DESCRIPTION.asKey()));
+        assertNull(spy.mdc().get(AuditField.RESPONSE_DESCRIPTION.asKey()));
     }
 
     @Test
-    public void clientIpAddressAvailableWhenPassed() {
+    public void metricsResponseDescriptionEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().build());
+        assertNull(spy.mdc().get(MetricsField.RESPONSE_DESCRIPTION.asKey()));
+    }
+
+    @Test
+    public void auditClientIpAddressAvailableWhenPassed() {
         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.asKey()), ipAddress);
+        assertEquals(spy.mdc().get(AuditField.CLIENT_IP_ADDRESS.asKey()), ipAddress);
     }
 
     @Test
-    public void clientIpAddressEmptyWhenNotPassed() {
+    public void metricsClientIpAddressAvailableWhenPassed() {
+        final String ipAddress = "10.56.20.22";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().clientIpAddress(ipAddress).build());
+        assertEquals(spy.mdc().get(MetricsField.CLIENT_IP_ADDRESS.asKey()), ipAddress);
+    }
+
+    @Test
+    public void auditClientIpAddressEmptyWhenNotPassed() {
         SpyLogger spy = createSpy();
         new SLF4JLoggerWrapper(spy).audit(AuditData.builder().build());
-        assertNull(spy.mdc().get(CLIENT_IP_ADDRESS.asKey()));
+        assertNull(spy.mdc().get(AuditField.CLIENT_IP_ADDRESS.asKey()));
     }
 
     @Test
-    public void elapsedTimeEqualsDifferenceBetweenStartAndEnd() {
+    public void metricsClientIpAddressEmptyWhenNotPassed() {
         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.asKey()), Long.toString(diff));
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().build());
+        assertNull(spy.mdc().get(MetricsField.CLIENT_IP_ADDRESS.asKey()));
     }
 
-    interface SpyLogger extends Logger {
+    @Test
+    public void metricsTargetEntityAvailableWhenPassed() {
+        final String targetEntity = "MetricsTargetEntity";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().targetEntity(targetEntity).build());
+        assertEquals(spy.mdc().get(MetricsField.TARGET_ENTITY.asKey()), targetEntity);
+    }
 
+    @Test
+    public void metricsTargetEntityEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().build());
+        assertNull(spy.mdc().get(MetricsField.TARGET_ENTITY.asKey()));
+    }
+
+    @Test
+    public void metricsTargetVirtualEntityAvailableWhenPassed() {
+        final String targetEntity = "MetricsTargetVirtualEntity";
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().targetVirtualEntity(targetEntity).build());
+        assertEquals(spy.mdc().get(MetricsField.TARGET_VIRTUAL_ENTITY.asKey()), targetEntity);
+    }
+
+    @Test
+    public void metricsTargetVirtualEntityEmptyWhenNotPassed() {
+        SpyLogger spy = createSpy();
+        new SLF4JLoggerWrapper(spy).metrics(MetricsData.builder().build());
+        assertNull(spy.mdc().get(MetricsField.TARGET_VIRTUAL_ENTITY.asKey()));
+    }
+
+    interface SpyLogger extends Logger {
         Map<String, String> mdc();
     }
 
@@ -168,16 +271,16 @@ public class SLF4JLoggerWrapperTest {
 
     private static class SpyingInvocationHandler implements InvocationHandler {
 
-        private Map<String, String> mdc;
+        private Map<String, String> lastMdc;
 
         @Override
         public Object invoke(Object proxy, Method method, Object[] args) {
 
             if (isReturnMdcMethod(method)) {
-                return mdc;
+                return lastMdc;
             }
 
-            if (!isAuditMethod(method, args)) {
+            if (!isAuditMethod(method, args) && !isMetricsMethod(method, args)) {
                 throw new UnsupportedOperationException("Method " + method.getName() + " with arguments "
                         + Arrays.toString(args) + " wasn't supposed to be called");
             }
@@ -186,12 +289,20 @@ public class SLF4JLoggerWrapperTest {
             return null;
         }
 
+        private boolean isMetricsMethod(Method method, Object[] args) {
+            return isSpecialLogMethod(method, args, Markers.METRICS);
+        }
+
         private boolean isAuditMethod(Method method, Object[] args) {
-            return (method.getName().equals("info") && args.length > 0 && args[0].equals(Markers.AUDIT));
+            return isSpecialLogMethod(method, args, Markers.AUDIT);
+        }
+
+        private boolean isSpecialLogMethod(Method method, Object[] args, Marker marker) {
+            return method.getName().equals("info") && args.length > 0 && args[0].equals(marker);
         }
 
         private void storeEffectiveMdc() {
-            mdc = MDC.getCopyOfContextMap();
+            lastMdc = MDC.getCopyOfContextMap();
         }
 
         private boolean isReturnMdcMethod(Method method) {