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