Posts Tagged ‘Log4J’
How to unit test Logger calls?
Case
Sometimes, you need test the Log4J’s loggers are called with the right parameters. How to perform these tests from with JUnit?
Let’s take an example: how to test these simple class and method?
public class ClassWithLogger { private static final Logger LOGGER = Logger.getLogger(ClassWithLogger.class); public void printMessage(Integer foo){ LOGGER.warn("this is the message#" + foo); } }
Example
Define an almost empty Log4J appender, such as:
public class TestAimedAppender extends ArrayList<String> implements Appender { private final Class clazz; public TestAimedAppender(Class clazz) { super(); this.clazz = clazz; } @Override public void addFilter(Filter newFilter) { } @Override public Filter getFilter() { return null; } @Override public void clearFilters() { } public void close() { } @Override public void doAppend(LoggingEvent event) { add(event.getRenderedMessage()); } @Override public String getName() { return "TestAppender for " + clazz.getSimpleName(); } @Override public void setErrorHandler(ErrorHandler errorHandler) { } @Override public ErrorHandler getErrorHandler() { return null; } @Override public void setLayout(Layout layout) { } @Override public Layout getLayout() { return null; } @Override public void setName(String name) { } public boolean requiresLayout() { return false; } }
Then create a TestCase with two fields:
public class ClassWithLoggerUnitTest { private ClassWithLogger classWithLogger; private TestAimedAppender appender; ... }
In the setup, remove all appenders, create an instance of our appender, and then add it to the logger related to the class which we want to test:
@Before public void setUp() throws Exception { final Logger classWithLoggerLogger = Logger.getLogger(ClassWithLogger.class); classWithLoggerLogger.removeAllAppenders(); appender = new TestAimedAppender(ClassWithLogger.class); classWithLoggerLogger.addAppender(appender); appender.clear(); classWithLogger = new ClassWithLogger(); }
Then write the following test. The code is documented:
@Test public void testPrintMessage() throws Exception { final String expectedMessage = "this is the message#18"; // empty the appender appender.clear(); // check it is actually empty before any call to the tested class assertTrue(appender.isEmpty()); // call to the tested class classWithLogger.printMessage(18); // check the appender is no more empty assertFalse(appender.isEmpty()); assertEquals(1, appender.size()); // check the content of the appender assertEquals(expectedMessage, appender.get(0)); }
Conclusion
This basic example shows how to perform tests on logger, without overriding the original code or using mocks. Of course, you can improve this basic example, for instance in discriminating owing to the log level (INFO
, WARN
, ERROR
, etc.), use generics, and even any other fantasy ;-).
How to attach a single class to a Log4J appender?
The issue looks simple, but I needed a little search.
Case
I have a log4j config file, with three appenders: INFO,
console
and trace
. I have to add a brand new appender (let’s say: JonathanNewAppender
) that will log the events of only one class (let’s say: JonathanComponent
). How to configure Log4J to perform that?
Solution
Edit the log4j.properties
file.
Do not change the property log4j.rootCategory
, do not mention JonathanNewAppender
.
log4j.rootCategory=INFO, console, trace
Add the properties of the appender, for instance: Donner les proprietes de l’appender:
log4j.appender.JonathanNewAppender=org.apache.log4j.DailyRollingFileAppender log4j.appender.JonathanNewAppender.Append=true log4j.appender.JonathanNewAppender.File=logs/prsl-sent-and-received.csv log4j.appender.JonathanNewAppender.layout=org.apache.log4j.PatternLayout log4j.appender.JonathanNewAppender.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss}%m%n log4j.appender.JonathanNewAppender.threshold=INFO
And here is the trick: add the log level and the actual appender for the new class.
log4j.logger.my.package.name.JonathanComponent=INFO, JonathanNewAppender
Indeed, the only field of a logger to be mandatory is the log level. When the appender is specified, it will be taked in account. Otherwise, the logger will be attached all the appenders available in log4j.rootCategory
property.
Useful DTD
DTDs are useful when your XML editor take them in account: detecting errors, suggestions, complete statements… For instance, I save much time with IntelliJ IDEA automatic completion ; unlike, Eclipse amazingly does not implement this feature.
Here is a list of some widely used DTDs:
File | DTD |
---|---|
weblogic-application.xml |
<!DOCTYPE weblogic-application PUBLIC "-//BEA Systems, Inc.//DTD WebLogic Application 7.0.0//EN" "http://www.oracle.com/technology/weblogic/weblogic-application/1.1/weblogic-application.xsd"> |
weblogic-application.xml |
<!DOCTYPE weblogic-application PUBLIC "-//BEA Systems, Inc.//DTD WebLogic Application 7.0.0//EN" "http://www.oracle.com/technology/weblogic/weblogic-application/1.1/weblogic-application.xsd"> |
web.xml |
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > |
*.hbm.xml |
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> |
GWT modules |
<!DOCTYPE module SYSTEM "http://google-web-toolkit.googlecode.com/svn/trunk/distro-source/core/src/gwt-module.dtd"> |
GWT UI |
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> |
Tangosol / Oracle Coherence |
<!DOCTYPE coherence SYSTEM "coherence.dtd"> |
Log4J |
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> |
Tangosol and Log4J DTDs are included within their distribution JARs: you have to extract them or to give their path to IntelliJ IDEA.
java.io.NotSerializableException: org.apache.log4j.Logger
Case
I use Oracle Coherence (Tangosol) as distributed cache for a given class. This class contains a non-static Log4J’s Logger
as field.
(what a Logger does in an “POJO” is not obvious and requires further development ; let’s say it is used during the development phase, but has nothing to do in a POJO and should be removed later).
When Tangosol tries to put the object in cache, I get this error:
java.io.NotSerializableException: org.apache.log4j.Logger
Complete stacktrace
java.io.NotSerializableException: org.apache.log4j.Logger at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1156) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1474) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1392) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1150) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326) at com.tangosol.util.ExternalizableHelper.writeSerializable(ExternalizableHelper.java:2181) at com.tangosol.util.ExternalizableHelper.writeObjectInternal(ExternalizableHelper.java:2603) at com.tangosol.util.ExternalizableHelper.serializeInternal(ExternalizableHelper.java:2529) at com.tangosol.util.ExternalizableHelper.toBinary(ExternalizableHelper.java:206) at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache$ConverterValueToBinary.convert(DistributedCache.CDB:3) at com.tangosol.util.ConverterCollections$ConverterMap.put(ConverterCollections.java:1566) at com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.DistributedCache$ViewMap.put(DistributedCache.CDB:1) at com.tangosol.coherence.component.util.SafeNamedCache.put(SafeNamedCache.CDB:1) at com.lalou.jonathan.business.cache.TypedEhCache.put(TypedEhCache.java:73) at com.lalou.jonathan.business.cache.TypedEhCache.setInCache(TypedEhCache.java:58) at com.lalou.jonathan.business.StringToRequestListTransformer.transform(JonathanTransformer.java:104) at org.mule.transformer.AbstractMessageAwareTransformer.doTransform(AbstractMessageAwareTransformer.java:68) at org.mule.transformer.AbstractTransformer.transform(AbstractTransformer.java:254) at org.mule.DefaultMuleMessage.applyAllTransformers(DefaultMuleMessage.java:621) at org.mule.DefaultMuleMessage.applyTransformers(DefaultMuleMessage.java:582) at org.mule.DefaultMuleMessage.applyTransformers(DefaultMuleMessage.java:575) at org.mule.DefaultMuleEvent.transformMessage(DefaultMuleEvent.java:326) at org.mule.DefaultMuleEvent.transformMessage(DefaultMuleEvent.java:321) at org.mule.component.simple.PassThroughComponent.doInvoke(PassThroughComponent.java:27) at org.mule.component.AbstractComponent.invokeInternal(AbstractComponent.java:133) at org.mule.component.AbstractComponent.invoke(AbstractComponent.java:161) at org.mule.service.AbstractService.invokeComponent(AbstractService.java:929) at org.mule.model.seda.SedaService.access$100(SedaService.java:56) at org.mule.model.seda.SedaService$ComponentStageWorker.run(SedaService.java:574) at org.mule.work.WorkerContext.run(WorkerContext.java:310) at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1061) at edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:575) at java.lang.Thread.run(Thread.java:619)
Fix
Declare the Logger
as static
:
private final Logger LOG = Logger.getLogger(CsvFileObject.class);
In my case, the issue was fixed. If it is not, try to declare the Logger
as static transient
, I assume it could help.