Brian Ploetz sent me this great unit test for threaded logging. In it we are trying to find if a deadlock occurs.
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
/**
* Unit test for the ThrottlingFilter in a multi-threaded environment
*/
public class ThrottlingFilterThreadUTest extends TestCase {
private static final Log logger = LogFactory.getLog(ThrottlingFilterThreadUTest.class);
private static ThreadMXBean threadMXBean;
@Override
protected void setUp() throws Exception {
super.setUp();
threadMXBean = ManagementFactory.getThreadMXBean();
logger.info("Thread contention monitoring supported: "
+ threadMXBean.isThreadContentionMonitoringSupported());
logger.info("Thread contention monitoring enabled: "
+ threadMXBean.isThreadContentionMonitoringEnabled());
threadMXBean.setThreadContentionMonitoringEnabled(true);
logger.info("Thread contention monitoring enabled: "
+ threadMXBean.isThreadContentionMonitoringEnabled());
}
/**
* Tests multiple threads using the same filter instance at the same time
*/
public void testThreads() {
Logger rootLogger = Logger.getRootLogger();
assertNotNull(rootLogger);
Appender fileAppender = rootLogger.getAppender("FILE");
assertNotNull(fileAppender);
ThrottlingFilter throttlingFilter = (ThrottlingFilter) fileAppender.getFilter();
assertNotNull(throttlingFilter);
ThreadGroup infoThreadGroup = new ThreadGroup("info-group");
ThreadGroup errorThreadGroup = new ThreadGroup("error-group");
Thread errorThread1 = new ErrorThread(errorThreadGroup, "error-thread-1");
Thread infoThread1 = new InfoThread(infoThreadGroup, "info-thread-1");
Thread errorThread2 = new ErrorThread(errorThreadGroup, "error-thread-2");
Thread infoThread2 = new InfoThread(infoThreadGroup, "info-thread-2");
infoThread1.start();
errorThread1.start();
errorThread2.start();
infoThread2.start();
while (true) {
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds());
for (int i = 0; i < threadInfos.length; i++) {
ThreadInfo threadInfo = threadInfos[i];
if (threadInfo != null && threadInfo.getThreadState() == Thread.State.BLOCKED) {
System.out.println("Thread '" + threadInfo.getThreadName()
+ "' is blocked on the monitor lock '" + threadInfo.getLockName()
+ "' held by thread '" + threadInfo.getLockOwnerName() + "'");
}
}
if (!infoThread1.isAlive() && !errorThread1.isAlive() && !infoThread2.isAlive()
&& !errorThread2.isAlive())
break;
}
}
public static class ErrorThread extends Thread {
private static final Log logger = LogFactory.getLog(ErrorThread.class);
public ErrorThread(ThreadGroup tg, String name) {
super(tg, name);
}
public void run() {
for (int i = 0; i < 10; i++) {
try {
test(0);
} catch (Exception e) {
long start = System.currentTimeMillis();
logger.error("Error!", e);
long end = System.currentTimeMillis();
System.out.println("Took " + (end-start) + "ms to log error");
}
}
}
// simulate large stack traces
private void test(int i) {
if (i >= 500)
throw new RuntimeException("D'OH!");
test(i+1);
}
}
public static class InfoThread extends Thread {
private static final Log logger = LogFactory.getLog(InfoThread.class);
public InfoThread(ThreadGroup tg, String name) {
super(tg, name);
}
public void run() {
for (int i = 0; i < 100; i++) {
logger.info("Hi!");
}
}
}
}
The log4j.xml test file.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
debug="false">
<!-- ================================= -->
<!-- Appenders -->
<!-- ================================= -->
<!-- A time/date based rolling file appender -->
<appender name="FILE"
class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="server.log" />
<param name="Append" value="true" />
<!-- Rollover at midnight each day -->
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<!-- The default pattern: Date Priority [Category] Message\n -->
<param name="ConversionPattern" value="%d %-5p [%c] %m%n" />
</layout>
<filter class="com.betweengo.log4j.ThrottlingFilter">
<param name="maxCountSameMessage" value="100"/>
<param name="maxCountSavedMessages" value="100"/>
<param name="waitInterval" value="60"/>
</filter>
</appender>
<!-- ======================= -->
<!-- Setup the Root category -->
<!-- ======================= -->
<root>
<level value="INFO" />
<appender-ref ref="FILE" />
</root>
</log4j:configuration>