X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=security%2Fruntime%2Forg.argeo.security.core%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fsecurity%2Flog4j%2FSecureLogger.java;h=a03d2b6d208401857cb4c8258444e9d61bb4f2e0;hb=6bb0606505be3e99021c5ff9771c719eb1e1f2e7;hp=70945f07c31f29181be3413dc820c46f67d81958;hpb=d4c2363c4c8dfdf3bc2031e61cbf8d1730a13cf9;p=lgpl%2Fargeo-commons.git diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java index 70945f07c..a03d2b6d2 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/log4j/SecureLogger.java @@ -16,25 +16,31 @@ package org.argeo.security.log4j; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; +import java.util.Map; import java.util.Properties; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; +import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.spi.LoggingEvent; import org.argeo.ArgeoException; import org.argeo.ArgeoLogListener; -import org.springframework.security.Authentication; -import org.springframework.security.context.SecurityContext; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken; +import org.argeo.ArgeoLogger; +import org.argeo.security.SecurityUtils; /** Not meant to be used directly in standard log4j config */ -public class SecureLogger { - private List listeners; +public class SecureLogger implements ArgeoLogger { private Boolean disabled = false; @@ -47,6 +53,18 @@ public class SecureLogger { private AppenderImpl appender; + private final List everythingListeners = Collections + .synchronizedList(new ArrayList()); + private final List allUsersListeners = Collections + .synchronizedList(new ArrayList()); + private final Map> userListeners = Collections + .synchronizedMap(new HashMap>()); + + private BlockingQueue events; + private LogDispatcherThread logDispatcherThread = new LogDispatcherThread(); + + private Integer maxLastEventsCount = 10 * 1000; + /** Marker to prevent stack overflow */ private ThreadLocal dispatching = new ThreadLocal() { @@ -58,16 +76,18 @@ public class SecureLogger { public void init() { try { + events = new LinkedBlockingQueue(); + // if (layout != null) // setLayout(layout); // else // setLayout(new PatternLayout(pattern)); appender = new AppenderImpl(); - - if (configuration != null) - PropertyConfigurator.configure(configuration); - + reloadConfiguration(); Logger.getRootLogger().addAppender(appender); + + logDispatcherThread = new LogDispatcherThread(); + logDispatcherThread.start(); } catch (Exception e) { throw new ArgeoException("Cannot initialize log4j"); } @@ -75,12 +95,71 @@ public class SecureLogger { public void destroy() throws Exception { Logger.getRootLogger().removeAppender(appender); + allUsersListeners.clear(); + for (List lst : userListeners.values()) + lst.clear(); + userListeners.clear(); + + events.clear(); + events = null; + logDispatcherThread.interrupt(); } // public void setLayout(Layout layout) { // this.layout = layout; // } + public synchronized void register(ArgeoLogListener listener, + Integer numberOfPreviousEvents) { + String username = SecurityUtils.getCurrentThreadUsername(); + if (username == null) + throw new ArgeoException( + "Only authenticated users can register a log listener"); + + if (!userListeners.containsKey(username)) { + List lst = Collections + .synchronizedList(new ArrayList()); + userListeners.put(username, lst); + } + userListeners.get(username).add(listener); + List lastEvents = logDispatcherThread.getLastEvents(username, + numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + dispatchEvent(listener, evt); + } + + public synchronized void registerForAll(ArgeoLogListener listener, + Integer numberOfPreviousEvents, boolean everything) { + if (everything) + everythingListeners.add(listener); + else + allUsersListeners.add(listener); + List lastEvents = logDispatcherThread.getLastEvents(null, + numberOfPreviousEvents); + for (LogEvent evt : lastEvents) + if (everything || evt.getUsername() != null) + dispatchEvent(listener, evt); + } + + public synchronized void unregister(ArgeoLogListener listener) { + String username = SecurityUtils.getCurrentThreadUsername(); + if (!userListeners.containsKey(username)) + throw new ArgeoException("No user listeners " + listener + + " registered for user " + username); + if (!userListeners.get(username).contains(listener)) + throw new ArgeoException("No user listeners " + listener + + " registered for user " + username); + userListeners.get(username).remove(listener); + if (userListeners.get(username).isEmpty()) + userListeners.remove(username); + + } + + public synchronized void unregisterForAll(ArgeoLogListener listener) { + everythingListeners.remove(listener); + allUsersListeners.remove(listener); + } + /** For development purpose, since using regular logging is not easy here */ static void stdOut(Object obj) { System.out.println(obj); @@ -98,14 +177,87 @@ public class SecureLogger { this.level = level; } - public void setListeners(List listeners) { - this.listeners = listeners; - } - public void setConfiguration(Properties configuration) { this.configuration = configuration; } + public void updateConfiguration(Properties configuration) { + setConfiguration(configuration); + reloadConfiguration(); + } + + public Properties getConfiguration() { + return configuration; + } + + /** Reloads configuration (if the configuration {@link Properties} is set) */ + protected void reloadConfiguration() { + if (configuration != null) { + LogManager.resetConfiguration(); + PropertyConfigurator.configure(configuration); + } + } + + protected synchronized void processLoggingEvent(LogEvent event) { + if (disabled) + return; + + if (dispatching.get()) + return; + + if (level != null && !level.trim().equals("")) { + if (log4jLevel == null || !log4jLevel.toString().equals(level)) + try { + log4jLevel = Level.toLevel(level); + } catch (Exception e) { + System.err + .println("Log4j level could not be set for level '" + + level + "', resetting it to null."); + e.printStackTrace(); + level = null; + } + + if (log4jLevel != null + && !event.getLoggingEvent().getLevel() + .isGreaterOrEqual(log4jLevel)) { + return; + } + } + + try { + // admin listeners + Iterator everythingIt = everythingListeners + .iterator(); + while (everythingIt.hasNext()) + dispatchEvent(everythingIt.next(), event); + + if (event.getUsername() != null) { + Iterator allUsersIt = allUsersListeners + .iterator(); + while (allUsersIt.hasNext()) + dispatchEvent(allUsersIt.next(), event); + + if (userListeners.containsKey(event.getUsername())) { + Iterator userIt = userListeners.get( + event.getUsername()).iterator(); + while (userIt.hasNext()) + dispatchEvent(userIt.next(), event); + } + } + } catch (Exception e) { + stdOut("Cannot process logging event"); + e.printStackTrace(); + } + } + + protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) { + LoggingEvent event = evt.getLoggingEvent(); + logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event + .getLevel().toString(), event.getLoggerName(), event + .getThreadName(), event.getMessage(), event + .getThrowableStrRep()); + } + private class AppenderImpl extends AppenderSkeleton { public boolean requiresLayout() { return false; @@ -116,61 +268,93 @@ public class SecureLogger { @Override protected void append(LoggingEvent event) { - if (disabled) - return; + if (events != null) { + try { + String username = SecurityUtils.getCurrentThreadUsername(); + events.put(new LogEvent(username, event)); + } catch (InterruptedException e) { + // silent + } + } + } - if (dispatching.get()) - return; + } - if (level != null && !level.trim().equals("")) { - if (log4jLevel == null || !log4jLevel.toString().equals(level)) - try { - log4jLevel = Level.toLevel(level); - } catch (Exception e) { - System.err - .println("Log4j level could not be set for level '" - + level + "', resetting it to null."); - e.printStackTrace(); - level = null; - } - - if (log4jLevel != null - && !event.getLevel().isGreaterOrEqual(log4jLevel)) { - return; + private class LogDispatcherThread extends Thread { + /** encapsulated in order to simplify concurrency management */ + private LinkedList lastEvents = new LinkedList(); + + public LogDispatcherThread() { + super("Argeo Logging Dispatcher Thread"); + } + + public void run() { + while (events != null) { + try { + LogEvent loggingEvent = events.take(); + processLoggingEvent(loggingEvent); + addLastEvent(loggingEvent); + } catch (InterruptedException e) { + if (events == null) + return; } } + } - try { - Thread currentThread = Thread.currentThread(); - String username = null; - - // find username - SecurityContext securityContext = SecurityContextHolder - .getContext(); - if (securityContext != null) { - Authentication authentication = securityContext - .getAuthentication(); - if (authentication != null) { - if (authentication instanceof AnonymousAuthenticationToken) { - username = null; - } else { - username = authentication.getName(); - } - } - } + protected synchronized void addLastEvent(LogEvent loggingEvent) { + if (lastEvents.size() >= maxLastEventsCount) + lastEvents.poll(); + lastEvents.add(loggingEvent); + } - // Spring OSGi safe - Iterator it = listeners.iterator(); - while (it.hasNext()) { - ArgeoLogListener logListener = it.next(); - logListener.appendLog(username, event.getTimeStamp(), event - .getLevel().toString(), event.getLoggerName(), - currentThread.getName(), event.getMessage(), event.getThrowableStrRep()); + public synchronized List getLastEvents(String username, + Integer maxCount) { + LinkedList evts = new LinkedList(); + ListIterator it = lastEvents.listIterator(lastEvents + .size()); + int count = 0; + while (it.hasPrevious() && (count < maxCount)) { + LogEvent evt = it.previous(); + if (username == null || username.equals(evt.getUsername())) { + evts.push(evt); + count++; } - } catch (Exception e) { - stdOut("Cannot process logging event"); - e.printStackTrace(); } + return evts; + } + } + + private class LogEvent { + private final String username; + private final LoggingEvent loggingEvent; + + public LogEvent(String username, LoggingEvent loggingEvent) { + super(); + this.username = username; + this.loggingEvent = loggingEvent; + } + + @Override + public int hashCode() { + return loggingEvent.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return loggingEvent.equals(obj); + } + + @Override + public String toString() { + return username + "@ " + loggingEvent.toString(); + } + + public String getUsername() { + return username; + } + + public LoggingEvent getLoggingEvent() { + return loggingEvent; } }