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 */ 017 package org.apache.logging.log4j.core.jmx; 018 019 import java.lang.management.ManagementFactory; 020 import java.util.List; 021 import java.util.Map; 022 import java.util.Set; 023 import java.util.concurrent.Executor; 024 import java.util.concurrent.Executors; 025 026 import javax.management.InstanceAlreadyExistsException; 027 import javax.management.MBeanRegistrationException; 028 import javax.management.MBeanServer; 029 import javax.management.NotCompliantMBeanException; 030 import javax.management.ObjectName; 031 032 import org.apache.logging.log4j.LogManager; 033 import org.apache.logging.log4j.core.Appender; 034 import org.apache.logging.log4j.core.LoggerContext; 035 import org.apache.logging.log4j.core.appender.AsyncAppender; 036 import org.apache.logging.log4j.core.async.AsyncLogger; 037 import org.apache.logging.log4j.core.async.AsyncLoggerConfig; 038 import org.apache.logging.log4j.core.async.AsyncLoggerContext; 039 import org.apache.logging.log4j.core.config.LoggerConfig; 040 import org.apache.logging.log4j.core.impl.Log4jContextFactory; 041 import org.apache.logging.log4j.core.selector.ContextSelector; 042 import org.apache.logging.log4j.spi.LoggerContextFactory; 043 import org.apache.logging.log4j.status.StatusLogger; 044 import org.apache.logging.log4j.util.PropertiesUtil; 045 046 /** 047 * Creates MBeans to instrument various classes in the log4j class hierarchy. 048 * <p> 049 * All instrumentation for Log4j 2 classes can be disabled by setting system 050 * property {@code -Dlog4j2.disable.jmx=true}. 051 * </p> 052 */ 053 public final class Server { 054 055 /** 056 * The domain part, or prefix ({@value} ) of the {@code ObjectName} of all 057 * 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 StatusLogger LOGGER = StatusLogger.getLogger(); 062 static final Executor executor = Executors.newFixedThreadPool(1); 063 064 private Server() { 065 } 066 067 /** 068 * Either returns the specified name as is, or returns a quoted value 069 * containing the specified name with the special characters (comma, equals, 070 * colon, quote, asterisk, or question mark) preceded with a backslash. 071 * 072 * @param name the name to escape so it can be used as a value in an 073 * {@link ObjectName}. 074 * @return the escaped name 075 */ 076 public static String escape(final String name) { 077 final StringBuilder sb = new StringBuilder(name.length() * 2); 078 boolean needsQuotes = false; 079 for (int i = 0; i < name.length(); i++) { 080 final char c = name.charAt(i); 081 switch (c) { 082 case '\\': 083 case '*': 084 case '?': 085 case '\"': 086 // quote, star, question & backslash must be escaped 087 sb.append('\\'); 088 needsQuotes = true; // ... and can only appear in quoted value 089 break; 090 case ',': 091 case '=': 092 case ':': 093 // no need to escape these, but value must be quoted 094 needsQuotes = true; 095 break; 096 case '\r': 097 // drop \r characters: \\r gives "invalid escape sequence" 098 continue; 099 case '\n': 100 // replace \n characters with \\n sequence 101 sb.append("\\n"); 102 needsQuotes = true; 103 continue; 104 } 105 sb.append(c); 106 } 107 if (needsQuotes) { 108 sb.insert(0, '\"'); 109 sb.append('\"'); 110 } 111 return sb.toString(); 112 } 113 114 public static void reregisterMBeansAfterReconfigure() { 115 // avoid creating Platform MBean Server if JMX disabled 116 if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) { 117 LOGGER.debug("JMX disabled for log4j2. Not registering MBeans."); 118 return; 119 } 120 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 121 reregisterMBeansAfterReconfigure(mbs); 122 } 123 124 public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) { 125 if (PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX)) { 126 LOGGER.debug("JMX disabled for log4j2. Not registering MBeans."); 127 return; 128 } 129 130 // now provide instrumentation for the newly configured 131 // LoggerConfigs and Appenders 132 try { 133 final ContextSelector selector = getContextSelector(); 134 if (selector == null) { 135 LOGGER.debug("Could not register MBeans: no ContextSelector found."); 136 return; 137 } 138 final List<LoggerContext> contexts = selector.getLoggerContexts(); 139 for (final LoggerContext ctx : contexts) { 140 // first unregister the context and all nested loggers, 141 // appenders, statusLogger, contextSelector, ringbuffers... 142 unregisterLoggerContext(ctx.getName(), mbs); 143 144 final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor); 145 register(mbs, mbean, mbean.getObjectName()); 146 147 if (ctx instanceof AsyncLoggerContext) { 148 final RingBufferAdmin rbmbean = AsyncLogger.createRingBufferAdmin(ctx.getName()); 149 register(mbs, rbmbean, rbmbean.getObjectName()); 150 } 151 152 // register the status logger and the context selector 153 // repeatedly 154 // for each known context: if one context is unregistered, 155 // these MBeans should still be available for the other 156 // contexts. 157 registerStatusLogger(ctx.getName(), mbs, executor); 158 registerContextSelector(ctx.getName(), selector, mbs, executor); 159 160 registerLoggerConfigs(ctx, mbs, executor); 161 registerAppenders(ctx, mbs, executor); 162 } 163 } catch (final Exception ex) { 164 LOGGER.error("Could not register mbeans", ex); 165 } 166 } 167 168 /** 169 * Unregister all log4j MBeans from the platform MBean server. 170 */ 171 public static void unregisterMBeans() { 172 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 173 unregisterMBeans(mbs); 174 } 175 176 /** 177 * Unregister all log4j MBeans from the specified MBean server. 178 * 179 * @param mbs the MBean server to unregister from. 180 */ 181 public static void unregisterMBeans(final MBeanServer mbs) { 182 unregisterStatusLogger("*", mbs); 183 unregisterContextSelector("*", mbs); 184 unregisterContexts(mbs); 185 unregisterLoggerConfigs("*", mbs); 186 unregisterAsyncLoggerRingBufferAdmins("*", mbs); 187 unregisterAsyncLoggerConfigRingBufferAdmins("*", mbs); 188 unregisterAppenders("*", mbs); 189 unregisterAsyncAppenders("*", mbs); 190 } 191 192 /** 193 * Returns the {@code ContextSelector} of the current 194 * {@code Log4jContextFactory}. 195 * 196 * @return the {@code ContextSelector} of the current 197 * {@code Log4jContextFactory} 198 */ 199 private static ContextSelector getContextSelector() { 200 final LoggerContextFactory factory = LogManager.getFactory(); 201 if (factory instanceof Log4jContextFactory) { 202 final ContextSelector selector = ((Log4jContextFactory) factory).getSelector(); 203 return selector; 204 } 205 return null; 206 } 207 208 /** 209 * Unregisters all MBeans associated with the specified logger context 210 * (including MBeans for {@code LoggerConfig}s and {@code Appender}s from 211 * the platform MBean server. 212 * 213 * @param loggerContextName name of the logger context to unregister 214 */ 215 public static void unregisterLoggerContext(final String loggerContextName) { 216 final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 217 unregisterLoggerContext(loggerContextName, mbs); 218 } 219 220 /** 221 * Unregisters all MBeans associated with the specified logger context 222 * (including MBeans for {@code LoggerConfig}s and {@code Appender}s from 223 * the platform MBean server. 224 * 225 * @param contextName name of the logger context to unregister 226 * @param mbs the MBean Server to unregister the instrumented objects from 227 */ 228 public static void unregisterLoggerContext(final String contextName, final MBeanServer mbs) { 229 final String pattern = LoggerContextAdminMBean.PATTERN; 230 final String search = String.format(pattern, escape(contextName), "*"); 231 unregisterAllMatching(search, mbs); // unregister context mbean 232 233 // now unregister all MBeans associated with this logger context 234 unregisterStatusLogger(contextName, mbs); 235 unregisterContextSelector(contextName, mbs); 236 unregisterLoggerConfigs(contextName, mbs); 237 unregisterAppenders(contextName, mbs); 238 unregisterAsyncAppenders(contextName, mbs); 239 unregisterAsyncLoggerRingBufferAdmins(contextName, mbs); 240 unregisterAsyncLoggerConfigRingBufferAdmins(contextName, mbs); 241 } 242 243 private static void registerStatusLogger(final String contextName, final MBeanServer mbs, final Executor executor) 244 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 245 246 final StatusLoggerAdmin mbean = new StatusLoggerAdmin(contextName, executor); 247 register(mbs, mbean, mbean.getObjectName()); 248 } 249 250 private static void registerContextSelector(final String contextName, final ContextSelector selector, 251 final MBeanServer mbs, final Executor executor) throws InstanceAlreadyExistsException, 252 MBeanRegistrationException, NotCompliantMBeanException { 253 254 final ContextSelectorAdmin mbean = new ContextSelectorAdmin(contextName, selector); 255 register(mbs, mbean, mbean.getObjectName()); 256 } 257 258 private static void unregisterStatusLogger(final String contextName, final MBeanServer mbs) { 259 final String pattern = StatusLoggerAdminMBean.PATTERN; 260 final String search = String.format(pattern, escape(contextName), "*"); 261 unregisterAllMatching(search, mbs); 262 } 263 264 private static void unregisterContextSelector(final String contextName, final MBeanServer mbs) { 265 final String pattern = ContextSelectorAdminMBean.PATTERN; 266 final String search = String.format(pattern, escape(contextName), "*"); 267 unregisterAllMatching(search, mbs); 268 } 269 270 private static void unregisterLoggerConfigs(final String contextName, final MBeanServer mbs) { 271 final String pattern = LoggerConfigAdminMBean.PATTERN; 272 final String search = String.format(pattern, escape(contextName), "*"); 273 unregisterAllMatching(search, mbs); 274 } 275 276 private static void unregisterContexts(final MBeanServer mbs) { 277 final String pattern = LoggerContextAdminMBean.PATTERN; 278 final String search = String.format(pattern, "*"); 279 unregisterAllMatching(search, mbs); 280 } 281 282 private static void unregisterAppenders(final String contextName, final MBeanServer mbs) { 283 final String pattern = AppenderAdminMBean.PATTERN; 284 final String search = String.format(pattern, escape(contextName), "*"); 285 unregisterAllMatching(search, mbs); 286 } 287 288 private static void unregisterAsyncAppenders(final String contextName, final MBeanServer mbs) { 289 final String pattern = AsyncAppenderAdminMBean.PATTERN; 290 final String search = String.format(pattern, escape(contextName), "*"); 291 unregisterAllMatching(search, mbs); 292 } 293 294 private static void unregisterAsyncLoggerRingBufferAdmins(final String contextName, final MBeanServer mbs) { 295 final String pattern1 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER; 296 final String search1 = String.format(pattern1, escape(contextName)); 297 unregisterAllMatching(search1, mbs); 298 } 299 300 private static void unregisterAsyncLoggerConfigRingBufferAdmins(final String contextName, final MBeanServer mbs) { 301 final String pattern2 = RingBufferAdminMBean.PATTERN_ASYNC_LOGGER_CONFIG; 302 final String search2 = String.format(pattern2, escape(contextName), "*"); 303 unregisterAllMatching(search2, mbs); 304 } 305 306 private static void unregisterAllMatching(final String search, final MBeanServer mbs) { 307 try { 308 final ObjectName pattern = new ObjectName(search); 309 final Set<ObjectName> found = mbs.queryNames(pattern, null); 310 for (final ObjectName objectName : found) { 311 LOGGER.debug("Unregistering MBean {}", objectName); 312 mbs.unregisterMBean(objectName); 313 } 314 } catch (final Exception ex) { 315 LOGGER.error("Could not unregister MBeans for " + search, ex); 316 } 317 } 318 319 private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor) 320 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 321 322 final Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers(); 323 for (final String name : map.keySet()) { 324 final LoggerConfig cfg = map.get(name); 325 final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg); 326 register(mbs, mbean, mbean.getObjectName()); 327 328 if (cfg instanceof AsyncLoggerConfig) { 329 final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg; 330 final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName()); 331 register(mbs, rbmbean, rbmbean.getObjectName()); 332 } 333 } 334 } 335 336 private static void registerAppenders(final LoggerContext ctx, final MBeanServer mbs, final Executor executor) 337 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 338 339 final Map<String, Appender> map = ctx.getConfiguration().getAppenders(); 340 for (final String name : map.keySet()) { 341 final Appender appender = map.get(name); 342 343 if (appender instanceof AsyncAppender) { 344 final AsyncAppender async = ((AsyncAppender) appender); 345 final AsyncAppenderAdmin mbean = new AsyncAppenderAdmin(ctx.getName(), async); 346 register(mbs, mbean, mbean.getObjectName()); 347 } else { 348 final AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender); 349 register(mbs, mbean, mbean.getObjectName()); 350 } 351 } 352 } 353 354 private static void register(final MBeanServer mbs, final Object mbean, final ObjectName objectName) 355 throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { 356 LOGGER.debug("Registering MBean {}", objectName); 357 mbs.registerMBean(mbean, objectName); 358 } 359 }