001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.jmx;
018
019import java.lang.management.ManagementFactory;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023import java.util.concurrent.Executor;
024import java.util.concurrent.Executors;
025
026import javax.management.InstanceAlreadyExistsException;
027import javax.management.MBeanRegistrationException;
028import javax.management.MBeanServer;
029import javax.management.NotCompliantMBeanException;
030import javax.management.ObjectName;
031
032import org.apache.logging.log4j.LogManager;
033import org.apache.logging.log4j.core.Appender;
034import org.apache.logging.log4j.core.LoggerContext;
035import org.apache.logging.log4j.core.appender.AsyncAppender;
036import org.apache.logging.log4j.core.async.AsyncLogger;
037import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
038import org.apache.logging.log4j.core.async.AsyncLoggerContext;
039import org.apache.logging.log4j.core.config.LoggerConfig;
040import org.apache.logging.log4j.core.impl.Log4jContextFactory;
041import org.apache.logging.log4j.core.selector.ContextSelector;
042import org.apache.logging.log4j.spi.LoggerContextFactory;
043import org.apache.logging.log4j.status.StatusLogger;
044import org.apache.logging.log4j.util.PropertiesUtil;
045
046/**
047 * Creates MBeans to instrument various classes in the log4j class hierarchy.
048 * <p>
049 * All instrumentation for Log4j 2 classes can be disabled by setting system
050 * property {@code -Dlog4j2.disable.jmx=true}.
051 * </p>
052 */
053public final class Server {
054
055    /**
056     * The domain part, or prefix ({@value} ) of the {@code ObjectName} of all
057     * MBeans that instrument Log4J2 components.
058     */
059    public static final String DOMAIN = "org.apache.logging.log4j2";
060    private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
061    private static final StatusLogger LOGGER = StatusLogger.getLogger();
062    static final Executor executor = Executors.newFixedThreadPool(1);
063
064    private Server() {
065    }
066
067    /**
068     * Either returns the specified name as is, or returns a quoted value
069     * containing the specified name with the special characters (comma, equals,
070     * colon, quote, asterisk, or question mark) preceded with a backslash.
071     *
072     * @param name the name to escape so it can be used as a value in an
073     *            {@link ObjectName}.
074     * @return the escaped name
075     */
076    public static String escape(final String name) {
077        final StringBuilder sb = new StringBuilder(name.length() * 2);
078        boolean needsQuotes = false;
079        for (int i = 0; i < name.length(); i++) {
080            final char c = name.charAt(i);
081            switch (c) {
082            case '\\':
083            case '*':
084            case '?':
085            case '\"':
086                // quote, star, question & backslash must be escaped
087                sb.append('\\');
088                needsQuotes = true; // ... and can only appear in quoted value
089                break;
090            case ',':
091            case '=':
092            case ':':
093                // no need to escape these, but value must be quoted
094                needsQuotes = true;
095                break;
096            case '\r':
097                // drop \r characters: \\r gives "invalid escape sequence"
098                continue;
099            case '\n':
100                // replace \n characters with \\n sequence
101                sb.append("\\n");
102                needsQuotes = true;
103                continue;
104            }
105            sb.append(c);
106        }
107        if (needsQuotes) {
108            sb.insert(0, '\"');
109            sb.append('\"');
110        }
111        return sb.toString();
112    }
113
114    public static void reregisterMBeansAfterReconfigure() {
115        // avoid creating Platform MBean Server if JMX disabled
116        if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) {
117            LOGGER.debug("JMX disabled for log4j2. Not registering MBeans.");
118            return;
119        }
120        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
121        reregisterMBeansAfterReconfigure(mbs);
122    }
123
124    public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) {
125        if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) {
126            LOGGER.debug("JMX disabled for log4j2. Not registering MBeans.");
127            return;
128        }
129
130        // now provide instrumentation for the newly configured
131        // LoggerConfigs and Appenders
132        try {
133            final ContextSelector selector = getContextSelector();
134            if (selector == null) {
135                LOGGER.debug("Could not register MBeans: no ContextSelector found.");
136                return;
137            }
138            final List<LoggerContext> contexts = selector.getLoggerContexts();
139            for (final LoggerContext ctx : contexts) {
140                // first unregister the context and all nested loggers,
141                // appenders, statusLogger, contextSelector, ringbuffers...
142                unregisterLoggerContext(ctx.getName(), mbs);
143
144                final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor);
145                register(mbs, mbean, mbean.getObjectName());
146
147                if (ctx instanceof AsyncLoggerContext) {
148                    final RingBufferAdmin rbmbean = AsyncLogger.createRingBufferAdmin(ctx.getName());
149                    register(mbs, rbmbean, rbmbean.getObjectName());
150                }
151
152                // register the status logger and the context selector
153                // repeatedly
154                // for each known context: if one context is unregistered,
155                // these MBeans should still be available for the other
156                // contexts.
157                registerStatusLogger(ctx.getName(), mbs, executor);
158                registerContextSelector(ctx.getName(), selector, mbs, executor);
159
160                registerLoggerConfigs(ctx, mbs, executor);
161                registerAppenders(ctx, mbs, executor);
162            }
163        } catch (final Exception ex) {
164            LOGGER.error("Could not register mbeans", ex);
165        }
166    }
167
168    /**
169     * Unregister all log4j MBeans from the platform MBean server.
170     */
171    public static void unregisterMBeans() {
172        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
173        unregisterMBeans(mbs);
174    }
175
176    /**
177     * Unregister all log4j MBeans from the specified MBean server.
178     *
179     * @param mbs the MBean server to unregister from.
180     */
181    public static void unregisterMBeans(final MBeanServer mbs) {
182        unregisterStatusLogger("*", mbs);
183        unregisterContextSelector("*", mbs);
184        unregisterContexts(mbs);
185        unregisterLoggerConfigs("*", mbs);
186        unregisterAsyncLoggerRingBufferAdmins("*", mbs);
187        unregisterAsyncLoggerConfigRingBufferAdmins("*", mbs);
188        unregisterAppenders("*", mbs);
189        unregisterAsyncAppenders("*", mbs);
190    }
191
192    /**
193     * Returns the {@code ContextSelector} of the current
194     * {@code Log4jContextFactory}.
195     *
196     * @return the {@code ContextSelector} of the current
197     *         {@code Log4jContextFactory}
198     */
199    private static ContextSelector getContextSelector() {
200        final LoggerContextFactory factory = LogManager.getFactory();
201        if (factory instanceof Log4jContextFactory) {
202            final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
203            return selector;
204        }
205        return null;
206    }
207
208    /**
209     * Unregisters all MBeans associated with the specified logger context
210     * (including MBeans for {@code LoggerConfig}s and {@code Appender}s from
211     * the platform MBean server.
212     *
213     * @param loggerContextName name of the logger context to unregister
214     */
215    public static void unregisterLoggerContext(final String loggerContextName) {
216        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
217        unregisterLoggerContext(loggerContextName, mbs);
218    }
219
220    /**
221     * Unregisters all MBeans associated with the specified logger context
222     * (including MBeans for {@code LoggerConfig}s and {@code Appender}s from
223     * the platform MBean server.
224     *
225     * @param contextName name of the logger context to unregister
226     * @param mbs the MBean Server to unregister the instrumented objects from
227     */
228    public static void unregisterLoggerContext(final String contextName, final MBeanServer mbs) {
229        final String pattern = LoggerContextAdminMBean.PATTERN;
230        final String search = String.format(pattern, escape(contextName), "*");
231        unregisterAllMatching(search, mbs); // unregister context mbean
232
233        // now unregister all MBeans associated with this logger context
234        unregisterStatusLogger(contextName, mbs);
235        unregisterContextSelector(contextName, mbs);
236        unregisterLoggerConfigs(contextName, mbs);
237        unregisterAppenders(contextName, mbs);
238        unregisterAsyncAppenders(contextName, mbs);
239        unregisterAsyncLoggerRingBufferAdmins(contextName, mbs);
240        unregisterAsyncLoggerConfigRingBufferAdmins(contextName, mbs);
241    }
242
243    private static void registerStatusLogger(final String contextName, final MBeanServer mbs, final Executor executor)
244            throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
245
246        final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor);
247        register(mbs, mbean, mbean.getObjectName());
248    }
249
250    private static void registerContextSelector(final String contextName, final ContextSelector selector,
251            final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException,
252            MBeanRegistrationException, NotCompliantMBeanException {
253
254        final ContextSelectorAdmin mbean = new ContextSelectorAdmin(contextName, selector);
255        register(mbs, mbean, mbean.getObjectName());
256    }
257
258    private static void unregisterStatusLogger(final String contextName, final MBeanServer mbs) {
259        final String pattern = StatusLoggerAdminMBean.PATTERN;
260        final String search = String.format(pattern, escape(contextName), "*");
261        unregisterAllMatching(search, mbs);
262    }
263
264    private static void unregisterContextSelector(final String contextName, final MBeanServer mbs) {
265        final String pattern = ContextSelectorAdminMBean.PATTERN;
266        final String search = String.format(pattern, escape(contextName), "*");
267        unregisterAllMatching(search, mbs);
268    }
269
270    private static void unregisterLoggerConfigs(final String contextName, final MBeanServer mbs) {
271        final String pattern = LoggerConfigAdminMBean.PATTERN;
272        final String search = String.format(pattern, escape(contextName), "*");
273        unregisterAllMatching(search, mbs);
274    }
275
276    private static void unregisterContexts(final MBeanServer mbs) {
277        final String pattern = LoggerContextAdminMBean.PATTERN;
278        final String search = String.format(pattern, "*");
279        unregisterAllMatching(search, mbs);
280    }
281
282    private static void unregisterAppenders(final String contextName, final MBeanServer mbs) {
283        final String pattern = AppenderAdminMBean.PATTERN;
284        final String search = String.format(pattern, escape(contextName), "*");
285        unregisterAllMatching(search, mbs);
286    }
287
288    private static void unregisterAsyncAppenders(final String contextName, final MBeanServer mbs) {
289        final String pattern = AsyncAppenderAdminMBean.PATTERN;
290        final String search = String.format(pattern, escape(contextName), "*");
291        unregisterAllMatching(search, mbs);
292    }
293
294    private static void unregisterAsyncLoggerRingBufferAdmins(final String contextName, final MBeanServer mbs) {
295        final String pattern1 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER;
296        final String search1 = String.format(pattern1, escape(contextName));
297        unregisterAllMatching(search1, mbs);
298    }
299
300    private static void unregisterAsyncLoggerConfigRingBufferAdmins(final String contextName, final MBeanServer mbs) {
301        final String pattern2 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER_CONFIG;
302        final String search2 = String.format(pattern2, escape(contextName), "*");
303        unregisterAllMatching(search2, mbs);
304    }
305
306    private static void unregisterAllMatching(final String search, final MBeanServer mbs) {
307        try {
308            final ObjectName pattern = new ObjectName(search);
309            final Set<ObjectName> found = mbs.queryNames(pattern, null);
310            for (final ObjectName objectName : found) {
311                LOGGER.debug("Unregistering MBean {}", objectName);
312                mbs.unregisterMBean(objectName);
313            }
314        } catch (final Exception ex) {
315            LOGGER.error("Could not unregister MBeans for " + search, ex);
316        }
317    }
318
319    private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
320            throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
321
322        final Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers();
323        for (final String name : map.keySet()) {
324            final LoggerConfig cfg = map.get(name);
325            final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg);
326            register(mbs, mbean, mbean.getObjectName());
327
328            if (cfg instanceof AsyncLoggerConfig) {
329                final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg;
330                final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName());
331                register(mbs, rbmbean, rbmbean.getObjectName());
332            }
333        }
334    }
335
336    private static void registerAppenders(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
337            throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
338
339        final Map<String, Appender> map = ctx.getConfiguration().getAppenders();
340        for (final String name : map.keySet()) {
341            final Appender appender = map.get(name);
342
343            if (appender instanceof AsyncAppender) {
344                final AsyncAppender async = ((AsyncAppender) appender);
345                final AsyncAppenderAdmin mbean = new AsyncAppenderAdmin(ctx.getName(), async);
346                register(mbs, mbean, mbean.getObjectName());
347            } else {
348                final AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender);
349                register(mbs, mbean, mbean.getObjectName());
350            }
351        }
352    }
353
354    private static void register(final MBeanServer mbs, final Object mbean, final ObjectName objectName)
355            throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
356        LOGGER.debug("Registering MBean {}", objectName);
357        mbs.registerMBean(mbean, objectName);
358    }
359}