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