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