Enhancements for the aai-common library
[aai/aai-common.git] / aai-els-onap-logging / src / main / java / org / onap / logging / ref / slf4j / ONAPLogAdapter.java
1 /**
2  * ============LICENSE_START=======================================================
3  * org.onap.logging
4  * ================================================================================
5  * Copyright © 2018 Amdocs
6  * All rights 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 package org.onap.logging.ref.slf4j;
23
24 import java.time.ZoneOffset;
25 import java.time.ZonedDateTime;
26 import java.time.format.DateTimeFormatter;
27 import java.util.UUID;
28
29 import javax.servlet.http.HttpServletRequest;
30
31 import org.slf4j.Logger;
32 import org.slf4j.MDC;
33 import org.slf4j.Marker;
34 import org.slf4j.event.Level;
35
36 /**
37  * Extensible adapter for cheaply meeting ONAP logging obligations using
38  * an SLF4J facade.
39  *
40  * <p>This can be used with any SLF4J-compatible logging provider, with
41  * appropriate provider configuration.</p>
42  *
43  * <p>The basics are that:
44  * <ul>
45  *     <li>{@link #entering} sets all MDCs.</li>
46  *     <li>{@link #exiting} unsets all MDCs *and* logs response information.</li>
47  *     <li>{@link #invoke} logs and returns a UUID to passed during invocation,
48  *     and optionally sets these for you on your downstream request by way of
49  *     an adapter.</li>
50  *     <li>Call {@link #getServiceDescriptor()} and its setters to set service-related MDCs.</li>
51  *     <li>Call {@link #getResponseDescriptor()} and its setters to set response-related MDCs.</li>
52  * </ul>
53  * </p>
54  *
55  * <p>Minimal usage is:
56  * <ol>
57  *     <li>#entering(RequestAdapter)</li>
58  *     <li>#invoke, #invoke, ...</li>
59  *     <li>#getResponse + setters (or #setResponse)</li>
60  *     <li>#exiting</li>
61  * </ol>
62  * </p>
63  *
64  * <p> ... if you're happy for service information to be automatically derived as follows:
65  * <ul>
66  *     <li><tt>ServiceName</tt> - from <tt>HttpServletRequest#getRequestURI()</tt></li>
67  *     <li><tt>InstanceUUID</tt> - classloader-scope UUID.</li>
68  * </ul>
69  * </p>
70  *
71  * <p>... and if those defaults don't suit, then you can override using properties on
72  * {@link #getServiceDescriptor()}, or by injecting your own adapter using
73  * {@link #setServiceDescriptor(ServiceDescriptor)}, or by overriding
74  * a <tt>protected</tt> methods like{@link #setEnteringMDCs}.</p>
75  *
76  * <p>For everything else:
77  * <ul>
78  *     <li>The underlying SLF4J {@link Logger} can be retrieved using {@link #unwrap}.
79  *     Use this or create your own using the usual SLF4J factor.</li>
80  *     <li>Set whatever MDCs you like.</li>
81  *     <li>Log whatever else you like.</li>
82  * </ul>
83  * </p>
84  */
85 public class ONAPLogAdapter {
86
87     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
88     //
89     // Constants.
90     //
91     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
92
93     /** String constant for messages <tt>ENTERING</tt>, <tt>EXITING</tt>, etc. */
94     private static final String EMPTY_MESSAGE = "";
95
96     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
97     //
98     // Fields.
99     //
100     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101
102     /** Automatic UUID, overrideable per adapter or per invocation. */
103     private static UUID sInstanceUUID = UUID.randomUUID();
104
105     /** Logger delegate. */
106     private Logger mLogger;
107
108     /** Overrideable descriptor for the service doing the logging. */
109     private ServiceDescriptor mServiceDescriptor = new ServiceDescriptor();
110
111     /** Overrideable descriptor for the response returned by the service doing the logging. */
112     private ResponseDescriptor mResponseDescriptor = new ResponseDescriptor();
113
114     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
115     //
116     // Constructors.
117     //
118     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
119
120     /**
121      * Construct adapter.
122      *
123      * @param logger non-null logger.
124      */
125     public ONAPLogAdapter(final Logger logger) {
126         this.mLogger = checkNotNull(logger);
127     }
128
129     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
130     //
131     // Public methods.
132     //
133     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
134
135     /**
136      * Get logger.
137      *
138      * @return unwrapped logger.
139      */
140     public Logger unwrap() {
141         return this.mLogger;
142     }
143
144     /**
145      * Report <tt>ENTERING</tt> marker.
146      *
147      * @param request non-null incoming request (wrapper).
148      * @return this.
149      */
150     public ONAPLogAdapter entering(final RequestAdapter request) {
151
152         checkNotNull(request);
153
154         // Default the service name.
155
156         this.setEnteringMDCs(request);
157         this.mLogger.info(ONAPLogConstants.Markers.ENTRY, EMPTY_MESSAGE);
158
159         return this;
160     }
161
162     /**
163      * Report <tt>ENTERING</tt> marker.
164      *
165      * @param request non-null incoming request.
166      * @return this.
167      */
168     public ONAPLogAdapter entering(final HttpServletRequest request) {
169         return this.entering(new HttpServletRequestAdapter(checkNotNull(request)));
170     }
171
172     /**
173      * Report <tt>EXITING</tt> marker.
174      *
175      * @return this.
176      */
177     public ONAPLogAdapter exiting() {
178         try {
179             this.mResponseDescriptor.setMDCs();
180             this.mLogger.info(ONAPLogConstants.Markers.EXIT, EMPTY_MESSAGE);
181         }
182         finally {
183             MDC.clear();
184         }
185         return this;
186     }
187
188     /**
189      * Report pending invocation with <tt>INVOKE</tt> marker.
190      *
191      * <p>If you call this variant, then YOU are assuming responsibility for
192      * setting the requisite ONAP headers.</p>
193      *
194      * @param sync whether synchronous.
195      * @return invocation ID to be passed with invocation.
196      */
197     public UUID invoke(final ONAPLogConstants.InvocationMode sync) {
198
199         final UUID invocationID = UUID.randomUUID();
200
201         // Derive SYNC/ASYNC marker.
202
203         final Marker marker = (sync == null) ? ONAPLogConstants.Markers.INVOKE : sync.getMarker();
204
205         // Log INVOKE*, with the invocationID as the message body.
206         // (We didn't really want this kind of behavior in the standard,
207         // but is it worse than new, single-message MDC?)
208
209         this.mLogger.info(marker, "{}", invocationID);
210         return invocationID;
211     }
212
213     /**
214      * Report pending invocation with <tt>INVOKE</tt> marker,
215      * setting standard ONAP logging headers automatically.
216      *
217      * @param builder request builder, for setting headers.
218      * @param sync whether synchronous, nullable.
219      * @return invocation ID to be passed with invocation.
220      */
221     public UUID invoke(final RequestBuilder builder,
222                        final ONAPLogConstants.InvocationMode sync) {
223
224         // Sync can be defaulted. Builder cannot.
225
226         checkNotNull(builder);
227
228         // Log INVOKE, and retain invocation ID for header + return.
229
230         final UUID invocationID = this.invoke(sync);
231
232         // Set standard HTTP headers on (southbound request) builder.
233
234         builder.setHeader(ONAPLogConstants.Headers.REQUEST_ID,
235                 defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.REQUEST_ID)));
236         builder.setHeader(ONAPLogConstants.Headers.INVOCATION_ID,
237                 defaultToEmpty(invocationID));
238         builder.setHeader(ONAPLogConstants.Headers.PARTNER_NAME,
239                 defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.PARTNER_NAME)));
240
241         return invocationID;
242     }
243
244     /**
245      * Report vanilla <tt>INVOKE</tt> marker.
246      *
247      * @param builder builder for downstream requests, if you want the
248      *                standard ONAP headers to be added automatically.
249      * @return invocation ID to be passed with invocation.
250      */
251     public UUID invoke(final RequestBuilder builder) {
252         return this.invoke(builder, (ONAPLogConstants.InvocationMode)null);
253     }
254
255     /**
256      * Get descriptor, for overriding service details.
257      * @return non-null descriptor.
258      */
259     public ServiceDescriptor getServiceDescriptor() {
260         return checkNotNull(this.mServiceDescriptor);
261     }
262
263     /**
264      * Override {@link ServiceDescriptor}.
265      * @param d non-null override.
266      * @return this.
267      */
268     public ONAPLogAdapter setServiceDescriptor(final ServiceDescriptor d) {
269         this.mServiceDescriptor = checkNotNull(d);
270         return this;
271     }
272
273     /**
274      * Get descriptor, for setting response details.
275      * @return non-null descriptor.
276      */
277     public ResponseDescriptor getResponseDescriptor() {
278         return checkNotNull(this.mResponseDescriptor);
279     }
280
281     /**
282      * Override {@link ResponseDescriptor}.
283      * @param d non-null override.
284      * @return this.
285      */
286     public ONAPLogAdapter setResponseDescriptor(final ResponseDescriptor d) {
287         this.mResponseDescriptor = checkNotNull(d);
288         return this;
289     }
290
291     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
292     //
293     // Protected methods.
294     //
295     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
296
297     /**
298      * Set MDCs that persist for the duration of an invocation.
299      *
300      * <p>It would be better to roll this into {@link #entering}, like
301      * with {@link #exiting}. Then it would be easier to do, but it
302      * would mean more work. </p>
303      *
304      * @param request incoming HTTP request.
305      * @return this.
306      */
307     protected ONAPLogAdapter setEnteringMDCs(final RequestAdapter<?> request) {
308
309         // Extract MDC values from standard HTTP headers.
310
311         final String requestID = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.REQUEST_ID));
312         final String invocationID = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.INVOCATION_ID));
313         final String partnerName = defaultToEmpty(request.getHeader(ONAPLogConstants.Headers.PARTNER_NAME));
314
315         // Set standard MDCs. Override this entire method if you want to set
316         // others, OR set them BEFORE or AFTER the invocation of #entering,
317         // depending on where you need them to appear, OR extend the
318         // ServiceDescriptor to add them.
319
320         MDC.put(ONAPLogConstants.MDCs.INVOKE_TIMESTAMP,
321                 ZonedDateTime.now(ZoneOffset.UTC)
322                         .format(DateTimeFormatter.ISO_INSTANT));
323         MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestID);
324         MDC.put(ONAPLogConstants.MDCs.INVOCATION_ID, invocationID);
325         MDC.put(ONAPLogConstants.MDCs.PARTNER_NAME, partnerName);
326         MDC.put(ONAPLogConstants.MDCs.CLIENT_IP_ADDRESS, defaultToEmpty(request.getClientAddress()));
327         MDC.put(ONAPLogConstants.MDCs.SERVER_FQDN, defaultToEmpty(request.getServerAddress()));
328
329         // Delegate to the service adapter, for service-related DMCs.
330
331         this.mServiceDescriptor.setMDCs();
332
333         // Default the service name to the requestURI, in the event that
334         // no value has been provided.
335
336         if (MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME) == null ||
337                 MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME).equalsIgnoreCase(EMPTY_MESSAGE)) {
338             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());
339         }
340
341         return this;
342     }
343
344     /**
345      * Dependency-free nullcheck.
346      *
347      * @param in to be checked.
348      * @param <T> argument (and return) type.
349      * @return input arg.
350      */
351     protected static <T> T checkNotNull(final T in) {
352         if (in == null) {
353             throw new NullPointerException();
354         }
355         return in;
356     }
357
358     /**
359      * Dependency-free string default.
360      *
361      * @param in to be filtered.
362      * @return input string or null.
363      */
364     protected static String defaultToEmpty(final Object in) {
365         if (in == null) {
366             return "";
367         }
368         return in.toString();
369     }
370
371     /**
372      * Dependency-free string default.
373      *
374      * @param in to be filtered.
375      * @return input string or null.
376      */
377     protected static String defaultToUUID(final String in) {
378         if (in == null) {
379             return UUID.randomUUID().toString();
380         }
381         return in;
382     }
383
384     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
385     //
386     // Inner classes.
387     //
388     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
389
390     /**
391      * Extensible descriptor for reporting service details.
392      *
393      * <p>In most cases extension isn't required. </p>
394      */
395     public static class ServiceDescriptor {
396
397         /** <tt>ServiceName</tt>. */
398         protected String mName;
399
400         /** <tt>InstanceUUID</tt>. */
401         protected String mUUID = sInstanceUUID.toString();
402
403         /**
404          * Set name.
405          * @param name <tt>ServiceName</tt>.
406          * @return this.
407          */
408         public ServiceDescriptor setServiceName(final String name) {
409             this.mName = name;
410             return this;
411         }
412
413         /**
414          * Set name.
415          * @param uuid <tt>InstanceUUID</tt>.
416          * @return this.
417          */
418         public ServiceDescriptor setServiceUUID(final String uuid) {
419             this.mUUID = uuid;
420             return this;
421         }
422
423         /**
424          * Set MDCs. Once set they remain set until everything is cleared.
425          */
426         protected void setMDCs() {
427             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, defaultToEmpty(this.mName));
428             MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(this.mUUID));
429         }
430     }
431
432     /**
433      * Response is different in that response MDCs are normally only
434      * reported once, for a single log message. (But there's no method
435      * for clearing them, because this is only expected to be called
436      * during <tt>#exiting</tt>.)
437      */
438     public static class ResponseDescriptor {
439
440         /** Response errorcode. */
441         protected String mCode;
442
443         /** Response description. */
444         protected String mDescription;
445
446         /** Response severity. */
447         protected Level mSeverity;
448
449         /** Response status, of {<tt>COMPLETED</tt>, <tt>ERROR</tt>}. */
450         protected ONAPLogConstants.ResponseStatus mStatus;
451
452         /**
453          * Setter.
454          *
455          * @param code response (error) code.
456          * @return this.
457          */
458         public ResponseDescriptor setResponseCode(final String code) {
459             this.mCode = code;
460             return this;
461         }
462
463         /**
464          * Setter.
465          *
466          * @param description response description.
467          * @return this.
468          */
469         public ResponseDescriptor setResponseDescription(final String description) {
470             this.mDescription = description;
471             return this;
472         }
473
474         /**
475          * Setter.
476          *
477          * @param severity response outcome severity.
478          * @return this.
479          */
480         public ResponseDescriptor setResponseSeverity(final Level severity) {
481             this.mSeverity = severity;
482             return this;
483         }
484
485         /**
486          * Setter.
487          *
488          * @param status response overall status.
489          * @return this.
490          */
491         public ResponseDescriptor setResponseStatus(final ONAPLogConstants.ResponseStatus status) {
492             this.mStatus = status;
493             return this;
494         }
495
496         /**
497          * Overrideable method to set MDCs based on property values.
498          */
499         protected void setMDCs() {
500             MDC.put(ONAPLogConstants.MDCs.RESPONSE_CODE, defaultToEmpty(this.mCode));
501             MDC.put(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION, defaultToEmpty(this.mDescription));
502             MDC.put(ONAPLogConstants.MDCs.RESPONSE_SEVERITY, defaultToEmpty(this.mSeverity));
503             MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, defaultToEmpty(this.mStatus));
504         }
505     }
506
507     /**
508      * Adapter for reading information from an incoming HTTP request.
509      *
510      * <p>Incoming is generally easy, because in most cases you'll be able to
511      * get your hands on the <tt>HttpServletRequest</tt>.</p>
512      *
513      * <p>Perhaps should be generalized to refer to constants instead of
514      * requiring the implementation of specific methods.</p>
515      *
516      * @param <T> type, for chaining.
517      */
518     public interface RequestAdapter<T extends RequestAdapter> {
519
520         /**
521          * Get header by name.
522          * @param name header name.
523          * @return header value, or null.
524          */
525         String getHeader(String name);
526
527         /**
528          * Get client address.
529          * @return address, if available.
530          */
531         String getClientAddress();
532
533         /**
534          * Get server address.
535          * @return address, if available.
536          */
537         String getServerAddress();
538
539         /**
540          * Get default service name, from service URI.
541          * @return service name default.
542          */
543         String getRequestURI();
544     }
545
546     /**
547      * Default {@link RequestBuilder} impl for {@link HttpServletRequest}, which
548      * will should available for most incoming REST requests.
549      */
550     public static class HttpServletRequestAdapter implements RequestAdapter<HttpServletRequestAdapter> {
551
552         /** Wrapped HTTP request. */
553         private final HttpServletRequest mRequest;
554
555         /**
556          * Construct adapter for HTTP request.
557          * @param request to be wrapped;
558          */
559         public HttpServletRequestAdapter(final HttpServletRequest request) {
560             this.mRequest = checkNotNull(request);
561         }
562
563         /**
564          * {@inheritDoc}
565          */
566         @Override
567         public String getHeader(final String name) {
568             return this.mRequest.getHeader(name);
569         }
570
571         /**
572          * {@inheritDoc}
573          */
574         @Override
575         public String getClientAddress() {
576             return this.mRequest.getRemoteAddr();
577         }
578
579         /**
580          * {@inheritDoc}
581          */
582         @Override
583         public String getServerAddress() {
584             return this.mRequest.getServerName();
585         }
586
587         /**
588          * {@inheritDoc}
589          */
590         @Override
591         public String getRequestURI() {
592             return this.mRequest.getRequestURI();
593         }
594     }
595
596     /**
597      * Header builder, which (unlike {@link RequestAdapter} will tend to
598      * vary a lot from caller to caller, since they each get to choose their
599      * own REST (or HTTP, or whatever) client APIs.
600      *
601      * <p>No default implementation, because there's no HTTP client that's
602      * sufficiently ubiquitous to warrant incurring a mandatory dependency.</p>
603      *
604      * @param <T> type, for chaining.
605      */
606     public interface RequestBuilder<T extends RequestBuilder> {
607
608         /**
609          * Set HTTP header.
610          * @param name header name.
611          * @param value header value.
612          * @return this.
613          */
614         T setHeader(String name, String value);
615     }
616 }