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