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     */
017    package org.apache.logging.log4j.core.jmx;
018    
019    import java.lang.management.ManagementFactory;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    import java.util.concurrent.Executor;
024    import java.util.concurrent.Executors;
025    
026    import javax.management.InstanceAlreadyExistsException;
027    import javax.management.MBeanRegistrationException;
028    import javax.management.MBeanServer;
029    import javax.management.NotCompliantMBeanException;
030    import javax.management.ObjectName;
031    
032    import org.apache.logging.log4j.LogManager;
033    import org.apache.logging.log4j.core.Appender;
034    import org.apache.logging.log4j.core.LoggerContext;
035    import org.apache.logging.log4j.core.appender.AsyncAppender;
036    import org.apache.logging.log4j.core.async.AsyncLogger;
037    import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
038    import org.apache.logging.log4j.core.async.AsyncLoggerContext;
039    import org.apache.logging.log4j.core.config.LoggerConfig;
040    import org.apache.logging.log4j.core.impl.Log4jContextFactory;
041    import org.apache.logging.log4j.core.selector.ContextSelector;
042    import org.apache.logging.log4j.spi.LoggerContextFactory;
043    import org.apache.logging.log4j.status.StatusLogger;
044    import 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     */
053    public 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    }