2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2018 Amdocs
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.logging.ref.slf4j;
24 import java.time.ZoneOffset;
25 import java.time.ZonedDateTime;
26 import java.time.format.DateTimeFormatter;
27 import java.util.UUID;
29 import javax.servlet.http.HttpServletRequest;
31 import org.slf4j.Logger;
33 import org.slf4j.Marker;
34 import org.slf4j.event.Level;
37 * Extensible adapter for cheaply meeting ONAP logging obligations using
41 * This can be used with any SLF4J-compatible logging provider, with
42 * appropriate provider configuration.
46 * The basics are that:
48 * <li>{@link #entering} sets all MDCs.</li>
49 * <li>{@link #exiting} unsets all MDCs *and* logs response information.</li>
50 * <li>{@link #invoke} logs and returns a UUID to passed during invocation,
51 * and optionally sets these for you on your downstream request by way of
53 * <li>Call {@link #getServiceDescriptor()} and its setters to set service-related MDCs.</li>
54 * <li>Call {@link #getResponseDescriptor()} and its setters to set response-related MDCs.</li>
61 * <li>#entering(RequestAdapter)</li>
62 * <li>#invoke, #invoke, ...</li>
63 * <li>#getResponse + setters (or #setResponse)</li>
69 * ... if you're happy for service information to be automatically derived as follows:
71 * <li><tt>ServiceName</tt> - from <tt>HttpServletRequest#getRequestURI()</tt></li>
72 * <li><tt>InstanceUUID</tt> - classloader-scope UUID.</li>
77 * ... and if those defaults don't suit, then you can override using properties on
78 * {@link #getServiceDescriptor()}, or by injecting your own adapter using
79 * {@link #setServiceDescriptor(ServiceDescriptor)}, or by overriding
80 * a <tt>protected</tt> methods like{@link #setEnteringMDCs}.
84 * For everything else:
86 * <li>The underlying SLF4J {@link Logger} can be retrieved using {@link #unwrap}.
87 * Use this or create your own using the usual SLF4J factor.</li>
88 * <li>Set whatever MDCs you like.</li>
89 * <li>Log whatever else you like.</li>
93 public class ONAPLogAdapter {
95 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
99 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101 /** String constant for messages <tt>ENTERING</tt>, <tt>EXITING</tt>, etc. */
102 private static final String EMPTY_MESSAGE = "";
104 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
108 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
110 /** Automatic UUID, overrideable per adapter or per invocation. */
111 private static UUID sInstanceUUID = UUID.randomUUID();
113 /** Logger delegate. */
114 private Logger mLogger;
116 /** Overrideable descriptor for the service doing the logging. */
117 private ServiceDescriptor mServiceDescriptor = new ServiceDescriptor();
119 /** Overrideable descriptor for the response returned by the service doing the logging. */
120 private ResponseDescriptor mResponseDescriptor = new ResponseDescriptor();
122 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
126 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
131 * @param logger non-null logger.
133 public ONAPLogAdapter(final Logger logger) {
134 this.mLogger = checkNotNull(logger);
137 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
141 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
146 * @return unwrapped logger.
148 public Logger unwrap() {
153 * Report <tt>ENTERING</tt> marker.
155 * @param request non-null incoming request (wrapper).
158 public ONAPLogAdapter entering(final RequestAdapter request) {
160 checkNotNull(request);
162 // Default the service name.
164 this.setEnteringMDCs(request);
165 this.mLogger.info(ONAPLogConstants.Markers.ENTRY, EMPTY_MESSAGE);
171 * Report <tt>ENTERING</tt> marker.
173 * @param request non-null incoming request.
176 public ONAPLogAdapter entering(final HttpServletRequest request) {
177 return this.entering(new HttpServletRequestAdapter(checkNotNull(request)));
181 * Report <tt>EXITING</tt> marker.
185 public ONAPLogAdapter exiting() {
187 this.mResponseDescriptor.setMDCs();
188 this.mLogger.info(ONAPLogConstants.Markers.EXIT, EMPTY_MESSAGE);
196 * Report pending invocation with <tt>INVOKE</tt> marker.
199 * If you call this variant, then YOU are assuming responsibility for
200 * setting the requisite ONAP headers.
203 * @param sync whether synchronous.
204 * @return invocation ID to be passed with invocation.
206 public UUID invoke(final ONAPLogConstants.InvocationMode sync) {
208 final UUID invocationID = UUID.randomUUID();
210 // Derive SYNC/ASYNC marker.
212 final Marker marker = (sync == null) ? ONAPLogConstants.Markers.INVOKE : sync.getMarker();
214 // Log INVOKE*, with the invocationID as the message body.
215 // (We didn't really want this kind of behavior in the standard,
216 // but is it worse than new, single-message MDC?)
218 this.mLogger.info(marker, "{}", invocationID);
223 * Report pending invocation with <tt>INVOKE</tt> marker,
224 * setting standard ONAP logging headers automatically.
226 * @param builder request builder, for setting headers.
227 * @param sync whether synchronous, nullable.
228 * @return invocation ID to be passed with invocation.
230 public UUID invoke(final RequestBuilder builder, final ONAPLogConstants.InvocationMode sync) {
232 // Sync can be defaulted. Builder cannot.
234 checkNotNull(builder);
236 // Log INVOKE, and retain invocation ID for header + return.
238 final UUID invocationID = this.invoke(sync);
240 // Set standard HTTP headers on (southbound request) builder.
242 builder.setHeader(ONAPLogConstants.Headers.REQUEST_ID,
243 defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.REQUEST_ID)));
244 builder.setHeader(ONAPLogConstants.Headers.INVOCATION_ID, defaultToEmpty(invocationID));
245 builder.setHeader(ONAPLogConstants.Headers.PARTNER_NAME,
246 defaultToEmpty(MDC.get(ONAPLogConstants.MDCs.PARTNER_NAME)));
252 * Report vanilla <tt>INVOKE</tt> marker.
254 * @param builder builder for downstream requests, if you want the
255 * standard ONAP headers to be added automatically.
256 * @return invocation ID to be passed with invocation.
258 public UUID invoke(final RequestBuilder builder) {
259 return this.invoke(builder, (ONAPLogConstants.InvocationMode) null);
263 * Get descriptor, for overriding service details.
265 * @return non-null descriptor.
267 public ServiceDescriptor getServiceDescriptor() {
268 return checkNotNull(this.mServiceDescriptor);
272 * Override {@link ServiceDescriptor}.
274 * @param d non-null override.
277 public ONAPLogAdapter setServiceDescriptor(final ServiceDescriptor d) {
278 this.mServiceDescriptor = checkNotNull(d);
283 * Get descriptor, for setting response details.
285 * @return non-null descriptor.
287 public ResponseDescriptor getResponseDescriptor() {
288 return checkNotNull(this.mResponseDescriptor);
292 * Override {@link ResponseDescriptor}.
294 * @param d non-null override.
297 public ONAPLogAdapter setResponseDescriptor(final ResponseDescriptor d) {
298 this.mResponseDescriptor = checkNotNull(d);
302 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
304 // Protected methods.
306 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
309 * Set MDCs that persist for the duration of an invocation.
312 * It would be better to roll this into {@link #entering}, like
313 * with {@link #exiting}. Then it would be easier to do, but it
314 * would mean more work.
317 * @param request incoming HTTP request.
320 protected ONAPLogAdapter setEnteringMDCs(final RequestAdapter<?> request) {
322 // Extract MDC values from standard HTTP headers.
324 final String requestID = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.REQUEST_ID));
325 final String invocationID = defaultToUUID(request.getHeader(ONAPLogConstants.Headers.INVOCATION_ID));
326 final String partnerName = defaultToEmpty(request.getHeader(ONAPLogConstants.Headers.PARTNER_NAME));
328 // Set standard MDCs. Override this entire method if you want to set
329 // others, OR set them BEFORE or AFTER the invocation of #entering,
330 // depending on where you need them to appear, OR extend the
331 // ServiceDescriptor to add them.
333 MDC.put(ONAPLogConstants.MDCs.INVOKE_TIMESTAMP,
334 ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
335 MDC.put(ONAPLogConstants.MDCs.REQUEST_ID, requestID);
336 MDC.put(ONAPLogConstants.MDCs.INVOCATION_ID, invocationID);
337 MDC.put(ONAPLogConstants.MDCs.PARTNER_NAME, partnerName);
338 MDC.put(ONAPLogConstants.MDCs.CLIENT_IP_ADDRESS, defaultToEmpty(request.getClientAddress()));
339 MDC.put(ONAPLogConstants.MDCs.SERVER_FQDN, defaultToEmpty(request.getServerAddress()));
341 // Delegate to the service adapter, for service-related DMCs.
343 this.mServiceDescriptor.setMDCs();
345 // Default the service name to the requestURI, in the event that
346 // no value has been provided.
348 if (MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME) == null
349 || MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME).equalsIgnoreCase(EMPTY_MESSAGE)) {
350 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());
357 * Dependency-free nullcheck.
359 * @param in to be checked.
360 * @param <T> argument (and return) type.
363 protected static <T> T checkNotNull(final T in) {
365 throw new NullPointerException();
371 * Dependency-free string default.
373 * @param in to be filtered.
374 * @return input string or null.
376 protected static String defaultToEmpty(final Object in) {
380 return in.toString();
384 * Dependency-free string default.
386 * @param in to be filtered.
387 * @return input string or null.
389 protected static String defaultToUUID(final String in) {
391 return UUID.randomUUID().toString();
396 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
400 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
403 * Extensible descriptor for reporting service details.
406 * In most cases extension isn't required.
409 public static class ServiceDescriptor {
411 /** <tt>ServiceName</tt>. */
412 protected String mName;
414 /** <tt>InstanceUUID</tt>. */
415 protected String mUUID = sInstanceUUID.toString();
420 * @param name <tt>ServiceName</tt>.
423 public ServiceDescriptor setServiceName(final String name) {
431 * @param uuid <tt>InstanceUUID</tt>.
434 public ServiceDescriptor setServiceUUID(final String uuid) {
440 * Set MDCs. Once set they remain set until everything is cleared.
442 protected void setMDCs() {
443 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, defaultToEmpty(this.mName));
444 MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(this.mUUID));
449 * Response is different in that response MDCs are normally only
450 * reported once, for a single log message. (But there's no method
451 * for clearing them, because this is only expected to be called
452 * during <tt>#exiting</tt>.)
454 public static class ResponseDescriptor {
456 /** Response errorcode. */
457 protected String mCode;
459 /** Response description. */
460 protected String mDescription;
462 /** Response severity. */
463 protected Level mSeverity;
465 /** Response status, of {<tt>COMPLETED</tt>, <tt>ERROR</tt>}. */
466 protected ONAPLogConstants.ResponseStatus mStatus;
471 * @param code response (error) code.
474 public ResponseDescriptor setResponseCode(final String code) {
482 * @param description response description.
485 public ResponseDescriptor setResponseDescription(final String description) {
486 this.mDescription = description;
493 * @param severity response outcome severity.
496 public ResponseDescriptor setResponseSeverity(final Level severity) {
497 this.mSeverity = severity;
504 * @param status response overall status.
507 public ResponseDescriptor setResponseStatus(final ONAPLogConstants.ResponseStatus status) {
508 this.mStatus = status;
513 * Overrideable method to set MDCs based on property values.
515 protected void setMDCs() {
516 MDC.put(ONAPLogConstants.MDCs.RESPONSE_CODE, defaultToEmpty(this.mCode));
517 MDC.put(ONAPLogConstants.MDCs.RESPONSE_DESCRIPTION, defaultToEmpty(this.mDescription));
518 MDC.put(ONAPLogConstants.MDCs.RESPONSE_SEVERITY, defaultToEmpty(this.mSeverity));
519 MDC.put(ONAPLogConstants.MDCs.RESPONSE_STATUS_CODE, defaultToEmpty(this.mStatus));
524 * Adapter for reading information from an incoming HTTP request.
527 * Incoming is generally easy, because in most cases you'll be able to
528 * get your hands on the <tt>HttpServletRequest</tt>.
532 * Perhaps should be generalized to refer to constants instead of
533 * requiring the implementation of specific methods.
536 * @param <T> type, for chaining.
538 public interface RequestAdapter<T extends RequestAdapter> {
541 * Get header by name.
543 * @param name header name.
544 * @return header value, or null.
546 String getHeader(String name);
549 * Get client address.
551 * @return address, if available.
553 String getClientAddress();
556 * Get server address.
558 * @return address, if available.
560 String getServerAddress();
563 * Get default service name, from service URI.
565 * @return service name default.
567 String getRequestURI();
571 * Default {@link RequestBuilder} impl for {@link HttpServletRequest}, which
572 * will should available for most incoming REST requests.
574 public static class HttpServletRequestAdapter implements RequestAdapter<HttpServletRequestAdapter> {
576 /** Wrapped HTTP request. */
577 private final HttpServletRequest mRequest;
580 * Construct adapter for HTTP request.
582 * @param request to be wrapped;
584 public HttpServletRequestAdapter(final HttpServletRequest request) {
585 this.mRequest = checkNotNull(request);
592 public String getHeader(final String name) {
593 return this.mRequest.getHeader(name);
600 public String getClientAddress() {
601 return this.mRequest.getRemoteAddr();
608 public String getServerAddress() {
609 return this.mRequest.getServerName();
616 public String getRequestURI() {
617 return this.mRequest.getRequestURI();
622 * Header builder, which (unlike {@link RequestAdapter} will tend to
623 * vary a lot from caller to caller, since they each get to choose their
624 * own REST (or HTTP, or whatever) client APIs.
627 * No default implementation, because there's no HTTP client that's
628 * sufficiently ubiquitous to warrant incurring a mandatory dependency.
631 * @param <T> type, for chaining.
633 public interface RequestBuilder<T extends RequestBuilder> {
638 * @param name header name.
639 * @param value header value.
642 T setHeader(String name, String value);