This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 8a55d5807ac0c1e73a28e020836bee9872fb9406
Author: Vic Romero <[email protected]>
AuthorDate: Wed Jun 29 03:53:36 2022 -0500

    FINERACT-1656 Correlation ID propagation and configuration
---
 docker-compose-postgresql.yml                      |  3 +
 docker-compose.yml                                 |  4 ++
 .../core/config/CorrelationIdConfig.java           | 50 ++++++++++++++
 .../core/config/FineractProperties.java            | 10 +++
 .../core/filters/CorrelationHeaderFilter.java      | 77 +++++++++++++++++++++
 .../service/MdcAdapter.java}                       | 39 +++++++++--
 .../security/utils/LogParameterEscapeUtil.java     |  4 ++
 .../src/main/resources/application.properties      |  6 ++
 .../src/main/resources/logback-spring.xml          |  1 +
 .../FineractCorrelationIdApiFilterTest.java        | 80 ++++++++++++++++++++++
 .../src/test/resources/application-test.properties |  3 +
 fineract-war/setenv.sh                             |  2 +
 12 files changed, 274 insertions(+), 5 deletions(-)

diff --git a/docker-compose-postgresql.yml b/docker-compose-postgresql.yml
index f5da1f263..217d77f17 100644
--- a/docker-compose-postgresql.yml
+++ b/docker-compose-postgresql.yml
@@ -94,4 +94,7 @@ services:
       - FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default
       - FINERACT_DEFAULT_TENANTDB_NAME=fineract_default
       - FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant
+      - FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED=false
+      - FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME=X-Correlation-ID
+      - CONSOLE_LOG_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} %thread 
%replace([%X{correlationId}]){'\[\]', ''} [%-5level] %class{0} - %msg%n
       - JAVA_TOOL_OPTIONS="-Xmx1G"
diff --git a/docker-compose.yml b/docker-compose.yml
index 08e0cc6b5..398e0b0d2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -90,4 +90,8 @@ services:
       - FINERACT_DEFAULT_TENANTDB_IDENTIFIER=default
       - FINERACT_DEFAULT_TENANTDB_NAME=fineract_default
       - FINERACT_DEFAULT_TENANTDB_DESCRIPTION=Default Demo Tenant
+      - FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED=false
+      - FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME=X-Correlation-ID
+      - CONSOLE_LOG_PATTERN=%d{yyyy-MM-dd HH:mm:ss.SSS} %thread 
%replace([%X{correlationId}]){'\[\]', ''} [%-5level] %class{0} - %msg%n
+      - FINERACT_LOGGING_LEVEL=warn
       - JAVA_TOOL_OPTIONS="-Xmx1G"
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CorrelationIdConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CorrelationIdConfig.java
new file mode 100644
index 000000000..d1ef8f354
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CorrelationIdConfig.java
@@ -0,0 +1,50 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.fineract.infrastructure.core.config;
+
+import java.util.Arrays;
+import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+@Configuration
+@ConditionalOnProperty("fineract.logging.http.correlation-id.enabled")
+public class CorrelationIdConfig implements EnvironmentAware {
+
+    private Environment environment;
+
+    @Override
+    public void setEnvironment(Environment environment) {
+        this.environment = environment;
+    }
+
+    @Bean
+    public FilterRegistrationBean<CorrelationHeaderFilter> 
correlationHeaderFilter() {
+        FilterRegistrationBean<CorrelationHeaderFilter> filterRegBean = new 
FilterRegistrationBean<CorrelationHeaderFilter>();
+        filterRegBean.setFilter(new CorrelationHeaderFilter(environment));
+        filterRegBean.setUrlPatterns(Arrays.asList("/*"));
+        return filterRegBean;
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
index 10c447990..fb888cb89 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
@@ -34,6 +34,8 @@ public class FineractProperties {
 
     private FineractModeProperties mode;
 
+    private FineractCorrelationProperties correlation;
+
     @Getter
     @Setter
     public static class FineractTenantProperties {
@@ -62,4 +64,12 @@ public class FineractProperties {
             return readEnabled && !writeEnabled && !batchWorkerEnabled && 
!batchManagerEnabled;
         }
     }
+
+    @Getter
+    @Setter
+    public static class FineractCorrelationProperties {
+
+        private boolean enabled;
+        private String headerName;
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java
new file mode 100644
index 000000000..495f872a1
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/filters/CorrelationHeaderFilter.java
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.fineract.infrastructure.core.filters;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import 
org.apache.fineract.infrastructure.security.utils.LogParameterEscapeUtil;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+@RequiredArgsConstructor
+@Slf4j
+public class CorrelationHeaderFilter extends OncePerRequestFilter {
+
+    private String correlationIdHeader;
+
+    public static final String correlationIdKey = "correlationId";
+
+    @Autowired
+    public CorrelationHeaderFilter(Environment env) {
+        correlationIdHeader = 
env.getRequiredProperty("fineract.logging.http.correlation-id.header-name");
+    }
+
+    @Override
+    protected void doFilterInternal(final HttpServletRequest request, final 
HttpServletResponse response, final FilterChain filterChain)
+            throws IOException, ServletException {
+
+        try {
+            final HttpServletRequest httpServletRequest = (HttpServletRequest) 
request;
+            String currentCorrId = 
httpServletRequest.getHeader(correlationIdHeader);
+            log.debug("Found correlationId in Header : {}", 
LogParameterEscapeUtil.escapeLogMDCParameter(currentCorrId));
+            MDC.put(correlationIdKey, currentCorrId);
+            filterChain.doFilter(request, response);
+        } finally {
+            MDC.remove(correlationIdKey);
+        }
+    }
+
+    public static String getCurrentValue() {
+        return MDC.get(correlationIdKey);
+    }
+
+    @Override
+    protected boolean isAsyncDispatch(final HttpServletRequest request) {
+        return false;
+    }
+
+    @Override
+    protected boolean shouldNotFilterErrorDispatch() {
+        return false;
+    }
+
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/MdcAdapter.java
similarity index 52%
copy from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/MdcAdapter.java
index 1e249f4f8..241aefdac 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/MdcAdapter.java
@@ -16,13 +16,42 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.security.utils;
 
-public final class LogParameterEscapeUtil {
+package org.apache.fineract.infrastructure.core.service;
 
-    private LogParameterEscapeUtil() {}
+import java.util.Map;
+import org.slf4j.MDC;
+import org.slf4j.spi.MDCAdapter;
 
-    public static String escapeLogParameter(String logParameter) {
-        return logParameter.replaceAll("[\n\r\t]", "_");
+public class MdcAdapter implements MDCAdapter {
+
+    @Override
+    public void put(String key, String val) {
+        MDC.put(key, val);
+    }
+
+    @Override
+    public String get(String key) {
+        return MDC.get(key);
+    }
+
+    @Override
+    public void remove(String key) {
+        MDC.remove(key);
+    }
+
+    @Override
+    public void clear() {
+        MDC.clear();
+    }
+
+    @Override
+    public Map<String, String> getCopyOfContextMap() {
+        return MDC.getCopyOfContextMap();
+    }
+
+    @Override
+    public void setContextMap(Map<String, String> map) {
+        MDC.setContextMap(map);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
index 1e249f4f8..8eb145f80 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/utils/LogParameterEscapeUtil.java
@@ -25,4 +25,8 @@ public final class LogParameterEscapeUtil {
     public static String escapeLogParameter(String logParameter) {
         return logParameter.replaceAll("[\n\r\t]", "_");
     }
+
+    public static String escapeLogMDCParameter(String logParameter) {
+        return logParameter.replaceAll("[\r\n]", "");
+    }
 }
diff --git a/fineract-provider/src/main/resources/application.properties 
b/fineract-provider/src/main/resources/application.properties
index e939a8642..3bf753e0a 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -40,6 +40,12 @@ 
fineract.mode.write-enabled=${FINERACT_MODE_WRITE_ENABLED:true}
 fineract.mode.batch-worker-enabled=${FINERACT_MODE_BATCH_WORKER_ENABLED:true}
 fineract.mode.batch-manager-enabled=${FINERACT_MODE_BATCH_MANAGER_ENABLED:true}
 
+fineract.logging.http.correlation-id.enabled=${FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED:false}
+fineract.logging.http.correlation-id.header-name=${FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME:X-Correlation-ID}
+
+# Logging pattern for the console
+logging.pattern.console=${CONSOLE_LOG_PATTERN:%d{yyyy-MM-dd HH\:mm\:ss.SSS} 
%thread %replace([%X{correlationId}]){'\\[\\]', ''} [%-5level] %class{0} - 
%msg%n}
+
 management.health.jms.enabled=false
 
 # FINERACT 1296
diff --git a/fineract-provider/src/main/resources/logback-spring.xml 
b/fineract-provider/src/main/resources/logback-spring.xml
index a24143495..8ede0e3e3 100644
--- a/fineract-provider/src/main/resources/logback-spring.xml
+++ b/fineract-provider/src/main/resources/logback-spring.xml
@@ -39,5 +39,6 @@
     <!-- But these three INFO are still handy ;-) just to see when it's up and 
running -->
     <logger 
name="org.springframework.boot.web.embedded.tomcat.TomcatWebServer" 
level="info" />
     <logger name="org.apache.fineract.ServerApplication" level="info" />
+    <logger name="org.apache.fineract" level="${FINERACT_LOGGING_LEVEL:-INFO}" 
/>
     <logger name="liquibase" level="info" />
 </configuration>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/filters/FineractCorrelationIdApiFilterTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/filters/FineractCorrelationIdApiFilterTest.java
new file mode 100644
index 000000000..b042b5928
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/filters/FineractCorrelationIdApiFilterTest.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.fineract.infrastructure.core.filters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.UUID;
+import org.apache.fineract.infrastructure.core.service.MdcAdapter;
+import org.junit.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+@ContextConfiguration(classes = { Configuration.class })
+@WebMvcTest
+class FineractCorrelationIdApiFilterTest {
+
+    @SpyBean
+    private MdcAdapter mdc;
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @ParameterizedTest
+    @ValueSource(strings = { "/fineract-provider/api/v1/loans", 
"/fineract-provider/api/v1/loans" })
+    void 
shouldGet200IfXCorrelationIdHeaderIsPresentAndRequestIsForV1Path(String url) 
throws Exception {
+        String correlationId = UUID.randomUUID().toString();
+        
mockMvc.perform(get(url).header(CorrelationHeaderFilter.correlationIdKey, 
correlationId)).andExpect(status().isOk())
+                
.andExpect(header().string(CorrelationHeaderFilter.correlationIdKey, 
correlationId));
+
+        verify(mdc).remove(CorrelationHeaderFilter.correlationIdKey);
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = { "/fineract-provider/api/v1/loans", 
"/fineract-provider/api/v1/loans" })
+    void 
shouldGet400IfXCorrelationIdHeaderIsNotPresentAndRequestIsForV1Path(String url) 
throws Exception {
+        mockMvc.perform(get(url)).andExpect(status().isBadRequest())
+                
.andExpect(header().doesNotExist(CorrelationHeaderFilter.correlationIdKey));
+    }
+
+    @Test
+    void shouldReturnCurrentCorrelationIdFromMDC() {
+        MDC.put(CorrelationHeaderFilter.correlationIdKey, "1");
+        assertThat(CorrelationHeaderFilter.getCurrentValue()).isEqualTo("1");
+    }
+
+}
diff --git a/fineract-provider/src/test/resources/application-test.properties 
b/fineract-provider/src/test/resources/application-test.properties
index 58981db96..54deaac7d 100644
--- a/fineract-provider/src/test/resources/application-test.properties
+++ b/fineract-provider/src/test/resources/application-test.properties
@@ -37,6 +37,9 @@ fineract.mode.read-enabled=true
 fineract.mode.write-enabled=true
 fineract.mode.batch-enabled=true
 
+fineract.logging.http.correlation-id.enabled=false
+fineract.logging.http.correlation-id.header-name=X-Correlation-ID
+
 management.health.jms.enabled=false
 
 # FINERACT 1296
diff --git a/fineract-war/setenv.sh b/fineract-war/setenv.sh
index 5d69b5aba..531936710 100644
--- a/fineract-war/setenv.sh
+++ b/fineract-war/setenv.sh
@@ -53,3 +53,5 @@ export FINERACT_DEFAULT_TENANTDB_TIMEZONE="Asia/Kolkata"
 export FINERACT_DEFAULT_TENANTDB_IDENTIFIER="default"
 export FINERACT_DEFAULT_TENANTDB_NAME="fineract_default"
 export FINERACT_DEFAULT_TENANTDB_DESCRIPTION="Default Demo Tenant"
+export FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED="false"
+export FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME="X-Correlation-ID"

Reply via email to