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