/** * 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; } } } }