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