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.io.ByteArrayInputStream; 022 import java.io.File; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.InputStreamReader; 026 import java.io.PrintWriter; 027 import java.io.Reader; 028 import java.io.StringWriter; 029 import java.net.URI; 030 import java.net.URISyntaxException; 031 import java.nio.charset.Charset; 032 import java.util.Map; 033 import java.util.concurrent.Executor; 034 import java.util.concurrent.atomic.AtomicLong; 035 036 import javax.management.MBeanNotificationInfo; 037 import javax.management.Notification; 038 import javax.management.NotificationBroadcasterSupport; 039 import javax.management.ObjectName; 040 041 import org.apache.logging.log4j.core.LoggerContext; 042 import org.apache.logging.log4j.core.config.Configuration; 043 import org.apache.logging.log4j.core.config.ConfigurationFactory; 044 import org.apache.logging.log4j.core.config.ConfigurationFactory.ConfigurationSource; 045 import org.apache.logging.log4j.core.helpers.Assert; 046 import org.apache.logging.log4j.core.helpers.Charsets; 047 import org.apache.logging.log4j.status.StatusLogger; 048 049 /** 050 * Implementation of the {@code LoggerContextAdminMBean} interface. 051 */ 052 public class LoggerContextAdmin extends NotificationBroadcasterSupport 053 implements LoggerContextAdminMBean, PropertyChangeListener { 054 private static final int PAGE = 4 * 1024; 055 private static final int TEXT_BUFFER = 64 * 1024; 056 private static final int BUFFER_SIZE = 2048; 057 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 058 059 private final AtomicLong sequenceNo = new AtomicLong(); 060 private final ObjectName objectName; 061 private final LoggerContext loggerContext; 062 private String customConfigText; 063 064 /** 065 * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to 066 * be used for sending {@code Notification}s asynchronously to listeners. 067 * 068 * @param executor used to send notifications asynchronously 069 * @param loggerContext the instrumented object 070 */ 071 public LoggerContextAdmin(LoggerContext loggerContext, Executor executor) { 072 super(executor, createNotificationInfo()); 073 this.loggerContext = Assert.isNotNull(loggerContext, "loggerContext"); 074 try { 075 String ctxName = Server.escape(loggerContext.getName()); 076 String name = String.format(PATTERN, ctxName); 077 objectName = new ObjectName(name); 078 } catch (Exception e) { 079 throw new IllegalStateException(e); 080 } 081 loggerContext.addPropertyChangeListener(this); 082 } 083 084 private static MBeanNotificationInfo createNotificationInfo() { 085 String[] notifTypes = new String[] {// 086 NOTIF_TYPE_RECONFIGURED }; 087 String name = Notification.class.getName(); 088 String description = "Configuration reconfigured"; 089 return new MBeanNotificationInfo(notifTypes, name, description); 090 } 091 092 @Override 093 public String getStatus() { 094 return loggerContext.getStatus().toString(); 095 } 096 097 @Override 098 public String getName() { 099 return loggerContext.getName(); 100 } 101 102 private Configuration getConfig() { 103 return loggerContext.getConfiguration(); 104 } 105 106 @Override 107 public String getConfigLocationURI() { 108 if (loggerContext.getConfigLocation() != null) { 109 return String.valueOf(loggerContext.getConfigLocation()); 110 } 111 if (getConfigName() != null) { 112 return String.valueOf(new File(getConfigName()).toURI()); 113 } 114 return ""; 115 } 116 117 @Override 118 public void setConfigLocationURI(String configLocation) 119 throws URISyntaxException, IOException { 120 LOGGER.debug("---------"); 121 LOGGER.debug("Remote request to reconfigure using location " 122 + configLocation); 123 URI uri = new URI(configLocation); 124 125 // validate the location first: invalid location will result in 126 // default configuration being configured, try to avoid that... 127 uri.toURL().openStream().close(); 128 129 loggerContext.setConfigLocation(uri); 130 LOGGER.debug("Completed remote request to reconfigure."); 131 } 132 133 @Override 134 public void propertyChange(PropertyChangeEvent evt) { 135 if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) { 136 return; 137 } 138 // erase custom text if new configuration was read from a location 139 if (loggerContext.getConfiguration().getName() != null) { 140 customConfigText = null; 141 } 142 Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED, 143 getObjectName(), nextSeqNo(), now(), null); 144 sendNotification(notif); 145 } 146 147 @Override 148 public String getConfigText() throws IOException { 149 return getConfigText(Charsets.UTF_8.name()); 150 } 151 152 @Override 153 public String getConfigText(String charsetName) throws IOException { 154 if (customConfigText != null) { 155 return customConfigText; 156 } 157 try { 158 Charset charset = Charset.forName(charsetName); 159 return readContents(new URI(getConfigLocationURI()), charset); 160 } catch (Exception ex) { 161 StringWriter sw = new StringWriter(BUFFER_SIZE); 162 ex.printStackTrace(new PrintWriter(sw)); 163 return sw.toString(); 164 } 165 } 166 167 @Override 168 public void setConfigText(String configText, String charsetName) { 169 String old = customConfigText; 170 customConfigText = Assert.isNotNull(configText, "configText"); 171 LOGGER.debug("---------"); 172 LOGGER.debug("Remote request to reconfigure from config text."); 173 174 try { 175 InputStream in = new ByteArrayInputStream( 176 configText.getBytes(charsetName)); 177 ConfigurationSource source = new ConfigurationSource(in); 178 Configuration updated = ConfigurationFactory.getInstance() 179 .getConfiguration(source); 180 loggerContext.start(updated); 181 LOGGER.debug("Completed remote request to reconfigure from config text."); 182 } catch (Exception ex) { 183 customConfigText = old; 184 String msg = "Could not reconfigure from config text"; 185 LOGGER.error(msg, ex); 186 throw new IllegalArgumentException(msg, ex); 187 } 188 } 189 190 private String readContents(URI uri, Charset charset) throws IOException { 191 InputStream in = null; 192 try { 193 in = uri.toURL().openStream(); 194 Reader reader = new InputStreamReader(in, charset); 195 StringBuilder result = new StringBuilder(TEXT_BUFFER); 196 char[] buff = new char[PAGE]; 197 int count = -1; 198 while ((count = reader.read(buff)) >= 0) { 199 result.append(buff, 0, count); 200 } 201 return result.toString(); 202 } finally { 203 try { 204 in.close(); 205 } catch (Exception ignored) { 206 // ignored 207 } 208 } 209 } 210 211 @Override 212 public String getConfigName() { 213 return getConfig().getName(); 214 } 215 216 @Override 217 public String getConfigClassName() { 218 return getConfig().getClass().getName(); 219 } 220 221 @Override 222 public String getConfigFilter() { 223 return String.valueOf(getConfig().getFilter()); 224 } 225 226 @Override 227 public String getConfigMonitorClassName() { 228 return getConfig().getConfigurationMonitor().getClass().getName(); 229 } 230 231 @Override 232 public Map<String, String> getConfigProperties() { 233 return getConfig().getProperties(); 234 } 235 236 /** 237 * Returns the {@code ObjectName} of this mbean. 238 * 239 * @return the {@code ObjectName} 240 * @see LoggerContextAdminMBean#PATTERN 241 */ 242 public ObjectName getObjectName() { 243 return objectName; 244 } 245 246 private long nextSeqNo() { 247 return sequenceNo.getAndIncrement(); 248 } 249 250 private long now() { 251 return System.currentTimeMillis(); 252 } 253 }