blob: a2a9288b7874039f35516d1f1da20e6a3ad94dc1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.jmx;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.MBeanNotificationInfo;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.util.Closer;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;
/**
* Implementation of the {@code LoggerContextAdminMBean} interface.
*/
public class LoggerContextAdmin extends NotificationBroadcasterSupport implements LoggerContextAdminMBean,
PropertyChangeListener {
private static final int PAGE = 4 * 1024;
private static final int TEXT_BUFFER = 64 * 1024;
private static final int BUFFER_SIZE = 2048;
private static final StatusLogger LOGGER = StatusLogger.getLogger();
private final AtomicLong sequenceNo = new AtomicLong();
private final ObjectName objectName;
private final LoggerContext loggerContext;
/**
* Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to be used for sending {@code Notification}
* s asynchronously to listeners.
*
* @param executor used to send notifications asynchronously
* @param loggerContext the instrumented object
*/
public LoggerContextAdmin(final LoggerContext loggerContext, final Executor executor) {
super(executor, createNotificationInfo());
this.loggerContext = Objects.requireNonNull(loggerContext, "loggerContext");
try {
final String ctxName = Server.escape(loggerContext.getName());
final String name = String.format(PATTERN, ctxName);
objectName = new ObjectName(name);
} catch (final Exception e) {
throw new IllegalStateException(e);
}
loggerContext.addPropertyChangeListener(this);
}
private static MBeanNotificationInfo createNotificationInfo() {
final String[] notifTypes = new String[] { NOTIF_TYPE_RECONFIGURED };
final String name = Notification.class.getName();
final String description = "Configuration reconfigured";
return new MBeanNotificationInfo(notifTypes, name, description);
}
@Override
public String getStatus() {
return loggerContext.getState().toString();
}
@Override
public String getName() {
return loggerContext.getName();
}
private Configuration getConfig() {
return loggerContext.getConfiguration();
}
@Override
public String getConfigLocationUri() {
if (loggerContext.getConfigLocation() != null) {
return String.valueOf(loggerContext.getConfigLocation());
}
if (getConfigName() != null) {
return String.valueOf(new File(getConfigName()).toURI());
}
return Strings.EMPTY;
}
@Override
public void setConfigLocationUri(final String configLocation) throws URISyntaxException, IOException {
if (configLocation == null || configLocation.isEmpty()) {
throw new IllegalArgumentException("Missing configuration location");
}
LOGGER.debug("---------");
LOGGER.debug("Remote request to reconfigure using location " + configLocation);
final File configFile = new File(configLocation);
ConfigurationSource configSource = null;
if (configFile.exists()) {
LOGGER.debug("Opening config file {}", configFile.getAbsolutePath());
configSource = new ConfigurationSource(new FileInputStream(configFile), configFile);
} else {
final URL configURL = new URL(configLocation);
LOGGER.debug("Opening config URL {}", configURL);
configSource = new ConfigurationSource(configURL.openStream(), configURL);
}
final Configuration config = ConfigurationFactory.getInstance().getConfiguration(loggerContext, configSource);
loggerContext.start(config);
LOGGER.debug("Completed remote request to reconfigure.");
}
@Override
public void propertyChange(final PropertyChangeEvent evt) {
if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) {
return;
}
final Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED, getObjectName(), nextSeqNo(), now(), null);
sendNotification(notif);
}
@Override
public String getConfigText() throws IOException {
return getConfigText(StandardCharsets.UTF_8.name());
}
@Override
public String getConfigText(final String charsetName) throws IOException {
try {
final ConfigurationSource source = loggerContext.getConfiguration().getConfigurationSource();
final ConfigurationSource copy = source.resetInputStream();
final Charset charset = Charset.forName(charsetName);
return readContents(copy.getInputStream(), charset);
} catch (final Exception ex) {
final StringWriter sw = new StringWriter(BUFFER_SIZE);
ex.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}
/**
* Returns the contents of the specified input stream as a String.
* @param in stream to read from
* @param charset MUST not be null
* @return stream contents
* @throws IOException if a problem occurred reading from the stream.
*/
private String readContents(final InputStream in, final Charset charset) throws IOException {
Reader reader = null;
try {
reader = new InputStreamReader(in, charset);
final StringBuilder result = new StringBuilder(TEXT_BUFFER);
final char[] buff = new char[PAGE];
int count = -1;
while ((count = reader.read(buff)) >= 0) {
result.append(buff, 0, count);
}
return result.toString();
} finally {
Closer.closeSilently(in);
Closer.closeSilently(reader);
}
}
@Override
public void setConfigText(final String configText, final String charsetName) {
LOGGER.debug("---------");
LOGGER.debug("Remote request to reconfigure from config text.");
try {
final InputStream in = new ByteArrayInputStream(configText.getBytes(charsetName));
final ConfigurationSource source = new ConfigurationSource(in);
final Configuration updated = ConfigurationFactory.getInstance().getConfiguration(loggerContext, source);
loggerContext.start(updated);
LOGGER.debug("Completed remote request to reconfigure from config text.");
} catch (final Exception ex) {
final String msg = "Could not reconfigure from config text";
LOGGER.error(msg, ex);
throw new IllegalArgumentException(msg, ex);
}
}
@Override
public String getConfigName() {
return getConfig().getName();
}
@Override
public String getConfigClassName() {
return getConfig().getClass().getName();
}
@Override
public String getConfigFilter() {
return String.valueOf(getConfig().getFilter());
}
@Override
public Map<String, String> getConfigProperties() {
return getConfig().getProperties();
}
/**
* Returns the {@code ObjectName} of this mbean.
*
* @return the {@code ObjectName}
* @see LoggerContextAdminMBean#PATTERN
*/
@Override
public ObjectName getObjectName() {
return objectName;
}
private long nextSeqNo() {
return sequenceNo.getAndIncrement();
}
private long now() {
return System.currentTimeMillis();
}
}