View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.jmx;
    18
    19  import java.lang.management.ManagementFactory;
    20  import java.util.List;
    21  import java.util.Map;
    22  import java.util.Set;
    23  import java.util.concurrent.Executor;
    24  import java.util.concurrent.ExecutorService;
    25  import java.util.concurrent.Executors;
    26
    27  import javax.management.InstanceAlreadyExistsException;
    28  import javax.management.MBeanRegistrationException;
    29  import javax.management.MBeanServer;
    30  import javax.management.NotCompliantMBeanException;
    31  import javax.management.ObjectName;
    32
    33  import org.apache.logging.log4j.LogManager;
    34  import org.apache.logging.log4j.core.Appender;
    35  import org.apache.logging.log4j.core.LoggerContext;
    36  import org.apache.logging.log4j.core.appender.AsyncAppender;
    37  import org.apache.logging.log4j.core.async.AsyncLogger;
    38  import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
    39  import org.apache.logging.log4j.core.async.AsyncLoggerContext;
    40  import org.apache.logging.log4j.core.async.DaemonThreadFactory;
    41  import org.apache.logging.log4j.core.config.LoggerConfig;
    42  import org.apache.logging.log4j.core.impl.Log4jContextFactory;
    43  import org.apache.logging.log4j.core.selector.ContextSelector;
    44  import org.apache.logging.log4j.core.util.Loader;
    45  import org.apache.logging.log4j.spi.LoggerContextFactory;
    46  import org.apache.logging.log4j.status.StatusLogger;
    47  import org.apache.logging.log4j.util.PropertiesUtil;
    48
    49  /**
    50   * Creates MBeans to instrument various classes in the log4j class hierarchy.
    51   * <p>
    52   * All instrumentation for Log4j 2 classes can be disabled by setting system property {@code -Dlog4j2.disable.jmx=true}.
    53   * </p>
    54   */
    55  public final class Server {
    56
    57      /**
    58       * The domain part, or prefix ({@value} ) of the {@code ObjectName} of all MBeans that instrument Log4J2 components.
    59       */
    60      public static final String DOMAIN = "org.apache.logging.log4j2";
    61      private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
    62      private static final String PROPERTY_ASYNC_NOTIF = "log4j2.jmx.notify.async";
    63      private static final String THREAD_NAME_PREFIX = "log4j2.jmx.notif";
    64      private static final StatusLogger LOGGER = StatusLogger.getLogger();
    65      static final Executor executor = createExecutor();
    66
    67      private Server() {
    68      }
    69
    70      /**
    71       * Returns either a {@code null} Executor (causing JMX notifications to be sent from the caller thread) or a daemon
    72       * background thread Executor, depending on the value of system property "log4j2.jmx.notify.async". If this
    73       * property is not set, use a {@code null} Executor for web apps to avoid memory leaks and other issues when the
    74       * web app is restarted.
    75       * @see LOG4J2-938
    76       */
    77      private static ExecutorService createExecutor() {
    78          boolean defaultAsync = !isWebApp();
    79          boolean async = PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_ASYNC_NOTIF, defaultAsync);
    80          return async ? Executors.newFixedThreadPool(1, new DaemonThreadFactory(THREAD_NAME_PREFIX)) : null;
    81      }
    82
    83      /**
    84       * Returns {@code true} if we think we are running in a web container, based on the presence of the
    85       * {@code javax.servlet.Servlet} class in the classpath.
    86       */
    87      private static boolean isWebApp() {
    88          return Loader.isClassAvailable("javax.servlet.Servlet");
    89      }
    90
    91      /**
    92       * Either returns the specified name as is, or returns a quoted value containing the specified name with the special
    93       * characters (comma, equals, colon, quote, asterisk, or question mark) preceded with a backslash.
    94       *
    95       * @param name the name to escape so it can be used as a value in an {@link ObjectName}.
    96       * @return the escaped name
    97       */
    98      public static String escape(final String name) {
    99          final StringBuilder sb = new StringBuilder(name.length() * 2);
    100         boolean needsQuotes = false;
    101         for (int i = 0; i < name.length(); i++) {
    102             final char c = name.charAt(i);
    103             switch (c) {
    104             case '\\':
    105             case '*':
    106             case '?':
    107             case '\"':
    108                 // quote, star, question & backslash must be escaped
    109                 sb.append('\\');
    110                 needsQuotes = true; // ... and can only appear in quoted value
    111                 break;
    112             case ',':
    113             case '=':
    114             case ':':
    115                 // no need to escape these, but value must be quoted
    116                 needsQuotes = true;
    117                 break;
    118             case '\r':
    119                 // drop \r characters: \\r gives "invalid escape sequence"
    120                 continue;
    121             case '\n':
    122                 // replace \n characters with \\n sequence
    123                 sb.append("\\n");
    124                 needsQuotes = true;
    125                 continue;
    126             }
    127             sb.append(c);
    128         }
    129         if (needsQuotes) {
    130             sb.insert(0, '\"');
    131             sb.append('\"');
    132         }
    133         return sb.toString();
    134     }
    135
    136     public static void reregisterMBeansAfterReconfigure() {
    137         // avoid creating Platform MBean Server if JMX disabled
    138         if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) {
    139             LOGGER.debug("JMX disabled for log4j2. Not registering MBeans.");
    140             return;
    141         }
    142         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    143         reregisterMBeansAfterReconfigure(mbs);
    144     }
    145
    146     public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) {
    147         if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) {
    148             LOGGER.debug("JMX disabled for log4j2. Not registering MBeans.");
    149             return;
    150         }
    151
    152         // now provide instrumentation for the newly configured
    153         // LoggerConfigs and Appenders
    154         try {
    155             final ContextSelector selector = getContextSelector();
    156             if (selector == null) {
    157                 LOGGER.debug("Could not register MBeans: no ContextSelector found.");
    158                 return;
    159             }
    160             final List<LoggerContext> contexts = selector.getLoggerContexts();
    161             for (final LoggerContext ctx : contexts) {
    162                 // first unregister the context and all nested loggers,
    163                 // appenders, statusLogger, contextSelector, ringbuffers...
    164                 unregisterLoggerContext(ctx.getName(), mbs);
    165
    166                 final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor);
    167                 register(mbs, mbean, mbean.getObjectName());
    168
    169                 if (ctx instanceof AsyncLoggerContext) {
    170                     final RingBufferAdmin rbmbean = AsyncLogger.createRingBufferAdmin(ctx.getName());
    171                     register(mbs, rbmbean, rbmbean.getObjectName());
    172                 }
    173
    174                 // register the status logger and the context selector
    175                 // repeatedly
    176                 // for each known context: if one context is unregistered,
    177                 // these MBeans should still be available for the other
    178                 // contexts.
    179                 registerStatusLogger(ctx.getName(), mbs, executor);
    180                 registerContextSelector(ctx.getName(), selector, mbs, executor);
    181
    182                 registerLoggerConfigs(ctx, mbs, executor);
    183                 registerAppenders(ctx, mbs, executor);
    184             }
    185         } catch (final Exception ex) {
    186             LOGGER.error("Could not register mbeans", ex);
    187         }
    188     }
    189
    190     /**
    191      * Unregister all log4j MBeans from the platform MBean server.
    192      */
    193     public static void unregisterMBeans() {
    194         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    195         unregisterMBeans(mbs);
    196     }
    197
    198     /**
    199      * Unregister all log4j MBeans from the specified MBean server.
    200      *
    201      * @param mbs the MBean server to unregister from.
    202      */
    203     public static void unregisterMBeans(final MBeanServer mbs) {
    204         unregisterStatusLogger("*", mbs);
    205         unregisterContextSelector("*", mbs);
    206         unregisterContexts(mbs);
    207         unregisterLoggerConfigs("*", mbs);
    208         unregisterAsyncLoggerRingBufferAdmins("*", mbs);
    209         unregisterAsyncLoggerConfigRingBufferAdmins("*", mbs);
    210         unregisterAppenders("*", mbs);
    211         unregisterAsyncAppenders("*", mbs);
    212     }
    213
    214     /**
    215      * Returns the {@code ContextSelector} of the current {@code Log4jContextFactory}.
    216      *
    217      * @return the {@code ContextSelector} of the current {@code Log4jContextFactory}
    218      */
    219     private static ContextSelector getContextSelector() {
    220         final LoggerContextFactory factory = LogManager.getFactory();
    221         if (factory instanceof Log4jContextFactory) {
    222             final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
    223             return selector;
    224         }
    225         return null;
    226     }
    227
    228     /**
    229      * Unregisters all MBeans associated with the specified logger context (including MBeans for {@code LoggerConfig}s
    230      * and {@code Appender}s from the platform MBean server.
    231      *
    232      * @param loggerContextName name of the logger context to unregister
    233      */
    234     public static void unregisterLoggerContext(final String loggerContextName) {
    235         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    236         unregisterLoggerContext(loggerContextName, mbs);
    237     }
    238
    239     /**
    240      * Unregisters all MBeans associated with the specified logger context (including MBeans for {@code LoggerConfig}s
    241      * and {@code Appender}s from the platform MBean server.
    242      *
    243      * @param contextName name of the logger context to unregister
    244      * @param mbs the MBean Server to unregister the instrumented objects from
    245      */
    246     public static void unregisterLoggerContext(final String contextName, final MBeanServer mbs) {
    247         final String pattern = LoggerContextAdminMBean.PATTERN;
    248         final String search = String.format(pattern, escape(contextName), "*");
    249         unregisterAllMatching(search, mbs); // unregister context mbean
    250
    251         // now unregister all MBeans associated with this logger context
    252         unregisterStatusLogger(contextName, mbs);
    253         unregisterContextSelector(contextName, mbs);
    254         unregisterLoggerConfigs(contextName, mbs);
    255         unregisterAppenders(contextName, mbs);
    256         unregisterAsyncAppenders(contextName, mbs);
    257         unregisterAsyncLoggerRingBufferAdmins(contextName, mbs);
    258         unregisterAsyncLoggerConfigRingBufferAdmins(contextName, mbs);
    259     }
    260
    261     private static void registerStatusLogger(final String contextName, final MBeanServer mbs, final Executor executor)
    262             throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
    263
    264         final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor);
    265         register(mbs, mbean, mbean.getObjectName());
    266     }
    267
    268     private static void registerContextSelector(final String contextName, final ContextSelector selector,
    269             final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException,
    270             MBeanRegistrationException, NotCompliantMBeanException {
    271
    272         final ContextSelectorAdmin mbean = new ContextSelectorAdmin(contextName, selector);
    273         register(mbs, mbean, mbean.getObjectName());
    274     }
    275
    276     private static void unregisterStatusLogger(final String contextName, final MBeanServer mbs) {
    277         final String pattern = StatusLoggerAdminMBean.PATTERN;
    278         final String search = String.format(pattern, escape(contextName), "*");
    279         unregisterAllMatching(search, mbs);
    280     }
    281
    282     private static void unregisterContextSelector(final String contextName, final MBeanServer mbs) {
    283         final String pattern = ContextSelectorAdminMBean.PATTERN;
    284         final String search = String.format(pattern, escape(contextName), "*");
    285         unregisterAllMatching(search, mbs);
    286     }
    287
    288     private static void unregisterLoggerConfigs(final String contextName, final MBeanServer mbs) {
    289         final String pattern = LoggerConfigAdminMBean.PATTERN;
    290         final String search = String.format(pattern, escape(contextName), "*");
    291         unregisterAllMatching(search, mbs);
    292     }
    293
    294     private static void unregisterContexts(final MBeanServer mbs) {
    295         final String pattern = LoggerContextAdminMBean.PATTERN;
    296         final String search = String.format(pattern, "*");
    297         unregisterAllMatching(search, mbs);
    298     }
    299
    300     private static void unregisterAppenders(final String contextName, final MBeanServer mbs) {
    301         final String pattern = AppenderAdminMBean.PATTERN;
    302         final String search = String.format(pattern, escape(contextName), "*");
    303         unregisterAllMatching(search, mbs);
    304     }
    305
    306     private static void unregisterAsyncAppenders(final String contextName, final MBeanServer mbs) {
    307         final String pattern = AsyncAppenderAdminMBean.PATTERN;
    308         final String search = String.format(pattern, escape(contextName), "*");
    309         unregisterAllMatching(search, mbs);
    310     }
    311
    312     private static void unregisterAsyncLoggerRingBufferAdmins(final String contextName, final MBeanServer mbs) {
    313         final String pattern1 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER;
    314         final String search1 = String.format(pattern1, escape(contextName));
    315         unregisterAllMatching(search1, mbs);
    316     }
    317
    318     private static void unregisterAsyncLoggerConfigRingBufferAdmins(final String contextName, final MBeanServer mbs) {
    319         final String pattern2 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER_CONFIG;
    320         final String search2 = String.format(pattern2, escape(contextName), "*");
    321         unregisterAllMatching(search2, mbs);
    322     }
    323
    324     private static void unregisterAllMatching(final String search, final MBeanServer mbs) {
    325         try {
    326             final ObjectName pattern = new ObjectName(search);
    327             final Set<ObjectName> found = mbs.queryNames(pattern, null);
    328             for (final ObjectName objectName : found) {
    329                 LOGGER.debug("Unregistering MBean {}", objectName);
    330                 mbs.unregisterMBean(objectName);
    331             }
    332         } catch (final Exception ex) {
    333             LOGGER.error("Could not unregister MBeans for " + search, ex);
    334         }
    335     }
    336
    337     private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
    338             throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
    339
    340         final Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers();
    341         for (final String name : map.keySet()) {
    342             final LoggerConfig cfg = map.get(name);
    343             final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg);
    344             register(mbs, mbean, mbean.getObjectName());
    345
    346             if (cfg instanceof AsyncLoggerConfig) {
    347                 final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg;
    348                 final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName());
    349                 register(mbs, rbmbean, rbmbean.getObjectName());
    350             }
    351         }
    352     }
    353
    354     private static void registerAppenders(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
    355             throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
    356
    357         final Map<String, Appender> map = ctx.getConfiguration().getAppenders();
    358         for (final String name : map.keySet()) {
    359             final Appender appender = map.get(name);
    360
    361             if (appender instanceof AsyncAppender) {
    362                 final AsyncAppender async = ((AsyncAppender) appender);
    363                 final AsyncAppenderAdmin mbean = new AsyncAppenderAdmin(ctx.getName(), async);
    364                 register(mbs, mbean, mbean.getObjectName());
    365             } else {
    366                 final AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender);
    367                 register(mbs, mbean, mbean.getObjectName());
    368             }
    369         }
    370     }
    371
    372     private static void register(final MBeanServer mbs, final Object mbean, final ObjectName objectName)
    373             throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
    374         LOGGER.debug("Registering MBean {}", objectName);
    375         mbs.registerMBean(mbean, objectName);
    376     }
    377 }