From 0107ff7506b8d06bd3547a049868060d089a4c4e Mon Sep 17 00:00:00 2001 From: Sebastian Pavunc <61882790+spavunc@users.noreply.github.com> Date: Sat, 19 Oct 2024 21:28:55 +0200 Subject: [PATCH] Merge develop (#13) * Added property ignoring, fixed charset mapping for payloads (#12) * Added property ignoring, fixed charset mapping for payloads * Added check for ignored properties in custom log properties * fixed a critical bug eating responses --- pom.xml | 4 +- .../SimpleLoggingConfiguration.java | 16 +++- .../model/CustomLogProperties.java | 30 ++++++- .../servlet/LoggableDispatcherServlet.java | 32 ++++---- .../logging/application/utility/Log.java | 6 +- .../application/utility/LogUtility.java | 47 +++++++++++ src/test/java/CustomLogPropertiesTest.java | 78 +++++++++++++++++++ src/test/java/LogUtilityTest.java | 45 +++++++++++ 8 files changed, 233 insertions(+), 25 deletions(-) create mode 100644 src/test/java/CustomLogPropertiesTest.java diff --git a/pom.xml b/pom.xml index 6701228..4b9f25b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,9 +8,9 @@ 3.1.4 - com.simple + com.simpleLogger logging - 0.8.0 + 0.8.1 logging Simple Logging for Spring Boot diff --git a/src/main/java/com/simple/logging/application/configuration/SimpleLoggingConfiguration.java b/src/main/java/com/simple/logging/application/configuration/SimpleLoggingConfiguration.java index 75f1ce4..4542fc3 100644 --- a/src/main/java/com/simple/logging/application/configuration/SimpleLoggingConfiguration.java +++ b/src/main/java/com/simple/logging/application/configuration/SimpleLoggingConfiguration.java @@ -10,6 +10,10 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; + /** * SimpleLoggingConfiguration is a configuration class that sets up a logging dispatcher servlet * with configurable parameters for logging file size, string size, file path, charset, and cache history logs. @@ -20,7 +24,7 @@ public class SimpleLoggingConfiguration implements WebMvcConfigurer { private final Integer maxFileSizeMb; private final Integer maxStringSizeMb; private final String logFilePath; - private final String charset; + private final Charset charset; private final Integer maxCacheHistoryLogs; private final Integer logRetentionLengthInDays; private final String logDeletionCronScheduler; @@ -67,7 +71,13 @@ public SimpleLoggingConfiguration(@Value("${maxFileSizeMb}") Integer maxFileSize this.maxFileSizeMb = maxFileSizeMb; this.maxStringSizeMb = maxStringSizeMb; this.logFilePath = logFilePath; - this.charset = charset; + Charset tempCharset; + try { + tempCharset = Charset.forName(charset); + } catch (UnsupportedCharsetException ex) { + tempCharset = StandardCharsets.UTF_8; + } + this.charset = tempCharset; this.maxCacheHistoryLogs = maxCacheHistoryLogs; this.logRetentionLengthInDays = logRetentionLengthInDays; this.logDeletionCronScheduler = logDeletionCronScheduler; @@ -97,7 +107,7 @@ public ServletRegistrationBean dispatcherRegistration() { */ @Bean(name = "loggingDispatcherServlet") public DispatcherServlet dispatcherServlet() { - return new LoggableDispatcherServlet(maxStringSizeMb, maxCacheHistoryLogs); + return new LoggableDispatcherServlet(maxStringSizeMb, maxCacheHistoryLogs, charset); } /** diff --git a/src/main/java/com/simple/logging/application/model/CustomLogProperties.java b/src/main/java/com/simple/logging/application/model/CustomLogProperties.java index b59bf7c..c6286d4 100644 --- a/src/main/java/com/simple/logging/application/model/CustomLogProperties.java +++ b/src/main/java/com/simple/logging/application/model/CustomLogProperties.java @@ -1,6 +1,8 @@ package com.simple.logging.application.model; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class CustomLogProperties { @@ -10,20 +12,40 @@ public class CustomLogProperties { } private static final ThreadLocal> customProperties = ThreadLocal.withInitial(HashMap::new); + private static final ThreadLocal> ignoredProperties = ThreadLocal.withInitial( + ArrayList::new); - public static void setProperty(String key, String value) { + public static void setCustomProperty(String key, String value) { customProperties.get().put(key, value); } - public static String getProperty(String key) { + public static void setCustomProperties(Map properties) { + customProperties.set(properties); + } + + public static String getCustomProperty(String key) { return customProperties.get().get(key); } - public static Map getProperties() { + public static Map getCustomProperties() { return new HashMap<>(customProperties.get()); } - public static void clear() { + public static void clearCustomProperties() { customProperties.remove(); } + + public static void addIgnoredProperty(String prop) { + ignoredProperties.get().add(prop); + } + + public static List getIgnoredProperties() { + return new ArrayList<>(ignoredProperties.get()); + } + + public static void clearIgnoredProperties() { + ignoredProperties.remove(); + } + + } diff --git a/src/main/java/com/simple/logging/application/servlet/LoggableDispatcherServlet.java b/src/main/java/com/simple/logging/application/servlet/LoggableDispatcherServlet.java index cd248ca..f96b1a5 100644 --- a/src/main/java/com/simple/logging/application/servlet/LoggableDispatcherServlet.java +++ b/src/main/java/com/simple/logging/application/servlet/LoggableDispatcherServlet.java @@ -16,7 +16,8 @@ import org.springframework.web.util.ContentCachingResponseWrapper; import org.springframework.web.util.WebUtils; -import java.nio.charset.StandardCharsets; +import java.io.IOException; +import java.nio.charset.Charset; import java.time.LocalDateTime; import java.util.Map; import java.util.Optional; @@ -32,6 +33,7 @@ public class LoggableDispatcherServlet extends DispatcherServlet { private final Integer maxStringSizeMb; private final Integer maxCacheHistoryLogs; + private final transient Charset charset; /** * Constructs a new LoggableDispatcherServlet with specified logging configurations. @@ -39,9 +41,11 @@ public class LoggableDispatcherServlet extends DispatcherServlet { * @param maxStringSizeMb the maximum size of the request/response body to be logged in megabytes. * @param maxCacheHistoryLogs the maximum number of logs to be cached in memory. */ - public LoggableDispatcherServlet(Integer maxStringSizeMb, Integer maxCacheHistoryLogs) { + public LoggableDispatcherServlet(Integer maxStringSizeMb, Integer maxCacheHistoryLogs, + Charset charset) { this.maxStringSizeMb = maxStringSizeMb * 1024 * 1024; this.maxCacheHistoryLogs = maxCacheHistoryLogs; + this.charset = charset; } /** @@ -51,8 +55,8 @@ public LoggableDispatcherServlet(Integer maxStringSizeMb, Integer maxCacheHistor * @param response the HTTP response. */ @Override - protected void doDispatch(@NotNull HttpServletRequest request, - @NotNull HttpServletResponse response) { + protected void doDispatch(HttpServletRequest request, + HttpServletResponse response) throws IOException { if (!(request instanceof ContentCachingRequestWrapper)) { request = new ContentCachingRequestWrapper(request); } @@ -67,8 +71,11 @@ protected void doDispatch(@NotNull HttpServletRequest request, } catch (Exception ex) { Log.error(String.format("Exception occurred - %s", ex.getMessage()), ex); } finally { + // Copy response content to the original response + ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response; + responseWrapper.copyBodyToResponse(); // This is critical // Clear custom properties after logging - CustomLogProperties.clear(); + CustomLogProperties.clearCustomProperties(); } } @@ -83,7 +90,7 @@ private void log(HttpServletRequest requestToCache, HttpServletResponse response HandlerExecutionChain handler) { String uuid = UUID.randomUUID().toString(); - Map customProperties = CustomLogProperties.getProperties(); + Map customProperties = LogUtility.getCustomLogPropertiesWithoutIgnored(); Payload log = Payload.builder() .httpMethod(requestToCache.getMethod()) @@ -153,21 +160,21 @@ private void getResponsePayload(HttpServletResponse response, Payload log) { */ private void printWrapper(byte[] byteArray, boolean isPayloadResponse, Payload log) { if (byteArray.length < maxStringSizeMb) { - String jsonStringFromByteArray = new String(byteArray, StandardCharsets.UTF_8); + String jsonString = LogUtility.removeIgnoredPropertiesFromJson(new String(byteArray, charset)); String payloadMarker = isPayloadResponse ? log.getUuid() + " RESPONSE BODY: {}" : log.getUuid() + " REQUEST BODY: {}"; - if (!jsonStringFromByteArray.isBlank()) { - Log.info(payloadMarker, LogUtility.minifyJsonString(jsonStringFromByteArray)); + if (!jsonString.isBlank()) { + Log.info(payloadMarker, LogUtility.minifyJsonString(jsonString)); } // Check whether the payload is a response or a request if (isPayloadResponse) { - log.setResponseBody(LogUtility.minifyJsonString(jsonStringFromByteArray)); + log.setResponseBody(LogUtility.minifyJsonString(jsonString)); } else { - log.setRequestBody(LogUtility.minifyJsonString(jsonStringFromByteArray)); + log.setRequestBody(LogUtility.minifyJsonString(jsonString)); } } else if (byteArray.length > maxStringSizeMb) { - Log.info("Content too long!"); + Log.warn("Content too long!"); } } @@ -181,7 +188,6 @@ private void printWrapper(byte[] byteArray, boolean isPayloadResponse, Payload l private boolean shouldLogRequest(HttpServletRequest request) throws Exception { HandlerExecutionChain handler = getHandler(request); if (handler != null && handler.getHandler() instanceof HandlerMethod handlerMethod) { - handlerMethod = (HandlerMethod) handler.getHandler(); if (handlerMethod.getBean().getClass().isAnnotationPresent(IgnoreLogging.class)) { return false; } else diff --git a/src/main/java/com/simple/logging/application/utility/Log.java b/src/main/java/com/simple/logging/application/utility/Log.java index fefb864..45671d2 100644 --- a/src/main/java/com/simple/logging/application/utility/Log.java +++ b/src/main/java/com/simple/logging/application/utility/Log.java @@ -17,7 +17,7 @@ public class Log { private final Integer maxFileSizeMb; private final String logFilePath; - private final String charset; + private final Charset charset; private final String applicationName; private final String loggingLevel; private final boolean logToConsole; @@ -34,7 +34,7 @@ public class Log { * @param maxBackupFiles maximum number of backup files. */ public Log(Integer maxFileSizeMb, String logFilePath, - String charset, String applicationName, + Charset charset, String applicationName, String loggingLevel, boolean logToConsole, Integer maxBackupFiles) { @@ -163,7 +163,7 @@ private void setupLogger() { try { // Create FileHandler with size limit and rotating file pattern FileHandler fileHandler = new CustomFileHandler(Paths.get(logFilePath), maxFileSizeMb, maxBackupFiles, - Charset.forName(charset), applicationName); + charset, applicationName); // Add the FileHandler to the logger. LOGGER.addHandler(fileHandler); setLoggingLevel(); diff --git a/src/main/java/com/simple/logging/application/utility/LogUtility.java b/src/main/java/com/simple/logging/application/utility/LogUtility.java index d2bc456..0285763 100644 --- a/src/main/java/com/simple/logging/application/utility/LogUtility.java +++ b/src/main/java/com/simple/logging/application/utility/LogUtility.java @@ -1,5 +1,7 @@ package com.simple.logging.application.utility; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.simple.logging.application.model.CustomLogProperties; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Getter; @@ -11,7 +13,9 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -350,4 +354,47 @@ public static String minifyJsonString(String jsonString) { return minifiedJson.toString(); } + + /** + * Removes properties specified in the ignored properties list from the given JSON string. + *

+ * This method parses the JSON string into a map, removes the properties listed in + * {@link CustomLogProperties#getIgnoredProperties()}, and then converts the map back to a JSON string. + * If an exception occurs during this process, the original JSON string is returned. + *

+ * + * @param jsonString the input JSON string from which properties need to be removed + * @return a new JSON string with the ignored properties removed, or the original JSON string if an error occurs + */ + public static String removeIgnoredPropertiesFromJson(String jsonString) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + Map map = objectMapper.readValue(jsonString, HashMap.class); + for(String propertyName : CustomLogProperties.getIgnoredProperties()) { + map.remove(propertyName); + } + return objectMapper.writeValueAsString(map); + } catch (Exception e) { + Log.error("Something went wrong when trying to remove ignored properties" + + " from the payload!"); + return jsonString; + } + } + + /** + * Retrieves custom log properties and removes any properties that are marked as ignored. + * + *

This method fetches a map of custom log properties and then removes entries + * whose keys are listed in the ignored properties set. The resulting map contains + * only those properties that are not ignored. + * + * @return a map containing the custom log properties with the ignored properties removed. + */ + public static Map getCustomLogPropertiesWithoutIgnored() { + Map customLogProps = CustomLogProperties.getCustomProperties(); + for(String propertyName : CustomLogProperties.getIgnoredProperties()) { + customLogProps.remove(propertyName); + } + return customLogProps; + } } diff --git a/src/test/java/CustomLogPropertiesTest.java b/src/test/java/CustomLogPropertiesTest.java new file mode 100644 index 0000000..d41e921 --- /dev/null +++ b/src/test/java/CustomLogPropertiesTest.java @@ -0,0 +1,78 @@ + +import com.simple.logging.LoggingApplication; +import com.simple.logging.application.model.CustomLogProperties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(classes = {LoggingApplication.class}) +class CustomLogPropertiesTest { + + @AfterEach + public void tearDown() { + CustomLogProperties.clearCustomProperties(); + CustomLogProperties.clearIgnoredProperties(); + } + + @Test + void testSetAndGetCustomProperty() { + CustomLogProperties.setCustomProperty("key1", "value1"); + assertEquals("value1", CustomLogProperties.getCustomProperty("key1")); + } + + @Test + void testSetCustomProperties() { + Map properties = new HashMap<>(); + properties.put("key1", "value1"); + properties.put("key2", "value2"); + CustomLogProperties.setCustomProperties(properties); + + assertEquals("value1", CustomLogProperties.getCustomProperty("key1")); + assertEquals("value2", CustomLogProperties.getCustomProperty("key2")); + } + + @Test + void testGetCustomProperties() { + CustomLogProperties.setCustomProperty("key1", "value1"); + CustomLogProperties.setCustomProperty("key2", "value2"); + + Map properties = CustomLogProperties.getCustomProperties(); + assertEquals(2, properties.size()); + assertEquals("value1", properties.get("key1")); + assertEquals("value2", properties.get("key2")); + } + + @Test + void testClearCustomProperties() { + CustomLogProperties.setCustomProperty("key1", "value1"); + CustomLogProperties.clearCustomProperties(); + + assertNull(CustomLogProperties.getCustomProperty("key1")); + assertTrue(CustomLogProperties.getCustomProperties().isEmpty()); + } + + @Test + void testAddAndGetIgnoredProperty() { + CustomLogProperties.addIgnoredProperty("prop1"); + CustomLogProperties.addIgnoredProperty("prop2"); + + List ignoredProperties = CustomLogProperties.getIgnoredProperties(); + assertEquals(2, ignoredProperties.size()); + assertTrue(ignoredProperties.contains("prop1")); + assertTrue(ignoredProperties.contains("prop2")); + } + + @Test + void testClearIgnoredProperties() { + CustomLogProperties.addIgnoredProperty("prop1"); + CustomLogProperties.clearIgnoredProperties(); + + assertTrue(CustomLogProperties.getIgnoredProperties().isEmpty()); + } +} diff --git a/src/test/java/LogUtilityTest.java b/src/test/java/LogUtilityTest.java index fa5fe91..86e599d 100644 --- a/src/test/java/LogUtilityTest.java +++ b/src/test/java/LogUtilityTest.java @@ -1,4 +1,6 @@ +import com.fasterxml.jackson.databind.ObjectMapper; import com.simple.logging.LoggingApplication; +import com.simple.logging.application.model.CustomLogProperties; import com.simple.logging.application.utility.LogUtility; import org.junit.jupiter.api.*; import org.junit.jupiter.api.io.TempDir; @@ -9,7 +11,10 @@ import java.nio.file.*; import java.time.LocalDate; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + import static org.junit.jupiter.api.Assertions.*; @SpringBootTest(classes = {LoggingApplication.class}) @@ -23,6 +28,11 @@ void setUp() { LogUtility.UtilityObjects.setObjects(tempDir.toString(), "testApp"); } + @AfterEach + public void tearDown() { + CustomLogProperties.clearIgnoredProperties(); + } + @Test void testMoveFile() throws IOException { Path sourceFile = Files.createFile(tempDir.resolve("source.log")); @@ -174,4 +184,39 @@ void testFindLogsForSpecificDateFailure() { assertEquals(expectedFiles, filteredLogFiles); } + + @Test + void testRemoveIgnoredPropertiesFromJson() throws Exception { + String jsonString = "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}"; + CustomLogProperties.addIgnoredProperty("key2"); + + String result = LogUtility.removeIgnoredPropertiesFromJson(jsonString); + + String expectedJsonString = "{\"key1\":\"value1\",\"key3\":\"value3\"}"; + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = objectMapper.readValue(result, HashMap.class); + Map expectedMap = objectMapper.readValue(expectedJsonString, HashMap.class); + assertEquals(expectedMap, resultMap); + } + + @Test + void testRemoveIgnoredPropertiesFromJsonWithNoIgnoredProperties() throws Exception { + String jsonString = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; + + String result = LogUtility.removeIgnoredPropertiesFromJson(jsonString); + + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = objectMapper.readValue(result, HashMap.class); + Map expectedMap = objectMapper.readValue(jsonString, HashMap.class); + assertEquals(expectedMap, resultMap); + } + + @Test + void testRemoveIgnoredPropertiesFromJsonWithException() { + String invalidJsonString = "{\"key1\":\"value1\",\"key2\":\"value2\""; // Invalid JSON + + String result = LogUtility.removeIgnoredPropertiesFromJson(invalidJsonString); + + assertEquals(invalidJsonString, result); + } } \ No newline at end of file