Skip to content

Commit

Permalink
Merge develop (#13)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
spavunc authored Oct 19, 2024
1 parent 1c38706 commit 0107ff7
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 25 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
<version>3.1.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.simple</groupId>
<groupId>com.simpleLogger</groupId>
<artifactId>logging</artifactId>
<version>0.8.0</version>
<version>0.8.1</version>
<name>logging</name>
<description>Simple Logging for Spring Boot</description>
<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -97,7 +107,7 @@ public ServletRegistrationBean<DispatcherServlet> dispatcherRegistration() {
*/
@Bean(name = "loggingDispatcherServlet")
public DispatcherServlet dispatcherServlet() {
return new LoggableDispatcherServlet(maxStringSizeMb, maxCacheHistoryLogs);
return new LoggableDispatcherServlet(maxStringSizeMb, maxCacheHistoryLogs, charset);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -10,20 +12,40 @@ public class CustomLogProperties {
}

private static final ThreadLocal<Map<String, String>> customProperties = ThreadLocal.withInitial(HashMap::new);
private static final ThreadLocal<List<String>> 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<String, String> properties) {
customProperties.set(properties);
}

public static String getCustomProperty(String key) {
return customProperties.get().get(key);
}

public static Map<String, String> getProperties() {
public static Map<String, String> 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<String> getIgnoredProperties() {
return new ArrayList<>(ignoredProperties.get());
}

public static void clearIgnoredProperties() {
ignoredProperties.remove();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,16 +33,19 @@
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.
*
* @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;
}

/**
Expand All @@ -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);
}
Expand All @@ -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();
}
}

Expand All @@ -83,7 +90,7 @@ private void log(HttpServletRequest requestToCache, HttpServletResponse response
HandlerExecutionChain handler) {

String uuid = UUID.randomUUID().toString();
Map<String, String> customProperties = CustomLogProperties.getProperties();
Map<String, String> customProperties = LogUtility.getCustomLogPropertiesWithoutIgnored();

Payload log = Payload.builder()
.httpMethod(requestToCache.getMethod())
Expand Down Expand Up @@ -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!");
}
}

Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/simple/logging/application/utility/Log.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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.
* <p>
* 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.
* </p>
*
* @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<String, Object> 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.
*
* <p>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<String, String> getCustomLogPropertiesWithoutIgnored() {
Map<String, String> customLogProps = CustomLogProperties.getCustomProperties();
for(String propertyName : CustomLogProperties.getIgnoredProperties()) {
customLogProps.remove(propertyName);
}
return customLogProps;
}
}
78 changes: 78 additions & 0 deletions src/test/java/CustomLogPropertiesTest.java
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> 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<String> 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());
}
}
Loading

0 comments on commit 0107ff7

Please sign in to comment.