/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.brooklyn.rest.filter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.brooklyn.core.BrooklynLogging;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Sets;
/**
* Handles logging of request information.
*/
public class LoggingFilter implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(BrooklynLogging.REST);
/** Methods logged at trace. */
private static final Set<String> UNINTERESTING_METHODS = Sets.newHashSet("GET", "HEAD");
/** Headers whose values will not be logged. */
private static final Set<String> CENSORED_HEADERS = Sets.newHashSet("Authorization");
/** Log all requests that take this time or longer to complete. */
private static final Duration REQUEST_DURATION_LOG_POINT = Duration.FIVE_SECONDS;
public void init(FilterConfig config) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String rid = RequestTaggingFilter.getTag();
boolean isInteresting = !UNINTERESTING_METHODS.contains(httpRequest.getMethod().toUpperCase());
boolean shouldLog = (isInteresting && LOG.isDebugEnabled()) || LOG.isTraceEnabled();
boolean requestErrored = false;
if (shouldLog) {
String message = "Request {} starting: {} {} from {}";
Object[] args = new Object[]{rid, httpRequest.getMethod(), httpRequest.getRequestURI(), httpRequest.getRemoteAddr()};
if (isInteresting) {
LOG.debug(message, args);
} else {
LOG.trace(message, args);
}
}
Stopwatch timer = Stopwatch.createStarted();
try {
chain.doFilter(request, response);
} catch (Throwable e) {
requestErrored = true;
isInteresting = true;
LOG.warn("Request " + rid + " ("+httpRequest.getMethod()+" "+httpRequest.getRequestURI()+" from "+httpRequest.getRemoteAddr()+") failed: " + e, e);
// Propagate for handling by other filter
throw Exceptions.propagate(e);
} finally {
timer.stop();
// This logging must not happen before chain.doFilter, or FormMapProvider will not work as expected.
// Getting the parameter map consumes the request body and only resource methods using @FormParam
// will work as expected.
isInteresting |= (timer.elapsed(TimeUnit.SECONDS) - REQUEST_DURATION_LOG_POINT.toSeconds()) > 0;
if (shouldLog) {
boolean includeHeaders = requestErrored || httpResponse.getStatus() / 100 == 5 || LOG.isTraceEnabled();
String message = getRequestCompletedMessage(includeHeaders, Duration.of(timer), rid, httpRequest, httpResponse);
if (requestErrored || isInteresting) {
LOG.debug(message);
} else {
LOG.trace(message);
}
}
}
}
private String getRequestCompletedMessage(boolean includeHeaders, Duration elapsed,
String id, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
StringBuilder message = new StringBuilder("Request ")
.append(id)
.append(" completed in ")
.append(elapsed)
.append(": response ")
.append(httpResponse.getStatus())
.append(" for ")
.append(httpRequest.getMethod())
.append(" ")
.append(httpRequest.getRequestURI())
.append(" from ")
.append(httpRequest.getRemoteAddr());
if (!httpRequest.getParameterMap().isEmpty()) {
message.append(", parameters: ")
.append(Joiner.on(", ").withKeyValueSeparator("=").join(httpRequest.getParameterMap()));
}
if (httpRequest.getContentLength() > 0) {
int len = httpRequest.getContentLength();
message.append(" contentType=").append(httpRequest.getContentType())
.append(" (length=").append(len).append(")");
}
if (includeHeaders) {
Enumeration<String> headerNames = httpRequest.getHeaderNames();
if (headerNames.hasMoreElements()) {
message.append(", headers: ");
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
message.append(headerName).append(": ");
if (CENSORED_HEADERS.contains(headerName)) {
message.append("******");
} else {
message.append(httpRequest.getHeader(headerName));
}
if (headerNames.hasMoreElements()) {
message.append(", ");
}
}
}
}
return message.toString();
}
}