Fix the X-InvocationID, removed ONAP-
[vid.git] / vid-app-common / src / main / java / org / onap / vid / logging / VidLoggerAspect.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.vid.logging;
22
23 import static com.att.eelf.configuration.Configuration.MDC_SERVER_FQDN;
24 import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
25
26 import java.net.InetAddress;
27 import java.net.UnknownHostException;
28 import java.util.Arrays;
29 import javax.servlet.http.HttpServletRequest;
30 import org.apache.commons.lang3.StringUtils;
31 import org.aspectj.lang.ProceedingJoinPoint;
32 import org.aspectj.lang.annotation.Around;
33 import org.aspectj.lang.annotation.Aspect;
34 import org.aspectj.lang.annotation.Pointcut;
35 import org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice;
36 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
37 import org.onap.portalsdk.core.service.AppService;
38 import org.onap.portalsdk.core.util.SystemProperties;
39 import org.onap.portalsdk.core.web.support.UserUtils;
40 import org.onap.vid.controller.ControllersUtils;
41 import org.onap.vid.utils.SystemPropertiesWrapper;
42 import org.springframework.beans.factory.annotation.Autowired;
43 import org.springframework.web.context.request.RequestContextHolder;
44 import org.springframework.web.context.request.ServletRequestAttributes;
45
46
47 @Aspect
48 @org.springframework.context.annotation.Configuration
49 public class VidLoggerAspect {
50
51     private String canonicalHostName;
52     private final ControllersUtils controllersUtils;
53     private final String appName;
54
55     private final EELFLoggerAdvice advice;
56
57     @Autowired
58     public VidLoggerAspect(EELFLoggerAdvice advice, SystemPropertiesWrapper systemPropertiesWrapper,
59         AppService appService) {
60         try {
61             final InetAddress localHost = InetAddress.getLocalHost();
62             canonicalHostName = localHost.getCanonicalHostName();
63         } catch (UnknownHostException e) {
64             // YOLO
65             canonicalHostName = null;
66         }
67         this.advice = advice;
68         this.controllersUtils = new ControllersUtils(systemPropertiesWrapper);
69
70         this.appName = defaultIfEmpty(appService.getDefaultAppName(), SystemProperties.SDK_NAME);
71     }
72
73     @Pointcut("execution(public * org.onap.vid.controller..*Controller.*(..))")
74     public void vidControllers() {}
75
76     @Around("vidControllers() && (" +
77             "  @within(org.onap.portalsdk.core.logging.aspect.AuditLog)" +
78             "  || @annotation(org.onap.portalsdk.core.logging.aspect.AuditLog)" +
79             "  || @annotation(org.springframework.web.bind.annotation.RequestMapping)" +
80             ")")
81     public Object logAuditMethodClassAround(ProceedingJoinPoint joinPoint) throws Throwable {
82         return logAroundMethod(joinPoint, SystemProperties.SecurityEventTypeEnum.INCOMING_REST_MESSAGE);
83     }
84
85     private Object logAroundMethod(ProceedingJoinPoint joinPoint, SystemProperties.SecurityEventTypeEnum securityEventType) throws Throwable {
86         //Before
87         Object[] passOnArgs = new Object[] {joinPoint.getSignature().getDeclaringType().getName(),joinPoint.getSignature().getName()};
88         Object[] returnArgs = advice.before(securityEventType, fabricateArgsWithNull(), passOnArgs);
89
90         HttpServletRequest httpServletRequest = httpServletRequestOrNull(joinPoint);
91         fixSetRequestBasedDefaultsIntoGlobalLoggingContext(httpServletRequest,
92             joinPoint.getSignature().getDeclaringType().getName());
93
94         fixServerFqdnInMDC();
95
96         //Execute the actual method
97         Object result;
98         String restStatus = "COMPLETE";
99         try {
100             result = joinPoint.proceed();
101         } catch(Exception e) {
102             restStatus = "ERROR";
103             throw e;
104         } finally {
105             fixStatusCodeInMDC(restStatus);
106             advice.after(securityEventType, restStatus, joinPoint.getArgs(), returnArgs, passOnArgs);
107         }
108
109         return result;
110     }
111
112     // Set the status code into MDC *before* the metrics log is written by advice.after()
113     private void fixStatusCodeInMDC(String restStatus) {
114         EELFLoggerDelegate.mdcPut(SystemProperties.STATUS_CODE, restStatus);
115     }
116
117     /**
118      * Returns an array with a single entry with a null value. This will stop org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice.before
119      * from throwing on ArrayIndexOutOfBound, and also prevent SessionExpiredException.
120      */
121     private Object[] fabricateArgsWithNull() {
122         return new Object[]{null};
123     }
124
125     /**
126      * Finds the first joinPoint's param which is an HttpServletRequest. If not found, use Spring's RequestContextHolder
127      * to retrieve it.
128      *
129      * @return null or the current httpServletRequest
130      */
131     private HttpServletRequest httpServletRequestOrNull(ProceedingJoinPoint joinPoint) {
132         final Object httpServletRequest = Arrays.stream(joinPoint.getArgs())
133             .filter(param -> param instanceof HttpServletRequest)
134             .findFirst()
135             .orElseGet(() -> {
136                 try {
137                     return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
138                 } catch (Exception e) { // ClassCast, IllegalState, etc.
139                     return null;
140                 }
141             });
142
143         return (HttpServletRequest) httpServletRequest;
144     }
145
146     /**
147      * Mimics a part from org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice.before, but with much more carefulness
148      * of exceptions and defaults. Main difference is that if no session, function does not throw. It just fallback to
149      * an empty loginId.
150      */
151     private void fixSetRequestBasedDefaultsIntoGlobalLoggingContext(HttpServletRequest httpServletRequest, String className) {
152         if (httpServletRequest != null) {
153
154             EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(className);
155             String requestId = UserUtils.getRequestId(httpServletRequest);
156             String loginId = controllersUtils.extractUserId(httpServletRequest);
157
158             logger.setRequestBasedDefaultsIntoGlobalLoggingContext(httpServletRequest, appName, requestId, loginId);
159         }
160     }
161
162     // Override the non-canonical hostname set by EELFLoggerDelegate::setGlobalLoggingContext()
163     // that was invoked by advice.before() (and some other SDK cases)
164     private void fixServerFqdnInMDC() {
165         if (!StringUtils.isBlank(canonicalHostName)) {
166             EELFLoggerDelegate.mdcPut(MDC_SERVER_FQDN, canonicalHostName);
167         }
168     }
169
170 }