2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
6 * Copyright © 2017-2018 European Software Marketing Ltd.
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=========================================================
21 package org.onap.aai.babel.logging;
23 import static com.att.eelf.configuration.Configuration.MDC_SERVICE_NAME;
25 import ch.qos.logback.classic.AsyncAppender;
26 import ch.qos.logback.core.FileAppender;
27 import com.att.eelf.configuration.EELFLogger;
28 import com.att.eelf.configuration.EELFManager;
29 import com.att.eelf.i18n.EELFResolvableErrorEnum;
31 import java.text.DateFormat;
32 import java.text.SimpleDateFormat;
33 import java.util.Date;
34 import java.util.Optional;
35 import java.util.function.BiConsumer;
36 import java.util.function.Consumer;
37 import javax.servlet.ServletRequest;
38 import javax.ws.rs.core.HttpHeaders;
39 import javax.ws.rs.core.Response.Status;
40 import org.apache.commons.lang3.time.StopWatch;
41 import org.onap.aai.babel.request.RequestHeaders;
42 import org.onap.aai.cl.api.LogFields;
43 import org.onap.aai.cl.api.Logger;
44 import org.onap.aai.cl.mdc.MdcContext;
45 import org.onap.aai.cl.mdc.MdcOverride;
46 import org.onap.aai.restclient.client.Headers;
50 * This Log Helper mimics the interface of a Common Logging Logger
51 * but adds helper methods for audit and metrics logging requirements.
53 * Messages are logged to the appropriate EELF functional logger as described below.
55 * Error Log: INFO/WARN/ERROR/FATAL
56 * Debug Log: DEBUG/TRACE
57 * Audit Log: summary view of transaction processing
58 * Metrics Log: detailed timings of transaction processing interactions
60 * Audit and Metrics log messages record the following fields:
62 * RequestID - an RFC4122 UUID for the transaction request
63 * ServiceName - the API provided by this service
64 * PartnerName - invoker of the API
65 * ClassName - name of the class creating the log record
67 * The above list is not exhaustive.
69 public enum LogHelper implements Logger {
70 INSTANCE; // Joshua Bloch's singleton pattern
73 public interface TriConsumer<T, U, V> {
74 public void accept(T t, U u, V v);
77 /** Audit log message status code values. See {@code MdcParameter.STATUS_CODE} */
78 public enum StatusCode {
84 * Mapped Diagnostic Context parameter names.
87 * Note that MdcContext.MDC_START_TIME is used for audit messages, and indicates the start of a transaction.
88 * Messages in the metrics log record sub-operations of a transaction and thus use different timestamps.
90 public enum MdcParameter {
91 REQUEST_ID(MdcContext.MDC_REQUEST_ID),
92 CLASS_NAME("ClassName"),
93 BEGIN_TIMESTAMP("BeginTimestamp"),
94 END_TIMESTAMP("EndTimestamp"),
95 ELAPSED_TIME("ElapsedTime"),
96 STATUS_CODE("StatusCode"),
97 RESPONSE_CODE("ResponseCode"),
98 RESPONSE_DESCRIPTION("ResponseDescription"),
99 TARGET_ENTITY("TargetEntity"),
100 TARGET_SERVICE_NAME("TargetServiceName"),
103 private final String parameterName;
105 MdcParameter(String parameterName) {
106 this.parameterName = parameterName;
110 * Get the MDC logging context parameter name as referenced by the logback configuration
112 * @return the MDC parameter name
114 public String value() {
115 return parameterName;
119 /** Our externally advertised service API */
120 private static final String SERVICE_NAME_VALUE = "AAI-BAS";
122 private static final EELFLogger errorLogger = EELFManager.getInstance().getErrorLogger();
123 private static final EELFLogger debugLogger = EELFManager.getInstance().getDebugLogger();
124 private static final EELFLogger auditLogger = EELFManager.getInstance().getAuditLogger();
125 private static final EELFLogger metricsLogger = EELFManager.getInstance().getMetricsLogger();
127 /** Formatting for timestamps logged as Strings (from the MDC) */
128 private DateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
130 // Records the elapsed time (since the start of servicing a request) for audit logging
131 private StopWatch auditStopwatch;
134 * Initialises the MDC (logging context) with default values, to support any logging of messages BEFORE the
135 * startAudit() method is invoked.
137 private LogHelper() {
138 setContextValue(MDC_SERVICE_NAME, SERVICE_NAME_VALUE);
139 // This value is not expected to be used in the default logging configuration
140 setContextValue(MdcContext.MDC_START_TIME, timestampFormat.format(new Date()));
144 * Begin recording transaction information for a new request. This data is intended for logging purposes. This
145 * method does not actually write any messages to the log(s).
148 * Initialise the MDC logging context for auditing and metrics, using the HTTP request headers. This information
149 * includes: the correlation ID, local application service name/ID, calling host details and authentication data.
152 * The request object is used to find the client details (e.g. IP address)
154 * @param headers raw HTTP headers
155 * @param servletRequest the request
157 public void startAudit(final HttpHeaders headers, ServletRequest servletRequest) {
158 auditStopwatch = new StopWatch();
159 auditStopwatch.start();
161 Optional<String> requestId = Optional.empty();
162 String serviceInstanceId = null;
163 Optional<String> partnerName = Optional.empty();
165 if (headers != null) {
166 RequestHeaders requestHeaders = new RequestHeaders(headers);
167 requestId = Optional.ofNullable(requestHeaders.getCorrelationId());
168 serviceInstanceId = requestHeaders.getInstanceId();
169 partnerName = Optional.ofNullable(headers.getHeaderString(Headers.FROM_APP_ID));
172 String clientHost = null;
173 String clientIPAddress = null;
174 String user = "<UNKNOWN_USER>";
176 if (servletRequest != null) {
177 clientHost = servletRequest.getRemoteHost();
178 clientIPAddress = servletRequest.getRemoteAddr();
180 if (!partnerName.isPresent()) {
181 partnerName = Optional.ofNullable(clientHost);
185 // Populate standard MDC keys - note that the initialize method calls MDC.clear()
186 MdcContext.initialize(requestId.orElse("missing-request-id"), SERVICE_NAME_VALUE, serviceInstanceId,
187 partnerName.orElse("<UNKNOWN_PARTNER>"), clientIPAddress);
189 setContextValue(MdcParameter.USER, user);
190 setContextValue(MdcContext.MDC_REMOTE_HOST, clientHost);
194 * Store a value in the current thread's logging context.
196 * @param key non-null parameter name
197 * @param value the value to store against the key
199 public void setContextValue(String key, String value) {
200 debug(key + "=" + value);
205 * Store a value in the current thread's logging context.
207 * @param param identifier of the context parameter
208 * @param value the value to store for this parameter
210 public void setContextValue(MdcParameter param, String value) {
211 setContextValue(param.value(), value);
215 * Set a value in the current thread's logging context, only if this is not already set.
217 * @param key non-null parameter name
218 * @param value the value to store against the key (only if the current value is null)
220 public void setDefaultContextValue(String key, String value) {
221 if (MDC.get(key) == null) {
222 setContextValue(key, value);
227 * Set a value in the current thread's logging context, only if this is not already set.
229 * @param param identifier of the context parameter
230 * @param value the value to store for this parameter (only if the current value is null)
232 public void setDefaultContextValue(MdcParameter param, String value) {
233 setContextValue(param.value(), value);
236 /** Clear all logging context values. This should be called at start-up only. */
237 public void clearContext() {
238 debug("Clearing MDC context");
243 * Log an audit message to the audit logger. This method is expected to be called when a response is returned to the
244 * caller and/or when the processing of the request completes.
246 * @param status status of the service request: COMPLETE/ERROR
247 * @param responseCode optional application error code
248 * @param responseDescription human-readable description of the response code
249 * @param args the argument(s) required to populate the Audit Message log content
251 public void logAudit(StatusCode status, String responseCode, String responseDescription, final String... args) {
252 if (auditStopwatch == null) {
253 debug("Unexpected program state: audit stopwatch not started");
254 auditStopwatch = new StopWatch();
255 auditStopwatch.start();
258 if (auditLogger.isInfoEnabled()) {
259 setMdcElapsedTime(auditStopwatch);
260 setContextValue(MdcParameter.STATUS_CODE, status.toString());
261 setContextValue(MdcParameter.RESPONSE_CODE, responseCode);
262 setContextValue(MdcParameter.RESPONSE_DESCRIPTION, responseDescription);
263 invokeLogger(auditLogger::info, ApplicationMsgs.MESSAGE_AUDIT, args);
268 * Log an audit message to the audit logger representing a non-specific processing success message.
272 public void logAuditSuccess(String msg) {
273 Status status = Status.OK;
274 logAudit(StatusCode.COMPLETE, Integer.toString(status.getStatusCode()), status.getReasonPhrase(), msg);
278 * Log an audit message to the audit logger representing an internal error (e.g. for an exception thrown by the
279 * implementation). This method is expected to be called when a generic error response is returned to the caller to
280 * indicate a processing failure.
284 public void logAuditError(Exception e) {
285 Status status = Status.INTERNAL_SERVER_ERROR;
286 logAudit(StatusCode.ERROR, Integer.toString(status.getStatusCode()), status.getReasonPhrase(), e.getMessage());
290 * Log a message to the metrics log.
292 * @param error the log code
293 * @param args the info messages
295 public void logMetrics(final String... args) {
296 if (metricsLogger.isInfoEnabled()) {
297 invokeLogger(metricsLogger::info, ApplicationMsgs.MESSAGE_METRIC, args);
305 public void logMetrics(final StopWatch stopwatch, String... args) {
306 setMdcElapsedTime(stopwatch);
311 public String formatMsg(@SuppressWarnings("rawtypes") Enum arg0, String... args) {
312 throw new UnsupportedOperationException();
316 public boolean isDebugEnabled() {
317 return debugLogger != null && debugLogger.isDebugEnabled();
321 public boolean isErrorEnabled() {
322 return errorLogger.isErrorEnabled();
326 public boolean isInfoEnabled() {
327 return errorLogger.isInfoEnabled();
331 public boolean isTraceEnabled() {
332 return debugLogger.isTraceEnabled();
336 public boolean isWarnEnabled() {
337 return errorLogger.isWarnEnabled();
341 * Log a DEBUG level message to the debug logger.
343 * @param message the debug message
346 public void debug(String message) {
347 if (isDebugEnabled()) {
348 invokeLogger(debugLogger::debug, message);
353 public void debug(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
354 if (isDebugEnabled()) {
355 invokeErrorCodeLogger(debugLogger::debug, (EELFResolvableErrorEnum) errorCode, args);
360 public void debug(@SuppressWarnings("rawtypes") Enum errorCode, LogFields arg1, String... args) {
361 throw new UnsupportedOperationException();
365 public void error(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
366 if (isErrorEnabled()) {
367 invokeErrorCodeLogger(errorLogger::error, (EELFResolvableErrorEnum) errorCode, args);
372 public void error(@SuppressWarnings("rawtypes") Enum errorCode, Throwable t, String... args) {
373 if (isErrorEnabled()) {
374 invokeErrorCodeLogger(errorLogger::error, (EELFResolvableErrorEnum) errorCode, t, args);
379 public void error(@SuppressWarnings("rawtypes") Enum errorCode, LogFields arg1, String... args) {
380 throw new UnsupportedOperationException();
384 public void error(@SuppressWarnings("rawtypes") Enum errorCode, LogFields arg1, Throwable arg2, String... args) {
385 throw new UnsupportedOperationException();
389 public void info(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
390 if (isInfoEnabled()) {
391 invokeErrorCodeLogger(errorLogger::info, (EELFResolvableErrorEnum) errorCode, args);
396 public void info(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, String... args) {
397 throw new UnsupportedOperationException();
401 public void info(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, MdcOverride arg2, String... args) {
402 throw new UnsupportedOperationException();
406 public void trace(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
407 if (isTraceEnabled()) {
408 invokeErrorCodeLogger(debugLogger::trace, (EELFResolvableErrorEnum) errorCode, args);
413 public void trace(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, String... args) {
414 throw new UnsupportedOperationException();
418 public void warn(@SuppressWarnings("rawtypes") Enum errorCode, String... args) {
419 if (isWarnEnabled()) {
420 invokeErrorCodeLogger(errorLogger::warn, (EELFResolvableErrorEnum) errorCode, args);
425 public void warn(@SuppressWarnings("rawtypes") Enum arg0, LogFields arg1, String... args) {
426 throw new UnsupportedOperationException();
430 * Get the method name for a calling method (from the current stack trace)
432 * @param level number of levels for the caller (not including this method)
433 * @return the class and name of the calling method in the form "class#method"
435 public static String getCallerMethodName(int level) {
436 StackTraceElement callingMethod = Thread.currentThread().getStackTrace()[level + 2];
437 return callingMethod.getClassName() + "#" + callingMethod.getMethodName();
441 * Convenience method to be used only for testing purposes.
443 * @return the directory storing the log files
445 public static String getLogDirectory() {
446 ch.qos.logback.classic.Logger logger =
447 (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger("com.att.eelf");
448 AsyncAppender appender = (AsyncAppender) logger.getAppender("asyncEELF");
449 FileAppender<?> fileAppender = (FileAppender<?>) appender.getAppender("EELF");
450 return new File(fileAppender.getFile()).getParent();
453 private void setMdcClassName() {
454 MDC.put(MdcParameter.CLASS_NAME.value(), getCallerMethodName(3));
457 private void unsetMdcClassName() {
458 MDC.put(MdcParameter.CLASS_NAME.value(), null);
462 * Set the Begin, End, and Elapsed time values in the MDC context.
466 private void setMdcElapsedTime(final StopWatch stopwatch) {
467 long startTime = stopwatch.getStartTime();
468 long elapsedTime = stopwatch.getTime();
470 setContextValue(MdcParameter.BEGIN_TIMESTAMP, timestampFormat.format(startTime));
471 setContextValue(MdcParameter.END_TIMESTAMP, timestampFormat.format(startTime + elapsedTime));
472 setContextValue(MdcParameter.ELAPSED_TIME, Long.toString(elapsedTime)); // Milliseconds
476 * @param logMethod the logger method to invoke
479 private void invokeLogger(Consumer<String> logMethod, String message) {
481 logMethod.accept(message);
490 private void invokeLogger(BiConsumer<ApplicationMsgs, String[]> logMethod, ApplicationMsgs msg, String[] args) {
492 logMethod.accept(msg, args);
501 private void invokeErrorCodeLogger(BiConsumer<EELFResolvableErrorEnum, String[]> logMethod,
502 EELFResolvableErrorEnum errorEnum, String[] args) {
504 logMethod.accept(errorEnum, args);
511 * @param t a Throwable
514 private void invokeErrorCodeLogger(TriConsumer<EELFResolvableErrorEnum, Throwable, String[]> logMethod,
515 EELFResolvableErrorEnum errorEnum, Throwable t, String[] args) {
517 logMethod.accept(errorEnum, t, args);