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.lang.management.ManagementFactory;
020 import java.util.List;
021 import java.util.Map;
022 import java.util.Set;
023 import java.util.concurrent.Executor;
024 import java.util.concurrent.ExecutorService;
025 import java.util.concurrent.Executors;
026
027 import javax.management.InstanceAlreadyExistsException;
028 import javax.management.MBeanRegistrationException;
029 import javax.management.MBeanServer;
030 import javax.management.NotCompliantMBeanException;
031 import javax.management.ObjectName;
032
033 import org.apache.logging.log4j.LogManager;
034 import org.apache.logging.log4j.core.Appender;
035 import org.apache.logging.log4j.core.LoggerContext;
036 import org.apache.logging.log4j.core.appender.AsyncAppender;
037 import org.apache.logging.log4j.core.async.AsyncLogger;
038 import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
039 import org.apache.logging.log4j.core.async.AsyncLoggerContext;
040 import org.apache.logging.log4j.core.async.DaemonThreadFactory;
041 import org.apache.logging.log4j.core.config.LoggerConfig;
042 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
043 import org.apache.logging.log4j.core.selector.ContextSelector;
044 import org.apache.logging.log4j.core.util.Loader;
045 import org.apache.logging.log4j.spi.LoggerContextFactory;
046 import org.apache.logging.log4j.status.StatusLogger;
047 import org.apache.logging.log4j.util.PropertiesUtil;
048
049 /**
050 * Creates MBeans to instrument various classes in the log4j class hierarchy.
051 * <p>
052 * All instrumentation for Log4j 2 classes can be disabled by setting system property {@code -Dlog4j2.disable.jmx=true}.
053 * </p>
054 */
055 public final class Server {
056
057 /**
058 * The domain part, or prefix ({@value} ) of the {@code ObjectName} of all MBeans that instrument Log4J2 components.
059 */
060 public static final String DOMAIN = "org.apache.logging.log4j2";
061 private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx";
062 private static final String PROPERTY_ASYNC_NOTIF = "log4j2.jmx.notify.async";
063 private static final String THREAD_NAME_PREFIX = "log4j2.jmx.notif";
064 private static final StatusLogger LOGGER = StatusLogger.getLogger();
065 static final Executor executor = createExecutor();
066
067 private Server() {
068 }
069
070 /**
071 * Returns either a {@code null} Executor (causing JMX notifications to be sent from the caller thread) or a daemon
072 * background thread Executor, depending on the value of system property "log4j2.jmx.notify.async". If this
073 * property is not set, use a {@code null} Executor for web apps to avoid memory leaks and other issues when the
074 * web app is restarted.
075 * @see LOG4J2-938
076 */
077 private static ExecutorService createExecutor() {
078 boolean defaultAsync = !isWebApp();
079 boolean async = PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_ASYNC_NOTIF, defaultAsync);
080 return async ? Executors.newFixedThreadPool(1, new DaemonThreadFactory(THREAD_NAME_PREFIX)) : null;
081 }
082
083 /**
084 * Returns {@code true} if we think we are running in a web container, based on the presence of the
085 * {@code javax.servlet.Servlet} class in the classpath.
086 */
087 private static boolean isWebApp() {
088 return Loader.isClassAvailable("javax.servlet.Servlet");
089 }
090
091 /**
092 * Either returns the specified name as is, or returns a quoted value containing the specified name with the special
093 * characters (comma, equals, colon, quote, asterisk, or question mark) preceded with a backslash.
094 *
095 * @param name the name to escape so it can be used as a value in an {@link ObjectName}.
096 * @return the escaped name
097 */
098 public static String escape(final String name) {
099 final StringBuilder sb = new StringBuilder(name.length() * 2);
100 boolean needsQuotes = false;
101 for (int i = 0; i < name.length(); i++) {
102 final char c = name.charAt(i);
103 switch (c) {
104 case '\\':
105 case '*':
106 case '?':
107 case '\"':
108 // quote, star, question & backslash must be escaped
109 sb.append('\\');
110 needsQuotes = true; // ... and can only appear in quoted value
111 break;
112 case ',':
113 case '=':
114 case ':':
115 // no need to escape these, but value must be quoted
116 needsQuotes = true;
117 break;
118 case '\r':
119 // drop \r characters: \\r gives "invalid escape sequence"
120 continue;
121 case '\n':
122 // replace \n characters with \\n sequence
123 sb.append("\\n");
124 needsQuotes = true;
125 continue;
126 }
127 sb.append(c);
128 }
129 if (needsQuotes) {
130 sb.insert(0, '\"');
131 sb.append('\"');
132 }
133 return sb.toString();
134 }
135
136 public static void reregisterMBeansAfterReconfigure() {
137 // avoid creating Platform MBean Server if JMX disabled
138 if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) {
139 LOGGER.debug("JMX disabled for log4j2. Not registering MBeans.");
140 return;
141 }
142 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
143 reregisterMBeansAfterReconfigure(mbs);
144 }
145
146 public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) {
147 if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) {
148 LOGGER.debug("JMX disabled for log4j2. Not registering MBeans.");
149 return;
150 }
151
152 // now provide instrumentation for the newly configured
153 // LoggerConfigs and Appenders
154 try {
155 final ContextSelector selector = getContextSelector();
156 if (selector == null) {
157 LOGGER.debug("Could not register MBeans: no ContextSelector found.");
158 return;
159 }
160 final List<LoggerContext> contexts = selector.getLoggerContexts();
161 for (final LoggerContext ctx : contexts) {
162 // first unregister the context and all nested loggers,
163 // appenders, statusLogger, contextSelector, ringbuffers...
164 unregisterLoggerContext(ctx.getName(), mbs);
165
166 final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor);
167 register(mbs, mbean, mbean.getObjectName());
168
169 if (ctx instanceof AsyncLoggerContext) {
170 final RingBufferAdmin rbmbean = AsyncLogger.createRingBufferAdmin(ctx.getName());
171 register(mbs, rbmbean, rbmbean.getObjectName());
172 }
173
174 // register the status logger and the context selector
175 // repeatedly
176 // for each known context: if one context is unregistered,
177 // these MBeans should still be available for the other
178 // contexts.
179 registerStatusLogger(ctx.getName(), mbs, executor);
180 registerContextSelector(ctx.getName(), selector, mbs, executor);
181
182 registerLoggerConfigs(ctx, mbs, executor);
183 registerAppenders(ctx, mbs, executor);
184 }
185 } catch (final Exception ex) {
186 LOGGER.error("Could not register mbeans", ex);
187 }
188 }
189
190 /**
191 * Unregister all log4j MBeans from the platform MBean server.
192 */
193 public static void unregisterMBeans() {
194 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
195 unregisterMBeans(mbs);
196 }
197
198 /**
199 * Unregister all log4j MBeans from the specified MBean server.
200 *
201 * @param mbs the MBean server to unregister from.
202 */
203 public static void unregisterMBeans(final MBeanServer mbs) {
204 unregisterStatusLogger("*", mbs);
205 unregisterContextSelector("*", mbs);
206 unregisterContexts(mbs);
207 unregisterLoggerConfigs("*", mbs);
208 unregisterAsyncLoggerRingBufferAdmins("*", mbs);
209 unregisterAsyncLoggerConfigRingBufferAdmins("*", mbs);
210 unregisterAppenders("*", mbs);
211 unregisterAsyncAppenders("*", mbs);
212 }
213
214 /**
215 * Returns the {@code ContextSelector} of the current {@code Log4jContextFactory}.
216 *
217 * @return the {@code ContextSelector} of the current {@code Log4jContextFactory}
218 */
219 private static ContextSelector getContextSelector() {
220 final LoggerContextFactory factory = LogManager.getFactory();
221 if (factory instanceof Log4jContextFactory) {
222 final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
223 return selector;
224 }
225 return null;
226 }
227
228 /**
229 * Unregisters all MBeans associated with the specified logger context (including MBeans for {@code LoggerConfig}s
230 * and {@code Appender}s from the platform MBean server.
231 *
232 * @param loggerContextName name of the logger context to unregister
233 */
234 public static void unregisterLoggerContext(final String loggerContextName) {
235 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
236 unregisterLoggerContext(loggerContextName, mbs);
237 }
238
239 /**
240 * Unregisters all MBeans associated with the specified logger context (including MBeans for {@code LoggerConfig}s
241 * and {@code Appender}s from the platform MBean server.
242 *
243 * @param contextName name of the logger context to unregister
244 * @param mbs the MBean Server to unregister the instrumented objects from
245 */
246 public static void unregisterLoggerContext(final String contextName, final MBeanServer mbs) {
247 final String pattern = LoggerContextAdminMBean.PATTERN;
248 final String search = String.format(pattern, escape(contextName), "*");
249 unregisterAllMatching(search, mbs); // unregister context mbean
250
251 // now unregister all MBeans associated with this logger context
252 unregisterStatusLogger(contextName, mbs);
253 unregisterContextSelector(contextName, mbs);
254 unregisterLoggerConfigs(contextName, mbs);
255 unregisterAppenders(contextName, mbs);
256 unregisterAsyncAppenders(contextName, mbs);
257 unregisterAsyncLoggerRingBufferAdmins(contextName, mbs);
258 unregisterAsyncLoggerConfigRingBufferAdmins(contextName, mbs);
259 }
260
261 private static void registerStatusLogger(final String contextName, final MBeanServer mbs, final Executor executor)
262 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
263
264 final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor);
265 register(mbs, mbean, mbean.getObjectName());
266 }
267
268 private static void registerContextSelector(final String contextName, final ContextSelector selector,
269 final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException,
270 MBeanRegistrationException, NotCompliantMBeanException {
271
272 final ContextSelectorAdmin mbean = new ContextSelectorAdmin(contextName, selector);
273 register(mbs, mbean, mbean.getObjectName());
274 }
275
276 private static void unregisterStatusLogger(final String contextName, final MBeanServer mbs) {
277 final String pattern = StatusLoggerAdminMBean.PATTERN;
278 final String search = String.format(pattern, escape(contextName), "*");
279 unregisterAllMatching(search, mbs);
280 }
281
282 private static void unregisterContextSelector(final String contextName, final MBeanServer mbs) {
283 final String pattern = ContextSelectorAdminMBean.PATTERN;
284 final String search = String.format(pattern, escape(contextName), "*");
285 unregisterAllMatching(search, mbs);
286 }
287
288 private static void unregisterLoggerConfigs(final String contextName, final MBeanServer mbs) {
289 final String pattern = LoggerConfigAdminMBean.PATTERN;
290 final String search = String.format(pattern, escape(contextName), "*");
291 unregisterAllMatching(search, mbs);
292 }
293
294 private static void unregisterContexts(final MBeanServer mbs) {
295 final String pattern = LoggerContextAdminMBean.PATTERN;
296 final String search = String.format(pattern, "*");
297 unregisterAllMatching(search, mbs);
298 }
299
300 private static void unregisterAppenders(final String contextName, final MBeanServer mbs) {
301 final String pattern = AppenderAdminMBean.PATTERN;
302 final String search = String.format(pattern, escape(contextName), "*");
303 unregisterAllMatching(search, mbs);
304 }
305
306 private static void unregisterAsyncAppenders(final String contextName, final MBeanServer mbs) {
307 final String pattern = AsyncAppenderAdminMBean.PATTERN;
308 final String search = String.format(pattern, escape(contextName), "*");
309 unregisterAllMatching(search, mbs);
310 }
311
312 private static void unregisterAsyncLoggerRingBufferAdmins(final String contextName, final MBeanServer mbs) {
313 final String pattern1 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER;
314 final String search1 = String.format(pattern1, escape(contextName));
315 unregisterAllMatching(search1, mbs);
316 }
317
318 private static void unregisterAsyncLoggerConfigRingBufferAdmins(final String contextName, final MBeanServer mbs) {
319 final String pattern2 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER_CONFIG;
320 final String search2 = String.format(pattern2, escape(contextName), "*");
321 unregisterAllMatching(search2, mbs);
322 }
323
324 private static void unregisterAllMatching(final String search, final MBeanServer mbs) {
325 try {
326 final ObjectName pattern = new ObjectName(search);
327 final Set<ObjectName> found = mbs.queryNames(pattern, null);
328 for (final ObjectName objectName : found) {
329 LOGGER.debug("Unregistering MBean {}", objectName);
330 mbs.unregisterMBean(objectName);
331 }
332 } catch (final Exception ex) {
333 LOGGER.error("Could not unregister MBeans for " + search, ex);
334 }
335 }
336
337 private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
338 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
339
340 final Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers();
341 for (final String name : map.keySet()) {
342 final LoggerConfig cfg = map.get(name);
343 final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg);
344 register(mbs, mbean, mbean.getObjectName());
345
346 if (cfg instanceof AsyncLoggerConfig) {
347 final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg;
348 final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName());
349 register(mbs, rbmbean, rbmbean.getObjectName());
350 }
351 }
352 }
353
354 private static void registerAppenders(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
355 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
356
357 final Map<String, Appender> map = ctx.getConfiguration().getAppenders();
358 for (final String name : map.keySet()) {
359 final Appender appender = map.get(name);
360
361 if (appender instanceof AsyncAppender) {
362 final AsyncAppender async = ((AsyncAppender) appender);
363 final AsyncAppenderAdmin mbean = new AsyncAppenderAdmin(ctx.getName(), async);
364 register(mbs, mbean, mbean.getObjectName());
365 } else {
366 final AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender);
367 register(mbs, mbean, mbean.getObjectName());
368 }
369 }
370 }
371
372 private static void register(final MBeanServer mbs, final Object mbean, final ObjectName objectName)
373 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
374 LOGGER.debug("Registering MBean {}", objectName);
375 mbs.registerMBean(mbean, objectName);
376 }
377 }