Skip to content

Commit

Permalink
feat: add incoming request logging filter
Browse files Browse the repository at this point in the history
  • Loading branch information
mcoburnmx committed Oct 8, 2024
1 parent f55185d commit 4c392fc
Show file tree
Hide file tree
Showing 3 changed files with 430 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

public class FilterOrderSequence {
public static final int REQUEST_CONTEXT_FILTER = 1;
public static final int ERROR_FILTER = 2;
public static final int HMAC_FILTER = 3;
public static final int SESSION_FILTER = 4;
public static final int AUTH_FILTER = 5;
public static final int REPO_FILTER = 6;
public static final int REQUEST_LOGGING_FILTER = 2;
public static final int ERROR_FILTER = 3;
public static final int HMAC_FILTER = 4;
public static final int SESSION_FILTER = 5;
public static final int AUTH_FILTER = 6;
public static final int REPO_FILTER = 7;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package com.mx.path.model.mdx.web.filter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mx.path.core.common.security.LogValueMasker;
import com.mx.path.core.context.RequestContext;
import com.mx.path.gateway.util.LoggingExceptionFormatter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

/**
* Request logging filter
* <p>
* This filter logs incoming requests.
* These values are inserted into the MDC.
*/
@Component
@Order(FilterOrderSequence.REQUEST_LOGGING_FILTER)
public class PathRequestLoggingFilter extends OncePerRequestFilter {

// Statics
private static final int CONTENT_CACHE_LIMIT_BYTES = 1024 * 1024;
private static final Gson GSON;
private static final LogValueMasker LOG_MASKER;
private static Logger logger;

public static void setLogger(Logger logger) {
PathRequestLoggingFilter.logger = logger;
}

public static void resetLogger() {
logger = LoggerFactory.getLogger(PathRequestLoggingFilter.class);
}

// Protected
@Override
protected final void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request, CONTENT_CACHE_LIMIT_BYTES);
final ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
final long requestStartTimeMillis = System.currentTimeMillis();

try {
filterChain.doFilter(requestWrapper, responseWrapper);
} finally {
try {
this.logRequest(requestWrapper, responseWrapper, requestStartTimeMillis);
} catch (Exception ex) {
MDC.put("exception", LoggingExceptionFormatter.formatLoggingExceptionWithStacktrace(ex));
logger.error("Failed to log incoming request", ex);
} finally {
resetMDC();
}
}
}

@SuppressWarnings("PMD.CyclomaticComplexity")
private void logRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response,
long requestStartTimeMillis) throws IOException {
final long elapsedTime = System.currentTimeMillis() - requestStartTimeMillis;
final RequestContext requestContext = RequestContext.current();

MDC.put("log_guid", UUID.randomUUID().toString());
MDC.put("client_guid", requestContext.getClientGuid());
MDC.put("client_id", requestContext.getClientId());
MDC.put("path", requestContext.getPath());

if (requestContext.getUserGuid() != null) {
MDC.put("user_guid", requestContext.getUserGuid());
} else {
MDC.remove("user_guid");
}

if (requestContext.getFeature() != null) {
MDC.put("feature", requestContext.getFeature());
} else {
MDC.remove("feature");
}

if (requestContext.getOriginatingIP() != null) {
MDC.put("ip_address", requestContext.getOriginatingIP());
} else {
MDC.remove("ip_address");
}

if (requestContext.getSessionTraceId() != null) {
MDC.put("session_trace_id", requestContext.getSessionTraceId());
} else {
MDC.remove("session_trace_id");
}

if (requestContext.getDeviceTraceId() != null) {
MDC.put("device_trace_id", requestContext.getDeviceTraceId());
} else {
MDC.remove("device_trace_id");
}

MDC.put("request_method", request.getMethod());
MDC.put("request_uri", request.getRequestURI());

if (request.getQueryString() != null) {
final Map<String, String> queryParams = this.buildQueryStringMap(request.getQueryString());
MDC.put("query_params", this.buildHeaderString(this.maskHeaders(queryParams)));
} else {
MDC.remove("query_params");
}

if (request.getHeaderNames() != null) {
final Map<String, String> requestHeaders = this.buildRequestHeadersMap(request);
final Map<String, String> maskedRequestHeaders = this.maskHeaders(requestHeaders);
MDC.put("request_headers_json", GSON.toJson(maskedRequestHeaders));
MDC.put("request_headers", this.buildHeaderString(maskedRequestHeaders));
} else {
MDC.remove("request_headers_json");
MDC.remove("request_headers");
}

final String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8);
if (!requestBody.isEmpty()) {
MDC.put("request_body", LOG_MASKER.maskPayload(requestBody));
} else {
MDC.remove("request_body");
}

MDC.put("request_duration", String.valueOf(elapsedTime));
MDC.put("status", String.valueOf(response.getStatus()));

if (response.getHeaderNames() != null) {
final Map<String, String> responseHeaders = this.buildResponseHeadersMap(response);
final Map<String, String> maskedResponseHeaders = this.maskHeaders(responseHeaders);

maskedResponseHeaders.put("Content-Type", response.getContentType());

MDC.put("response_headers_json", GSON.toJson(maskedResponseHeaders));
MDC.put("response_headers", this.buildHeaderString(maskedResponseHeaders));
} else {
MDC.remove("response_headers_json");
MDC.remove("response_headers");
}

final String responseBody = new String(response.getContentAsByteArray(), StandardCharsets.UTF_8);
if (!responseBody.isEmpty()) {
MDC.put("response_body", LOG_MASKER.maskPayload(responseBody));
} else {
MDC.remove("response_body");
}
response.copyBodyToResponse();

logger.info("Incoming request");
}

private void resetMDC() {
MDC.remove("client_guid");
MDC.remove("client_id");
MDC.remove("device_trace_id");
MDC.remove("exception");
MDC.remove("feature");
MDC.remove("ip_address");
MDC.remove("log_guid");
MDC.remove("path");
MDC.remove("query_params");
MDC.remove("request_body");
MDC.remove("request_duration");
MDC.remove("request_headers");
MDC.remove("request_headers_json");
MDC.remove("request_method");
MDC.remove("response_body");
MDC.remove("response_headers");
MDC.remove("response_headers_json");
MDC.remove("session_trace_id");
MDC.remove("status");
MDC.remove("user_guid");
}

private Map<String, String> buildRequestHeadersMap(HttpServletRequest request) {
final Enumeration<String> headerNames = request.getHeaderNames();
final Map<String, String> headerMap = new HashMap<>();

if (headerNames == null) {
return headerMap;
}

while (headerNames.hasMoreElements()) {
final String headerName = headerNames.nextElement();
final String headerValue = request.getHeader(headerName);
headerMap.put(headerName, headerValue);
}

return headerMap;
}

private Map<String, String> buildResponseHeadersMap(HttpServletResponse response) {
final Collection<String> headerNames = response.getHeaderNames();
final Map<String, String> headerMap = new HashMap<>();

if (headerNames == null) {
return headerMap;
}

for (String headerName : headerNames) {
final Collection<String> headerValues = response.getHeaders(headerName);
String headerValuesFlattened = "";

if (headerValues != null) {
final List<String> headerValuesList = new ArrayList<>(headerValues);
headerValuesFlattened = String.join(", ", headerValuesList);
}

headerMap.put(headerName, headerValuesFlattened);
}

return headerMap;
}

private Map<String, String> buildQueryStringMap(String queryString) {
final Map<String, String> querystringMap = new HashMap<>();
final String[] parameters = queryString.split("&");

for (String parameter : parameters) {
final String[] keyValuePair = parameter.split("=");
if (keyValuePair.length == 2) {
querystringMap.put(keyValuePair[0], keyValuePair[1]);
}
}

return querystringMap;
}

private String buildHeaderString(Map<String, String> headers) {
final StringBuilder headerStr = new StringBuilder();
headers.forEach((name, value) -> {
headerStr.append(name);
headerStr.append(": ");
headerStr.append(value);
headerStr.append("\n");
});
return headerStr.toString();
}

private Map<String, String> maskHeaders(Map<String, String> headers) {
if (headers == null) {
return null;
}

final Map<String, String> maskedHeaders = new HashMap<>();
headers.forEach((name, value) -> {
maskedHeaders.put(name, LOG_MASKER.maskHeaderValue(name, value));
});
return maskedHeaders;
}

static {
GSON = new GsonBuilder().disableHtmlEscaping().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create();
LOG_MASKER = new LogValueMasker();
logger = LoggerFactory.getLogger(PathRequestLoggingFilter.class);
}
}
Loading

0 comments on commit 4c392fc

Please sign in to comment.