diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java index 90ae02b..6bf8ce6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java @@ -17,11 +17,21 @@ package org.apache.logging.log4j.core.jmx; import java.lang.management.ManagementFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; @@ -59,7 +69,7 @@ public static final String DOMAIN = "org.apache.logging.log4j2"; private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx"; private static final StatusLogger LOGGER = StatusLogger.getLogger(); - static final Executor executor = Executors.newFixedThreadPool(1); + private static final Map> executors = new HashMap>(); private Server() { } @@ -141,6 +151,17 @@ // appenders, statusLogger, contextSelector, ringbuffers... unregisterLoggerContext(ctx.getName(), mbs); + Map mbsExecutors = executors.get(mbs); + if(mbsExecutors == null) { + mbsExecutors = new HashMap(); + executors.put(mbs, mbsExecutors); + } + ExecutorService executor = mbsExecutors.get(ctx.getName()); + if(executor == null || executor.isShutdown()) { + executor = new LazyExecutorService(); + mbsExecutors.put(ctx.getName(), executor); + } + final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor); register(mbs, mbean, mbean.getObjectName()); @@ -238,12 +259,23 @@ unregisterAsyncAppenders(contextName, mbs); unregisterAsyncLoggerRingBufferAdmins(contextName, mbs); unregisterAsyncLoggerConfigRingBufferAdmins(contextName, mbs); + + final Map mbsExecutors = executors.get(mbs); + if (mbsExecutors != null) { + final ExecutorService executor = mbsExecutors.get(contextName); + if (executor != null && !executor.isShutdown()) { + executor.shutdown(); + mbsExecutors.remove(executor); + } + if(mbsExecutors.isEmpty()) + executors.remove(mbs); + } } private static void registerStatusLogger(final String contextName, final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { - final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor); + final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor, mbs); register(mbs, mbean, mbean.getObjectName()); } @@ -356,4 +388,133 @@ LOGGER.debug("Registering MBean {}", objectName); mbs.registerMBean(mbean, objectName); } -} + + /** + * Determines whether a given logger context is registered on a specificed + * {@code MBeanServer} instance. + * + * @param contextName + * the context name + * @param mbs + * the MBean server instance + * @return true if the specified logger context is registered + * in the given MBean server, false otherwise + */ + public static boolean isLoggerContextRegistered(final String contextName, + final MBeanServer mbs) { + final Map mbsExecutors = executors.get(mbs); + if (mbsExecutors != null) { + final ExecutorService executor = mbsExecutors.get(contextName); + return executor != null && !executor.isShutdown(); + } + return false; + } + + /** + * Executor service that is actually instantiated only when a task is submitted. + * + * @author Mauro Molinari + */ + protected static class LazyExecutorService implements ExecutorService { + private ExecutorService delegate; + private boolean shutdown = false; + + /** Lazy instantiate the delegate executor service. */ + protected void instantiate() { + if(delegate == null) + delegate = Executors.newFixedThreadPool(1); + } + + @Override + public void execute(final Runnable command) { + instantiate(); + delegate.execute(command); + } + + @Override + public void shutdown() { + if(delegate != null) + delegate.shutdown(); + shutdown = true; + } + + @Override + public List shutdownNow() { + if(delegate != null) + return delegate.shutdownNow(); + shutdown = true; + return Collections.emptyList(); + } + + @Override + public boolean isShutdown() { + if(delegate != null) + return delegate.isShutdown(); + return shutdown; + } + + @Override + public boolean isTerminated() { + if(delegate != null) + return delegate.isTerminated(); + return shutdown; + } + + @Override + public boolean awaitTermination(final long timeout, final TimeUnit unit) + throws InterruptedException { + if(delegate != null) + return delegate.awaitTermination(timeout, unit); + return true; + } + + @Override + public Future submit(final Callable task) { + instantiate(); + return delegate.submit(task); + } + + @Override + public Future submit(final Runnable task, final T result) { + instantiate(); + return delegate.submit(task, result); + } + + @Override + public Future submit(final Runnable task) { + instantiate(); + return delegate.submit(task); + } + + @Override + public List> invokeAll( + final Collection> tasks) + throws InterruptedException { + instantiate(); + return delegate.invokeAll(tasks); + } + + @Override + public List> invokeAll( + final Collection> tasks, final long timeout, + final TimeUnit unit) throws InterruptedException { + instantiate(); + return delegate.invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(final Collection> tasks) + throws InterruptedException, ExecutionException { + instantiate(); + return delegate.invokeAny(tasks); + } + + @Override + public T invokeAny(final Collection> tasks, + final long timeout, final TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + instantiate(); + return invokeAny(tasks, timeout, unit); + } + } +} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java index 980944e..3087e81 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java @@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicLong; import javax.management.MBeanNotificationInfo; +import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; @@ -39,6 +40,7 @@ private final AtomicLong sequenceNo = new AtomicLong(); private final ObjectName objectName; private final String contextName; + private final MBeanServer mbs; private Level level = Level.WARN; /** @@ -53,10 +55,12 @@ * associated MBeans can be unloaded when the web application is * undeployed. * @param executor used to send notifications asynchronously + * @param mbs the MBeanServer where the {@code StatusLoggerAdmin} instance will be registered */ - public StatusLoggerAdmin(final String contextName, final Executor executor) { + public StatusLoggerAdmin(final String contextName, final Executor executor, final MBeanServer mbs) { super(executor, createNotificationInfo()); this.contextName = contextName; + this.mbs = mbs; try { final String mbeanName = String.format(PATTERN, Server.escape(contextName)); objectName = new ObjectName(mbeanName); @@ -118,13 +122,17 @@ */ @Override public void log(final StatusData data) { - final Notification notifMsg = new Notification(NOTIF_TYPE_MESSAGE, getObjectName(), nextSeqNo(), now(), - data.getFormattedStatus()); - sendNotification(notifMsg); + if (Server.isLoggerContextRegistered(contextName, mbs)) { + final Notification notifMsg = new Notification(NOTIF_TYPE_MESSAGE, + getObjectName(), nextSeqNo(), now(), + data.getFormattedStatus()); + sendNotification(notifMsg); - final Notification notifData = new Notification(NOTIF_TYPE_DATA, getObjectName(), nextSeqNo(), now()); - notifData.setUserData(data); - sendNotification(notifData); + final Notification notifData = new Notification(NOTIF_TYPE_DATA, + getObjectName(), nextSeqNo(), now()); + notifData.setUserData(data); + sendNotification(notifData); + } } /**