Merge pull request #754 from garyrussell/INT-2939

* garyrussell-INT-2939:
  INT-2939 Polishing
  INT-2939 LoggingHandler Improvements
This commit is contained in:
Gunnar Hillert
2013-04-03 00:14:03 -04:00
5 changed files with 223 additions and 47 deletions

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Copyright 2002-2013 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.
@@ -15,11 +15,9 @@ package org.springframework.integration.handler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -34,13 +32,14 @@ import org.springframework.util.StringUtils;
* MessageHandler implementation that simply logs the Message or its payload depending on the value of the
* 'shouldLogFullMessage' 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
* @since 1.0.1
*/
public class LoggingHandler extends AbstractMessageHandler {
private static enum Level {
public static enum Level {
FATAL, ERROR, WARN, INFO, DEBUG, TRACE
}
@@ -49,7 +48,11 @@ public class LoggingHandler extends AbstractMessageHandler {
private volatile Expression expression;
private final Level level;
private volatile boolean expressionSet;
private volatile boolean shouldLogFullMessageSet;
private volatile Level level;
private final EvaluationContext evaluationContext;
@@ -62,6 +65,7 @@ public class LoggingHandler extends AbstractMessageHandler {
* The valid levels are: FATAL, ERROR, WARN, INFO, DEBUG, or TRACE
*/
public LoggingHandler(String level) {
Assert.notNull(level, "'level' cannot be null");
try {
this.level = Level.valueOf(level.toUpperCase());
}
@@ -76,11 +80,28 @@ public class LoggingHandler extends AbstractMessageHandler {
this.expression = EXPRESSION_PARSER.parseExpression("payload");
}
public void setExpression(String expressionString) {
Assert.isTrue(!(this.shouldLogFullMessageSet), "Cannot set both 'expression' AND 'shouldLogFullMessage' properties");
this.expressionSet = true;
this.expression = EXPRESSION_PARSER.parseExpression(expressionString);
}
/**
* @return The current logging {@link Level}.
*/
public Level getLevel() {
return level;
}
/**
* Set the logging {@link Level}.
* @param level the level.
*/
public void setLevel(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);
@@ -91,6 +112,8 @@ public class LoggingHandler extends AbstractMessageHandler {
* <code>false</code> by default.
*/
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");
}
@@ -102,51 +125,59 @@ public class LoggingHandler extends AbstractMessageHandler {
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
switch (this.level) {
case FATAL:
if (messageLogger.isFatalEnabled()) {
messageLogger.fatal(createLogMessage(message));
}
break;
case ERROR:
if (messageLogger.isErrorEnabled()) {
messageLogger.error(createLogMessage(message));
}
break;
case WARN:
if (messageLogger.isWarnEnabled()) {
messageLogger.warn(createLogMessage(message));
}
break;
case INFO:
if (messageLogger.isInfoEnabled()) {
messageLogger.info(createLogMessage(message));
}
break;
case DEBUG:
if (messageLogger.isDebugEnabled()) {
messageLogger.debug(createLogMessage(message));
}
break;
case TRACE:
if (messageLogger.isTraceEnabled()) {
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);
if (logMessage instanceof Throwable) {
StringWriter stringWriter = new StringWriter();
if (logMessage instanceof AggregateMessageDeliveryException) {
stringWriter.append(((Throwable) logMessage).getMessage());
for (Exception exception : (List<? extends Exception>) ((AggregateMessageDeliveryException)logMessage).getAggregatedExceptions()) {
for (Exception exception : ((AggregateMessageDeliveryException)logMessage).getAggregatedExceptions()) {
exception.printStackTrace(new PrintWriter(stringWriter, true));
}
} else {
}
else {
((Throwable) logMessage).printStackTrace(new PrintWriter(stringWriter, true));
}
logMessage = stringWriter.toString();
}
switch (this.level) {
case FATAL:
if (messageLogger.isFatalEnabled()) {
messageLogger.fatal(logMessage);
}
break;
case ERROR:
if (messageLogger.isErrorEnabled()) {
messageLogger.error(logMessage);
}
break;
case WARN:
if (messageLogger.isWarnEnabled()) {
messageLogger.warn(logMessage);
}
break;
case INFO:
if (messageLogger.isInfoEnabled()) {
messageLogger.info(logMessage);
}
break;
case DEBUG:
if (messageLogger.isDebugEnabled()) {
messageLogger.debug(logMessage);
}
break;
case TRACE:
if (messageLogger.isTraceEnabled()) {
messageLogger.trace(logMessage);
}
break;
}
return logMessage;
}
}

View File

@@ -22,14 +22,12 @@ import static org.junit.Assert.fail;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.LoggingHandler;
import org.springframework.integration.test.util.TestUtils;
@@ -39,6 +37,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Artem Bilan
* @author Gary Russell
* @since 2.1
*/
@ContextConfiguration

View File

@@ -16,10 +16,25 @@
package org.springframework.integration.handler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.commons.logging.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.handler.LoggingHandler.Level;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -41,11 +56,71 @@ public class LoggingHandlerTests {
input.send(MessageBuilder.withPayload(bean).setHeader("foo", "bar").build());
}
@Test
public void assertMutuallyExclusive() {
LoggingHandler loggingHandler = new LoggingHandler("INFO");
loggingHandler.setExpression("'foo'");
try {
loggingHandler.setShouldLogFullMessage(true);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
assertEquals("Cannot set both 'expression' AND 'shouldLogFullMessage' properties", e.getMessage());
}
loggingHandler = new LoggingHandler("INFO");
loggingHandler.setShouldLogFullMessage(true);
try {
loggingHandler.setExpression("'foo'");
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
assertEquals("Cannot set both 'expression' AND 'shouldLogFullMessage' properties", e.getMessage());
}
}
@Test
public void testDontEvaluateIfNotEnabled() {
LoggingHandler loggingHandler = new LoggingHandler("INFO");
DirectFieldAccessor accessor = new DirectFieldAccessor(loggingHandler);
Log log = (Log) accessor.getPropertyValue("messageLogger");
log = spy(log);
accessor.setPropertyValue("messageLogger", log);
Expression expression = (Expression) accessor.getPropertyValue("expression");
expression = spy(expression);
accessor.setPropertyValue("expression", expression);
when(log.isInfoEnabled()).thenReturn(false);
loggingHandler.handleMessage(new GenericMessage<String>("foo"));
verify(expression, never()).getValue(Mockito.any(EvaluationContext.class), Mockito.any());
when(log.isInfoEnabled()).thenReturn(true);
loggingHandler.handleMessage(new GenericMessage<String>("foo"));
verify(expression, times(1)).getValue(Mockito.any(EvaluationContext.class), Mockito.any());
}
@Test
public void testChangeLevel() {
LoggingHandler loggingHandler = new LoggingHandler("INFO");
DirectFieldAccessor accessor = new DirectFieldAccessor(loggingHandler);
Log log = (Log) accessor.getPropertyValue("messageLogger");
log = spy(log);
accessor.setPropertyValue("messageLogger", log);
when(log.isInfoEnabled()).thenReturn(true);
loggingHandler.handleMessage(new GenericMessage<String>("foo"));
verify(log, times(1)).info(Mockito.anyString());
verify(log, never()).warn(Mockito.anyString());
loggingHandler.setLevel(Level.WARN);
loggingHandler.handleMessage(new GenericMessage<String>("foo"));
verify(log, times(1)).info(Mockito.anyString());
verify(log, times(1)).warn(Mockito.anyString());
}
public static class TestBean {
private final String name;
private int age;
private final int age;
public TestBean(String name, int age) {
this.name = name;

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<section version="5.0" xml:id="logging-channel-adapter" xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ns5="http://www.w3.org/1999/xhtml"
xmlns:ns4="http://www.w3.org/1998/Math/MathML"
xmlns:ns3="http://www.w3.org/2000/svg"
xmlns:ns="http://docbook.org/ns/docbook">
<title>Logging Channel Adapter</title>
<para>
The <code>&lt;logging-channel-adapter/&gt;</code> is often used in conjunction with a
Wire Tap, as discussed in
<xref linkend="channel-wiretap"/>. However, it can also be used as the ultimate consumer
of any flow. For example, consider a flow that ends with a <code>&lt;service-activator/&gt;</code>
that returns a result, but you wish to discard that result. To do that, you could send the
result to <classname>NullChannel</classname>. Alternatively, you can route it to an <code>INFO</code>
level <code>&lt;logging-channel-adapter/&gt;</code>; that way, you can see
the discarded message when logging at <code>INFO</code> level, but not see it when logging at,
say, <code>WARN</code> level. With a <classname>NullChannel</classname>, you would only see the
discarded message when logging at <code>DEBUG</code> level.
</para>
<programlisting><![CDATA[
<int:logging-channel-adapter
channel=""]]><co id="log-ca-010" /><![CDATA[
level="INFO"]]><co id="log-ca-020" /><![CDATA[
expression=""]]><co id="log-ca-030" /><![CDATA[
log-full-message="false"]]><co id="log-ca-040" /><![CDATA[
logger-name=""]]><co id="log-ca-050" /><![CDATA[/>
]]></programlisting>
<calloutlist>
<callout arearefs="log-ca-010">
<para>
The channel connecting the logging adapter to an upstream component.
</para>
</callout>
<callout arearefs="log-ca-020">
<para>
The logging level at which messages sent to this adapter will be logged.
Default: <code>INFO</code>.
</para>
</callout>
<callout arearefs="log-ca-030">
<para>
A SpEL expression representing exactly what part(s) of the message will be
logged. Default: <code>payload</code> - just the payload will be logged.
This attribute cannot be specified if <code>log-full-message</code> is
specified.
</para>
</callout>
<callout arearefs="log-ca-040">
<para>
When <code>true</code>, the entire message will be logged (including headers).
Default: <code>false</code> - just the payload will be logged.
This attribute cannot be specified if <code>expression</code> is
specified.
</para>
</callout>
<callout arearefs="log-ca-050">
<para>
Specifies the <emphasis>name</emphasis> of the logger (known as
<code>category</code> in <code>log4j</code>)
used for log messages created by this adapter. This enables setting the
log name (in the logging subsystem) for individual adapters. By default, all
adapters will log under the name
<code>org.springframework.integration.handler.LoggingHandler</code>.
</para>
</callout>
</calloutlist>
</section>

View File

@@ -11,5 +11,6 @@
<xi:include href="./scripting.xml"/>
<xi:include href="./groovy.xml"/>
<xi:include href="./handler-advice.xml"/>
<xi:include href="./logging-adapter.xml"/>
</chapter>