/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.edex.log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.LoggingEvent;
/**
* TODO Add Description
*
*
*
* SOFTWARE HISTORY
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Aug 25, 2010 rjpeter Initial creation
*
*
*
* @author rjpeter
* @version 1.0
*/
public class ThreadBasedAppender extends AppenderSkeleton implements Appender,
AppenderAttachable {
private Map appenderMap = new HashMap();
private Map> threadPatterns = new HashMap>();
private Map threadAppenderCache = new HashMap();
private Map threadAppenderTimeCache = new HashMap();
private String defaultAppenderName;
private Appender defaultAppender;
// keep thread names and their associated appender mapped for 10 minutes
private static final long RETENTION_TIME = 1000 * 60 * 1;
private Timer cacheTimer;
@Override
public void activateOptions() {
if (defaultAppenderName != null) {
defaultAppender = appenderMap.get(defaultAppenderName);
}
// only keep around thread patterns that match the list of appenders
if (threadPatterns.size() > 0) {
Iterator threadPatKeyIter = threadPatterns.keySet()
.iterator();
while (threadPatKeyIter.hasNext()) {
String appender = threadPatKeyIter.next();
if (!appenderMap.containsKey(appender)) {
threadPatKeyIter.remove();
}
}
}
// setup a timed purge of the cached threads
cacheTimer = new Timer();
TimerTask purgeCache = new TimerTask() {
@Override
public void run() {
removeOldEntries();
}
};
cacheTimer.schedule(purgeCache, 1000, 60000);
}
@Override
public void addAppender(Appender newAppender) {
if (newAppender != null && newAppender.getName() != null) {
appenderMap.put(newAppender.getName(), newAppender);
}
}
@Override
public Enumeration getAllAppenders() {
return Collections.enumeration(appenderMap.values());
}
@Override
public Appender getAppender(String name) {
if (name != null) {
return appenderMap.get(name);
}
return null;
}
@Override
public boolean isAttached(Appender appender) {
if (appender != null) {
return appenderMap.containsKey(appender.getName());
}
return false;
}
@Override
public void removeAllAppenders() {
appenderMap.clear();
}
@Override
public void removeAppender(Appender appender) {
if (appender != null) {
appenderMap.remove(appender.getName());
}
}
@Override
public void removeAppender(String name) {
if (name != null) {
appenderMap.remove(name);
}
}
@Override
protected void append(LoggingEvent event) {
String threadName = event.getThreadName();
Appender app = null;
app = threadAppenderCache.get(threadName);
// TODO: Is the null appender possible?
if (app == null) {
// determine which appender to use
APPENDER_SEARCH: for (Entry> entry : threadPatterns
.entrySet()) {
for (Pattern pat : entry.getValue()) {
if (pat.matcher(threadName).matches()) {
String appenderName = entry.getKey();
app = appenderMap.get(appenderName);
break APPENDER_SEARCH;
}
}
}
if (app == null && defaultAppender != null) {
app = defaultAppender;
}
if (app != null) {
synchronized (this) {
// not modifiying the map directly to avoid concurrent
// exceptions without forcing synchronization
Map tmp = new HashMap(
threadAppenderCache);
tmp.put(threadName, app);
threadAppenderCache = tmp;
Map tmpTime = new HashMap(
threadAppenderTimeCache);
tmpTime.put(threadName, System.currentTimeMillis());
threadAppenderTimeCache = tmpTime;
}
}
}
if (app != null) {
// value already exists, no sync block necessary
threadAppenderTimeCache.put(threadName, System.currentTimeMillis());
app.doAppend(event);
}
}
@Override
public void close() {
}
@Override
public boolean requiresLayout() {
return true;
}
public void setThreadPatterns(String value) {
String[] appenderPatterns = value.split("[;\n]");
for (String appenderPattern : appenderPatterns) {
String[] tokens = appenderPattern.split("[:,]");
if (tokens.length > 1) {
String appender = tokens[0];
List patterns = new ArrayList(
tokens.length - 1);
for (int i = 1; i < tokens.length; i++) {
patterns.add(Pattern.compile(tokens[i]));
}
threadPatterns.put(appender, patterns);
}
}
}
public void setDefaultAppender(String defaultAppender) {
defaultAppenderName = defaultAppender;
}
private void removeOldEntries() {
long curTime = System.currentTimeMillis();
List keysToRemove = new ArrayList(threadAppenderCache
.size());
for (Entry entry : threadAppenderTimeCache.entrySet()) {
if (curTime - entry.getValue() > RETENTION_TIME) {
keysToRemove.add(entry.getKey());
}
}
for (String key : keysToRemove) {
// synchornized inside the loop so that it is locked for short
// periods so new log threads can continue to run
synchronized (this) {
// not modifiying the map directly to avoid concurrent
// exceptions without forcing synchronization
Map tmp = new HashMap(
threadAppenderCache);
tmp.remove(key);
threadAppenderCache = tmp;
Map tmpTime = new HashMap(
threadAppenderTimeCache);
tmpTime.remove(key);
threadAppenderTimeCache = tmpTime;
}
}
}
}