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.beans.PropertyChangeEvent;
20  import java.beans.PropertyChangeListener;
21  import java.lang.management.ManagementFactory;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.concurrent.Executor;
26  import java.util.concurrent.Executors;
27  
28  import javax.management.InstanceAlreadyExistsException;
29  import javax.management.JMException;
30  import javax.management.MBeanRegistrationException;
31  import javax.management.MBeanServer;
32  import javax.management.MalformedObjectNameException;
33  import javax.management.NotCompliantMBeanException;
34  import javax.management.ObjectName;
35  
36  import org.apache.logging.log4j.core.Appender;
37  import org.apache.logging.log4j.core.LoggerContext;
38  import org.apache.logging.log4j.core.config.LoggerConfig;
39  import org.apache.logging.log4j.core.selector.ContextSelector;
40  import org.apache.logging.log4j.status.StatusLogger;
41  
42  /**
43   * Creates MBeans to instrument various classes in the log4j class hierarchy.
44   * <p>
45   * All instrumentation for Log4J2 classes can be disabled by setting system
46   * property {@code -Dlog4j2.disable.jmx=true}.
47   */
48  public class Server {
49  
50      private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
51  
52      /**
53       * Either returns the specified name as is, or returns a quoted value
54       * containing the specified name with the special characters (comma, equals,
55       * colon, quote, asterisk, or question mark) preceded with a backslash.
56       * 
57       * @param name
58       *            the name to escape so it can be used as a value in an
59       *            {@link ObjectName}.
60       * @return the escaped name
61       */
62      public static String escape(String name) {
63          StringBuilder sb = new StringBuilder(name.length() * 2);
64          boolean needsQuotes = false;
65          for (int i = 0; i < name.length(); i++) {
66              char c = name.charAt(i);
67              switch (c) {
68              case ',':
69              case '=':
70              case ':':
71              case '\\':
72              case '*':
73              case '?':
74                  sb.append('\\');
75                  needsQuotes = true;
76              }
77              sb.append(c);
78          }
79          if (needsQuotes) {
80              sb.insert(0, '\"');
81              sb.append('\"');
82          }
83          return sb.toString();
84      }
85  
86      /**
87       * Creates MBeans to instrument the specified selector and other classes in
88       * the log4j class hierarchy and registers the MBeans in the platform MBean
89       * server so they can be accessed by remote clients.
90       * 
91       * @param selector
92       *            starting point in the log4j class hierarchy
93       * @throws JMException
94       *             if a problem occurs during registration
95       */
96      public static void registerMBeans(ContextSelector selector)
97              throws JMException {
98  
99          // 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 }