08bb976080939598d5d803dd68cf3998153b6300
[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     /**\r
68      * Constructor.\r
69      */\r
70     public LoggingUtils(final EELFLogger loggerP) {\r
71         this.mlogger = checkNotNull(loggerP);\r
72     }\r
73 \r
74     /**\r
75      * Set request related logging variables in thread local data via MDC\r
76      *\r
77      * @param service Service Name of API (ex. "PUT template")\r
78      * @param partner Partner name (client or user invoking API)\r
79      */\r
80     public static void setRequestContext(String service, String partner) {\r
81         MDC.put("RequestId", UUID.randomUUID().toString());\r
82         MDC.put("ServiceName", service);\r
83         MDC.put("PartnerName", partner);\r
84         //Defaulting to HTTP/1.1 protocol\r
85         MDC.put("Protocol", "HTTP/1.1");\r
86         try {\r
87             MDC.put("ServerFQDN", InetAddress.getLocalHost().getCanonicalHostName());\r
88             MDC.put("ServerIPAddress", InetAddress.getLocalHost().getHostAddress());\r
89         } catch (UnknownHostException e) {\r
90             logger.error("Failed to initiate setRequestContext", e);\r
91         }\r
92     }\r
93 \r
94     /**\r
95      * Set time related logging variables in thread local data via MDC.\r
96      *\r
97      * @param beginTimeStamp Start time\r
98      * @param endTimeStamp End time\r
99      */\r
100     public static void setTimeContext(@NotNull Date beginTimeStamp, @NotNull Date endTimeStamp) {\r
101         MDC.put("BeginTimestamp", generateTimestampStr(beginTimeStamp));\r
102         MDC.put("EndTimestamp", generateTimestampStr(endTimeStamp));\r
103         MDC.put("ElapsedTime", String.valueOf(endTimeStamp.getTime() - beginTimeStamp.getTime()));\r
104     }\r
105 \r
106     /**\r
107      * Set response related logging variables in thread local data via MDC.\r
108      *\r
109      * @param code Response code ("0" indicates success)\r
110      * @param description Response description\r
111      * @param className class name of invoking class\r
112      */\r
113     public static void setResponseContext(String code, String description, String className) {\r
114         MDC.put("ResponseCode", code);\r
115         MDC.put("StatusCode", code.equals("0") ? "COMPLETE" : "ERROR");\r
116         MDC.put("ResponseDescription", description != null ? description : "");\r
117         MDC.put("ClassName", className != null ? className : "");\r
118     }\r
119 \r
120     /**\r
121      * Set target related logging variables in thread local data via MDC\r
122      *\r
123      * @param targetEntity Target entity (an external/sub component, for ex. "sdc")\r
124      * @param targetServiceName Target service name (name of API invoked on target)\r
125      */\r
126     public static void setTargetContext(String targetEntity, String targetServiceName) {\r
127         MDC.put("TargetEntity", targetEntity != null ? targetEntity : "");\r
128         MDC.put("TargetServiceName", targetServiceName != null ? targetServiceName : "");\r
129     }\r
130 \r
131     /**\r
132      * Set error related logging variables in thread local data via MDC.\r
133      *\r
134      * @param code Error code\r
135      * @param description Error description\r
136      */\r
137     public static void setErrorContext(String code, String description) {\r
138         MDC.put("ErrorCode", code);\r
139         MDC.put("ErrorDescription", description != null ? description : "");\r
140     }\r
141 \r
142     private static String generateTimestampStr(Date timeStamp) {\r
143         return DATE_FORMAT.format(timeStamp);\r
144     }\r
145 \r
146     /**\r
147      * Get a previously stored RequestID for the thread local data via MDC. If\r
148      * one was not previously stored, generate one, store it, and return that\r
149      * one.\r
150      *\r
151      * @return A string with the request ID\r
152      */\r
153     public static String getRequestId() {\r
154         String requestId = MDC.get(ONAPLogConstants.MDCs.REQUEST_ID);\r
155         if (requestId == null || requestId.isEmpty()) {\r
156             requestId = UUID.randomUUID().toString();\r
157             MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestId);\r
158         }\r
159         return requestId;\r
160     }\r
161 \r
162     private static DateFormat createDateFormat() {\r
163         DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");\r
164         dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));\r
165         return dateFormat;\r
166     }\r
167 \r
168 \r
169 \r
170     /*********************************************************************************************\r
171      * Method for ONAP Application Logging Specification v1.2\r
172      ********************************************************************************************/\r
173 \r
174     /**\r
175      * Report <tt>ENTERING</tt> marker.\r
176      *\r
177      * @param request non-null incoming request (wrapper)\r
178      * @param serviceName service name\r
179      */\r
180     public void entering(HttpServletRequest request, String serviceName) {\r
181         MDC.clear();\r
182         checkNotNull(request);\r
183         // Extract MDC values from standard HTTP headers.\r
184         final String requestId = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.REQUEST_ID));\r
185         final String invocationId = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.INVOCATION_ID));\r
186         final String partnerName = defaultToEmpty(request.getHeader(ONAPLogConstants.Headers.PARTNER_NAME));\r
187 \r
188         // Default the partner name to the user name used to login to clamp\r
189         if (partnerName.equalsIgnoreCase(EMPTY_MESSAGE)) {\r
190             MDC.put(ONAPLogConstants.MDCs.PARTNER_NAME, new DefaultUserNameHandler()\r
191                     .retrieveUserName(SecurityContextHolder.getContext()));\r
192         }\r
193 \r
194         // Set standard MDCs. Override this entire method if you want to set\r
195         // others, OR set them BEFORE or AFTER the invocation of #entering,\r
196         // depending on where you need them to appear, OR extend the\r
197         // ServiceDescriptor to add them.\r
198         MDC.put(ONAPLogConstants.MDCs.ENTRY_TIMESTAMP,\r
199             ZonedDateTime.now(ZoneOffset.UTC)\r
200             .format(DateTimeFormatter.ISO_INSTANT));\r
201         MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestId);\r
202         MDC.put(ONAPLogConstants.MDCs.INVOCATION_ID, invocationId);\r
203         MDC.put(ONAPLogConstants.MDCs.CLIENT_IP_ADDRESS, defaultToEmpty(request.getRemoteAddr()));\r
204         MDC.put(ONAPLogConstants.MDCs.SERVER_FQDN, defaultToEmpty(request.getServerName()));\r
205         MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(sInstanceUUID));\r
206 \r
207         // Default the service name to the requestURI, in the event that\r
208         // no value has been provided.\r
209         if (serviceName == null\r
210                 || serviceName.equalsIgnoreCase(EMPTY_MESSAGE)) {\r
211             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());\r
212         } else {\r
213             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, serviceName);\r
214         }\r
215 \r
216         this.mlogger.info(ONAPLogConstants.Markers.ENTRY);\r
217     }\r
218 \r
219     /**\r
220      * Report <tt>EXITING</tt> marker.\r
221      *\r
222      * @param code response code\r
223      * @param descrption response description\r
224      * @param severity response severity\r
225      * @param status response status code\r
226      */\r
227     public void exiting(String code, String descrption, Level severity, ONAPLogConstants.ResponseStatus status) {\r
228         try {\r
229             MDC.put(ONAPLogConstants.MDCs.RESPONSE_CODE, defaultToEmpty(code));\r
230             MDC.put(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION, defaultToEmpty(descrption));\r
231             MDC.put(ONAPLogConstants.MDCs.RESPONSE_SEVERITY, defaultToEmpty(severity));\r
232             MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, defaultToEmpty(status));\r
233             this.mlogger.info(ONAPLogConstants.Markers.EXIT);\r
234         }\r
235         finally {\r
236             MDC.clear();\r
237         }\r
238     }\r
239 \r
240     /**\r
241      * Report pending invocation with <tt>INVOKE</tt> marker,\r
242      * setting standard ONAP logging headers automatically.\r
243      *\r
244      * @param con http URL connection\r
245      * @param targetEntity target entity\r
246      * @param targetServiceName target service name\r
247      * @return invocation ID to be passed with invocation\r
248      */\r
249     public HttpURLConnection invoke(final HttpURLConnection con, String targetEntity, String targetServiceName) {\r
250         final String invocationId = UUID.randomUUID().toString();\r
251 \r
252         // Set standard HTTP headers on (southbound request) builder.\r
253         con.setRequestProperty(ONAPLogConstants.Headers.REQUEST_ID,\r
254             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.REQUEST_ID)));\r
255         con.setRequestProperty(ONAPLogConstants.Headers.INVOCATION_ID,\r
256             invocationId);\r
257         con.setRequestProperty(ONAPLogConstants.Headers.PARTNER_NAME,\r
258             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.PARTNER_NAME)));\r
259 \r
260         invokeContext(targetEntity, targetServiceName, invocationId);\r
261 \r
262         // Log INVOKE*, with the invocationID as the message body.\r
263         // (We didn't really want this kind of behavior in the standard,\r
264         // but is it worse than new, single-message MDC?)\r
265         this.mlogger.info(ONAPLogConstants.Markers.INVOKE);\r
266         this.mlogger.info(ONAPLogConstants.Markers.INVOKE_SYNC + "{" + invocationId + "}");\r
267         return con;\r
268     }\r
269 \r
270     /**\r
271      * Report pending invocation with <tt>INVOKE</tt> marker,\r
272      * setting standard ONAP logging headers automatically.\r
273      *\r
274      * @param con http URL connection\r
275      * @param targetEntity target entity\r
276      * @param targetServiceName target service name\r
277      * @return invocation ID to be passed with invocation\r
278      */\r
279     public HttpsURLConnection invokeHttps(final HttpsURLConnection con, String targetEntity, String targetServiceName) {\r
280         final String invocationId = UUID.randomUUID().toString();\r
281 \r
282         // Set standard HTTP headers on (southbound request) builder.\r
283         con.setRequestProperty(ONAPLogConstants.Headers.REQUEST_ID,\r
284             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.REQUEST_ID)));\r
285         con.setRequestProperty(ONAPLogConstants.Headers.INVOCATION_ID,\r
286             invocationId);\r
287         con.setRequestProperty(ONAPLogConstants.Headers.PARTNER_NAME,\r
288             defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.PARTNER_NAME)));\r
289 \r
290         invokeContext(targetEntity, targetServiceName, invocationId);\r
291 \r
292         // Log INVOKE*, with the invocationID as the message body.\r
293         // (We didn't really want this kind of behavior in the standard,\r
294         // but is it worse than new, single-message MDC?)\r
295         this.mlogger.info(ONAPLogConstants.Markers.INVOKE);\r
296         this.mlogger.info(ONAPLogConstants.Markers.INVOKE_SYNC + "{" + invocationId + "}");\r
297         return con;\r
298     }\r
299 \r
300     /**\r
301      * Invokes return context.\r
302      */\r
303     public void invokeReturn() {\r
304         // Add the Invoke-return marker and clear the needed MDC\r
305         this.mlogger.info(ONAPLogConstants.Markers.INVOKE_RETURN);\r
306         invokeReturnContext();\r
307     }\r
308 \r
309     /**\r
310      * Dependency-free nullcheck.\r
311      *\r
312      * @param in to be checked\r
313      * @param <T> argument (and return) type\r
314      * @return input arg\r
315      */\r
316     private static <T> T checkNotNull(final T in) {\r
317         if (in == null) {\r
318             throw new NullPointerException();\r
319         }\r
320         return in;\r
321     }\r
322 \r
323     /**\r
324      * Dependency-free string default.\r
325      *\r
326      * @param in to be filtered\r
327      * @return input string or null\r
328      */\r
329     private static String defaultToEmpty(final Object in) {\r
330         if (in == null) {\r
331             return "";\r
332         }\r
333         return in.toString();\r
334     }\r
335 \r
336     /**\r
337      * Dependency-free string default.\r
338      *\r
339      * @param in to be filtered\r
340      * @return input string or null\r
341      */\r
342     private static String defaultToUUID(final String in) {\r
343         if (in == null) {\r
344             return UUID.randomUUID().toString();\r
345         }\r
346         return in;\r
347     }\r
348 \r
349     /**\r
350      * Set target related logging variables in thread local data via MDC.\r
351      *\r
352      * @param targetEntity Target entity (an external/sub component, for ex. "sdc")\r
353      * @param targetServiceName Target service name (name of API invoked on target)\r
354      * @param invocationID The invocation ID\r
355      */\r
356     private void invokeContext(String targetEntity, String targetServiceName, String invocationID) {\r
357         MDC.put(ONAPLogConstants.MDCs.TARGET_ENTITY, defaultToEmpty(targetEntity));\r
358         MDC.put(ONAPLogConstants.MDCs.TARGET_SERVICE_NAME, defaultToEmpty(targetServiceName));\r
359         MDC.put(ONAPLogConstants.MDCs.INVOCATIONID_OUT, invocationID);\r
360         MDC.put(ONAPLogConstants.MDCs.INVOKE_TIMESTAMP,\r
361             ZonedDateTime.now(ZoneOffset.UTC)\r
362             .format(DateTimeFormatter.ISO_INSTANT));\r
363     }\r
364 \r
365     /**\r
366      * Clear target related logging variables in thread local data via MDC.\r
367      */\r
368     private void invokeReturnContext() {\r
369         MDC.remove(ONAPLogConstants.MDCs.TARGET_ENTITY);\r
370         MDC.remove(ONAPLogConstants.MDCs.TARGET_SERVICE_NAME);\r
371         MDC.remove(ONAPLogConstants.MDCs.INVOCATIONID_OUT);\r
372     }\r
373 }\r