From f2cee7829ae7d8fae58239dd0018b2aa790c0251 Mon Sep 17 00:00:00 2001 From: olegb Date: Mon, 19 Feb 2018 16:24:54 +0200 Subject: Added JAX-RS filters for logging Change-Id: I57ab333278d4ea26530e916ef89670df8b51b555 Issue-ID: SDC-772 Signed-off-by: olegb Signed-off-by: vempo --- .../openecomp/sdc/logging/LoggingConstants.java | 40 ++++++ .../openecomp/sdc/logging/api/ServiceBinder.java | 22 ++- .../openecomp/sdc/logging/servlet/HttpHeader.java | 55 ++++++++ .../sdc/logging/servlet/LoggingFilter.java | 136 +++++++++++++++++++ .../servlet/jaxrs/LoggingRequestFilter.java | 118 +++++++++++++++++ .../servlet/jaxrs/LoggingResponseFilter.java | 147 +++++++++++++++++++++ 6 files changed, 505 insertions(+), 13 deletions(-) create mode 100644 openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java create mode 100644 openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java create mode 100644 openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java create mode 100644 openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java create mode 100644 openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java (limited to 'openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main') diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java new file mode 100644 index 0000000000..e8635b2418 --- /dev/null +++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/LoggingConstants.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2016-2018 European Support Limited + * + * Licensed 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.openecomp.sdc.logging; + +/** + * Constants that must be visible across the logging API. + * + * @author evitaliy + * @since 18 Mar 2018 + */ +public class LoggingConstants { + + /** + * Default HTTP header for propagation of a request ID for distributed tracing. + */ + public static final String DEFAULT_REQUEST_ID_HEADER = "X-ECOMP-RequestID"; + + /** + * Default HTTP header for exchanging a partner name between components. + */ + public static final String DEFAULT_PARTNER_NAME_HEADER = "USER_ID"; + + private LoggingConstants() { + // prevent instantiation of the constants class + } +} diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java index e3c9ea02de..6706d6fa46 100644 --- a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java +++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/api/ServiceBinder.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -16,25 +16,22 @@ package org.openecomp.sdc.logging.api; -import org.openecomp.sdc.logging.spi.LoggerCreationService; -import org.openecomp.sdc.logging.spi.LoggingContextService; -import org.openecomp.sdc.logging.spi.LoggingServiceProvider; - import java.util.Iterator; import java.util.Optional; import java.util.ServiceLoader; +import org.openecomp.sdc.logging.spi.LoggerCreationService; +import org.openecomp.sdc.logging.spi.LoggingContextService; +import org.openecomp.sdc.logging.spi.LoggingServiceProvider; /** *

Binds to a concrete implementation of logging services.

- * *

In order to use the factory, a particular (e.g. framework-specific) implementation of a service must be * configured as described in * java.util.ServiceLoader).

* * @author evitaliy - * @since 13/09/2016. - * * @see ServiceLoader + * @since 13 Sep 2016 */ // No advanced logging can be used here because we don't know @@ -44,7 +41,7 @@ class ServiceBinder { private static final LoggingServiceProvider PROVIDER = lookupProvider(); - private ServiceBinder () { + private ServiceBinder() { // prevent instantiation } @@ -54,9 +51,8 @@ class ServiceBinder { Iterator iterator = loader.iterator(); if (!iterator.hasNext()) { - System.err.printf("[ERROR] No provider configured for logging services %s. " + - "Default implementation will be used.\n", - LoggingServiceProvider.class.getName()); + System.err.printf("[ERROR] No provider configured for logging services %s. " + + "Default implementation will be used.\n", LoggingServiceProvider.class.getName()); return null; } diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java new file mode 100644 index 0000000000..4dcc197796 --- /dev/null +++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/HttpHeader.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2016-2018 European Support Limited + * + * Licensed 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.openecomp.sdc.logging.servlet; + +import java.util.function.Function; + +/** + * Handles any of possible header names to read a value for that header. This is useful for backward compatibility, if + * multiple headers may have the same meaning. For instance, when requests come from multiple service, some using an old + * header and others using a new header to pass the same information. + * + * @author evitaliy + * @since 25 Mar 2018 + */ +public class HttpHeader { + + private final String[] keys; + + public HttpHeader(String... keys) { + this.keys = keys; + } + + /** + * Returns the value of any of the possible headers. + * + * @param reader function for reading an HTTP header. + * @return value or null if not found + */ + public String getAny(Function reader) { + + for (String k : keys) { + + String value = reader.apply(k); + if (value != null) { + return value; + } + } + + return null; + } +} diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java new file mode 100644 index 0000000000..e53f28119c --- /dev/null +++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/LoggingFilter.java @@ -0,0 +1,136 @@ +/* + * Copyright © 2016-2018 European Support Limited + * + * Licensed 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.openecomp.sdc.logging.servlet; + +import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_PARTNER_NAME_HEADER; +import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_REQUEST_ID_HEADER; + +import java.io.IOException; +import java.util.UUID; +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 org.openecomp.sdc.logging.api.ContextData; +import org.openecomp.sdc.logging.api.Logger; +import org.openecomp.sdc.logging.api.LoggerFactory; +import org.openecomp.sdc.logging.api.LoggingContext; +import org.openecomp.sdc.logging.servlet.jaxrs.LoggingRequestFilter; +import org.openecomp.sdc.logging.servlet.jaxrs.LoggingResponseFilter; + +/** + *

Places logging context information. Must be configured as a servlet filter in web.xml. The behavior can be + * customized via init-params.

+ *

Example:

+ *
+ *
+ *  <filter>
+ *      <filter-name>LoggingServletFilter</filter-name>
+ *      <filter-class>org.openecomp.sdc.logging.servlet.LoggingFilter</filter-class>
+ *      <init-param>
+ *          <param-name>requestIdHeaders</param-name>
+ *          <param-value>X-ONAP-RequestID</param-value>
+ *      </init-param>
+ *      <init-param>
+ *          <param-name>partnerNameHeaders</param-name>
+ *          <param-value>USER_ID</param-value>
+ *      </init-param>
+ *  </filter>
+ *
+ *  <filter-mapping>
+ *      <filter-name>LoggingServletFilter</filter-name>
+ *      <url-pattern>/*</url-pattern>
+ *  </filter-mapping>
+ *
+ * 
+ * + * @author evitaliy + * @since 25 Jul 2016 + * @deprecated Kept for backward compatibility. For JAX-RS application, use + * {@link LoggingRequestFilter} and + * {@link LoggingResponseFilter} instead. + */ +@Deprecated +public class LoggingFilter implements Filter { + + static final String MULTI_VALUE_SEPARATOR = ","; + + static final String REQUEST_ID_HEADERS_PARAM = "requestIdHeaders"; + static final String PARTNER_NAME_HEADERS_PARAM = "partnerNameHeaders"; + + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class); + + private HttpHeader requestIdHeaders; + private HttpHeader partnerNameHeaders; + + @Override + public void init(FilterConfig config) { + requestIdHeaders = getInitParam(config, REQUEST_ID_HEADERS_PARAM, DEFAULT_REQUEST_ID_HEADER); + partnerNameHeaders = getInitParam(config, PARTNER_NAME_HEADERS_PARAM, DEFAULT_PARTNER_NAME_HEADER); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = HttpServletRequest.class.cast(request); + + try { + + LoggingContext.clear(); + + ContextData.ContextDataBuilder contextData = ContextData.builder(); + + contextData.serviceName(httpRequest.getRequestURI()); + + String requestId = requestIdHeaders.getAny(httpRequest::getHeader); + contextData.requestId(requestId == null ? UUID.randomUUID().toString() : requestId); + + String partner = partnerNameHeaders.getAny(httpRequest::getHeader); + if (partner != null) { + contextData.partnerName(partner); + } + + LoggingContext.put(contextData.build()); + + chain.doFilter(request, response); + + } finally { + LoggingContext.clear(); + } + } + + @Override + public void destroy() { + // forced by the interface - not implemented + } + + private HttpHeader getInitParam(FilterConfig config, String paramName, String defaultValue) { + + String value = config.getInitParameter(paramName); + LOGGER.debug("Logging filter configuration param '{}' value '{}'", paramName, value); + + if (value == null) { + return new HttpHeader(defaultValue); + } else { + return new HttpHeader(value.split(MULTI_VALUE_SEPARATOR)); + } + } +} diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java new file mode 100644 index 0000000000..06660f35f4 --- /dev/null +++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingRequestFilter.java @@ -0,0 +1,118 @@ +/* + * Copyright © 2016-2018 European Support Limited + * + * Licensed 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.openecomp.sdc.logging.servlet.jaxrs; + +import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_PARTNER_NAME_HEADER; +import static org.openecomp.sdc.logging.LoggingConstants.DEFAULT_REQUEST_ID_HEADER; + +import java.util.UUID; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; +import org.openecomp.sdc.logging.api.ContextData; +import org.openecomp.sdc.logging.api.LoggingContext; +import org.openecomp.sdc.logging.servlet.HttpHeader; + +/** + *

Takes care of logging initialization an HTTP request hits the application. This includes populating logging + * context and storing the request processing start time, so that it can be used for audit. The filter was built + * works in tandem with {@link LoggingResponseFilter} or a similar implementation.

+ *

The filter requires a few HTTP header names to be configured. These HTTP headers are used for propagating logging + * and tracing information between ONAP components.

+ *

Sample configuration for a Spring environment:

+ *
+ *     <jaxrs:providers>
+ *         <bean class="org.openecomp.sdc.logging.ws.rs.LoggingRequestFilter">
+ *             <property name="requestIdHeaders" value="X-ONAP-RequestID"/>
+ *             <property name="partnerNameHeaders" value="X-ONAP-InstanceID"/>
+ *         </bean>
+ *     </jaxrs:providers>
+ * 
+ *

Keep in mind that the filters does nothing in case when a request cannot be mapped to a working JAX-RS resource + * (implementation). For instance, when the path is invalid (404), or there is no handler for a particular method (405). + *

+ * + * @author evitaliy, katyr + * @since 29 Oct 17 + * + * @see ContainerRequestFilter + */ +@Provider +public class LoggingRequestFilter implements ContainerRequestFilter { + + static final String MULTI_VALUE_SEPARATOR = ","; + + static final String START_TIME_KEY = "audit.start.time"; + + private ResourceInfo resource; + + private HttpHeader requestIdHeader = new HttpHeader(DEFAULT_REQUEST_ID_HEADER); + private HttpHeader partnerNameHeader = new HttpHeader(DEFAULT_PARTNER_NAME_HEADER); + + /** + * Injection of a resource that matches the request from JAX-RS context. + * + * @param resource automatically injected by JAX-RS container + */ + @Context + public void setResource(ResourceInfo resource) { + this.resource = resource; + } + + /** + * Configuration parameter for request ID HTTP header. + */ + public void setRequestIdHeaders(String requestIdHeaders) { + this.requestIdHeader = new HttpHeader(requestIdHeaders.split(MULTI_VALUE_SEPARATOR)); + } + + /** + * Configuration parameter for partner name HTTP header. + */ + public void setPartnerNameHeaders(String partnerNameHeaders) { + this.partnerNameHeader = new HttpHeader(partnerNameHeaders.split(MULTI_VALUE_SEPARATOR)); + } + + @Override + public void filter(ContainerRequestContext containerRequestContext) { + + if (resource == null) { + // JAX-RS could not find a mapping this response, probably due to HTTP 404 (not found), + // 405 (method not allowed), 415 (unsupported media type), etc. with a message in Web server log + return; + } + + containerRequestContext.setProperty(START_TIME_KEY, System.currentTimeMillis()); + + LoggingContext.clear(); + + ContextData.ContextDataBuilder contextData = ContextData.builder(); + contextData.serviceName(resource.getResourceClass().getName() + "." + resource.getResourceMethod().getName()); + + String partnerName = partnerNameHeader.getAny(containerRequestContext::getHeaderString); + if (partnerName != null) { + contextData.partnerName(partnerName); + } + + String requestId = requestIdHeader.getAny(containerRequestContext::getHeaderString); + contextData.requestId(requestId == null ? UUID.randomUUID().toString() : requestId); + + LoggingContext.put(contextData.build()); + } +} diff --git a/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java new file mode 100644 index 0000000000..fbe28a79eb --- /dev/null +++ b/openecomp-be/lib/openecomp-sdc-logging-lib/openecomp-sdc-logging-api/src/main/java/org/openecomp/sdc/logging/servlet/jaxrs/LoggingResponseFilter.java @@ -0,0 +1,147 @@ +/* + * Copyright © 2016-2018 European Support Limited + * + * Licensed 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.openecomp.sdc.logging.servlet.jaxrs; + +import static org.openecomp.sdc.logging.api.StatusCode.COMPLETE; +import static org.openecomp.sdc.logging.api.StatusCode.ERROR; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; +import org.openecomp.sdc.logging.api.AuditData; +import org.openecomp.sdc.logging.api.Logger; +import org.openecomp.sdc.logging.api.LoggerFactory; +import org.openecomp.sdc.logging.api.LoggingContext; +import org.openecomp.sdc.logging.api.StatusCode; + +/** + *

Takes care of logging when an HTTP request leaves the application. This includes writing to audit and clearing + * logging context. This filter only works properly in tandem with {@link LoggingRequestFilter} or a similar + * implementation.

+ *

Sample configuration for a Spring environment:

+ *
+ *     <jaxrs:providers>
+ *         <bean class="org.openecomp.sdc.logging.ws.rs.LoggingResponseFilter"/>
+ *     </jaxrs:providers>
+ * 
+ *

It is highly recommended to configure a custom JAX-RS exception mapper so that this filter will not be bypassed + * due to unhandled application or container exceptions.

+ * + * @author evitaliy + * @since 29 Oct 17 + * + * @see ContainerResponseFilter + */ +@Provider +public class LoggingResponseFilter implements ContainerResponseFilter { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Tracks reporting configuration problems to the log. We want to report them only once, and not to write to log + * upon every request, as the configuration will not change in runtime. + */ + private boolean reportBadConfiguration = true; + + private HttpServletRequest httpRequest; + + /** + * Injection of HTTP request object from JAX-RS context. + * + * @param httpRequest automatically injected by JAX-RS container + */ + @Context + public void setHttpRequest(HttpServletRequest httpRequest) { + this.httpRequest = httpRequest; + } + + @Override + public void filter(ContainerRequestContext containerRequestContext, + ContainerResponseContext containerResponseContext) { + + try { + writeAudit(containerRequestContext, containerResponseContext); + } finally { + LoggingContext.clear(); + } + } + + private void writeAudit(ContainerRequestContext containerRequestContext, + ContainerResponseContext containerResponseContext) { + + if (!logger.isAuditEnabled()) { + return; + } + + long start = readStartTime(containerRequestContext); + long end = System.currentTimeMillis(); + + Response.StatusType statusInfo = containerResponseContext.getStatusInfo(); + int responseCode = statusInfo.getStatusCode(); + StatusCode statusCode = isSuccess(responseCode) ? COMPLETE : ERROR; + + AuditData auditData = AuditData.builder().startTime(start).endTime(end).statusCode(statusCode) + .responseCode(Integer.toString(responseCode)) + .responseDescription(statusInfo.getReasonPhrase()) + .clientIpAddress(httpRequest.getRemoteAddr()).build(); + logger.audit(auditData); + } + + private boolean isSuccess(int responseCode) { + return responseCode > 199 && responseCode < 400; + } + + private long readStartTime(ContainerRequestContext containerRequestContext) { + + Object startTime = containerRequestContext.getProperty(LoggingRequestFilter.START_TIME_KEY); + if (startTime == null) { + return handleMissingStartTime(); + } + + return parseStartTime(startTime); + } + + private long handleMissingStartTime() { + reportConfigProblem("{} key was not found in JAX-RS request context. " + + "Make sure you configured a request filter", LoggingRequestFilter.START_TIME_KEY); + return 0; + } + + private long parseStartTime(Object startTime) { + + try { + return Long.class.cast(startTime); + } catch (ClassCastException e) { + reportConfigProblem("{} key in JAX-RS request context contains an object of type '{}', but 'java.lang.Long'" + + " is expected", LoggingRequestFilter.START_TIME_KEY, startTime.getClass().getName()); + return 0; + } + } + + private void reportConfigProblem(String message, Object... arguments) { + + if (reportBadConfiguration) { + reportBadConfiguration = false; + logger.error(message, arguments); + } + } +} + -- cgit 1.2.3-korg