View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.jmx;
18  
19  import java.lang.management.ManagementFactory;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.concurrent.Executor;
24  import java.util.concurrent.Executors;
25  
26  import javax.management.InstanceAlreadyExistsException;
27  import javax.management.MBeanRegistrationException;
28  import javax.management.MBeanServer;
29  import javax.management.NotCompliantMBeanException;
30  import javax.management.ObjectName;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.core.Appender;
34  import org.apache.logging.log4j.core.LoggerContext;
35  import org.apache.logging.log4j.core.appender.AsyncAppender;
36  import org.apache.logging.log4j.core.async.AsyncLogger;
37  import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
38  import org.apache.logging.log4j.core.async.AsyncLoggerContext;
39  import org.apache.logging.log4j.core.config.LoggerConfig;
40  import org.apache.logging.log4j.core.impl.Log4jContextFactory;
41  import org.apache.logging.log4j.core.selector.ContextSelector;
42  import org.apache.logging.log4j.spi.LoggerContextFactory;
43  import org.apache.logging.log4j.status.StatusLogger;
44  import org.apache.logging.log4j.util.PropertiesUtil;
45  
46  /**
47   * Creates MBeans to instrument various classes in the log4j class hierarchy.
48   * <p>
49   * All instrumentation for Log4j 2 classes can be disabled by setting system
50   * property {@code -Dlog4j2.disable.jmx=true}.
51   * </p>
52   */
53  public final class Server {
54  
55      /**
56       * The domain part, or prefix ({@value} ) of the {@code ObjectName} of all
57       * MBeans that instrument Log4J2 components.
58       */
59      public static final String DOMAIN = "org.apache.logging.log4j2";
60      private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
61      private static final StatusLogger LOGGER = StatusLogger.getLogger();
62      static final Executor executor = Executors.newFixedThreadPool(1);
63  
64      private Server() {
65      }
66  
67      /**
68       * Either returns the specified name as is, or returns a quoted value
69       * containing the specified name with the special characters (comma, equals,
70       * colon, quote, asterisk, or question mark) preceded with a backslash.
71       *
72       * @param name the name to escape so it can be used as a value in an
73       *            {@link ObjectName}.
74       * @return the escaped name
75       */
76      public static String escape(final String name) {
77          final StringBuilder sb = new StringBuilder(name.length() * 2);
78          boolean needsQuotes = false;
79          for (int i = 0; i < name.length(); i++) {
80              final char c = name.charAt(i);
81              switch (c) {
82              case '\\':
83              case '*':
84              case '?':
85              case '\"':
86                  // quote, star, question & backslash must be escaped
87                  sb.append('\\');
88                  needsQuotes = true; // ... and can only appear in quoted value
89                  break;
90              case ',':
91              case '=':
92              case ':':
93                  // no need to escape these, but value must be quoted
94                  needsQuotes = true;
95                  break;
96              case '\r':
97                  // drop \r characters: \\r gives "invalid escape sequence"
98                  continue;
99              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 }