/*
 * 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 LoggerContextAdminMBean interface.
/** * 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 LoggerContextAdmin with the Executor to be used for sending Notification s asynchronously to listeners.
Params:
  • executor – used to send notifications asynchronously
  • loggerContext – the instrumented object
/** * 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.
Params:
  • in – stream to read from
  • charset – MUST not be null
Throws:
  • IOException – if a problem occurred reading from the stream.
Returns:stream contents
/** * 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 ObjectName of this mbean.
See Also:
Returns:the ObjectName
/** * 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(); } }