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.beans.PropertyChangeEvent; 020 import java.beans.PropertyChangeListener; 021 import java.lang.management.ManagementFactory; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.Set; 025 import java.util.concurrent.Executor; 026 import java.util.concurrent.Executors; 027 028 import javax.management.InstanceAlreadyExistsException; 029 import javax.management.JMException; 030 import javax.management.MBeanRegistrationException; 031 import javax.management.MBeanServer; 032 import javax.management.MalformedObjectNameException; 033 import javax.management.NotCompliantMBeanException; 034 import javax.management.ObjectName; 035 036 import org.apache.logging.log4j.core.Appender; 037 import org.apache.logging.log4j.core.LoggerContext; 038 import org.apache.logging.log4j.core.config.LoggerConfig; 039 import org.apache.logging.log4j.core.selector.ContextSelector; 040 import org.apache.logging.log4j.status.StatusLogger; 041 042 /** 043 * Creates MBeans to instrument various classes in the log4j class hierarchy. 044 * <p> 045 * All instrumentation for Log4J2 classes can be disabled by setting system 046 * property {@code -Dlog4j2.disable.jmx=true}. 047 */ 048 public class Server { 049 050 private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx"; 051 052 /** 053 * Either returns the specified name as is, or returns a quoted value 054 * containing the specified name with the special characters (comma, equals, 055 * colon, quote, asterisk, or question mark) preceded with a backslash. 056 * 057 * @param name 058 * the name to escape so it can be used as a value in an 059 * {@link ObjectName}. 060 * @return the escaped name 061 */ 062 public static String escape(String name) { 063 StringBuilder sb = new StringBuilder(name.length() * 2); 064 boolean needsQuotes = false; 065 for (int i = 0; i < name.length(); i++) { 066 char c = name.charAt(i); 067 switch (c) { 068 case ',': 069 case '=': 070 case ':': 071 case '\\': 072 case '*': 073 case '?': 074 sb.append('\\'); 075 needsQuotes = true; 076 } 077 sb.append(c); 078 } 079 if (needsQuotes) { 080 sb.insert(0, '\"'); 081 sb.append('\"'); 082 } 083 return sb.toString(); 084 } 085 086 /** 087 * Creates MBeans to instrument the specified selector and other classes in 088 * the log4j class hierarchy and registers the MBeans in the platform MBean 089 * server so they can be accessed by remote clients. 090 * 091 * @param selector 092 * starting point in the log4j class hierarchy 093 * @throws JMException 094 * if a problem occurs during registration 095 */ 096 public static void registerMBeans(ContextSelector selector) 097 throws JMException { 098 099 // avoid creating Platform MBean Server if JMX disabled 100 if (Boolean.getBoolean(PROPERTY_DISABLE_JMX)) { 101 StatusLogger.getLogger().debug( 102 "JMX disabled for log4j2. Not registering MBeans."); 103 return; 104 } 105 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 106 registerMBeans(selector, mbs); 107 } 108 109 /** 110 * Creates MBeans to instrument the specified selector and other classes in 111 * the log4j class hierarchy and registers the MBeans in the specified MBean 112 * server so they can be accessed by remote clients. 113 * 114 * @param selector 115 * starting point in the log4j class hierarchy 116 * @param mbs 117 * the MBean Server to register the instrumented objects in 118 * @throws JMException 119 * if a problem occurs during registration 120 */ 121 public static void registerMBeans(ContextSelector selector, 122 final MBeanServer mbs) throws JMException { 123 124 if (Boolean.getBoolean(PROPERTY_DISABLE_JMX)) { 125 StatusLogger.getLogger().debug( 126 "JMX disabled for log4j2. Not registering MBeans."); 127 return; 128 } 129 final Executor executor = Executors.newFixedThreadPool(1); 130 registerStatusLogger(mbs, executor); 131 registerContextSelector(selector, mbs, executor); 132 133 List<LoggerContext> contexts = selector.getLoggerContexts(); 134 registerContexts(contexts, mbs, executor); 135 136 for (final LoggerContext context : contexts) { 137 context.addPropertyChangeListener(new PropertyChangeListener() { 138 139 @Override 140 public void propertyChange(PropertyChangeEvent evt) { 141 if (!LoggerContext.PROPERTY_CONFIG.equals(evt 142 .getPropertyName())) { 143 return; 144 } 145 // first unregister the MBeans that instrument the 146 // previous instrumented LoggerConfigs and Appenders 147 unregisterLoggerConfigs(context, mbs); 148 unregisterAppenders(context, mbs); 149 150 // now provide instrumentation for the newly configured 151 // LoggerConfigs and Appenders 152 try { 153 registerLoggerConfigs(context, mbs, executor); 154 registerAppenders(context, mbs, executor); 155 } catch (Exception ex) { 156 StatusLogger.getLogger().error( 157 "Could not register mbeans", ex); 158 } 159 } 160 }); 161 } 162 } 163 164 private static void registerStatusLogger(MBeanServer mbs, Executor executor) 165 throws MalformedObjectNameException, 166 InstanceAlreadyExistsException, MBeanRegistrationException, 167 NotCompliantMBeanException { 168 169 StatusLoggerAdmin mbean = new StatusLoggerAdmin(executor); 170 mbs.registerMBean(mbean, mbean.getObjectName()); 171 } 172 173 private static void registerContextSelector(ContextSelector selector, 174 MBeanServer mbs, Executor executor) 175 throws MalformedObjectNameException, 176 InstanceAlreadyExistsException, MBeanRegistrationException, 177 NotCompliantMBeanException { 178 179 ContextSelectorAdmin mbean = new ContextSelectorAdmin(selector); 180 mbs.registerMBean(mbean, mbean.getObjectName()); 181 } 182 183 private static void registerContexts(List<LoggerContext> contexts, 184 MBeanServer mbs, Executor executor) 185 throws MalformedObjectNameException, 186 InstanceAlreadyExistsException, MBeanRegistrationException, 187 NotCompliantMBeanException { 188 189 for (LoggerContext ctx : contexts) { 190 LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor); 191 mbs.registerMBean(mbean, mbean.getObjectName()); 192 } 193 } 194 195 private static void unregisterLoggerConfigs(LoggerContext context, 196 MBeanServer mbs) { 197 String pattern = LoggerConfigAdminMBean.PATTERN; 198 String search = String.format(pattern, context.getName(), "*"); 199 unregisterAllMatching(search, mbs); 200 } 201 202 private static void unregisterAppenders(LoggerContext context, 203 MBeanServer mbs) { 204 String pattern = AppenderAdminMBean.PATTERN; 205 String search = String.format(pattern, context.getName(), "*"); 206 unregisterAllMatching(search, mbs); 207 } 208 209 private static void unregisterAllMatching(String search, MBeanServer mbs) { 210 try { 211 ObjectName pattern = new ObjectName(search); 212 Set<ObjectName> found = mbs.queryNames(pattern, null); 213 for (ObjectName objectName : found) { 214 mbs.unregisterMBean(objectName); 215 } 216 } catch (Exception ex) { 217 StatusLogger.getLogger() 218 .error("Could not unregister " + search, ex); 219 } 220 } 221 222 private static void registerLoggerConfigs(LoggerContext ctx, 223 MBeanServer mbs, Executor executor) 224 throws MalformedObjectNameException, 225 InstanceAlreadyExistsException, MBeanRegistrationException, 226 NotCompliantMBeanException { 227 228 Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers(); 229 for (String name : map.keySet()) { 230 LoggerConfig cfg = map.get(name); 231 LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx.getName(), cfg); 232 mbs.registerMBean(mbean, mbean.getObjectName()); 233 } 234 } 235 236 private static void registerAppenders(LoggerContext ctx, MBeanServer mbs, 237 Executor executor) throws MalformedObjectNameException, 238 InstanceAlreadyExistsException, MBeanRegistrationException, 239 NotCompliantMBeanException { 240 241 Map<String, Appender<?>> map = ctx.getConfiguration().getAppenders(); 242 for (String name : map.keySet()) { 243 Appender<?> appender = map.get(name); 244 AppenderAdmin mbean = new AppenderAdmin(ctx.getName(), appender); 245 mbs.registerMBean(mbean, mbean.getObjectName()); 246 } 247 } 248 }