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
40 * <p>This can be used with any SLF4J-compatible logging provider, with
41 * appropriate provider configuration.</p>
43 * <p>The basics are that:
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
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>
55 * <p>Minimal usage is:
57 * <li>#entering(RequestAdapter)</li>
58 * <li>#invoke, #invoke, ...</li>
59 * <li>#getResponse + setters (or #setResponse)</li>
64 * <p> ... if you're happy for service information to be automatically derived as follows:
66 * <li><tt>ServiceName</tt> - from <tt>HttpServletRequest#getRequestURI()</tt></li>
67 * <li><tt>InstanceUUID</tt> - classloader-scope UUID.</li>
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>
76 * <p>For everything else:
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>
85 public class ONAPLogAdapter {
87 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
91 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
93 /** String constant for messages <tt>ENTERING</tt>, <tt>EXITING</tt>, etc. */
94 private static final String EMPTY_MESSAGE = "";
96 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
100 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
102 /** Automatic UUID, overrideable per adapter or per invocation. */
103 private static UUID sInstanceUUID = UUID.randomUUID();
105 /** Logger delegate. */
106 private Logger mLogger;
108 /** Overrideable descriptor for the service doing the logging. */
109 private ServiceDescriptor mServiceDescriptor = new ServiceDescriptor();
111 /** Overrideable descriptor for the response returned by the service doing the logging. */
112 private ResponseDescriptor mResponseDescriptor = new ResponseDescriptor();
114 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
118 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
123 * @param logger non-null logger.
125 public ONAPLogAdapter(final Logger logger) {
126 this.mLogger = checkNotNull(logger);
129 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
133 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
138 * @return unwrapped logger.
140 public Logger unwrap() {
145 * Report <tt>ENTERING</tt> marker.
147 * @param request non-null incoming request (wrapper).
150 public ONAPLogAdapter entering(final RequestAdapter request) {
152 checkNotNull(request);
154 // Default the service name.
156 this.setEnteringMDCs(request);
157 this.mLogger.info(ONAPLogConstants.Markers.ENTRY, EMPTY_MESSAGE);
163 * Report <tt>ENTERING</tt> marker.
165 * @param request non-null incoming request.
168 public ONAPLogAdapter entering(final HttpServletRequest request) {
169 return this.entering(new HttpServletRequestAdapter(checkNotNull(request)));
173 * Report <tt>EXITING</tt> marker.
177 public ONAPLogAdapter exiting() {
179 this.mResponseDescriptor.setMDCs();
180 this.mLogger.info(ONAPLogConstants.Markers.EXIT, EMPTY_MESSAGE);
189 * Report pending invocation with <tt>INVOKE</tt> marker.
191 * <p>If you call this variant, then YOU are assuming responsibility for
192 * setting the requisite ONAP headers.</p>
194 * @param sync whether synchronous.
195 * @return invocation ID to be passed with invocation.
197 public UUID invoke(final ONAPLogConstants.InvocationMode sync) {
199 final UUID invocationID = UUID.randomUUID();
201 // Derive SYNC/ASYNC marker.
203 final Marker marker = (sync == null) ? ONAPLogConstants.Markers.INVOKE : sync.getMarker();
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?)
209 this.mLogger.info(marker, "{}", invocationID);
214 * Report pending invocation with <tt>INVOKE</tt> marker,
215 * setting standard ONAP logging headers automatically.
217 * @param builder request builder, for setting headers.
218 * @param sync whether synchronous, nullable.
219 * @return invocation ID to be passed with invocation.
221 public UUID invoke(final RequestBuilder builder,
222 final ONAPLogConstants.InvocationMode sync) {
224 // Sync can be defaulted. Builder cannot.
226 checkNotNull(builder);
228 // Log INVOKE, and retain invocation ID for header + return.
230 final UUID invocationID = this.invoke(sync);
232 // Set standard HTTP headers on (southbound request) builder.
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)));
245 * Report vanilla <tt>INVOKE</tt> marker.
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.
251 public UUID invoke(final RequestBuilder builder) {
252 return this.invoke(builder, (ONAPLogConstants.InvocationMode)null);
256 * Get descriptor, for overriding service details.
257 * @return non-null descriptor.
259 public ServiceDescriptor getServiceDescriptor() {
260 return checkNotNull(this.mServiceDescriptor);
264 * Override {@link ServiceDescriptor}.
265 * @param d non-null override.
268 public ONAPLogAdapter setServiceDescriptor(final ServiceDescriptor d) {
269 this.mServiceDescriptor = checkNotNull(d);
274 * Get descriptor, for setting response details.
275 * @return non-null descriptor.
277 public ResponseDescriptor getResponseDescriptor() {
278 return checkNotNull(this.mResponseDescriptor);
282 * Override {@link ResponseDescriptor}.
283 * @param d non-null override.
286 public ONAPLogAdapter setResponseDescriptor(final ResponseDescriptor d) {
287 this.mResponseDescriptor = checkNotNull(d);
291 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
293 // Protected methods.
295 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
298 * Set MDCs that persist for the duration of an invocation.
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>
304 * @param request incoming HTTP request.
307 protected ONAPLogAdapter setEnteringMDCs(final RequestAdapter<?> request) {
309 // Extract MDC values from standard HTTP headers.
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));
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.
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()));
329 // Delegate to the service adapter, for service-related DMCs.
331 this.mServiceDescriptor.setMDCs();
333 // Default the service name to the requestURI, in the event that
334 // no value has been provided.
336 if (MDC.get(ONAPLogConstants.MDCs.SERVICE_NAME) == null) {
337 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, request.getRequestURI());
344 * Dependency-free nullcheck.
346 * @param in to be checked.
347 * @param <T> argument (and return) type.
350 protected static <T> T checkNotNull(final T in) {
352 throw new NullPointerException();
358 * Dependency-free string default.
360 * @param in to be filtered.
361 * @return input string or null.
363 protected static String defaultToEmpty(final Object in) {
367 return in.toString();
371 * Dependency-free string default.
373 * @param in to be filtered.
374 * @return input string or null.
376 protected static String defaultToUUID(final String in) {
378 return UUID.randomUUID().toString();
383 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
387 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
390 * Extensible descriptor for reporting service details.
392 * <p>In most cases extension isn't required. </p>
394 public static class ServiceDescriptor {
396 /** <tt>ServiceName</tt>. */
397 protected String mName;
399 /** <tt>InstanceUUID</tt>. */
400 protected String mUUID = sInstanceUUID.toString();
404 * @param name <tt>ServiceName</tt>.
407 public ServiceDescriptor setServiceName(final String name) {
414 * @param uuid <tt>InstanceUUID</tt>.
417 public ServiceDescriptor setServiceUUID(final String uuid) {
423 * Set MDCs. Once set they remain set until everything is cleared.
425 protected void setMDCs() {
426 MDC.put(ONAPLogConstants.MDCs.SERVICE_NAME, defaultToEmpty(this.mName));
427 MDC.put(ONAPLogConstants.MDCs.INSTANCE_UUID, defaultToEmpty(this.mUUID));
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>.)
437 public static class ResponseDescriptor {
439 /** Response errorcode. */
440 protected String mCode;
442 /** Response description. */
443 protected String mDescription;
445 /** Response severity. */
446 protected Level mSeverity;
448 /** Response status, of {<tt>COMPLETED</tt>, <tt>ERROR</tt>}. */
449 protected ONAPLogConstants.ResponseStatus mStatus;
454 * @param code response (error) code.
457 public ResponseDescriptor setResponseCode(final String code) {
465 * @param description response description.
468 public ResponseDescriptor setResponseDescription(final String description) {
469 this.mDescription = description;
476 * @param severity response outcome severity.
479 public ResponseDescriptor setResponseSeverity(final Level severity) {
480 this.mSeverity = severity;
487 * @param status response overall status.
490 public ResponseDescriptor setResponseStatus(final ONAPLogConstants.ResponseStatus status) {
491 this.mStatus = status;
496 * Overrideable method to set MDCs based on property values.
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));
507 * Adapter for reading information from an incoming HTTP request.
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>
512 * <p>Perhaps should be generalized to refer to constants instead of
513 * requiring the implementation of specific methods.</p>
515 * @param <T> type, for chaining.
517 public interface RequestAdapter<T extends RequestAdapter> {
520 * Get header by name.
521 * @param name header name.
522 * @return header value, or null.
524 String getHeader(String name);
527 * Get client address.
528 * @return address, if available.
530 String getClientAddress();
533 * Get server address.
534 * @return address, if available.
536 String getServerAddress();
539 * Get default service name, from service URI.
540 * @return service name default.
542 String getRequestURI();
546 * Default {@link RequestBuilder} impl for {@link HttpServletRequest}, which
547 * will should available for most incoming REST requests.
549 public static class HttpServletRequestAdapter implements RequestAdapter<HttpServletRequestAdapter> {
551 /** Wrapped HTTP request. */
552 private final HttpServletRequest mRequest;
555 * Construct adapter for HTTP request.
556 * @param request to be wrapped;
558 public HttpServletRequestAdapter(final HttpServletRequest request) {
559 this.mRequest = checkNotNull(request);
566 public String getHeader(final String name) {
567 return this.mRequest.getHeader(name);
574 public String getClientAddress() {
575 return this.mRequest.getRemoteAddr();
582 public String getServerAddress() {
583 return this.mRequest.getServerName();
590 public String getRequestURI() {
591 return this.mRequest.getRequestURI();
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.
600 * <p>No default implementation, because there's no HTTP client that's
601 * sufficiently ubiquitous to warrant incurring a mandatory dependency.</p>
603 * @param <T> type, for chaining.
605 public interface RequestBuilder<T extends RequestBuilder> {
609 * @param name header name.
610 * @param value header value.
613 T setHeader(String name, String value);