/*
* Copyright 2002-2016 the original author or authors.
*
* 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.springframework.integration.handler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.dispatcher.AggregateMessageDeliveryException;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* MessageHandler implementation that simply logs the Message or its payload depending on the value of the
* 'shouldLogFullMessage' or SpEL 'logExpression' property.
* If logging the payload, and it is assignable to Throwable, it will log the stack
* trace. By default, it will log the payload only.
*
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
* @author Andriy Kryvtsun
* @since 1.0.1
*/
public class LoggingHandler extends AbstractMessageHandler {
public enum Level {
FATAL, ERROR, WARN, INFO, DEBUG, TRACE
}
private volatile Level level;
private volatile boolean expressionSet;
private volatile Expression expression = EXPRESSION_PARSER.parseExpression("payload");
private volatile EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext();
private volatile boolean shouldLogFullMessageSet;
private volatile Log messageLogger = this.logger;
/**
* Create a LoggingHandler with the given log level (case-insensitive).
* <p>
* The valid levels are: FATAL, ERROR, WARN, INFO, DEBUG, or TRACE
* </p>
* @param level The level.
* @see #LoggingHandler(Level)
*/
public LoggingHandler(String level) {
this(convertLevel(level));
}
private static Level convertLevel(String level) {
Assert.hasText(level, "'level' cannot be empty");
try {
return Level.valueOf(level.toUpperCase());
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid log level '" + level
+ "'. The (case-insensitive) supported values are: "
+ StringUtils.arrayToCommaDelimitedString(Level.values()));
}
}
/**
* Create a {@link LoggingHandler} with the given log {@link Level}.
* @param level the {@link Level} to use.
* @since 4.3
*/
public LoggingHandler(Level level) {
doSetLevel(level);
}
/**
* Set a SpEL expression string to use.
* @param expressionString the SpEL expression string to use.
* @since 4.3
* @see #setLogExpression(Expression)
*/
public void setLogExpressionString(String expressionString) {
Assert.hasText(expressionString, "'expressionString' must not be empty");
setLogExpression(EXPRESSION_PARSER.parseExpression(expressionString));
}
/**
* Set an {@link Expression} to evaluate a log entry at runtime against the request {@link Message}.
* @param expression the {@link Expression} to use.
* @since 4.3
* @see #setLogExpressionString(String)
*/
public void setLogExpression(Expression expression) {
Assert.isTrue(!(this.shouldLogFullMessageSet),
"Cannot set both 'expression' AND 'shouldLogFullMessage' properties");
this.expressionSet = true;
this.expression = expression;
}
/**
* @return The current logging {@link Level}.
*/
public Level getLevel() {
return this.level;
}
/**
* Set the logging {@link Level} to change the behavior at runtime.
* @param level the level.
*/
public void setLevel(Level level) {
doSetLevel(level);
}
private void doSetLevel(Level level) {
Assert.notNull(level, "'level' cannot be null");
this.level = level;
}
public void setLoggerName(String loggerName) {
Assert.hasText(loggerName, "loggerName must not be empty");
this.messageLogger = LogFactory.getLog(loggerName);
}
/**
* Specify whether to log the full Message. Otherwise, only the payload will be logged. This value is
* <code>false</code> by default.
* @param shouldLogFullMessage true if the complete message should be logged.
*/
public void setShouldLogFullMessage(boolean shouldLogFullMessage) {
Assert.isTrue(!(this.expressionSet), "Cannot set both 'expression' AND 'shouldLogFullMessage' properties");
this.shouldLogFullMessageSet = true;
this.expression = shouldLogFullMessage
? EXPRESSION_PARSER.parseExpression("#root")
: EXPRESSION_PARSER.parseExpression("payload");
}
@Override
public String getComponentType() {
return "logging-channel-adapter";
}
@Override
protected void onInit() throws Exception {
super.onInit();
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
switch (this.level) {
case FATAL:
if (this.messageLogger.isFatalEnabled()) {
this.messageLogger.fatal(createLogMessage(message));
}
break;
case ERROR:
if (this.messageLogger.isErrorEnabled()) {
this.messageLogger.error(createLogMessage(message));
}
break;
case WARN:
if (this.messageLogger.isWarnEnabled()) {
this.messageLogger.warn(createLogMessage(message));
}
break;
case INFO:
if (this.messageLogger.isInfoEnabled()) {
this.messageLogger.info(createLogMessage(message));
}
break;
case DEBUG:
if (this.messageLogger.isDebugEnabled()) {
this.messageLogger.debug(createLogMessage(message));
}
break;
case TRACE:
if (this.messageLogger.isTraceEnabled()) {
this.messageLogger.trace(createLogMessage(message));
}
break;
default:
throw new IllegalStateException("Level '" + this.level + "' is not supported");
}
}
private Object createLogMessage(Message<?> message) {
Object logMessage = this.expression.getValue(this.evaluationContext, message);
return logMessage instanceof Throwable
? createLogMessage((Throwable) logMessage)
: logMessage;
}
private String createLogMessage(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
if (throwable instanceof AggregateMessageDeliveryException) {
stringWriter.append(throwable.getMessage());
for (Exception exception : ((AggregateMessageDeliveryException) throwable).getAggregatedExceptions()) {
printStackTrace(exception, stringWriter);
}
}
else {
printStackTrace(throwable, stringWriter);
}
return stringWriter.toString();
}
private void printStackTrace(Throwable throwable, Writer writer) {
throwable.printStackTrace(new PrintWriter(writer, true));
}
}