use logging interceptor in SDC client
[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.logging.ref.slf4j.ONAPLogConstants;
36 import org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice;
37 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
38 import org.onap.portalsdk.core.service.AppService;
39 import org.onap.portalsdk.core.util.SystemProperties;
40 import org.onap.portalsdk.core.web.support.UserUtils;
41 import org.onap.vid.controller.ControllersUtils;
42 import org.onap.vid.utils.SystemPropertiesWrapper;
43 import org.slf4j.MDC;
44 import org.springframework.beans.factory.annotation.Autowired;
45 import org.springframework.web.context.request.RequestContextHolder;
46 import org.springframework.web.context.request.ServletRequestAttributes;
47
48
49 @Aspect
50 @org.springframework.context.annotation.Configuration
51 public class VidLoggerAspect {
52
53     private String canonicalHostName;
54     private final ControllersUtils controllersUtils;
55     private final String appName;
56
57     private final EELFLoggerAdvice advice;
58
59     @Autowired
60     public VidLoggerAspect(EELFLoggerAdvice advice, SystemPropertiesWrapper systemPropertiesWrapper,
61         AppService appService) {
62         try {
63             final InetAddress localHost = InetAddress.getLocalHost();
64             canonicalHostName = localHost.getCanonicalHostName();
65         } catch (UnknownHostException e) {
66             // YOLO
67             canonicalHostName = null;
68         }
69         this.advice = advice;
70         this.controllersUtils = new ControllersUtils(systemPropertiesWrapper);
71
72         this.appName = defaultIfEmpty(appService.getDefaultAppName(), SystemProperties.SDK_NAME);
73     }
74
75     @Pointcut("execution(public * org.onap.vid.controller..*Controller.*(..))")
76     public void vidControllers() {}
77
78     @Around("vidControllers() && (" +
79             "  @within(org.onap.portalsdk.core.logging.aspect.AuditLog)" +
80             "  || @annotation(org.onap.portalsdk.core.logging.aspect.AuditLog)" +
81             "  || @annotation(org.springframework.web.bind.annotation.RequestMapping)" +
82             ")")
83     public Object logAuditMethodClassAround(ProceedingJoinPoint joinPoint) throws Throwable {
84         return logAroundMethod(joinPoint, SystemProperties.SecurityEventTypeEnum.INCOMING_REST_MESSAGE);
85     }
86
87     private Object logAroundMethod(ProceedingJoinPoint joinPoint, SystemProperties.SecurityEventTypeEnum securityEventType) throws Throwable {
88         //Before
89         Object[] passOnArgs = new Object[] {joinPoint.getSignature().getDeclaringType().getName(),joinPoint.getSignature().getName()};
90         Object[] returnArgs = advice.before(securityEventType, fabricateArgsWithNull(), passOnArgs);
91
92         HttpServletRequest httpServletRequest = httpServletRequestOrNull(joinPoint);
93         fixSetRequestBasedDefaultsIntoGlobalLoggingContext(httpServletRequest,
94             joinPoint.getSignature().getDeclaringType().getName());
95         addRequestIdToMdcForMetricFilter(httpServletRequest);
96         fixServerFqdnInMDC();
97
98         //Execute the actual method
99         Object result;
100         String restStatus = "COMPLETE";
101         try {
102             result = joinPoint.proceed();
103         } catch(Exception e) {
104             restStatus = "ERROR";
105             throw e;
106         } finally {
107             fixStatusCodeInMDC(restStatus);
108             advice.after(securityEventType, restStatus, joinPoint.getArgs(), returnArgs, passOnArgs);
109         }
110
111         return result;
112     }
113
114     //prepare MDC for org.onap.logging.filter.base.AbstractMetricLogFilter
115     private void addRequestIdToMdcForMetricFilter(HttpServletRequest httpServletRequest) {
116         if (httpServletRequest!=null) {
117             String requestId = UserUtils.getRequestId(httpServletRequest);
118             MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestId);
119         }
120     }
121
122     // Set the status code into MDC *before* the metrics log is written by advice.after()
123     private void fixStatusCodeInMDC(String restStatus) {
124         EELFLoggerDelegate.mdcPut(SystemProperties.STATUS_CODE, restStatus);
125     }
126
127     /**
128      * Returns an array with a single entry with a null value. This will stop org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice.before
129      * from throwing on ArrayIndexOutOfBound, and also prevent SessionExpiredException.
130      */
131     private Object[] fabricateArgsWithNull() {
132         return new Object[]{null};
133     }
134
135     /**
136      * Finds the first joinPoint's param which is an HttpServletRequest. If not found, use Spring's RequestContextHolder
137      * to retrieve it.
138      *
139      * @return null or the current httpServletRequest
140      */
141     private HttpServletRequest httpServletRequestOrNull(ProceedingJoinPoint joinPoint) {
142         final Object httpServletRequest = Arrays.stream(joinPoint.getArgs())
143             .filter(param -> param instanceof HttpServletRequest)
144             .findFirst()
145             .orElseGet(() -> {
146                 try {
147                     return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
148                 } catch (Exception e) { // ClassCast, IllegalState, etc.
149                     return null;
150                 }
151             });
152
153         return (HttpServletRequest) httpServletRequest;
154     }
155
156     /**
157      * Mimics a part from org.onap.portalsdk.core.logging.aspect.EELFLoggerAdvice.before, but with much more carefulness
158      * of exceptions and defaults. Main difference is that if no session, function does not throw. It just fallback to
159      * an empty loginId.
160      */
161     private void fixSetRequestBasedDefaultsIntoGlobalLoggingContext(HttpServletRequest httpServletRequest, String className) {
162         if (httpServletRequest != null) {
163
164             EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(className);
165             String requestId = UserUtils.getRequestId(httpServletRequest);
166             String loginId = controllersUtils.extractUserId(httpServletRequest);
167
168             logger.setRequestBasedDefaultsIntoGlobalLoggingContext(httpServletRequest, appName, requestId, loginId);
169
170         }
171     }
172
173     // Override the non-canonical hostname set by EELFLoggerDelegate::setGlobalLoggingContext()
174     // that was invoked by advice.before() (and some other SDK cases)
175     private void fixServerFqdnInMDC() {
176         if (!StringUtils.isBlank(canonicalHostName)) {
177             EELFLoggerDelegate.mdcPut(MDC_SERVER_FQDN, canonicalHostName);
178         }
179     }
180
181 }