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