Convert log in json format in ACM-runtime 31/139631/3
authorFrancescoFioraEst <francesco.fiora@est.tech>
Thu, 28 Nov 2024 11:15:38 +0000 (11:15 +0000)
committerFrancesco Fiora <francesco.fiora@est.tech>
Mon, 2 Dec 2024 17:02:53 +0000 (17:02 +0000)
Issue-ID: POLICY-5147
Change-Id: If87acc597dfc6cffb6bfc69a70a620a00a991536
Signed-off-by: FrancescoFioraEst <francesco.fiora@est.tech>
runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/LoggingConsoleLayout.java [new file with mode: 0644]
runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/config/LoggingConsoleLayoutTest.java [new file with mode: 0644]
runtime-acm/src/test/resources/logback.xml [new file with mode: 0644]

diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/LoggingConsoleLayout.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/config/LoggingConsoleLayout.java
new file mode 100644 (file)
index 0000000..d80ff02
--- /dev/null
@@ -0,0 +1,124 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.acm.runtime.config;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxyUtil;
+import ch.qos.logback.core.CoreConstants;
+import ch.qos.logback.core.LayoutBase;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Setter;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+public class LoggingConsoleLayout extends LayoutBase<ILoggingEvent> {
+
+    private static final String DEFAULT_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS+00:00";
+
+    @Setter
+    private String timestampFormat = DEFAULT_FORMAT;
+
+    @Setter
+    private String timestampFormatTimezoneId = "";
+
+    @Setter
+    private String staticParameters = "";
+
+    private final Map<String, String> staticParameterMap = new HashMap<>();
+
+    private final Coder coder = new StandardCoder();
+
+    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DEFAULT_FORMAT);
+
+    @Override
+    public void start() {
+        super.start();
+        extractParameters();
+        if (timestampFormat != null && !timestampFormat.isBlank()) {
+            formatter = extractDateTimeFormatter();
+        }
+    }
+
+    private void extractParameters() {
+        staticParameterMap.clear();
+        if (staticParameters == null || staticParameters.isBlank()) {
+            return;
+        }
+        var split = staticParameters.split("\\|");
+        for (var str : split) {
+            var s = str.split("=");
+            if (s.length == 2) {
+                staticParameterMap.put(s[0], s[1]);
+            }
+        }
+    }
+
+    private DateTimeFormatter extractDateTimeFormatter() {
+        try {
+            var dtf = DateTimeFormatter.ofPattern(timestampFormat);
+            if (timestampFormatTimezoneId != null && !timestampFormatTimezoneId.isBlank()) {
+                dtf = dtf.withZone(ZoneId.of(timestampFormatTimezoneId));
+            }
+            return dtf;
+        } catch (RuntimeException e) {
+            return DateTimeFormatter.ofPattern(DEFAULT_FORMAT);
+        }
+    }
+
+    @Override
+    public String doLayout(ILoggingEvent event) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("timestamp", getTimestamp(event.getInstant()));
+        map.put("severity", event.getLevel().toString());
+        map.put("message", event.getFormattedMessage());
+        Map<String, String> extraDatamap = new HashMap<>();
+        extraDatamap.put("logger", event.getLoggerName());
+        extraDatamap.put("thread", event.getThreadName());
+        var throwableProxy = event.getThrowableProxy();
+        if (throwableProxy != null) {
+            extraDatamap.put("exception", ThrowableProxyUtil.asString(throwableProxy));
+        }
+        map.put("extra_data", extraDatamap);
+        map.putAll(staticParameterMap);
+        return getJson(map);
+    }
+
+    private String getJson(Map<String, Object> map) {
+        try {
+            return coder.encode(map) + CoreConstants.LINE_SEPARATOR;
+        } catch (CoderException e) {
+            return map.get("message").toString();
+        }
+    }
+
+    private String getTimestamp(Instant instant) {
+        try {
+            return formatter.format(instant);
+        } catch (RuntimeException e) {
+            return instant.toString();
+        }
+    }
+}
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/config/LoggingConsoleLayoutTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/config/LoggingConsoleLayoutTest.java
new file mode 100644 (file)
index 0000000..d4f4c3e
--- /dev/null
@@ -0,0 +1,153 @@
+/*-
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.acm.runtime.config;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+import java.util.Map;
+import lombok.Data;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.slf4j.Marker;
+import org.slf4j.event.KeyValuePair;
+
+class LoggingConsoleLayoutTest {
+
+    private static  final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSX";
+
+    private static final Coder CODER = new StandardCoder();
+
+    @Data
+    private static class DummyEvent implements ILoggingEvent {
+
+        private String threadName = "main";
+        private Level level = Level.INFO;
+        private String message = "{\"key\": \"value\"}";
+        private Object[] argumentArray;
+        private String loggerName = LoggingConsoleLayoutTest.class.getCanonicalName();
+        private LoggerContextVO loggerContextVO;
+        private IThrowableProxy throwableProxy;
+        private StackTraceElement[] callerData;
+        private List<Marker> markerList;
+        private Map<String, String> mdc;
+        private long timeStamp = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+        private int nanoseconds;
+        private long sequenceNumber;
+        private List<KeyValuePair> keyValuePairs;
+
+        @Override
+        public void prepareForDeferredProcessing() {
+            // dummy implementation
+        }
+
+        @Override
+        public boolean hasCallerData() {
+            return false;
+        }
+
+        @Override
+        public Map<String, String> getMDCPropertyMap() {
+            return Map.of();
+        }
+
+        @Override
+        public String getFormattedMessage() {
+            return message;
+        }
+    }
+
+    @Test
+    void testLog() throws CoderException {
+        var layout = new LoggingConsoleLayout();
+        var event = new DummyEvent();
+
+        // start() not called
+        testingResult(layout, event);
+
+        // start() with default data format
+        layout.start();
+        testingResult(layout, event);
+
+        // TimestampFormat
+        layout.setTimestampFormat(FORMAT);
+        layout.start();
+        testingResult(layout, event);
+
+        // wrong format
+        layout.setTimestampFormat("wrong Timestamp");
+        layout.setStaticParameters("wrong Parameter");
+        layout.start();
+        testingResult(layout, event);
+
+        // TimestampFormat and TimestampFormatTimezoneId
+        layout.setTimestampFormat(FORMAT);
+        layout.setTimestampFormatTimezoneId("UTC");
+        layout.setStaticParameters("service_id=policy-acm|application_id=policy-acm");
+        layout.start();
+        testingResult(layout, event);
+
+        // null TimestampFormat
+        layout.setTimestampFormat(null);
+        layout.setStaticParameters(null);
+        layout.start();
+        testingResult(layout, event);
+
+        // blank TimestampFormat
+        layout.setTimestampFormat("");
+        layout.setStaticParameters("");
+        layout.start();
+        testingResult(layout, event);
+
+        // null TimestampFormatTimezoneId
+        layout.setTimestampFormat(FORMAT);
+        layout.setTimestampFormatTimezoneId(null);
+        layout.start();
+        testingResult(layout, event);
+
+        // blank TimestampFormatTimezoneId
+        layout.setTimestampFormat(FORMAT);
+        layout.setTimestampFormatTimezoneId("");
+        layout.start();
+        testingResult(layout, event);
+    }
+
+    private void testingResult(LoggingConsoleLayout layout, DummyEvent event) throws CoderException {
+        var result = layout.doLayout(event);
+        assertThat(result).isNotNull();
+        var map = CODER.decode(result, Map.class);
+        assertEquals(event.level.toString(), map.get("severity"));
+        assertEquals(event.message, map.get("message"));
+        @SuppressWarnings("unchecked")
+        var extraData = (Map<String, String>) map.get("extra_data");
+        assertEquals(event.loggerName, extraData.get("logger"));
+        assertEquals(event.threadName, extraData.get("thread"));
+    }
+}
diff --git a/runtime-acm/src/test/resources/logback.xml b/runtime-acm/src/test/resources/logback.xml
new file mode 100644 (file)
index 0000000..b8934df
--- /dev/null
@@ -0,0 +1,40 @@
+<!--
+  ============LICENSE_START=======================================================
+   Copyright (C) 2024 Nordix Foundation.
+  ================================================================================
+  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.
+
+  SPDX-License-Identifier: Apache-2.0
+  ============LICENSE_END=========================================================
+ -->
+<configuration scan="true" scanPeriod="30 seconds" debug="false">
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+            <layout class="org.onap.policy.clamp.acm.runtime.config.LoggingConsoleLayout">
+                <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSS+00:00</timestampFormat>
+                <timestampFormatTimezoneId>UTC</timestampFormatTimezoneId>
+                <staticParameters>version=1.2.0|service_id=policy-acm|application_id=policy-acm</staticParameters>
+            </layout>
+        </encoder>
+    </appender>
+
+    <logger name="network" level="INFO" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+
+</configuration>