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