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 final class Server {
49  
50      private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
51      
52      private Server() {
53      }
54  
55      /**
56       * Either returns the specified name as is, or returns a quoted value
57       * containing the specified name with the special characters (comma, equals,
58       * colon, quote, asterisk, or question mark) preceded with a backslash.
59       * 
60       * @param name
61       *            the name to escape so it can be used as a value in an
62       *            {@link ObjectName}.
63       * @return the escaped name
64       */
65      public static String escape(String name) {
66          StringBuilder sb = new StringBuilder(name.length() * 2);
67          boolean needsQuotes = false;
68          for (int i = 0; i < name.length(); i++) {
69              char c = name.charAt(i);
70              switch (c) {
71              case ',':
72              case '=':
73              case ':':
74              case '\\':
75              case '*':
76              case '?':
77                  sb.append('\\');
78                  needsQuotes = true;
79              }
80              sb.append(c);
81          }
82          if (needsQuotes) {
83              sb.insert(0, '\"');
84              sb.append('\"');
85          }
86          return sb.toString();
87      }
88  
89      /**
90       * Creates MBeans to instrument the specified selector and other classes in
91       * the log4j class hierarchy and registers the MBeans in the platform MBean
92       * server so they can be accessed by remote clients.
93       * 
94       * @param selector
95       *            starting point in the log4j class hierarchy
96       * @throws JMException
97       *             if a problem occurs during registration
98       */
99      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 }