View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.jmx;
    18
    19  import java.beans.PropertyChangeEvent;
    20  import java.beans.PropertyChangeListener;
    21  import java.io.ByteArrayInputStream;
    22  import java.io.File;
    23  import java.io.FileInputStream;
    24  import java.io.IOException;
    25  import java.io.InputStream;
    26  import java.io.InputStreamReader;
    27  import java.io.PrintWriter;
    28  import java.io.Reader;
    29  import java.io.StringWriter;
    30  import java.net.URISyntaxException;
    31  import java.net.URL;
    32  import java.nio.charset.Charset;
    33  import java.util.Map;
    34  import java.util.concurrent.Executor;
    35  import java.util.concurrent.atomic.AtomicLong;
    36
    37  import javax.management.MBeanNotificationInfo;
    38  import javax.management.Notification;
    39  import javax.management.NotificationBroadcasterSupport;
    40  import javax.management.ObjectName;
    41
    42  import org.apache.logging.log4j.core.LoggerContext;
    43  import org.apache.logging.log4j.core.config.Configuration;
    44  import org.apache.logging.log4j.core.config.ConfigurationFactory;
    45  import org.apache.logging.log4j.core.config.ConfigurationSource;
    46  import org.apache.logging.log4j.core.util.Assert;
    47  import org.apache.logging.log4j.core.util.Closer;
    48  import org.apache.logging.log4j.core.util.Constants;
    49  import org.apache.logging.log4j.status.StatusLogger;
    50  import org.apache.logging.log4j.util.Strings;
    51
    52  /**
    53   * Implementation of the {@code LoggerContextAdminMBean} interface.
    54   */
    55  public class LoggerContextAdmin extends NotificationBroadcasterSupport implements LoggerContextAdminMBean,
    56          PropertyChangeListener {
    57      private static final int PAGE = 4 * 1024;
    58      private static final int TEXT_BUFFER = 64 * 1024;
    59      private static final int BUFFER_SIZE = 2048;
    60      private static final StatusLogger LOGGER = StatusLogger.getLogger();
    61
    62      private final AtomicLong sequenceNo = new AtomicLong();
    63      private final ObjectName objectName;
    64      private final LoggerContext loggerContext;
    65
    66      /**
    67       * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to be used for sending {@code Notification}
    68       * s asynchronously to listeners.
    69       *
    70       * @param executor used to send notifications asynchronously
    71       * @param loggerContext the instrumented object
    72       */
    73      public LoggerContextAdmin(final LoggerContext loggerContext, final Executor executor) {
    74          super(executor, createNotificationInfo());
    75          this.loggerContext = Assert.requireNonNull(loggerContext, "loggerContext");
    76          try {
    77              final String ctxName = Server.escape(loggerContext.getName());
    78              final String name = String.format(PATTERN, ctxName);
    79              objectName = new ObjectName(name);
    80          } catch (final Exception e) {
    81              throw new IllegalStateException(e);
    82          }
    83          loggerContext.addPropertyChangeListener(this);
    84      }
    85
    86      private static MBeanNotificationInfo createNotificationInfo() {
    87          final String[] notifTypes = new String[] { NOTIF_TYPE_RECONFIGURED };
    88          final String name = Notification.class.getName();
    89          final String description = "Configuration reconfigured";
    90          return new MBeanNotificationInfo(notifTypes, name, description);
    91      }
    92
    93      @Override
    94      public String getStatus() {
    95          return loggerContext.getState().toString();
    96      }
    97
    98      @Override
    99      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(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(Constants.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(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 String getConfigMonitorClassName() {
    227         return getConfig().getConfigurationMonitor().getClass().getName();
    228     }
    229
    230     @Override
    231     public Map<String, String> getConfigProperties() {
    232         return getConfig().getProperties();
    233     }
    234
    235     /**
    236      * Returns the {@code ObjectName} of this mbean.
    237      *
    238      * @return the {@code ObjectName}
    239      * @see LoggerContextAdminMBean#PATTERN
    240      */
    241     @Override
    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 }