Skip to content

Instantly share code, notes, and snippets.

@michael-pratt
Last active June 25, 2025 14:59
Show Gist options
  • Select an option

  • Save michael-pratt/89eb8800be8ad47e79fe9edab8945c69 to your computer and use it in GitHub Desktop.

Select an option

Save michael-pratt/89eb8800be8ad47e79fe9edab8945c69 to your computer and use it in GitHub Desktop.

Revisions

  1. michael-pratt revised this gist Jan 16, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions RequestAndResponseLoggingFilter.java
    Original file line number Diff line number Diff line change
    @@ -85,14 +85,14 @@ protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCach
    }

    protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, StringBuilder msg) {
    if (log.isInfoEnabled()) {
    if (enabled && log.isInfoEnabled()) {
    msg.append("\n-- REQUEST --\n");
    logRequestHeader(request, request.getRemoteAddr() + "|>", msg);
    }
    }

    protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, StringBuilder msg) {
    if (log.isInfoEnabled()) {
    if (enabled && log.isInfoEnabled()) {
    logRequestBody(request, request.getRemoteAddr() + "|>", msg);
    msg.append("\n-- RESPONSE --\n");
    logResponse(response, request.getRemoteAddr() + "|<", msg);
  2. michael-pratt revised this gist Apr 16, 2019. 1 changed file with 15 additions and 0 deletions.
    15 changes: 15 additions & 0 deletions RequestAndResponseLoggingFilter.java
    Original file line number Diff line number Diff line change
    @@ -6,6 +6,8 @@
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.jmx.export.annotation.ManagedOperation;
    import org.springframework.jmx.export.annotation.ManagedResource;
    import org.springframework.web.filter.OncePerRequestFilter;
    import org.springframework.web.util.ContentCachingRequestWrapper;
    import org.springframework.web.util.ContentCachingResponseWrapper;
    @@ -21,6 +23,7 @@
    import java.util.List;
    import java.util.stream.Stream;

    @ManagedResource
    public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {

    private final static Logger log = LoggerFactory.getLogger(RequestAndResponseLoggingFilter.class);
    @@ -42,6 +45,18 @@ public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {
    "authorization",
    "proxy-authorization"
    );

    private boolean enabled = true;

    @ManagedOperation(description = "Enable logging of HTTP requests and responses")
    public void enable() {
    this.enabled = true;
    }

    @ManagedOperation(description = "Disable logging of HTTP requests and responses")
    public void disable() {
    this.enabled = false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  3. michael-pratt created this gist Apr 16, 2019.
    176 changes: 176 additions & 0 deletions RequestAndResponseLoggingFilter.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,176 @@
    // Adapted from https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

    package com.elvtn.logging;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.web.filter.OncePerRequestFilter;
    import org.springframework.web.util.ContentCachingRequestWrapper;
    import org.springframework.web.util.ContentCachingResponseWrapper;

    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    import java.util.stream.Stream;

    public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {

    private final static Logger log = LoggerFactory.getLogger(RequestAndResponseLoggingFilter.class);

    private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
    MediaType.valueOf("text/*"),
    MediaType.APPLICATION_FORM_URLENCODED,
    MediaType.APPLICATION_JSON,
    MediaType.APPLICATION_XML,
    MediaType.valueOf("application/*+json"),
    MediaType.valueOf("application/*+xml"),
    MediaType.MULTIPART_FORM_DATA
    );

    /**
    * List of HTTP headers whose values should not be logged.
    */
    private static final List<String> SENSITIVE_HEADERS = Arrays.asList(
    "authorization",
    "proxy-authorization"
    );

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    if (isAsyncDispatch(request)) {
    filterChain.doFilter(request, response);
    } else {
    doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
    }
    }

    protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {

    StringBuilder msg = new StringBuilder();

    try {
    beforeRequest(request, response, msg);
    filterChain.doFilter(request, response);
    }
    finally {
    afterRequest(request, response, msg);
    if(log.isInfoEnabled()) {
    log.info(msg.toString());
    }
    response.copyBodyToResponse();
    }
    }

    protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, StringBuilder msg) {
    if (log.isInfoEnabled()) {
    msg.append("\n-- REQUEST --\n");
    logRequestHeader(request, request.getRemoteAddr() + "|>", msg);
    }
    }

    protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, StringBuilder msg) {
    if (log.isInfoEnabled()) {
    logRequestBody(request, request.getRemoteAddr() + "|>", msg);
    msg.append("\n-- RESPONSE --\n");
    logResponse(response, request.getRemoteAddr() + "|<", msg);
    }
    }

    private static void logRequestHeader(ContentCachingRequestWrapper request, String prefix, StringBuilder msg) {
    String queryString = request.getQueryString();
    if (queryString == null) {
    msg.append(String.format("%s %s %s", prefix, request.getMethod(), request.getRequestURI())).append("\n");
    } else {
    msg.append(String.format("%s %s %s?%s", prefix, request.getMethod(), request.getRequestURI(), queryString)).append("\n");
    }
    Collections.list(request.getHeaderNames())
    .forEach(headerName ->
    Collections.list(request.getHeaders(headerName))
    .forEach(headerValue -> {
    if(isSensitiveHeader(headerName)) {
    msg.append(String.format("%s %s: %s", prefix, headerName, "*******")).append("\n");
    }
    else {
    msg.append(String.format("%s %s: %s", prefix, headerName, headerValue)).append("\n");
    }
    }));
    msg.append(prefix).append("\n");
    }

    private static void logRequestBody(ContentCachingRequestWrapper request, String prefix, StringBuilder msg) {
    byte[] content = request.getContentAsByteArray();
    if (content.length > 0) {
    logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix, msg);
    }
    }

    private static void logResponse(ContentCachingResponseWrapper response, String prefix, StringBuilder msg) {
    int status = response.getStatus();
    msg.append(String.format("%s %s %s", prefix, status, HttpStatus.valueOf(status).getReasonPhrase())).append("\n");
    response.getHeaderNames()
    .forEach(headerName ->
    response.getHeaders(headerName)
    .forEach(headerValue ->
    {
    if(isSensitiveHeader(headerName)) {
    msg.append(String.format("%s %s: %s", prefix, headerName, "*******")).append("\n");
    }
    else {
    msg.append(String.format("%s %s: %s", prefix, headerName, headerValue)).append("\n");
    }
    }));
    msg.append(prefix).append("\n");
    byte[] content = response.getContentAsByteArray();
    if (content.length > 0) {
    logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix, msg);
    }
    }

    private static void logContent(byte[] content, String contentType, String contentEncoding, String prefix, StringBuilder msg) {
    MediaType mediaType = MediaType.valueOf(contentType);
    boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
    if (visible) {
    try {
    String contentString = new String(content, contentEncoding);
    Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> msg.append(prefix).append(" ").append(line).append("\n"));
    } catch (UnsupportedEncodingException e) {
    msg.append(String.format("%s [%d bytes content]", prefix, content.length)).append("\n");
    }
    } else {
    msg.append(String.format("%s [%d bytes content]", prefix, content.length)).append("\n");
    }
    }

    /**
    * Determine if a given header name should have its value logged.
    * @param headerName HTTP header name.
    * @return True if the header is sensitive (i.e. its value should <b>not</b> be logged).
    */
    private static boolean isSensitiveHeader(String headerName) {
    return SENSITIVE_HEADERS.contains(headerName.toLowerCase());
    }

    private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
    if (request instanceof ContentCachingRequestWrapper) {
    return (ContentCachingRequestWrapper) request;
    } else {
    return new ContentCachingRequestWrapper(request);
    }
    }

    private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
    if (response instanceof ContentCachingResponseWrapper) {
    return (ContentCachingResponseWrapper) response;
    } else {
    return new ContentCachingResponseWrapper(response);
    }
    }
    }