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