From f735a648140233dac86321aed27198894cd60bab Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Sun, 10 Jan 2016 15:06:29 +0100 Subject: [PATCH] Improved request logging --- .../webdav/filters/LoggingHttpFilter.java | 174 ++++++++++++++++++ .../filters/RecordingHttpServletRequest.java | 27 +++ .../filters/RecordingHttpServletResponse.java | 27 +++ .../filters/RecordingServletInputStream.java | 73 ++++++++ .../filters/RecordingServletOutputStream.java | 54 ++++++ .../FileSystemBasedWebDavServer.java | 2 + .../src/test/resources/log4j2.xml | 1 - 7 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/LoggingHttpFilter.java create mode 100644 main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java create mode 100644 main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java create mode 100644 main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java create mode 100644 main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/LoggingHttpFilter.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/LoggingHttpFilter.java new file mode 100644 index 000000000..4e76673c2 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/LoggingHttpFilter.java @@ -0,0 +1,174 @@ +package org.cryptomator.webdav.filters; + +import static java.lang.String.format; +import static java.util.Arrays.asList; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoggingHttpFilter implements HttpFilter { + + private static final Set METHODS_TO_LOG_DETAILED = methodsToLog(); + + private static final Set methodsToLog() { + String methodsToLog = System.getProperty("cryptomator.LoggingHttpFilter.methodsToLogDetailed"); + if (methodsToLog == null) { + return Collections.emptySet(); + } else { + return new HashSet<>(asList(methodsToLog.toUpperCase().split(","))); + } + } + + private final Logger LOG = LoggerFactory.getLogger(LoggingHttpFilter.class); + + @Override + public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + if (METHODS_TO_LOG_DETAILED.contains(request.getMethod().toUpperCase())) { + logDetailed(request, response, chain); + } else { + logBasic(request, response, chain); + } + } + + private void logBasic(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + Optional thrown = Optional.empty(); + try { + chain.doFilter(request, response); + } catch (IOException | ServletException e) { + thrown = Optional.of(e); + throw e; + } catch (RuntimeException | Error e) { + thrown = Optional.of(e); + throw e; + } finally { + if (thrown.isPresent()) { + logError(request, thrown.get()); + } else { + logSuccess(request, response); + } + } + } + + private void logDetailed(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + RecordingHttpServletRequest recordingRequest = new RecordingHttpServletRequest(request); + RecordingHttpServletResponse recordingResponse = new RecordingHttpServletResponse(response); + Optional thrown = Optional.empty(); + try { + chain.doFilter(recordingRequest, recordingResponse); + } catch (IOException | ServletException e) { + thrown = Optional.of(e); + throw e; + } catch (RuntimeException | Error e) { + thrown = Optional.of(e); + throw e; + } finally { + if (thrown.isPresent()) { + logError(recordingRequest, thrown.get()); + } else { + logSuccess(recordingRequest, recordingResponse); + } + } + } + + private void logSuccess(HttpServletRequest request, HttpServletResponse response) { + LOG.debug(format( + "## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "## Response ##\n" // + + "%s %s\n" // + + "%s\n", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request), // + request.getProtocol(), response.getStatus(), // + headers(response))); + } + + private void logError(HttpServletRequest request, Throwable throwable) { + LOG.error( + format("## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "%s\n\n", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request)), // + throwable); + } + + private void logSuccess(RecordingHttpServletRequest request, RecordingHttpServletResponse response) { + LOG.debug(format( + "## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "%s\n\n" // + + "## Response ##\n" // + + "%s %s\n" // + + "%s\n" // + + "%s", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request), // + new String(request.getRecording()), // + request.getProtocol(), response.getStatus(), // + headers(response), // + new String(response.getRecording()))); + } + + private void logError(RecordingHttpServletRequest request, Throwable throwable) { + LOG.error( + format("## Request ##\n" + // + "%s %s %s\n" // + + "%s\n" // + + "%s\n\n", // + request.getMethod(), request.getRequestURI(), request.getProtocol(), // + headers(request), // + new String(request.getRecording())), // + throwable); + } + + private String headers(HttpServletResponse response) { + StringBuilder result = new StringBuilder(); + for (String headerName : response.getHeaderNames()) { + for (String value : response.getHeaders(headerName)) { + result.append(headerName).append(": ").append(value).append('\n'); + } + } + return result.toString(); + } + + private String headers(HttpServletRequest request) { + StringBuilder result = new StringBuilder(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + Enumeration values = request.getHeaders(headerName); + while (values.hasMoreElements()) { + result.append(headerName).append(": ").append(values.nextElement()).append('\n'); + } + } + return result.toString(); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // empty + } + + @Override + public void destroy() { + // empty + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java new file mode 100644 index 000000000..dc4734dd3 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletRequest.java @@ -0,0 +1,27 @@ +package org.cryptomator.webdav.filters; + +import java.io.IOException; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +class RecordingHttpServletRequest extends HttpServletRequestWrapper { + + private final RecordingServletInputStream recording; + + public RecordingHttpServletRequest(HttpServletRequest request) throws IOException { + super(request); + recording = new RecordingServletInputStream(request.getInputStream()); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return recording; + } + + public byte[] getRecording() { + return recording.getRecording(); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java new file mode 100644 index 000000000..76444f2ab --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingHttpServletResponse.java @@ -0,0 +1,27 @@ +package org.cryptomator.webdav.filters; + +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +class RecordingHttpServletResponse extends HttpServletResponseWrapper { + + private final RecordingServletOutputStream recording; + + public RecordingHttpServletResponse(HttpServletResponse response) throws IOException { + super(response); + recording = new RecordingServletOutputStream(response.getOutputStream()); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return recording; + } + + public byte[] getRecording() { + return recording.getRecording(); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java new file mode 100644 index 000000000..8ed3b0c55 --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletInputStream.java @@ -0,0 +1,73 @@ +package org.cryptomator.webdav.filters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +import org.apache.commons.io.input.TeeInputStream; + +class RecordingServletInputStream extends ServletInputStream { + + private final ServletInputStream delegate; + private final TeeInputStream teeInputStream; + private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096); + + public RecordingServletInputStream(ServletInputStream delegate) { + this.delegate = delegate; + this.teeInputStream = new TeeInputStream(delegate, recording); + } + + public int read() throws IOException { + return teeInputStream.read(); + } + + public int read(byte[] b) throws IOException { + return teeInputStream.read(b); + } + + public int read(byte[] b, int off, int len) throws IOException { + return teeInputStream.read(b, off, len); + } + + public boolean isFinished() { + return delegate.isFinished(); + } + + public boolean isReady() { + return delegate.isReady(); + } + + public void setReadListener(ReadListener readListener) { + delegate.setReadListener(readListener); + } + + public long skip(long n) throws IOException { + return teeInputStream.skip(n); + } + + public int available() throws IOException { + return teeInputStream.available(); + } + + public void close() throws IOException { + teeInputStream.close(); + } + + public byte[] getRecording() { + return recording.toByteArray(); + } + + public void mark(int readlimit) { + } + + public void reset() throws IOException { + throw new IOException("Mark not supported"); + } + + public boolean markSupported() { + return false; + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java new file mode 100644 index 000000000..6c291163d --- /dev/null +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/filters/RecordingServletOutputStream.java @@ -0,0 +1,54 @@ +package org.cryptomator.webdav.filters; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +import org.apache.commons.io.output.TeeOutputStream; + +class RecordingServletOutputStream extends ServletOutputStream { + + private final ServletOutputStream delegate; + private final TeeOutputStream teeOutputStream; + private final ByteArrayOutputStream recording = new ByteArrayOutputStream(4096); + + public RecordingServletOutputStream(ServletOutputStream delegate) { + this.delegate = delegate; + this.teeOutputStream = new TeeOutputStream(delegate, recording); + } + + public void write(int b) throws IOException { + teeOutputStream.write(b); + } + + public void write(byte[] b) throws IOException { + teeOutputStream.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + teeOutputStream.write(b, off, len); + } + + public void flush() throws IOException { + teeOutputStream.flush(); + } + + public void close() throws IOException { + teeOutputStream.close(); + } + + public boolean isReady() { + return delegate.isReady(); + } + + public void setWriteListener(WriteListener writeListener) { + delegate.setWriteListener(writeListener); + } + + public byte[] getRecording() { + return recording.toByteArray(); + } + +} diff --git a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java index 54dae472e..9081b6fe2 100644 --- a/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java +++ b/main/jackrabbit-filesystem-adapter/src/test/java/org/cryptomator/webdav/jackrabbitservlet/FileSystemBasedWebDavServer.java @@ -18,6 +18,7 @@ import javax.servlet.DispatcherType; import org.cryptomator.filesystem.FileSystem; import org.cryptomator.webdav.filters.AcceptRangeFilter; +import org.cryptomator.webdav.filters.LoggingHttpFilter; import org.cryptomator.webdav.filters.UriNormalizationFilter; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; @@ -54,6 +55,7 @@ class FileSystemBasedWebDavServer { servletContext.addServlet(servletHolder, "/*"); servletContext.addFilter(AcceptRangeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); servletContext.addFilter(UriNormalizationFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + servletContext.addFilter(LoggingHttpFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); servletCollection.mapContexts(); server.setConnectors(new Connector[] { localConnector }); diff --git a/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml b/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml index 4f9b18066..39c2f8545 100644 --- a/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml +++ b/main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml @@ -23,7 +23,6 @@ -