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.common;
24 import java.time.LocalDateTime;
25 import java.util.UUID;
27 import javax.servlet.http.HttpServletRequest;
29 import org.slf4j.Logger;
31 import org.slf4j.Marker;
32 import org.slf4j.event.Level;
35 * Extensible adapter for cheaply meeting ONAP logging obligations using
38 * <p>This can be used with any SLF4J-compatible logging provider, with
39 * appropriate provider configuration.</p>
41 * <p>The basics are that:
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
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>
53 * <p>Minimal usage is:
55 * <li>#entering(RequestAdapter)</li>
56 * <li>#invoke, #invoke, ...</li>
57 * <li>#getResponse + setters (or #setResponse)</li>
62 * <p> ... if you're happy for service information to be automatically derived as follows:
64 * <li><tt>ServiceName</tt> - from <tt>HttpServletRequest#getRequestURI()</tt></li>
65 * <li><tt>InstanceUUID</tt> - classloader-scope UUID.</li>
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>
74 * <p>For everything else:
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>
83 public class ONAPLogAdapter {
85 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
89 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
91 /** String constant for messages <tt>ENTERING</tt>, <tt>EXITING</tt>, etc. */
92 private static final String EMPTY_MESSAGE = "";
94 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
98 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
100 /** Automatic UUID, overrideable per adapter or per invocation. */
101 private static UUID sInstanceUUID = UUID.randomUUID();
103 /** Logger delegate. */
104 private Logger mLogger;
106 /** Overrideable descriptor for the service doing the logging. */
107 private ServiceDescriptor mServiceDescriptor = new ServiceDescriptor();
109 /** Overrideable descriptor for the response returned by the service doing the logging. */
110 private ResponseDescriptor mResponseDescriptor = new ResponseDescriptor();
112 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
116 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
121 * @param logger non-null logger.
123 public ONAPLogAdapter(final Logger logger) {
124 this.mLogger = checkNotNull(logger);
127 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
131 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
136 * @return unwrapped logger.
138 public Logger unwrap() {
143 * Report <tt>ENTERING</tt> marker.
145 * @param request non-null incoming request (wrapper).
148 public ONAPLogAdapter entering(final RequestAdapter request) {
150 checkNotNull(request);
152 // Default the service name.
154 this.setEnteringMDCs(request);
155 this.mLogger.info(ONAPLogConstants.Markers.ENTRY, EMPTY_MESSAGE);
161 * Report <tt>ENTERING</tt> marker.
163 * @param request non-null incoming request.
166 public ONAPLogAdapter entering(final HttpServletRequest request) {
167 return this.entering(new HttpServletRequestAdapter(checkNotNull(request)));
171 * Report <tt>EXITING</tt> marker.
175 public ONAPLogAdapter exiting() {
177 this.mResponseDescriptor.setMDCs();
178 this.mLogger.info(ONAPLogConstants.Markers.EXIT, EMPTY_MESSAGE);
187 * Report pending invocation with <tt>INVOKE</tt> marker.
189 * <p>If you call this variant, then YOU are assuming responsibility for
190 * setting the requisite ONAP headers.</p>
192 * @param sync whether synchronous.
193 * @return invocation ID to be passed with invocation.
195 public UUID invoke(final ONAPLogConstants.InvocationMode sync) {
197 final UUID invocationID = UUID.randomUUID();
199 // Derive SYNC/ASYNC marker.
201 final Marker marker = (sync == null) ? ONAPLogConstants.Markers.INVOKE : sync.getMarker();
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?)
207 this.mLogger.info(marker, "{}", invocationID);
212 * Report pending invocation with <tt>INVOKE</tt> marker,
213 * setting standard ONAP logging headers automatically.
215 * @param builder request builder, for setting headers.
216 * @param sync whether synchronous, nullable.
217 * @return invocation ID to be passed with invocation.
219 public UUID invoke(final RequestBuilder builder,
220 final ONAPLogConstants.InvocationMode sync) {
222 // Sync can be defaulted. Builder cannot.
224 checkNotNull(builder);
226 // Log INVOKE, and retain invocation ID for header + return.
228 final UUID invocationID = this.invoke(sync);
230 // Set standard HTTP headers on (southbound request) builder.
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)));
243 * Report vanilla <tt>INVOKE</tt> marker.
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.
249 public UUID invoke(final RequestBuilder builder) {
250 return this.invoke(builder, (ONAPLogConstants.InvocationMode)null);
254 * Get descriptor, for overriding service details.
255 * @return non-null descriptor.
257 public ServiceDescriptor getServiceDescriptor() {
258 return checkNotNull(this.mServiceDescriptor);
262 * Override {@link ServiceDescriptor}.
263 * @param d non-null override.
266 public ONAPLogAdapter setServiceDescriptor(final ServiceDescriptor d) {
267 this.mServiceDescriptor = checkNotNull(d);
272 * Get descriptor, for setting response details.
273 * @return non-null descriptor.
275 public ResponseDescriptor getResponseDescriptor() {
276 return checkNotNull(this.mResponseDescriptor);
280 * Override {@link ResponseDescriptor}.
281 * @param d non-null override.
284 public ONAPLogAdapter setResponseDescriptor(final ResponseDescriptor d) {
285 this.mResponseDescriptor = checkNotNull(d);
289 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
291 // Protected methods.
293 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
296 * Set MDCs that persist for the duration of an invocation.
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>
302 * @param request incoming HTTP request.
305 protected ONAPLogAdapter setEnteringMDCs(final RequestAdapter<?> request) {
307 // Extract MDC values from standard HTTP headers.
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));
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.
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()));
325 // Delegate to the service adapter, for service-related DMCs.
327 this.mServiceDescriptor.setMDCs();
329 // Default the service name to the requestURI, in the event that
330 // no value has been provided.
332 if (MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME) == null) {
333 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());
340 * Dependency-free nullcheck.
342 * @param in to be checked.
343 * @param <T> argument (and return) type.
346 protected static <T> T checkNotNull(final T in) {
348 throw new NullPointerException();
354 * Dependency-free string default.
356 * @param in to be filtered.
357 * @return input string or null.
359 protected static String defaultToEmpty(final Object in) {
363 return in.toString();
367 * Dependency-free string default.
369 * @param in to be filtered.
370 * @return input string or null.
372 protected static String defaultToUUID(final String in) {
374 return UUID.randomUUID().toString();
379 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
383 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
386 * Extensible descriptor for reporting service details.
388 * <p>In most cases extension isn't required. </p>
390 public static class ServiceDescriptor {
392 /** <tt>ServiceName</tt>. */
393 protected String mName;
395 /** <tt>InstanceUUID</tt>. */
396 protected String mUUID = sInstanceUUID.toString();
400 * @param name <tt>ServiceName</tt>.
403 public ServiceDescriptor setServiceName(final String name) {
410 * @param uuid <tt>InstanceUUID</tt>.
413 public ServiceDescriptor setServiceUUID(final String uuid) {
419 * Set MDCs. Once set they remain set until everything is cleared.
421 protected void setMDCs() {
422 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, defaultToEmpty(this.mName));
423 MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(this.mUUID));
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>.)
433 public static class ResponseDescriptor {
435 /** Response errorcode. */
436 protected String mCode;
438 /** Response description. */
439 protected String mDescription;
441 /** Response severity. */
442 protected Level mSeverity;
444 /** Response status, of {<tt>COMPLETED</tt>, <tt>ERROR</tt>}. */
445 protected ONAPLogConstants.ResponseStatus mStatus;
450 * @param code response (error) code.
453 public ResponseDescriptor setResponseCode(final String code) {
461 * @param description response description.
464 public ResponseDescriptor setResponseDescription(final String description) {
465 this.mDescription = description;
472 * @param severity response outcome severity.
475 public ResponseDescriptor setResponseSeverity(final Level severity) {
476 this.mSeverity = severity;
483 * @param status response overall status.
486 public ResponseDescriptor setResponseStatus(final ONAPLogConstants.ResponseStatus status) {
487 this.mStatus = status;
492 * Overrideable method to set MDCs based on property values.
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));
503 * Adapter for reading information from an incoming HTTP request.
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>
508 * <p>Perhaps should be generalized to refer to constants instead of
509 * requiring the implementation of specific methods.</p>
511 * @param <T> type, for chaining.
513 public interface RequestAdapter<T extends RequestAdapter> {
516 * Get header by name.
517 * @param name header name.
518 * @return header value, or null.
520 String getHeader(String name);
523 * Get client address.
524 * @return address, if available.
526 String getClientAddress();
529 * Get server address.
530 * @return address, if available.
532 String getServerAddress();
535 * Get default service name, from service URI.
536 * @return service name default.
538 String getRequestURI();
542 * Default {@link RequestBuilder} impl for {@link HttpServletRequest}, which
543 * will should available for most incoming REST requests.
545 public static class HttpServletRequestAdapter implements RequestAdapter<HttpServletRequestAdapter> {
547 /** Wrapped HTTP request. */
548 private final HttpServletRequest mRequest;
551 * Construct adapter for HTTP request.
552 * @param request to be wrapped;
554 public HttpServletRequestAdapter(final HttpServletRequest request) {
555 this.mRequest = checkNotNull(request);
562 public String getHeader(final String name) {
563 return this.mRequest.getHeader(name);
570 public String getClientAddress() {
571 return this.mRequest.getRemoteAddr();
578 public String getServerAddress() {
579 return this.mRequest.getServerName();
586 public String getRequestURI() {
587 return this.mRequest.getRequestURI();
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.
596 * <p>No default implementation, because there's no HTTP client that's
597 * sufficiently ubiquitous to warrant incurring a mandatory dependency.</p>
599 * @param <T> type, for chaining.
601 public interface RequestBuilder<T extends RequestBuilder> {
605 * @param name header name.
606 * @param value header value.
609 T setHeader(String name, String value);