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