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    
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     */
051    public 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(final 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 (final 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                        final 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(final 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                final 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(final 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 contextName 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(final String contextName, final 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, cfg);
324                register(mbs, mbean, mbean.getObjectName());
325    
326                if (cfg instanceof AsyncLoggerConfig) {
327                    final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg;
328                    final 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                    final 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(final MBeanServer mbs, final Object mbean, final ObjectName objectName)
353                throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
354            LOGGER.debug("Registering MBean {}", objectName);
355            mbs.registerMBean(mbean, objectName);
356        }
357    }