/* * Copyright (C) 2013-2017 NTT DATA Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.terasoluna.gfw.common.exception; import java.text.MessageFormat; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.StringUtils; /** * Class that outputs the exception information to the log. * <p> * Creates a log message using specified exception and uses {@link org.slf4j.Logger} to output the log.<br> * Actual destination of log output varies depending on the log definition of log output library. * </p> * <p> * <strong>[Default Log format]</strong><br> * * <pre> * Application Log : * [{exceptionCode}] {exceptionMessage} * {StackTrace} * * Monitoring Log : * [{exceptionCode}] {exceptionMessage} * </pre> * * <strong>[Output example]</strong> * * <pre> * throw new SystemException("e.xx.xx.0001", "system error.", e); * </pre> * * <strong>Application Log :</strong> * * <pre> * [e.xx.xx.0001] system error. * org.terasoluna.gfw.common.exception.SystemException: system error. * at org.xxxx ... * at org.xxxx ... * at org.xxxx ... * at org.xxxx ... * Caused by: java.io.IOException: .. * at org.xxxx ... * at org.xxxx ... * </pre> * * <strong>Monitoring Log :</strong> * * <pre> * Monitoring Log : * [e.xx.xx.0001] system error. * </pre> */ public class ExceptionLogger implements InitializingBean { /** * Logger suffix of monitoring log. */ private static final String MONITORING_LOG_LOGGER_SUFFIX = ".Monitoring"; /** * Logger for application log output. */ private final Logger applicationLogger; /** * Logger for monitoring log output. */ private final Logger monitoringLogger; /** * Logger for each log level. */ private final Map<ExceptionLevel, LogLevelWrappingLogger> exceptionLevelLoggers = new ConcurrentHashMap<ExceptionLevel, LogLevelWrappingLogger>(); /** * Logger that outputs log at INFO log level. */ private final InfoLogger infoLogger; /** * Logger that outputs log at WARN log level. */ private final WarnLogger warnLogger; /** * Logger that outputs log at ERROR log level. */ private final ErrorLogger errorLogger; /** * Object that resolves exception code. */ private ExceptionCodeResolver exceptionCodeResolver = new SimpleMappingExceptionCodeResolver(); /** * Object that resolves exception level. */ private ExceptionLevelResolver exceptionLevelResolver; /** * placeholder for exception code of log formatter. */ private String PLACEHOLDER_OF_EXCEPTION_CODE = "{0}"; /** * placeholder for exception message of log formatter. */ private String PLACEHOLDER_OF_EXCEPTION_MESSAGE = "{1}"; /** * Message formatter for log output. */ private String logMessageFormat = String.format("[%s] %s", PLACEHOLDER_OF_EXCEPTION_CODE, PLACEHOLDER_OF_EXCEPTION_MESSAGE); /** * Default exception code in case it is not specified. */ private String defaultCode = "UNDEFINED-CODE"; /** * Default exception message in case it is not specified. */ private String defaultMessage = "UNDEFINED-MESSAGE"; /** * Log message trim flag. */ private boolean trimLogMessage = true; /** * Default constructor. * <p> * {@link #ExceptionLogger(String)} is called with FQCN of this class as parameter. * </p> */ public ExceptionLogger() { this(ExceptionLogger.class.getName()); } /** * Constructor * <p> * Based on the name specified in the parameters, logger for application log and <br> * logger for monitoring log is fetched. * </p> * <p> * Logger Name<br> * <ul> * <li>logger for output of application log: {name}</li> * <li>logger for output of monitoring log: {name} + ".Monitoring"</li> * </ul> * @param name name of logger */ public ExceptionLogger(String name) { this.applicationLogger = LoggerFactory.getLogger(name); this.monitoringLogger = LoggerFactory.getLogger(name + MONITORING_LOG_LOGGER_SUFFIX); this.infoLogger = new InfoLogger(); this.warnLogger = new WarnLogger(); this.errorLogger = new ErrorLogger(); } /** * Set the resolver object for exception code. * <p> * If the exception code resolution object is not set, exception code is not output to the log. * </p> * @param exceptionCodeResolver exception code resolution object */ public void setExceptionCodeResolver( ExceptionCodeResolver exceptionCodeResolver) { this.exceptionCodeResolver = exceptionCodeResolver; } /** * set the resolution object for exception level. * <p> * If the exception level resolution object is not set, exception level is not output to the log. * </p> * @param exceptionLevelResolver exception level resolution object. */ public void setExceptionLevelResolver( ExceptionLevelResolver exceptionLevelResolver) { this.exceptionLevelResolver = exceptionLevelResolver; } /** * set the log format. * <p> * It is possible to specify the output position of the exception message and exception code in log format. <br> * The position can specified using "{0}" for exception code and "{1}" for exception message.<br> * "{0}" and "{1}" is must be specified. if changed validation rule of logMessageFormat, please override * {@link #validateLogMessageFormat(String)} method. * </p> * @param logMessageFormat log format. */ public void setLogMessageFormat(String logMessageFormat) { this.logMessageFormat = logMessageFormat; } /** * Set the trim flag of log message. * <p> * Default is <code>true</code> * </p> * @param trimLogMessage set <code>true</code> for trimming */ public void setTrimLogMessage(boolean trimLogMessage) { this.trimLogMessage = trimLogMessage; } /** * Set default exception code. * @param defaultCode default exception code. */ public void setDefaultCode(String defaultCode) { this.defaultCode = defaultCode; } /** * Set default exception message. * @param defaultMessage default exception message. */ public void setDefaultMessage(String defaultMessage) { this.defaultMessage = defaultMessage; } /** * Initializes the exception logger. * <p> * If exception resolution object is not set, use {@link org.terasoluna.gfw.common.exception.DefaultExceptionLevelResolver}. * </p> * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() */ @Override public void afterPropertiesSet() { validateLogMessageFormat(logMessageFormat); if (exceptionLevelResolver == null) { exceptionLevelResolver = new DefaultExceptionLevelResolver(exceptionCodeResolver); } registerExceptionLevelLoggers(ExceptionLevel.INFO, infoLogger); registerExceptionLevelLoggers(ExceptionLevel.WARN, warnLogger); registerExceptionLevelLoggers(ExceptionLevel.ERROR, errorLogger); } /** * Output the log related to exception level. * @param ex Exception */ public void log(Exception ex) { ExceptionLevel level = exceptionLevelResolver.resolveExceptionLevel(ex); LogLevelWrappingLogger logger = null; if (level != null) { logger = exceptionLevelLoggers.get(level); } if (logger == null) { logger = errorLogger; } log(ex, logger); } /** * Output the information log. * <p> * Outputs INFO level log to application log and monitoring log. * </p> * @param ex Exception */ public void info(Exception ex) { log(ex, infoLogger); } /** * Output WARN level log * <p> * Outputs WARN level log to application log and monitoring log. * </p> * @param ex Exception */ public void warn(Exception ex) { log(ex, warnLogger); } /** * Ouputs ERROR log. * <p> * Outputs ERROR level log to application log and monitoring log. * </p> * @param ex Exception */ public void error(Exception ex) { log(ex, errorLogger); } /** * validate a logMessageFormat. * <p> * logMessageFormat must have placeholder("{0}" and "{1}"). "{0}" is replaced with exception code. "{1}" is replaced with * exception message. * </p> * @param logMessageFormat Message formatter for log output. */ protected void validateLogMessageFormat(String logMessageFormat) { if (logMessageFormat == null || !logMessageFormat.contains(PLACEHOLDER_OF_EXCEPTION_CODE) || !logMessageFormat.contains(PLACEHOLDER_OF_EXCEPTION_MESSAGE)) { String message = "logMessageFormat must have placeholder({0} and {1})." + " {0} is replaced with exception code." + " {1} is replaced with exception message. current logMessageFormat is \"" + logMessageFormat + "\"."; throw new IllegalArgumentException(message); } } /** * Resolved exception code. * <p> * Fetches exception code from the occured exception. * </p> * @param ex Exception * @return Exception code */ protected String resolveExceptionCode(Exception ex) { String exceptionCode = null; if (exceptionCodeResolver != null) { exceptionCode = exceptionCodeResolver.resolveExceptionCode(ex); } return exceptionCode; } /** * Creats log message. * @param ex Exception * @return log message */ protected String makeLogMessage(Exception ex) { String exceptionCode = resolveExceptionCode(ex); return formatLogMessage(exceptionCode, ex.getMessage()); } /** * Formats the message for log output. * <p> * Formats exception code and exception message and creates message for log output. <br> * </p> * <p> * [Default format of message for log output]<br> * <ul> * <li>If exception code is resolved: "[${exceptionCode}] ${exceptionMessage}" format</li> * <li>If exception code is not resolved: "${exceptionMessage}" format</li> * </ul> * @param exceptionCode exception code * @param exceptionMessage exception message * @return message ready for log output */ protected String formatLogMessage(String exceptionCode, String exceptionMessage) { String bindingExceptionCode = exceptionCode; String bindingExceptionMessage = exceptionMessage; if (StringUtils.isEmpty(bindingExceptionCode)) { bindingExceptionCode = defaultCode; } if (StringUtils.isEmpty(bindingExceptionMessage)) { bindingExceptionMessage = defaultMessage; } String message = MessageFormat.format(logMessageFormat, bindingExceptionCode, bindingExceptionMessage); if (trimLogMessage) { message = message.trim(); } return message; } /** * Registers the logger corresponding to the level of specified exception. * <p> * If a logger of same level has already been registered, it will be overwritten. * </p> * @param level exception level * @param logger delegating logger */ protected void registerExceptionLevelLoggers(ExceptionLevel level, LogLevelWrappingLogger logger) { this.exceptionLevelLoggers.put(level, logger); } /** * Returns logger for output of application log. * @return logger for output of application log */ protected Logger getApplicationLogger() { return applicationLogger; } /** * Returns logger for output of monitoring log. * @return logger for output of monitoring log. */ protected Logger getMonitoringLogger() { return monitoringLogger; } /** * Outputs the log using specified logger. * @param ex Exception * @param logger delegating logger */ private void log(Exception ex, LogLevelWrappingLogger logger) { if (!logger.isEnabled()) { return; } String logMessage = makeLogMessage(ex); logger.log(logMessage, ex); } /** * Logger instance that wraps the log level. */ protected interface LogLevelWrappingLogger { /** * Determines if the logger is valid. * @return Returns <code>true</code> if valid. */ boolean isEnabled(); /** * Outputs the log. * @param logMessage log message * @param ex Exception */ void log(String logMessage, Exception ex); } /** * Wrapper logger for output of INFO level of log. */ private final class InfoLogger implements LogLevelWrappingLogger { /** * Checks whether Info logging is enabled in either monitoring log or application log * @see org.terasoluna.gfw.common.exception.ExceptionLogger.LogLevelWrappingLogger#isEnabled() */ @Override public boolean isEnabled() { return monitoringLogger.isInfoEnabled() || applicationLogger.isInfoEnabled(); } /** * Logs messages of Info level. * <p> * Logs messages of Info level in Monitoring log and Application log if Info logging in these loggers are enabled. * </p> * @see org.terasoluna.gfw.common.exception.ExceptionLogger.LogLevelWrappingLogger#log(java.lang.String, * java.lang.Exception) */ @Override public void log(String logMessage, Exception ex) { if (monitoringLogger.isInfoEnabled()) { monitoringLogger.info(logMessage); } if (applicationLogger.isInfoEnabled()) { applicationLogger.info(logMessage, ex); } } } /** * Wrapper logger for output of WARN level of log. */ private final class WarnLogger implements LogLevelWrappingLogger { /** * Checks whether Warn logging is enabled in either monitoring log or application log * @see org.terasoluna.gfw.common.exception.ExceptionLogger.LogLevelWrappingLogger#isEnabled() */ @Override public boolean isEnabled() { return monitoringLogger.isWarnEnabled() || applicationLogger.isWarnEnabled(); } /** * Logs messages of Warn level. * <p> * Logs messages of Warn level in Monitoring log and Application log if Warn logging in these loggers are enabled. * </p> * @see org.terasoluna.gfw.common.exception.ExceptionLogger.LogLevelWrappingLogger#log(java.lang.String, * java.lang.Exception) */ @Override public void log(String logMessage, Exception ex) { if (monitoringLogger.isWarnEnabled()) { monitoringLogger.warn(logMessage); } if (applicationLogger.isWarnEnabled()) { applicationLogger.warn(logMessage, ex); } } } /** * Wrapper logger for output of ERROR level of log. */ private final class ErrorLogger implements LogLevelWrappingLogger { /** * Checks whether Error logging is enabled in either monitoring log or application log * @see org.terasoluna.gfw.common.exception.ExceptionLogger.LogLevelWrappingLogger#isEnabled() */ @Override public boolean isEnabled() { return monitoringLogger.isErrorEnabled() || applicationLogger.isErrorEnabled(); } /** * Logs messages of Error level. * <p> * Logs messages of Error level in Monitoring log and Application log if error logging in these loggers are enabled. * </p> * @see org.terasoluna.gfw.common.exception.ExceptionLogger.LogLevelWrappingLogger#log(java.lang.String, * java.lang.Exception) */ @Override public void log(String logMessage, Exception ex) { if (monitoringLogger.isErrorEnabled()) { monitoringLogger.error(logMessage); } if (applicationLogger.isErrorEnabled()) { applicationLogger.error(logMessage, ex); } } } }