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.AsyncLogger; 038import org.apache.logging.log4j.core.async.AsyncLoggerConfig; 039import org.apache.logging.log4j.core.async.AsyncLoggerContext; 040import org.apache.logging.log4j.core.async.DaemonThreadFactory; 041import org.apache.logging.log4j.core.config.LoggerConfig; 042import org.apache.logging.log4j.core.impl.Log4jContextFactory; 043import org.apache.logging.log4j.core.selector.ContextSelector; 044import org.apache.logging.log4j.core.util.Loader; 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 = "log4j2.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 = !isWebApp(); 079 final 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 private static boolean isJmxDisabled() { 137 return PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX); 138 } 139 140 public static void reregisterMBeansAfterReconfigure() { 141 // avoid creating Platform MBean Server if JMX disabled 142 if (isJmxDisabled()) { 143 LOGGER.debug("JMX disabled for log4j2. Not registering MBeans."); 144 return; 145 } 146 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 147 reregisterMBeansAfterReconfigure(mbs); 148 } 149 150 public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) { 151 if (isJmxDisabled()) { 152 LOGGER.debug("JMX disabled for log4j2. Not registering MBeans."); 153 return; 154 } 155 156 // now provide instrumentation for the newly configured 157 // LoggerConfigs and Appenders 158 try { 159 final ContextSelector selector = getContextSelector(); 160 if (selector == null) { 161 LOGGER.debug("Could not register MBeans: no ContextSelector found."); 162 return; 163 } 164 final List<LoggerContext> contexts = selector.getLoggerContexts(); 165 for (final LoggerContext ctx : contexts) { 166 // first unregister the context and all nested loggers, 167 // appenders, statusLogger, contextSelector, ringbuffers... 168 unregisterLoggerContext(ctx.getName(), mbs); 169 170 final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor); 171 register(mbs, mbean, mbean.getObjectName()); 172 173 if (ctx instanceof AsyncLoggerContext) { 174 final RingBufferAdmin rbmbean = AsyncLogger.createRingBufferAdmin(ctx.getName()); 175 register(mbs, rbmbean, rbmbean.getObjectName()); 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 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 199 unregisterMBeans(mbs); 200 } 201 202 /** 203 * Unregister all log4j MBeans from the specified MBean server. 204 * 205 * @param mbs the MBean server to unregister from. 206 */ 207 public static void unregisterMBeans(final MBeanServer mbs) { 208 unregisterStatusLogger("*", mbs); 209 unregisterContextSelector("*", mbs); 210 unregisterContexts(mbs); 211 unregisterLoggerConfigs("*", mbs); 212 unregisterAsyncLoggerRingBufferAdmins("*", mbs); 213 unregisterAsyncLoggerConfigRingBufferAdmins("*", mbs); 214 unregisterAppenders("*", mbs); 215 unregisterAsyncAppenders("*", mbs); 216 } 217 218 /** 219 * Returns the {@code ContextSelector} of the current {@code Log4jContextFactory}. 220 * 221 * @return the {@code ContextSelector} of the current {@code Log4jContextFactory} 222 */ 223 private static ContextSelector getContextSelector() { 224 final LoggerContextFactory factory = LogManager.getFactory(); 225 if (factory instanceof Log4jContextFactory) { 226 final ContextSelector selector = ((Log4jContextFactory) factory).getSelector(); 227 return selector; 228 } 229 return null; 230 } 231 232 /** 233 * Unregisters all MBeans associated with the specified logger context (including MBeans for {@code LoggerConfig}s 234 * and {@code Appender}s from the platform MBean server. 235 * 236 * @param loggerContextName name of the logger context to unregister 237 */ 238 public static void unregisterLoggerContext(final String loggerContextName) { 239 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 240 unregisterLoggerContext(loggerContextName, mbs); 241 } 242 243 /** 244 * Unregisters all MBeans associated with the specified logger context (including MBeans for {@code LoggerConfig}s 245 * and {@code Appender}s from the platform MBean server. 246 * 247 * @param contextName name of the logger context to unregister 248 * @param mbs the MBean Server to unregister the instrumented objects from 249 */ 250 public static void unregisterLoggerContext(final String contextName, final MBeanServer mbs) { 251 final String pattern = LoggerContextAdminMBean.PATTERN; 252 final String search = String.format(pattern, escape(contextName), "*"); 253 unregisterAllMatching(search, mbs); // unregister context mbean 254 255 // now unregister all MBeans associated with this logger context 256 unregisterStatusLogger(contextName, mbs); 257 unregisterContextSelector(contextName, mbs); 258 unregisterLoggerConfigs(contextName, mbs); 259 unregisterAppenders(contextName, mbs); 260 unregisterAsyncAppenders(contextName, mbs); 261 unregisterAsyncLoggerRingBufferAdmins(contextName, mbs); 262 unregisterAsyncLoggerConfigRingBufferAdmins(contextName, mbs); 263 } 264 265 private static void registerStatusLogger(final String contextName, final MBeanServer mbs, final Executor executor) 266 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 267 268 final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor); 269 register(mbs, mbean, mbean.getObjectName()); 270 } 271 272 private static void registerContextSelector(final String contextName, final ContextSelector selector, 273 final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException, 274 MBeanRegistrationException, NotCompliantMBeanException { 275 276 final ContextSelectorAdmin mbean = new ContextSelectorAdmin(contextName, selector); 277 register(mbs, mbean, mbean.getObjectName()); 278 } 279 280 private static void unregisterStatusLogger(final String contextName, final MBeanServer mbs) { 281 final String pattern = StatusLoggerAdminMBean.PATTERN; 282 final String search = String.format(pattern, escape(contextName), "*"); 283 unregisterAllMatching(search, mbs); 284 } 285 286 private static void unregisterContextSelector(final String contextName, final MBeanServer mbs) { 287 final String pattern = ContextSelectorAdminMBean.PATTERN; 288 final String search = String.format(pattern, escape(contextName), "*"); 289 unregisterAllMatching(search, mbs); 290 } 291 292 private static void unregisterLoggerConfigs(final String contextName, final MBeanServer mbs) { 293 final String pattern = LoggerConfigAdminMBean.PATTERN; 294 final String search = String.format(pattern, escape(contextName), "*"); 295 unregisterAllMatching(search, mbs); 296 } 297 298 private static void unregisterContexts(final MBeanServer mbs) { 299 final String pattern = LoggerContextAdminMBean.PATTERN; 300 final String search = String.format(pattern, "*"); 301 unregisterAllMatching(search, mbs); 302 } 303 304 private static void unregisterAppenders(final String contextName, final MBeanServer mbs) { 305 final String pattern = AppenderAdminMBean.PATTERN; 306 final String search = String.format(pattern, escape(contextName), "*"); 307 unregisterAllMatching(search, mbs); 308 } 309 310 private static void unregisterAsyncAppenders(final String contextName, final MBeanServer mbs) { 311 final String pattern = AsyncAppenderAdminMBean.PATTERN; 312 final String search = String.format(pattern, escape(contextName), "*"); 313 unregisterAllMatching(search, mbs); 314 } 315 316 private static void unregisterAsyncLoggerRingBufferAdmins(final String contextName, final MBeanServer mbs) { 317 final String pattern1 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER; 318 final String search1 = String.format(pattern1, escape(contextName)); 319 unregisterAllMatching(search1, mbs); 320 } 321 322 private static void unregisterAsyncLoggerConfigRingBufferAdmins(final String contextName, final MBeanServer mbs) { 323 final String pattern2 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER_CONFIG; 324 final String search2 = String.format(pattern2, escape(contextName), "*"); 325 unregisterAllMatching(search2, mbs); 326 } 327 328 private static void unregisterAllMatching(final String search, final MBeanServer mbs) { 329 try { 330 final ObjectName pattern = new ObjectName(search); 331 final Set<ObjectName> found = mbs.queryNames(pattern, null); 332 for (final ObjectName objectName : found) { 333 LOGGER.debug("Unregistering MBean {}", objectName); 334 mbs.unregisterMBean(objectName); 335 } 336 } catch (final Exception ex) { 337 LOGGER.error("Could not unregister MBeans for " + search, ex); 338 } 339 } 340 341 private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor) 342 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 343 344 final Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers(); 345 for (final String name : map.keySet()) { 346 final LoggerConfig cfg = map.get(name); 347 final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg); 348 register(mbs, mbean, mbean.getObjectName()); 349 350 if (cfg instanceof AsyncLoggerConfig) { 351 final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg; 352 final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName()); 353 register(mbs, rbmbean, rbmbean.getObjectName()); 354 } 355 } 356 } 357 358 private static void registerAppenders(final LoggerContext ctx, final MBeanServer mbs, final Executor executor) 359 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 360 361 final Map<String, Appender> map = ctx.getConfiguration().getAppenders(); 362 for (final String name : map.keySet()) { 363 final Appender appender = map.get(name); 364 365 if (appender instanceof AsyncAppender) { 366 final AsyncAppender async = ((AsyncAppender) appender); 367 final AsyncAppenderAdmin mbean = new AsyncAppenderAdmin(ctx.getName(), async); 368 register(mbs, mbean, mbean.getObjectName()); 369 } else { 370 final AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender); 371 register(mbs, mbean, mbean.getObjectName()); 372 } 373 } 374 } 375 376 private static void register(final MBeanServer mbs, final Object mbean, final ObjectName objectName) 377 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 378 LOGGER.debug("Registering MBean {}", objectName); 379 mbs.registerMBean(mbean, objectName); 380 } 381}