47b4f9ab1c17e06da97dbe1bd7ee16c36db1a5b2
[clamp.git] / src / main / java / org / onap / clamp / clds / util / LoggingUtils.java
1 /*-\r
2  * ============LICENSE_START=======================================================\r
3  * ONAP CLAMP\r
4  * ================================================================================\r
5  * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights\r
6  *                             reserved.\r
7  * ================================================================================\r
8  * Licensed under the Apache License, Version 2.0 (the "License");\r
9  * you may not use this file except in compliance with the License.\r
10  * You may obtain a copy of the License at\r
11  *\r
12  * http://www.apache.org/licenses/LICENSE-2.0\r
13  *\r
14  * Unless required by applicable law or agreed to in writing, software\r
15  * distributed under the License is distributed on an "AS IS" BASIS,\r
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
17  * See the License for the specific language governing permissions and\r
18  * limitations under the License.\r
19  * ============LICENSE_END============================================\r
20  * ===================================================================\r
21  *\r
22  */\r
23 \r
24 package org.onap.clamp.clds.util;\r
25 \r
26 import com.att.eelf.configuration.EELFLogger;\r
27 import com.att.eelf.configuration.EELFManager;\r
28 \r
29 import java.net.HttpURLConnection;\r
30 import java.net.InetAddress;\r
31 import java.net.UnknownHostException;\r
32 import java.text.DateFormat;\r
33 import java.text.SimpleDateFormat;\r
34 import java.time.ZoneOffset;\r
35 import java.time.ZonedDateTime;\r
36 import java.time.format.DateTimeFormatter;\r
37 import java.util.Date;\r
38 import java.util.TimeZone;\r
39 import java.util.UUID;\r
40 \r
41 import javax.net.ssl.HttpsURLConnection;\r
42 import javax.servlet.http.HttpServletRequest;\r
43 import javax.validation.constraints.NotNull;\r
44 \r
45 import org.onap.clamp.clds.service.DefaultUserNameHandler;\r
46 import org.slf4j.MDC;\r
47 import org.slf4j.event.Level;\r
48 import org.springframework.security.core.context.SecurityContextHolder;\r
49 \r
50 /**\r
51  * This class handles the special info that appear in the log, like RequestID,\r
52  * time context, ...\r
53  */\r
54 public class LoggingUtils {\r
55     protected static final EELFLogger logger = EELFManager.getInstance().getLogger(LoggingUtils.class);\r
56 \r
57     private static final DateFormat DATE_FORMAT = createDateFormat();\r
58 \r
59     /** String constant for messages <tt>ENTERING</tt>, <tt>EXITING</tt>, etc. */\r
60     private static final String EMPTY_MESSAGE = "";\r
61 \r
62     /** Logger delegate. */\r
63     private EELFLogger mLogger;\r
64     /** Automatic UUID, overrideable per adapter or per invocation. */\r
65     private static UUID sInstanceUUID = UUID.randomUUID();\r
66     /**\r
67      * Constructor\r
68      */\r
69     public LoggingUtils(final EELFLogger loggerP) {\r
70         this.mLogger = checkNotNull(loggerP);\r
71     }\r
72 \r
73     /**\r
74      * Set request related logging variables in thread local data via MDC\r
75      *\r
76      * @param service Service Name of API (ex. "PUT template")\r
77      * @param partner Partner name (client or user invoking API)\r
78      */\r
79     public static void setRequestContext(String service, String partner) {\r
80         MDC.put("RequestId", UUID.randomUUID().toString());\r
81         MDC.put("ServiceName", service);\r
82         MDC.put("PartnerName", partner);\r
83         //Defaulting to HTTP/1.1 protocol\r
84         MDC.put("Protocol", "HTTP/1.1");\r
85         try {\r
86             MDC.put("ServerFQDN", InetAddress.getLocalHost().getCanonicalHostName());\r
87             MDC.put("ServerIPAddress", InetAddress.getLocalHost().getHostAddress());\r
88         } catch (UnknownHostException e) {\r
89             logger.error("Failed to initiate setRequestContext", e);\r
90         }\r
91     }\r
92 \r
93     /**\r
94      * Set time related logging variables in thread local data via MDC.\r
95      *\r
96      * @param beginTimeStamp Start time\r
97      * @param endTimeStamp End time\r
98      */\r
99     public static void setTimeContext(@NotNull Date beginTimeStamp, @NotNull Date endTimeStamp) {\r
100         MDC.put("BeginTimestamp", generateTimestampStr(beginTimeStamp));\r
101         MDC.put("EndTimestamp", generateTimestampStr(endTimeStamp));\r
102         MDC.put("ElapsedTime", String.valueOf(endTimeStamp.getTime() - beginTimeStamp.getTime()));\r
103     }\r
104 \r
105     /**\r
106      * Set response related logging variables in thread local data via MDC.\r
107      *\r
108      * @param code Response code ("0" indicates success)\r
109      * @param description Response description\r
110      * @param className class name of invoking class\r
111      */\r
112     public static void setResponseContext(String code, String description, String className) {\r
113         MDC.put("ResponseCode", code);\r
114         MDC.put("StatusCode", code.equals("0") ? "COMPLETE" : "ERROR");\r
115         MDC.put("ResponseDescription", description != null ? description : "");\r
116         MDC.put("ClassName", className != null ? className : "");\r
117     }\r
118 \r
119     /**\r
120      * Set target related logging variables in thread local data via MDC\r
121      *\r
122      * @param targetEntity Target entity (an external/sub component, for ex. "sdc")\r
123      * @param targetServiceName Target service name (name of API invoked on target)\r
124      */\r
125     public static void setTargetContext(String targetEntity, String targetServiceName) {\r
126         MDC.put("TargetEntity", targetEntity != null ? targetEntity : "");\r
127         MDC.put("TargetServiceName", targetServiceName != null ? targetServiceName : "");\r
128     }\r
129 \r
130     /**\r
131      * Set error related logging variables in thread local data via MDC.\r
132      *\r
133      * @param code Error code\r
134      * @param description Error description\r
135      */\r
136     public static void setErrorContext(String code, String description) {\r
137         MDC.put("ErrorCode", code);\r
138         MDC.put("ErrorDescription", description != null ? description : "");\r
139     }\r
140 \r
141     private static String generateTimestampStr(Date timeStamp) {\r
142         return DATE_FORMAT.format(timeStamp);\r
143     }\r
144 \r
145     /**\r
146      * Get a previously stored RequestID for the thread local data via MDC. If\r
147      * one was not previously stored, generate one, store it, and return that\r
148      * one.\r
149      *\r
150      * @return A string with the request ID\r
151      */\r
152     public static String getRequestId() {\r
153         String requestId = MDC.get(ONAPLogConstants.MDCs.REQUEST_ID);\r
154         if (requestId == null || requestId.isEmpty()) {\r
155             requestId = UUID.randomUUID().toString();\r
156             MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestId);\r
157         }\r
158         return requestId;\r
159     }\r
160 \r
161     private static DateFormat createDateFormat() {\r
162         DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");\r
163         dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));\r
164         return dateFormat;\r
165     }\r
166 \r
167 \r
168 \r
169     /*********************************************************************************************\r
170      * Method for ONAP Application Logging Specification v1.2\r
171      ********************************************************************************************/\r
172 \r
173     /**\r
174      * Report <tt>ENTERING</tt> marker.\r
175      *\r
176      * @param request non-null incoming request (wrapper).\r
177      * @return this.\r
178      */\r
179     public void entering(HttpServletRequest request, String serviceName) {\r
180         MDC.clear();\r
181         checkNotNull(request);\r
182         // Extract MDC values from standard HTTP headers.\r
183         final String requestID = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.REQUEST_ID));\r
184         final String invocationID = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.INVOCATION_ID));\r
185         final String partnerName = defaultToEmpty(request.getHeader(ONAPLogConstants.Headers.PARTNER_NAME));\r
186 \r
187         // Default the partner name to the user name used to login to clamp\r
188         if (partnerName.equalsIgnoreCase(EMPTY_MESSAGE)) {\r
189             MDC.put(ONAPLogConstants.MDCs.PARTNER_NAME, new DefaultUserNameHandler().retrieveUserName(SecurityContextHolder.getContext()));\r
190         }\r
191 \r
192         // Set standard MDCs. Override this entire method if you want to set\r
193         // others, OR set them BEFORE or AFTER the invocation of #entering,\r
194         // depending on where you need them to appear, OR extend the\r
195         // ServiceDescriptor to add them.\r
196         MDC.put(ONAPLogConstants.MDCs.ENTRY_TIMESTAMP,\r
197             ZonedDateTime.now(ZoneOffset.UTC)\r
198             .format(DateTimeFormatter.ISO_INSTANT));\r
199         MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestID);\r
200         MDC.put(ONAPLogConstants.MDCs.INVOCATION_ID, invocationID);\r
201         MDC.put(ONAPLogConstants.MDCs.CLIENT_IP_ADDRESS, defaultToEmpty(request.getRemoteAddr()));\r
202         MDC.put(ONAPLogConstants.MDCs.SERVER_FQDN, defaultToEmpty(request.getServerName()));\r
203         MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(sInstanceUUID));\r
204 \r
205         // Default the service name to the requestURI, in the event that\r
206         // no value has been provided.\r
207         if (serviceName == null ||\r
208             serviceName.equalsIgnoreCase(EMPTY_MESSAGE)) {\r
209             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());\r
210         } else {\r
211             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, serviceName);\r
212         }\r
213 \r
214         this.mLogger.info(ONAPLogConstants.Markers.ENTRY);\r
215     }\r
216 \r
217     /**\r
218      * Report <tt>EXITING</tt> marker.\r
219      *\r
220      * @return this.\r
221      */\r
222     public void exiting(String code, String descrption, Level severity, ONAPLogConstants.ResponseStatus status) {\r
223         try {\r
224             MDC.put(ONAPLogConstants.MDCs.RESPONSE_CODE, defaultToEmpty(code));\r
225             MDC.put(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION, defaultToEmpty(descrption));\r
226             MDC.put(ONAPLogConstants.MDCs.RESPONSE_SEVERITY, defaultToEmpty(severity));\r
227             MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, defaultToEmpty(status));\r
228             this.mLogger.info(ONAPLogConstants.Markers.EXIT);\r
229         }\r
230         finally {\r
231             MDC.clear();\r
232         }\r
233     }\r
234 \r
235     /**\r
236      * Report pending invocation with <tt>INVOKE</tt> marker,\r
237      * setting standard ONAP logging headers automatically.\r
238      *\r
239      * @param builder request builder, for setting headers.\r
240      * @param sync whether synchronous, nullable.\r
241      * @return invocation ID to be passed with invocation.\r
242      */\r
243     public HttpURLConnection invoke(final HttpURLConnection con, String targetEntity, String targetServiceName) {\r
244         final String invocationID = UUID.randomUUID().toString();\r
245 \r
246         // Set standard HTTP headers on (southbound request) builder.\r
247         con.setRequestProperty(ONAPLogConstants.Headers.REQUEST_ID,\r
248             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.REQUEST_ID)));\r
249         con.setRequestProperty(ONAPLogConstants.Headers.INVOCATION_ID,\r
250             invocationID);\r
251         con.setRequestProperty(ONAPLogConstants.Headers.PARTNER_NAME,\r
252             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.PARTNER_NAME)));\r
253 \r
254         invokeContext(targetEntity, targetServiceName, invocationID);\r
255 \r
256         // Log INVOKE*, with the invocationID as the message body.\r
257         // (We didn't really want this kind of behavior in the standard,\r
258         // but is it worse than new, single-message MDC?)\r
259         this.mLogger.info(ONAPLogConstants.Markers.INVOKE);\r
260         this.mLogger.info(ONAPLogConstants.Markers.INVOKE_SYNC + "{"+ invocationID +"}");\r
261         return con;\r
262     }\r
263 \r
264     /**\r
265      * Report pending invocation with <tt>INVOKE</tt> marker,\r
266      * setting standard ONAP logging headers automatically.\r
267      *\r
268      * @param builder request builder, for setting headers.\r
269      * @param sync whether synchronous, nullable.\r
270      * @return invocation ID to be passed with invocation.\r
271      */\r
272     public HttpsURLConnection invokeHttps(final HttpsURLConnection con, String targetEntity, String targetServiceName) {\r
273         final String invocationID = UUID.randomUUID().toString();\r
274 \r
275         // Set standard HTTP headers on (southbound request) builder.\r
276         con.setRequestProperty(ONAPLogConstants.Headers.REQUEST_ID,\r
277             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.REQUEST_ID)));\r
278         con.setRequestProperty(ONAPLogConstants.Headers.INVOCATION_ID,\r
279             invocationID);\r
280         con.setRequestProperty(ONAPLogConstants.Headers.PARTNER_NAME,\r
281             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.PARTNER_NAME)));\r
282 \r
283         invokeContext(targetEntity, targetServiceName, invocationID);\r
284 \r
285         // Log INVOKE*, with the invocationID as the message body.\r
286         // (We didn't really want this kind of behavior in the standard,\r
287         // but is it worse than new, single-message MDC?)\r
288         this.mLogger.info(ONAPLogConstants.Markers.INVOKE);\r
289         this.mLogger.info(ONAPLogConstants.Markers.INVOKE_SYNC + "{"+ invocationID +"}");\r
290         return con;\r
291     }\r
292 \r
293     public void invokeReturn() {\r
294         // Add the Invoke-return marker and clear the needed MDC\r
295         this.mLogger.info(ONAPLogConstants.Markers.INVOKE_RETURN);\r
296         invokeReturnContext();\r
297     }\r
298 \r
299     /**\r
300      * Dependency-free nullcheck.\r
301      *\r
302      * @param in to be checked.\r
303      * @param <T> argument (and return) type.\r
304      * @return input arg.\r
305      */\r
306     private static <T> T checkNotNull(final T in) {\r
307         if (in == null) {\r
308             throw new NullPointerException();\r
309         }\r
310         return in;\r
311     }\r
312 \r
313     /**\r
314      * Dependency-free string default.\r
315      *\r
316      * @param in to be filtered.\r
317      * @return input string or null.\r
318      */\r
319     private static String defaultToEmpty(final Object in) {\r
320         if (in == null) {\r
321             return "";\r
322         }\r
323         return in.toString();\r
324     }\r
325 \r
326     /**\r
327      * Dependency-free string default.\r
328      *\r
329      * @param in to be filtered.\r
330      * @return input string or null.\r
331      */\r
332     private static String defaultToUUID(final String in) {\r
333         if (in == null) {\r
334             return UUID.randomUUID().toString();\r
335         }\r
336         return in;\r
337     }\r
338 \r
339     /**\r
340      * Set target related logging variables in thread local data via MDC\r
341      *\r
342      * @param targetEntity Target entity (an external/sub component, for ex. "sdc")\r
343      * @param targetServiceName Target service name (name of API invoked on target)\r
344      * @param invocationId The invocation ID\r
345      */\r
346     private void invokeContext (String targetEntity, String targetServiceName, String invocationID) {\r
347         MDC.put(ONAPLogConstants.MDCs.TARGET_ENTITY, defaultToEmpty(targetEntity));\r
348         MDC.put(ONAPLogConstants.MDCs.TARGET_SERVICE_NAME, defaultToEmpty(targetServiceName));\r
349         MDC.put(ONAPLogConstants.MDCs.INVOCATIONID_OUT, invocationID);\r
350         MDC.put(ONAPLogConstants.MDCs.INVOKE_TIMESTAMP,\r
351             ZonedDateTime.now(ZoneOffset.UTC)\r
352             .format(DateTimeFormatter.ISO_INSTANT));\r
353     }\r
354 \r
355     /**\r
356      * Clear target related logging variables in thread local data via MDC\r
357      *\r
358      */\r
359     private void invokeReturnContext () {\r
360         MDC.remove(ONAPLogConstants.MDCs.TARGET_ENTITY);\r
361         MDC.remove(ONAPLogConstants.MDCs.TARGET_SERVICE_NAME);\r
362         MDC.remove(ONAPLogConstants.MDCs.INVOCATIONID_OUT);\r
363     }\r
364 }\r