309ead40c395429473d6f6343b20d2e343127817
[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         fixSetRequestBasedDefaultsIntoGlobalLoggingContext(httpServletRequestOrNull(joinPoint),
91             joinPoint.getSignature().getDeclaringType().getName());
92
93         fixServerFqdnInMDC();
94
95         //Execute the actual method
96         Object result;
97         String restStatus = "COMPLETE";
98         try {
99             result = joinPoint.proceed();
100         } catch(Exception e) {
101             restStatus = "ERROR";
102             throw e;
103         } finally {
104             fixStatusCodeInMDC(restStatus);
105             advice.after(securityEventType, restStatus, joinPoint.getArgs(), returnArgs, passOnArgs);
106         }
107
108         return result;
109     }
110
111     // Set the status code into MDC *before* the metrics log is written by advice.after()
112     private void fixStatusCodeInMDC(String restStatus) {
113         EELFLoggerDelegate.mdcPut(SystemProperties.STATUS_CODE, restStatus);
114     }
115
116     /**
117      * Returns an array with a single entry with a null value. This will stop org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice.before
118      * from throwing on ArrayIndexOutOfBound, and also prevent SessionExpiredException.
119      */
120     private Object[] fabricateArgsWithNull() {
121         return new Object[]{null};
122     }
123
124     /**
125      * Finds the first joinPoint's param which is an HttpServletRequest. If not found, use Spring's RequestContextHolder
126      * to retrieve it.
127      *
128      * @return null or the current httpServletRequest
129      */
130     private HttpServletRequest httpServletRequestOrNull(ProceedingJoinPoint joinPoint) {
131         final Object httpServletRequest = Arrays.stream(joinPoint.getArgs())
132             .filter(param -> param instanceof HttpServletRequest)
133             .findFirst()
134             .orElseGet(() -> {
135                 try {
136                     return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
137                 } catch (Exception e) { // ClassCast, IllegalState, etc.
138                     return null;
139                 }
140             });
141
142         return (HttpServletRequest) httpServletRequest;
143     }
144
145     /**
146      * Mimics a part from org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice.before, but with much more carefulness
147      * of exceptions and defaults. Main difference is that if no session, function does not throw. It just fallback to
148      * an empty loginId.
149      */
150     private void fixSetRequestBasedDefaultsIntoGlobalLoggingContext(HttpServletRequest httpServletRequest, String className) {
151         if (httpServletRequest != null) {
152
153             EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(className);
154             String requestId = UserUtils.getRequestId(httpServletRequest);
155             String loginId = controllersUtils.extractUserId(httpServletRequest);
156
157             logger.setRequestBasedDefaultsIntoGlobalLoggingContext(httpServletRequest, appName, requestId, loginId);
158         }
159     }
160
161     // Override the non-canonical hostname set by EELFLoggerDelegate::setGlobalLoggingContext()
162     // that was invoked by advice.before() (and some other SDK cases)
163     private void fixServerFqdnInMDC() {
164         if (!StringUtils.isBlank(canonicalHostName)) {
165             EELFLoggerDelegate.mdcPut(MDC_SERVER_FQDN, canonicalHostName);
166         }
167     }
168
169 }