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