You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by an...@apache.org on 2018/01/16 10:01:45 UTC
brooklyn-server git commit: Log all REST api calls (when running as
OSGi)
Repository: brooklyn-server
Updated Branches:
refs/heads/master d48217dbf -> 49bd6c785
Log all REST api calls (when running as OSGi)
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/49bd6c78
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/49bd6c78
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/49bd6c78
Branch: refs/heads/master
Commit: 49bd6c7858aecfc44384dc628dac2bc81e84a7b9
Parents: d48217d
Author: Aled Sage <al...@gmail.com>
Authored: Fri Jan 12 10:43:14 2018 +0000
Committer: andreaturli <an...@gmail.com>
Committed: Tue Jan 16 10:01:28 2018 +0000
----------------------------------------------------------------------
.../apache/brooklyn/core/BrooklynLogging.java | 4 +-
.../rest/filter/LoggingResourceFilter.java | 235 +++++++++++++++++++
.../resources/OSGI-INF/blueprint/service.xml | 1 +
3 files changed, 238 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/49bd6c78/core/src/main/java/org/apache/brooklyn/core/BrooklynLogging.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/BrooklynLogging.java b/core/src/main/java/org/apache/brooklyn/core/BrooklynLogging.java
index db9eb52..ae9921d 100644
--- a/core/src/main/java/org/apache/brooklyn/core/BrooklynLogging.java
+++ b/core/src/main/java/org/apache/brooklyn/core/BrooklynLogging.java
@@ -25,9 +25,9 @@ import org.slf4j.Logger;
/** contains common logging categories */
public class BrooklynLogging {
- public static final String SSH_IO = "brooklyn.SSH";
+ public static final String SSH_IO = "org.apache.brooklyn.SSH";
- public static final String REST = "brooklyn.REST";
+ public static final String REST = "org.apache.brooklyn.REST";
/** For convenience here, since SLF4J does not define such an enum */
public static enum LoggingLevel { ERROR, WARN, INFO, DEBUG, TRACE }
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/49bd6c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/LoggingResourceFilter.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/LoggingResourceFilter.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/filter/LoggingResourceFilter.java
new file mode 100644
index 0000000..5d9c8f8
--- /dev/null
+++ b/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");
+
+ /** 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: ")
+ .append(method)
+ .append(" ")
+ .append(path)
+ .append(" from ")
+ .append(userName)
+ .append(" @ ")
+ .append(remoteAddr);
+
+ log(LOG, level, message.toString());
+ }
+
+ private void logResponse(ContainerRequestContext requestContext, ContainerResponseContext responseContext, Duration requestDuration, LogLevel level) {
+ if (!isLogEnabled(LOG, level)) return;
+
+ int status = responseContext.getStatus();
+ String method = requestContext.getMethod();
+ String path = requestContext.getUriInfo().getPath();
+ requestContext.getSecurityContext();
+ MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
+
+ SecurityContext securityContext = requestContext.getSecurityContext();
+ Principal userPrincipal = (securityContext != null) ? requestContext.getSecurityContext().getUserPrincipal() : null;
+ String userName = (userPrincipal != null) ? userPrincipal.getName() : "<no-user>";
+ String remoteAddr = servletRequest.getRemoteAddr();
+
+ boolean includeHeaders = (responseContext.getStatus() / 100 == 5) || LOG.isDebugEnabled();
+
+ StringBuilder message = new StringBuilder("Request completed: ")
+ .append("status ")
+ .append(status)
+ .append(" in ")
+ .append(requestDuration)
+ .append(", ")
+ .append(method)
+ .append(" ")
+ .append(path)
+ .append(" from ")
+ .append(userName)
+ .append(" @ ")
+ .append(remoteAddr);
+
+ if (!queryParams.isEmpty()) {
+ message.append(", queryParams: {");
+ message.append(Joiner.on(", ").withKeyValueSeparator("=").join(queryParams));
+ message.append("}");
+ }
+ if (requestContext.getLength() > 0) {
+ // TODO `getLength` is based on the presence of `Content-Length` header, rather than the measured length.
+ int len = requestContext.getLength();
+ message.append(", mediaType=").append(requestContext.getMediaType())
+ .append(" (length=").append(len).append(")");
+ }
+ if (includeHeaders) {
+ MultivaluedMap<String, String> headers = requestContext.getHeaders();
+ message.append(", headers={");
+ if (!headers.isEmpty()) {
+ boolean first = true;
+ for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+ if (first) {
+ first = false;
+ } else {
+ message.append(", ");
+ }
+ String headerName = entry.getKey();
+ message.append(headerName).append(": ");
+ if (CENSORED_HEADERS.contains(headerName)) {
+ message.append("******");
+ } else {
+ message.append(entry.getValue());
+ }
+ }
+ }
+ message.append("}");
+ }
+
+ log(LOG, level, message.toString());
+ }
+
+ private boolean isLogEnabled(Logger logger, LogLevel level) {
+ switch (level) {
+ case TRACE: return logger.isTraceEnabled();
+ case DEBUG: return logger.isDebugEnabled();
+ case INFO: return logger.isInfoEnabled();
+ default: throw new IllegalStateException("Unexpected log level: "+level);
+ }
+ }
+
+ private void log(Logger logger, LogLevel level, String msg) {
+ switch (level) {
+ case TRACE:
+ logger.trace(msg);
+ break;
+ case DEBUG:
+ logger.debug(msg);
+ break;
+ case INFO:
+ logger.info(msg);
+ break;
+ default: throw new IllegalStateException("Unexpected log level: "+level);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/49bd6c78/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
index 9d7e5e7..5fe7f02 100644
--- a/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
+++ b/rest/rest-resources/src/main/resources/OSGI-INF/blueprint/service.xml
@@ -132,6 +132,7 @@ limitations under the License.
<bean class="org.apache.brooklyn.rest.filter.NoCacheFilter"/>
<bean class="org.apache.brooklyn.rest.filter.HaHotCheckResourceFilter"/>
<bean class="org.apache.brooklyn.rest.filter.EntitlementContextFilter"/>
+ <bean class="org.apache.brooklyn.rest.filter.LoggingResourceFilter"/>
<bean class="io.swagger.jaxrs.listing.SwaggerSerializers"/>
<bean class="org.apache.brooklyn.rest.util.ShutdownHandlerProvider">
<argument ref="shutdownHandler"/>