Github user geomacy commented on a diff in the pull request: https://github.com/apache/brooklyn-server/pull/933#discussion_r162954737 --- Diff: rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/LoggingResourceFilter.java --- @@ -0,0 +1,235 @@ +/* + * 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.security.Principal; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import javax.annotation.Priority; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.Provider; + +import org.apache.brooklyn.core.BrooklynLogging; +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.collect.ImmutableSet; + +/** + * Logs inbound REST api calls, and their responses. + */ +@Provider +@Priority(200) +public class LoggingResourceFilter implements ContainerRequestFilter, ContainerResponseFilter { + + /* + * TODO: + * - containerRequestContext.getLength() seems to only be non-negative if 'Content-Length' + * header is supplied (which we do not do from `br` etc). + * - We don't log details of the request body (e.g. we just get told 'applications' when + * deploying an app, rather than anything about the yaml of the app being deployed. + * - Should we have separate loggers for request headers etc (so can be enabled/disabled + * more easily by configuring logging levels)? + */ + + private enum LogLevel { + TRACE, + DEBUG, + INFO; + } + + private static final Logger LOG = LoggerFactory.getLogger(BrooklynLogging.REST); + + /** Methods logged at trace. */ + private static final Set<String> UNINTERESTING_METHODS = ImmutableSet.of("GET", "HEAD"); + + /** Headers whose values will not be logged. */ + private static final Set<String> CENSORED_HEADERS = ImmutableSet.of("Authorization", "Cookie"); + + /** Log all requests that take this time or longer to complete. */ + private static final Duration REQUEST_DURATION_LOG_POINT = Duration.FIVE_SECONDS; + + /** + * Used to correlate the requests and responses - records the timestamp (in nanos) when the + * request was received, so can calculate how long it took to process the request. + */ + private final WeakHashMap<HttpServletRequest, Long> requestTimestamps = new WeakHashMap<>(); + + @Context + private HttpServletRequest servletRequest; + + public LoggingResourceFilter() { + } + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + requestTimestamps.put(servletRequest, System.nanoTime()); + + String method = requestContext.getMethod(); + boolean isInteresting = !UNINTERESTING_METHODS.contains(method.toUpperCase()); + LogLevel logLevel = isInteresting ? LogLevel.DEBUG : LogLevel.TRACE; + + logRequest(requestContext, logLevel); + } + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { + Long requestTime = requestTimestamps.remove(servletRequest); + Duration requestDuration = null; + if (requestTime != null) { + long responseTime = System.nanoTime(); + requestDuration = Duration.nanos(responseTime - requestTime); + } + + String method = requestContext.getMethod(); + boolean isInteresting = !UNINTERESTING_METHODS.contains(method.toUpperCase()) + || (requestDuration != null && requestDuration.isLongerThan(REQUEST_DURATION_LOG_POINT)); + LogLevel logLevel = isInteresting ? LogLevel.DEBUG : LogLevel.TRACE; + + logResponse(requestContext, responseContext, requestDuration, logLevel); + } + + private void logRequest(ContainerRequestContext requestContext, LogLevel level) { + if (!isLogEnabled(LOG, level)) return; + + String method = requestContext.getMethod(); + String path = requestContext.getUriInfo().getPath(); + requestContext.getSecurityContext(); + + SecurityContext securityContext = requestContext.getSecurityContext(); + Principal userPrincipal = (securityContext != null) ? requestContext.getSecurityContext().getUserPrincipal() : null; + String userName = (userPrincipal != null) ? userPrincipal.getName() : "<no-user>"; + String remoteAddr = servletRequest.getRemoteAddr(); + + StringBuilder message = new StringBuilder("Request received: ") --- End diff -- Think that looks like it's pretty closely tied to Spring. I'm guessing it works in with Spring's request handling. Certainly some of the examples on that page use Spring wiring.
---