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