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  
45  /**
46   * Creates MBeans to instrument various classes in the log4j class hierarchy.
47   * <p>
48   * All instrumentation for Log4j 2 classes can be disabled by setting system
49   * property {@code -Dlog4j2.disable.jmx=true}.
50   */
51  public final class Server {
52  
53      /**
54       * The domain part, or prefix ({@value} ) of the {@code ObjectName} of all
55       * MBeans that instrument Log4J2 components.
56       */
57      public static final String DOMAIN = "org.apache.logging.log4j2";
58      private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
59      private static final StatusLogger LOGGER = StatusLogger.getLogger();
60      static final Executor executor = Executors.newFixedThreadPool(1);
61  
62      private Server() {
63      }
64  
65      /**
66       * Either returns the specified name as is, or returns a quoted value
67       * containing the specified name with the special characters (comma, equals,
68       * colon, quote, asterisk, or question mark) preceded with a backslash.
69       * 
70       * @param name the name to escape so it can be used as a value in an
71       *            {@link ObjectName}.
72       * @return the escaped name
73       */
74      public static String escape(final String name) {
75          final StringBuilder sb = new StringBuilder(name.length() * 2);
76          boolean needsQuotes = false;
77          for (int i = 0; i < name.length(); i++) {
78              final char c = name.charAt(i);
79              switch (c) {
80              case '\\':
81              case '*':
82              case '?':
83              case '\"':
84                  // quote, star, question & backslash must be escaped
85                  sb.append('\\');
86                  needsQuotes = true; // ... and can only appear in quoted value
87                  break;
88              case ',':
89              case '=':
90              case ':':
91                  // no need to escape these, but value must be quoted
92                  needsQuotes = true;
93                  break;
94              case '\r':
95                  // drop \r characters: \\r gives "invalid escape sequence"
96                  continue;
97              case '\n':
98                  // replace \n characters with \\n sequence
99                  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(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 (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                     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(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             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(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 loggerContextName 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(String contextName, 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.getName(), cfg);
324             register(mbs, mbean, mbean.getObjectName());
325 
326             if (cfg instanceof AsyncLoggerConfig) {
327                 AsyncLoggerConfig async = (AsyncLoggerConfig) cfg;
328                 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                 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(MBeanServer mbs, Object mbean, ObjectName objectName)
353             throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
354         LOGGER.debug("Registering MBean {}", objectName);
355         mbs.registerMBean(mbean, objectName);
356     }
357 }