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