d26cd130383352f84d212137104780ef1ed57769
[logging-analytics.git] / reference / logging-slf4j / 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.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());
338         }
339
340         return this;
341     }
342
343     /**
344      * Dependency-free nullcheck.
345      *
346      * @param in to be checked.
347      * @param <T> argument (and return) type.
348      * @return input arg.
349      */
350     protected static <T> T checkNotNull(final T in) {
351         if (in == null) {
352             throw new NullPointerException();
353         }
354         return in;
355     }
356
357     /**
358      * Dependency-free string default.
359      *
360      * @param in to be filtered.
361      * @return input string or null.
362      */
363     protected static String defaultToEmpty(final Object in) {
364         if (in == null) {
365             return "";
366         }
367         return in.toString();
368     }
369
370     /**
371      * Dependency-free string default.
372      *
373      * @param in to be filtered.
374      * @return input string or null.
375      */
376     protected static String defaultToUUID(final String in) {
377         if (in == null) {
378             return UUID.randomUUID().toString();
379         }
380         return in;
381     }
382
383     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
384     //
385     // Inner classes.
386     //
387     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
388
389     /**
390      * Extensible descriptor for reporting service details.
391      *
392      * <p>In most cases extension isn't required. </p>
393      */
394     public static class ServiceDescriptor {
395
396         /** <tt>ServiceName</tt>. */
397         protected String mName;
398
399         /** <tt>InstanceUUID</tt>. */
400         protected String mUUID = sInstanceUUID.toString();
401
402         /**
403          * Set name.
404          * @param name <tt>ServiceName</tt>.
405          * @return this.
406          */
407         public ServiceDescriptor setServiceName(final String name) {
408             this.mName = name;
409             return this;
410         }
411
412         /**
413          * Set name.
414          * @param uuid <tt>InstanceUUID</tt>.
415          * @return this.
416          */
417         public ServiceDescriptor setServiceUUID(final String uuid) {
418             this.mUUID = uuid;
419             return this;
420         }
421
422         /**
423          * Set MDCs. Once set they remain set until everything is cleared.
424          */
425         protected void setMDCs() {
426             MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, defaultToEmpty(this.mName));
427             MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(this.mUUID));
428         }
429     }
430
431     /**
432      * Response is different in that response MDCs are normally only
433      * reported once, for a single log message. (But there's no method
434      * for clearing them, because this is only expected to be called
435      * during <tt>#exiting</tt>.)
436      */
437     public static class ResponseDescriptor {
438
439         /** Response errorcode. */
440         protected String mCode;
441
442         /** Response description. */
443         protected String mDescription;
444
445         /** Response severity. */
446         protected Level mSeverity;
447
448         /** Response status, of {<tt>COMPLETED</tt>, <tt>ERROR</tt>}. */
449         protected ONAPLogConstants.ResponseStatus mStatus;
450
451         /**
452          * Setter.
453          *
454          * @param code response (error) code.
455          * @return this.
456          */
457         public ResponseDescriptor setResponseCode(final String code) {
458             this.mCode = code;
459             return this;
460         }
461
462         /**
463          * Setter.
464          *
465          * @param description response description.
466          * @return this.
467          */
468         public ResponseDescriptor setResponseDescription(final String description) {
469             this.mDescription = description;
470             return this;
471         }
472
473         /**
474          * Setter.
475          *
476          * @param severity response outcome severity.
477          * @return this.
478          */
479         public ResponseDescriptor setResponseSeverity(final Level severity) {
480             this.mSeverity = severity;
481             return this;
482         }
483
484         /**
485          * Setter.
486          *
487          * @param status response overall status.
488          * @return this.
489          */
490         public ResponseDescriptor setResponseStatus(final ONAPLogConstants.ResponseStatus status) {
491             this.mStatus = status;
492             return this;
493         }
494
495         /**
496          * Overrideable method to set MDCs based on property values.
497          */
498         protected void setMDCs() {
499             MDC.put(ONAPLogConstants.MDCs.RESPONSE_CODE, defaultToEmpty(this.mCode));
500             MDC.put(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION, defaultToEmpty(this.mDescription));
501             MDC.put(ONAPLogConstants.MDCs.RESPONSE_SEVERITY, defaultToEmpty(this.mSeverity));
502             MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, defaultToEmpty(this.mStatus));
503         }
504     }
505
506     /**
507      * Adapter for reading information from an incoming HTTP request.
508      *
509      * <p>Incoming is generally easy, because in most cases you'll be able to
510      * get your hands on the <tt>HttpServletRequest</tt>.</p>
511      *
512      * <p>Perhaps should be generalized to refer to constants instead of
513      * requiring the implementation of specific methods.</p>
514      *
515      * @param <T> type, for chaining.
516      */
517     public interface RequestAdapter<T extends RequestAdapter> {
518
519         /**
520          * Get header by name.
521          * @param name header name.
522          * @return header value, or null.
523          */
524         String getHeader(String name);
525
526         /**
527          * Get client address.
528          * @return address, if available.
529          */
530         String getClientAddress();
531
532         /**
533          * Get server address.
534          * @return address, if available.
535          */
536         String getServerAddress();
537
538         /**
539          * Get default service name, from service URI.
540          * @return service name default.
541          */
542         String getRequestURI();
543     }
544
545     /**
546      * Default {@link RequestBuilder} impl for {@link HttpServletRequest}, which
547      * will should available for most incoming REST requests.
548      */
549     public static class HttpServletRequestAdapter implements RequestAdapter<HttpServletRequestAdapter> {
550
551         /** Wrapped HTTP request. */
552         private final HttpServletRequest mRequest;
553
554         /**
555          * Construct adapter for HTTP request.
556          * @param request to be wrapped;
557          */
558         public HttpServletRequestAdapter(final HttpServletRequest request) {
559             this.mRequest = checkNotNull(request);
560         }
561
562         /**
563          * {@inheritDoc}
564          */
565         @Override
566         public String getHeader(final String name) {
567             return this.mRequest.getHeader(name);
568         }
569
570         /**
571          * {@inheritDoc}
572          */
573         @Override
574         public String getClientAddress() {
575             return this.mRequest.getRemoteAddr();
576         }
577
578         /**
579          * {@inheritDoc}
580          */
581         @Override
582         public String getServerAddress() {
583             return this.mRequest.getServerName();
584         }
585
586         /**
587          * {@inheritDoc}
588          */
589         @Override
590         public String getRequestURI() {
591             return this.mRequest.getRequestURI();
592         }
593     }
594
595     /**
596      * Header builder, which (unlike {@link RequestAdapter} will tend to
597      * vary a lot from caller to caller, since they each get to choose their
598      * own REST (or HTTP, or whatever) client APIs.
599      *
600      * <p>No default implementation, because there's no HTTP client that's
601      * sufficiently ubiquitous to warrant incurring a mandatory dependency.</p>
602      *
603      * @param <T> type, for chaining.
604      */
605     public interface RequestBuilder<T extends RequestBuilder> {
606
607         /**
608          * Set HTTP header.
609          * @param name header name.
610          * @param value header value.
611          * @return this.
612          */
613         T setHeader(String name, String value);
614     }
615 }