diff options
7 files changed, 579 insertions, 2 deletions
diff --git a/services/pom.xml b/services/pom.xml index af1377fe..d89385d5 100644 --- a/services/pom.xml +++ b/services/pom.xml @@ -112,6 +112,10 @@ <version>${logging.analytics.version}</version> </dependency> <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> <groupId>javax.ws.rs</groupId> <artifactId>javax.ws.rs-api</artifactId> </dependency> diff --git a/services/src/main/java/org/onap/ccsdk/apps/filters/AbstractAuditLogFilter.java b/services/src/main/java/org/onap/ccsdk/apps/filters/AbstractAuditLogFilter.java new file mode 100644 index 00000000..a332fcbf --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/filters/AbstractAuditLogFilter.java @@ -0,0 +1,93 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - Logging + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + + package org.onap.ccsdk.apps.filters; + + import jakarta.servlet.http.HttpServletRequest; + import org.onap.logging.ref.slf4j.ONAPLogConstants; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.slf4j.MDC; + import org.onap.logging.filter.base.SimpleMap; + + public abstract class AbstractAuditLogFilter<GenericRequest, GenericResponse> extends MDCSetup { + protected static final Logger logger = LoggerFactory.getLogger(AbstractAuditLogFilter.class); + + protected void pre(SimpleMap headers, GenericRequest request, HttpServletRequest httpServletRequest) { + try { + String requestId = getRequestId(headers); + MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestId); + setInvocationId(headers); + setServiceName(request); + setMDCPartnerName(headers); + setServerFQDN(); + setClientIPAddress(httpServletRequest); + setInstanceID(); + setEntryTimeStamp(); + MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, ONAPLogConstants.ResponseStatus.INPROGRESS.toString()); + additionalPreHandling(request); + setLogTimestamp(); + setElapsedTime(); + logEntering(); + } catch (Exception e) { + logger.warn("Error in AbstractInboundFilter pre", e); + } + } + + protected void post(GenericResponse response) { + try { + MDC.put(ONAPLogConstants.MDCs.INVOCATION_ID, MDC.get(ONAPLogConstants.MDCs.SERVER_INVOCATION_ID)); + int responseCode = getResponseCode(response); + setResponseStatusCode(responseCode); + MDC.put(ONAPLogConstants.MDCs.RESPONSE_CODE, String.valueOf(responseCode)); + setResponseDescription(responseCode); + setLogTimestamp(); + setElapsedTime(); + additionalPostHandling(response); + logExiting(); + } catch (Exception e) { + logger.warn("Error in AbstractInboundFilter post", e); + } finally { + MDC.clear(); + } + } + + protected abstract int getResponseCode(GenericResponse response); + + protected abstract void setServiceName(GenericRequest request); + + protected void additionalPreHandling(GenericRequest request) { + // override to add additional pre handling + } + + protected void additionalPostHandling(GenericResponse response) { + // override to add additional post handling + } + + protected void logEntering() { + logger.info(ONAPLogConstants.Markers.ENTRY, "Entering"); + } + + protected void logExiting() { + logger.info(ONAPLogConstants.Markers.EXIT, "Exiting."); + } + + } +
\ No newline at end of file diff --git a/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogFilter.java b/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogFilter.java index 45ce55f4..0c292380 100644 --- a/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogFilter.java +++ b/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogFilter.java @@ -1,8 +1,7 @@ package org.onap.ccsdk.apps.filters ; -import javax.servlet.http.HttpServletRequest; -import org.onap.logging.filter.base.AuditLogServletFilter; +import jakarta.servlet.http.HttpServletRequest; import org.onap.logging.ref.slf4j.ONAPLogConstants; import org.slf4j.MDC; import org.springframework.stereotype.Component; diff --git a/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogServletFilter.java b/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogServletFilter.java new file mode 100644 index 00000000..cd9f8558 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/filters/AuditLogServletFilter.java @@ -0,0 +1,87 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - Logging + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Modifications Copyright (C) 2018 IBM. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + + package org.onap.ccsdk.apps.filters; + + import java.io.IOException; + import jakarta.servlet.Filter; + import jakarta.servlet.FilterChain; + import jakarta.servlet.FilterConfig; + import jakarta.servlet.ServletException; + import jakarta.servlet.ServletRequest; + import jakarta.servlet.ServletResponse; + import jakarta.servlet.http.HttpServletRequest; + import jakarta.servlet.http.HttpServletResponse; + import org.onap.logging.ref.slf4j.ONAPLogConstants; + import org.onap.logging.filter.base.SimpleMap; + import org.slf4j.MDC; + + public class AuditLogServletFilter extends AbstractAuditLogFilter<HttpServletRequest, HttpServletResponse> + implements Filter { + + @Override + public void destroy() { + // this method does nothing + } + + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) + throws IOException, ServletException { + try { + if (request != null && request instanceof HttpServletRequest) { + pre((HttpServletRequest) request); + } + filterChain.doFilter(request, response); + } finally { + if (request != null && request instanceof HttpServletRequest) { + post((HttpServletRequest) request, (HttpServletResponse) response); + } + MDC.clear(); + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // this method does nothing + } + + protected void pre(HttpServletRequest request) { + SimpleMap headers = new SimpleServletHeadersMap(request); + pre(headers, request, request); + } + + @Override + protected void setServiceName(HttpServletRequest request) { + MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI()); + } + + private void post(HttpServletRequest request, HttpServletResponse response) { + post(response); + } + + @Override + protected int getResponseCode(HttpServletResponse response) { + return response.getStatus(); + } + + } +
\ No newline at end of file diff --git a/services/src/main/java/org/onap/ccsdk/apps/filters/CustomResponseStatus.java b/services/src/main/java/org/onap/ccsdk/apps/filters/CustomResponseStatus.java new file mode 100644 index 00000000..c44466df --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/filters/CustomResponseStatus.java @@ -0,0 +1,63 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - Logging + * ================================================================================ + * Copyright (C) 2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + + package org.onap.ccsdk.apps.filters; + + public enum CustomResponseStatus { + PROCESSING(102, "Processing"), + MULTI_STATUS(207, "Multi-Status"), + ALREADY_REPORTED(208, "Already Reported"), + UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"), + LOCKED(423, "Locked"), + FAILED_DEPENDENCY(424, "Failed Dependency"), + INSUFFICIENT_STORAGE(508, "Insufficient Storage"), + LOOP_DETECTED(508, "Loop Detected"); + + private final int code; + private final String reason; + + CustomResponseStatus(int statusCode, String reasonPhrase) { + this.code = statusCode; + this.reason = reasonPhrase; + } + + public static CustomResponseStatus fromStatusCode(int statusCode) { + for (CustomResponseStatus s : values()) { + if (s.code == statusCode) { + return s; + } + } + + return null; + } + + public int getStatusCode() { + return this.code; + } + + public String getReasonPhrase() { + return this.toString(); + } + + public String toString() { + return this.reason; + } + } +
\ No newline at end of file diff --git a/services/src/main/java/org/onap/ccsdk/apps/filters/MDCSetup.java b/services/src/main/java/org/onap/ccsdk/apps/filters/MDCSetup.java new file mode 100644 index 00000000..6b7f38d3 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/filters/MDCSetup.java @@ -0,0 +1,291 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - Logging + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + + package org.onap.ccsdk.apps.filters; + + import java.net.InetAddress; + import java.net.UnknownHostException; + import java.time.ZoneOffset; + import java.time.ZonedDateTime; + import java.time.format.DateTimeFormatter; + import java.time.format.DateTimeFormatterBuilder; + import java.time.temporal.ChronoUnit; + import java.util.Base64; + import java.util.UUID; + import jakarta.servlet.http.HttpServletRequest; + import javax.ws.rs.core.HttpHeaders; + import javax.ws.rs.core.Response; + +import org.onap.logging.filter.base.Constants; +import org.onap.logging.filter.base.ONAPComponentsList; +import org.onap.logging.filter.base.SimpleMap; +import org.onap.logging.ref.slf4j.ONAPLogConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + + public class MDCSetup { + + protected static Logger logger = LoggerFactory.getLogger(MDCSetup.class); + private static final String INSTANCE_UUID = UUID.randomUUID().toString(); + protected static final String serverIpAddressOverride = "SERVER_IP_ADDRESS_OVERRIDE"; + protected static final String serverFqdnOverride = "SERVER_FQDN_OVERRIDE"; + protected static final String INSTANT_PRECISION_OVERRIDE = "INSTANT_PRECISION_OVERRIDE"; + protected static final String checkHeaderLogPattern = "Checking {} header to determine the value of {}"; + protected String serverFqdn; + protected String serverIpAddress; + protected String[] prioritizedIdHeadersNames; + protected String[] prioritizedPartnerHeadersNames; + protected DateTimeFormatter iso8601Formatter; + + public MDCSetup() { + this.prioritizedIdHeadersNames = + new String[] {ONAPLogConstants.Headers.REQUEST_ID, Constants.HttpHeaders.HEADER_REQUEST_ID, + Constants.HttpHeaders.TRANSACTION_ID, Constants.HttpHeaders.ECOMP_REQUEST_ID}; + this.prioritizedPartnerHeadersNames = + new String[] {HttpHeaders.AUTHORIZATION, ONAPLogConstants.Headers.PARTNER_NAME, HttpHeaders.USER_AGENT}; + initServerFqdnandIp(); + this.iso8601Formatter = createFormatter(); + } + + protected String getCurrentTimeStamp() { + return ZonedDateTime.now(ZoneOffset.UTC).format(iso8601Formatter); + } + + protected DateTimeFormatter createFormatter() { + DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); + try { + Integer instantPrecision = Integer.valueOf(System.getProperty(INSTANT_PRECISION_OVERRIDE, "3")); + builder.appendInstant(instantPrecision); + } catch (NumberFormatException nfe) { + logger.warn("instant precision could not be read and thus won't be set, the default will be used instead." + + nfe.getMessage()); + } + return builder.toFormatter(); + } + + public void setInstanceID() { + MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, INSTANCE_UUID); + } + + protected void initServerFqdnandIp() { + serverFqdn = getProperty(serverFqdnOverride); + serverIpAddress = getProperty(serverIpAddressOverride); + + if (serverIpAddress.equals(Constants.DefaultValues.UNKNOWN) + || serverFqdn.equals(Constants.DefaultValues.UNKNOWN)) { + try { + InetAddress addr = InetAddress.getLocalHost(); + if (serverFqdn.equals(Constants.DefaultValues.UNKNOWN)) { + serverFqdn = addr.getCanonicalHostName(); + } + if (serverIpAddress.equals(Constants.DefaultValues.UNKNOWN)) { + serverIpAddress = addr.getHostAddress(); + } + } catch (UnknownHostException e) { + logger.trace("Cannot Resolve Host Name." + e.getMessage()); + } + } + } + + public void setServerFQDN() { + MDC.put(ONAPLogConstants.MDCs.SERVER_FQDN, serverFqdn); + MDC.put(ONAPLogConstants.MDCs.SERVER_IP_ADDRESS, serverIpAddress); + } + + public void setClientIPAddress(HttpServletRequest httpServletRequest) { + String clientIpAddress = ""; + if (httpServletRequest != null) { + // This logic is to avoid setting the client ip address to that of the load + // balancer in front of the application + String getForwadedFor = httpServletRequest.getHeader("X-Forwarded-For"); + if (getForwadedFor != null) { + clientIpAddress = getForwadedFor; + } else { + clientIpAddress = httpServletRequest.getRemoteAddr(); + } + } + MDC.put(ONAPLogConstants.MDCs.CLIENT_IP_ADDRESS, clientIpAddress); + } + + public void setEntryTimeStamp() { + MDC.put(ONAPLogConstants.MDCs.ENTRY_TIMESTAMP, getCurrentTimeStamp()); + } + + public String getRequestId(SimpleMap headers) { + String requestId = null; + for (String headerName : this.prioritizedIdHeadersNames) { + logger.trace(checkHeaderLogPattern, headerName, ONAPLogConstants.Headers.REQUEST_ID); + requestId = headers.get(headerName); + if (requestId != null && !requestId.isEmpty()) { + return requestId; + } + } + logger.trace("No valid requestId headers. Generating requestId: {}", requestId); + return UUID.randomUUID().toString(); + } + + public void setInvocationId(SimpleMap headers) { + String invocationId = headers.get(ONAPLogConstants.Headers.INVOCATION_ID); + if (invocationId == null || invocationId.isEmpty()) + invocationId = UUID.randomUUID().toString(); + MDC.put(ONAPLogConstants.MDCs.SERVER_INVOCATION_ID, invocationId); + MDC.put(ONAPLogConstants.MDCs.INVOCATION_ID, invocationId); + } + + public void setMDCPartnerName(SimpleMap headers) { + String partnerName = getMDCPartnerName(headers); + MDC.put(ONAPLogConstants.MDCs.PARTNER_NAME, partnerName); + } + + protected String getMDCPartnerName(SimpleMap headers) { + String partnerName = null; + for (String headerName : prioritizedPartnerHeadersNames) { + logger.trace(checkHeaderLogPattern, headerName, ONAPLogConstants.MDCs.PARTNER_NAME); + if (headerName.equals(HttpHeaders.AUTHORIZATION)) { + partnerName = getBasicAuthUserName(headers); + } else { + partnerName = headers.get(headerName); + } + if (partnerName != null && !partnerName.isEmpty()) { + return partnerName; + } + + } + logger.trace("{} value could not be determined, defaulting partnerName to {}.", + ONAPLogConstants.MDCs.PARTNER_NAME, Constants.DefaultValues.UNKNOWN); + return Constants.DefaultValues.UNKNOWN; + } + + public void setLogTimestamp() { + MDC.put(ONAPLogConstants.MDCs.LOG_TIMESTAMP, getCurrentTimeStamp()); + } + + public void setElapsedTime() { + try { + DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME; + ZonedDateTime entryTimestamp = + ZonedDateTime.parse(MDC.get(ONAPLogConstants.MDCs.ENTRY_TIMESTAMP), timeFormatter); + ZonedDateTime endTimestamp = + ZonedDateTime.parse(MDC.get(ONAPLogConstants.MDCs.LOG_TIMESTAMP), timeFormatter); + + MDC.put(ONAPLogConstants.MDCs.ELAPSED_TIME, + Long.toString(ChronoUnit.MILLIS.between(entryTimestamp, endTimestamp))); + } catch (Exception e) { + logger.trace("Unable to calculate elapsed time due to error: {}", e.getMessage()); + } + } + + public void setElapsedTimeInvokeTimestamp() { + try { + DateTimeFormatter timeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME; + ZonedDateTime entryTimestamp = + ZonedDateTime.parse(MDC.get(ONAPLogConstants.MDCs.INVOKE_TIMESTAMP), timeFormatter); + ZonedDateTime endTimestamp = + ZonedDateTime.parse(MDC.get(ONAPLogConstants.MDCs.LOG_TIMESTAMP), timeFormatter); + + MDC.put(ONAPLogConstants.MDCs.ELAPSED_TIME, + Long.toString(ChronoUnit.MILLIS.between(entryTimestamp, endTimestamp))); + } catch (Exception e) { + logger.trace("Unable to calculate elapsed time due to error: {}", e.getMessage()); + } + } + + public void setResponseStatusCode(int code) { + String statusCode; + if (Response.Status.Family.familyOf(code).equals(Response.Status.Family.SUCCESSFUL)) { + statusCode = ONAPLogConstants.ResponseStatus.COMPLETE.toString(); + } else { + statusCode = ONAPLogConstants.ResponseStatus.ERROR.toString(); + setErrorCode(code); + setErrorDescription(code); + } + MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, statusCode); + } + + public void setTargetEntity(ONAPComponentsList targetEntity) { + MDC.put(ONAPLogConstants.MDCs.TARGET_ENTITY, targetEntity.toString()); + } + + public void clearClientMDCs() { + MDC.remove(ONAPLogConstants.MDCs.CLIENT_INVOCATION_ID); + MDC.remove(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION); + MDC.remove(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE); + MDC.remove(ONAPLogConstants.MDCs.RESPONSE_CODE); + MDC.remove(ONAPLogConstants.MDCs.TARGET_ENTITY); + MDC.remove(ONAPLogConstants.MDCs.TARGET_SERVICE_NAME); + MDC.remove(ONAPLogConstants.MDCs.INVOKE_TIMESTAMP); + MDC.remove(ONAPLogConstants.MDCs.ERROR_CODE); + MDC.remove(ONAPLogConstants.MDCs.ERROR_DESC); + } + + public void setResponseDescription(int statusCode) { + MDC.put(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION, extractDescription(statusCode)); + } + + private String extractDescription(int statusCode) { + Response.Status responseStatus = Response.Status.fromStatusCode(statusCode); + if (responseStatus != null) { + return responseStatus.toString(); + } + CustomResponseStatus customResponseStatus = CustomResponseStatus.fromStatusCode(statusCode); + if (customResponseStatus != null) { + return customResponseStatus.toString(); + } + return String.format("Unknown description for response code %d.", statusCode); + } + + public void setErrorCode(int statusCode) { + MDC.put(ONAPLogConstants.MDCs.ERROR_CODE, String.valueOf(statusCode)); + } + + public void setErrorDescription(int statusCode) { + MDC.put(ONAPLogConstants.MDCs.ERROR_DESC, extractDescription(statusCode)); + } + + public String getProperty(String property) { + String propertyValue = System.getProperty(property); + if (propertyValue == null || propertyValue.isEmpty()) { + propertyValue = System.getenv(property); + if (propertyValue == null || propertyValue.isEmpty()) { + propertyValue = Constants.DefaultValues.UNKNOWN; + } + } + return propertyValue; + } + + protected String getBasicAuthUserName(SimpleMap headers) { + String encodedAuthorizationValue = headers.get(HttpHeaders.AUTHORIZATION); + if (encodedAuthorizationValue != null && encodedAuthorizationValue.startsWith("Basic")) { + try { + // This will strip the word Basic and single space + encodedAuthorizationValue = encodedAuthorizationValue.substring(6); + byte[] decodedBytes = Base64.getDecoder().decode(encodedAuthorizationValue); + String decodedString = new String(decodedBytes); + int idx = decodedString.indexOf(':'); + return decodedString.substring(0, idx); + } catch (IllegalArgumentException e) { + logger.error("could not decode basic auth value " + encodedAuthorizationValue, e); + } + } + return null; + } + } +
\ No newline at end of file diff --git a/services/src/main/java/org/onap/ccsdk/apps/filters/SimpleServletHeadersMap.java b/services/src/main/java/org/onap/ccsdk/apps/filters/SimpleServletHeadersMap.java new file mode 100644 index 00000000..244fc844 --- /dev/null +++ b/services/src/main/java/org/onap/ccsdk/apps/filters/SimpleServletHeadersMap.java @@ -0,0 +1,40 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP - Logging + * ================================================================================ + * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + + package org.onap.ccsdk.apps.filters; + + import org.onap.logging.filter.base.SimpleMap; + +import jakarta.servlet.http.HttpServletRequest; + + public class SimpleServletHeadersMap implements SimpleMap { + private HttpServletRequest request; + + public SimpleServletHeadersMap(HttpServletRequest request) { + this.request = request; + } + + @Override + public String get(String key) { + return request.getHeader(key); + } + + } +
\ No newline at end of file |