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.beans.PropertyChangeEvent;
020    import java.beans.PropertyChangeListener;
021    import java.lang.management.ManagementFactory;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Set;
025    import java.util.concurrent.Executor;
026    import java.util.concurrent.Executors;
027    
028    import javax.management.InstanceAlreadyExistsException;
029    import javax.management.JMException;
030    import javax.management.MBeanRegistrationException;
031    import javax.management.MBeanServer;
032    import javax.management.MalformedObjectNameException;
033    import javax.management.NotCompliantMBeanException;
034    import javax.management.ObjectName;
035    
036    import org.apache.logging.log4j.core.Appender;
037    import org.apache.logging.log4j.core.LoggerContext;
038    import org.apache.logging.log4j.core.config.LoggerConfig;
039    import org.apache.logging.log4j.core.selector.ContextSelector;
040    import org.apache.logging.log4j.status.StatusLogger;
041    
042    /**
043     * Creates MBeans to instrument various classes in the log4j class hierarchy.
044     * <p>
045     * All instrumentation for Log4j 2 classes can be disabled by setting system
046     * property {@code -Dlog4j2.disable.jmx=true}.
047     */
048    public final class Server {
049    
050        private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
051        
052        private Server() {
053        }
054    
055        /**
056         * Either returns the specified name as is, or returns a quoted value
057         * containing the specified name with the special characters (comma, equals,
058         * colon, quote, asterisk, or question mark) preceded with a backslash.
059         * 
060         * @param name
061         *            the name to escape so it can be used as a value in an
062         *            {@link ObjectName}.
063         * @return the escaped name
064         */
065        public static String escape(String name) {
066            StringBuilder sb = new StringBuilder(name.length() * 2);
067            boolean needsQuotes = false;
068            for (int i = 0; i < name.length(); i++) {
069                char c = name.charAt(i);
070                switch (c) {
071                case ',':
072                case '=':
073                case ':':
074                case '\\':
075                case '*':
076                case '?':
077                    sb.append('\\');
078                    needsQuotes = true;
079                }
080                sb.append(c);
081            }
082            if (needsQuotes) {
083                sb.insert(0, '\"');
084                sb.append('\"');
085            }
086            return sb.toString();
087        }
088    
089        /**
090         * Creates MBeans to instrument the specified selector and other classes in
091         * the log4j class hierarchy and registers the MBeans in the platform MBean
092         * server so they can be accessed by remote clients.
093         * 
094         * @param selector
095         *            starting point in the log4j class hierarchy
096         * @throws JMException
097         *             if a problem occurs during registration
098         */
099        public static void registerMBeans(ContextSelector selector)
100                throws JMException {
101    
102            // avoid creating Platform MBean Server if JMX disabled
103            if (Boolean.getBoolean(PROPERTY_DISABLE_JMX)) {
104                StatusLogger.getLogger().debug(
105                        "JMX disabled for log4j2. Not registering MBeans.");
106                return;
107            }
108            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
109            registerMBeans(selector, mbs);
110        }
111    
112        /**
113         * Creates MBeans to instrument the specified selector and other classes in
114         * the log4j class hierarchy and registers the MBeans in the specified MBean
115         * server so they can be accessed by remote clients.
116         * 
117         * @param selector
118         *            starting point in the log4j class hierarchy
119         * @param mbs
120         *            the MBean Server to register the instrumented objects in
121         * @throws JMException
122         *             if a problem occurs during registration
123         */
124        public static void registerMBeans(ContextSelector selector,
125                final MBeanServer mbs) throws JMException {
126    
127            if (Boolean.getBoolean(PROPERTY_DISABLE_JMX)) {
128                StatusLogger.getLogger().debug(
129                        "JMX disabled for log4j2. Not registering MBeans.");
130                return;
131            }
132            final Executor executor = Executors.newFixedThreadPool(1);
133            registerStatusLogger(mbs, executor);
134            registerContextSelector(selector, mbs, executor);
135    
136            List<LoggerContext> contexts = selector.getLoggerContexts();
137            registerContexts(contexts, mbs, executor);
138    
139            for (final LoggerContext context : contexts) {
140                context.addPropertyChangeListener(new PropertyChangeListener() {
141    
142                    @Override
143                    public void propertyChange(PropertyChangeEvent evt) {
144                        if (!LoggerContext.PROPERTY_CONFIG.equals(evt
145                                .getPropertyName())) {
146                            return;
147                        }
148                        // first unregister the MBeans that instrument the
149                        // previous instrumented LoggerConfigs and Appenders
150                        unregisterLoggerConfigs(context, mbs);
151                        unregisterAppenders(context, mbs);
152    
153                        // now provide instrumentation for the newly configured
154                        // LoggerConfigs and Appenders
155                        try {
156                            registerLoggerConfigs(context, mbs, executor);
157                            registerAppenders(context, mbs, executor);
158                        } catch (Exception ex) {
159                            StatusLogger.getLogger().error(
160                                    "Could not register mbeans", ex);
161                        }
162                    }
163                });
164            }
165        }
166    
167        private static void registerStatusLogger(MBeanServer mbs, Executor executor)
168                throws MalformedObjectNameException,
169                InstanceAlreadyExistsException, MBeanRegistrationException,
170                NotCompliantMBeanException {
171    
172            StatusLoggerAdmin mbean = new StatusLoggerAdmin(executor);
173            mbs.registerMBean(mbean, mbean.getObjectName());
174        }
175    
176        private static void registerContextSelector(ContextSelector selector,
177                MBeanServer mbs, Executor executor)
178                throws MalformedObjectNameException,
179                InstanceAlreadyExistsException, MBeanRegistrationException,
180                NotCompliantMBeanException {
181    
182            ContextSelectorAdmin mbean = new ContextSelectorAdmin(selector);
183            mbs.registerMBean(mbean, mbean.getObjectName());
184        }
185    
186        private static void registerContexts(List<LoggerContext> contexts,
187                MBeanServer mbs, Executor executor)
188                throws MalformedObjectNameException,
189                InstanceAlreadyExistsException, MBeanRegistrationException,
190                NotCompliantMBeanException {
191    
192            for (LoggerContext ctx : contexts) {
193                LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor);
194                mbs.registerMBean(mbean, mbean.getObjectName());
195            }
196        }
197    
198        private static void unregisterLoggerConfigs(LoggerContext context,
199                MBeanServer mbs) {
200            String pattern = LoggerConfigAdminMBean.PATTERN;
201            String search = String.format(pattern, context.getName(), "*");
202            unregisterAllMatching(search, mbs);
203        }
204    
205        private static void unregisterAppenders(LoggerContext context,
206                MBeanServer mbs) {
207            String pattern = AppenderAdminMBean.PATTERN;
208            String search = String.format(pattern, context.getName(), "*");
209            unregisterAllMatching(search, mbs);
210        }
211    
212        private static void unregisterAllMatching(String search, MBeanServer mbs) {
213            try {
214                ObjectName pattern = new ObjectName(search);
215                Set<ObjectName> found = mbs.queryNames(pattern, null);
216                for (ObjectName objectName : found) {
217                    mbs.unregisterMBean(objectName);
218                }
219            } catch (Exception ex) {
220                StatusLogger.getLogger()
221                        .error("Could not unregister " + search, ex);
222            }
223        }
224    
225        private static void registerLoggerConfigs(LoggerContext ctx,
226                MBeanServer mbs, Executor executor)
227                throws MalformedObjectNameException,
228                InstanceAlreadyExistsException, MBeanRegistrationException,
229                NotCompliantMBeanException {
230    
231            Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers();
232            for (String name : map.keySet()) {
233                LoggerConfig cfg = map.get(name);
234                LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx.getName(), cfg);
235                mbs.registerMBean(mbean, mbean.getObjectName());
236            }
237        }
238    
239        private static void registerAppenders(LoggerContext ctx, MBeanServer mbs,
240                Executor executor) throws MalformedObjectNameException,
241                InstanceAlreadyExistsException, MBeanRegistrationException,
242                NotCompliantMBeanException {
243    
244            Map<String, Appender<?>> map = ctx.getConfiguration().getAppenders();
245            for (String name : map.keySet()) {
246                Appender<?> appender = map.get(name);
247                AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender);
248                mbs.registerMBean(mbean, mbean.getObjectName());
249            }
250        }
251    }