Browse Source

Improved request logging

Markus Kreusch 9 years ago
parent
commit
f735a64814

+ 174 - 0
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<String> METHODS_TO_LOG_DETAILED = methodsToLog();
+
+	private static final Set<String> 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<Throwable> 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<Throwable> 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<String> headerNames = request.getHeaderNames();
+		while (headerNames.hasMoreElements()) {
+			String headerName = headerNames.nextElement();
+			Enumeration<String> 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
+	}
+
+}

+ 27 - 0
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();
+	}
+
+}

+ 27 - 0
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();
+	}
+
+}

+ 73 - 0
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;
+	}
+
+}

+ 54 - 0
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();
+	}
+
+}

+ 2 - 0
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 });

+ 0 - 1
main/jackrabbit-filesystem-adapter/src/test/resources/log4j2.xml

@@ -23,7 +23,6 @@
 	<Loggers>
 		<!-- show our own debug messages: -->
 		<Logger name="org.cryptomator" level="DEBUG" />
-		<Logger name="org.eclipse.jetty.server.Server" level="DEBUG" />
 		<!-- mute dependencies: -->
 		<Root level="INFO">
 			<AppenderRef ref="Console" />